Compare commits

..

214 Commits

Author SHA1 Message Date
Kovid Goyal
458f10be10 version 0.9.0 2018-04-15 20:18:52 +05:30
Kovid Goyal
c1667d9fc1 Fix run_kitten shortcuts in readme 2018-04-15 20:04:32 +05:30
Kovid Goyal
52f4e81d59 hints kitten: Reverse order of hints
Gives hints at the bottom of the screen smaller numbers. In
non-fullscreen usage matches closer to the bottom are more likely to be
the ones the user is looking for. Fixes #460
2018-04-14 23:07:53 +05:30
Kovid Goyal
1056ab1b81 Store marks in the index map 2018-04-14 23:01:49 +05:30
Kovid Goyal
3f9d04b616 Fix #458 2018-04-14 08:41:40 +05:30
Kovid Goyal
d9cc065c99 ... 2018-04-13 22:34:28 +05:30
Kovid Goyal
612fb3b596 Process events when timeout is zero 2018-04-13 22:34:08 +05:30
Kovid Goyal
532d51c411 Neaten up main_loop() 2018-04-13 15:19:25 +05:30
Kovid Goyal
5630c0e434 Fix merging of shortcuts
Multi-key shortcuts take precedence over conflicting single key
shortcuts defined at the same and lower levels
2018-04-12 19:40:22 +05:30
Kovid Goyal
5ba8ca2745 ... 2018-04-12 16:53:46 +05:30
Kovid Goyal
e9ffb8d185 Clarify --config help text 2018-04-12 16:47:26 +05:30
Kovid Goyal
339a2e0800 Add a word select type to the hints kitten 2018-04-12 13:49:12 +05:30
Kovid Goyal
98c04fb7da Pass some common option values to the kittens 2018-04-12 13:21:05 +05:30
Kovid Goyal
c95d1393cc Add some more default keybindings for the hints kitten 2018-04-12 13:01:16 +05:30
Kovid Goyal
1779cdd353 Fix #455 2018-04-12 10:45:29 +05:30
Kovid Goyal
6a090ac740 Add a --type argument to hints kitten
Allows addition of more sophisticated types of matches later.
2018-04-12 10:30:32 +05:30
Kovid Goyal
3c7236557c hints: Use --program=@ to copy to clipboard 2018-04-12 10:16:48 +05:30
Kovid Goyal
83a3d99824 Make --help work for all kittens 2018-04-12 09:33:50 +05:30
Kovid Goyal
808750a76a Rename url_hints to just hints 2018-04-12 09:23:48 +05:30
Kovid Goyal
32caea4197 Better error message for unknown uniform in cursor program 2018-04-12 09:11:43 +05:30
Kovid Goyal
038248f55d Remove unnecessary handling of passed in files to url_hints 2018-04-12 09:04:54 +05:30
Kovid Goyal
21b7bf6009 url_hints: Allow using a group to ignore prefix/suffix when matching via regex 2018-04-12 08:54:36 +05:30
Kovid Goyal
9dc571d8a9 Add a conveninet shortcut to test kittens 2018-04-12 08:52:00 +05:30
Kovid Goyal
805baad9cd More appropriate message when URLs not found 2018-04-12 07:27:40 +05:30
Kovid Goyal
cc910f6b79 ... 2018-04-12 07:16:20 +05:30
Kovid Goyal
56dd442e2c Flush output streams on kitten end 2018-04-11 20:24:39 +05:30
Kovid Goyal
3192b61385 ... 2018-04-11 20:19:03 +05:30
Kovid Goyal
eef3a215a1 ... 2018-04-11 14:42:07 +05:30
Kovid Goyal
cb7fff6986 Refactor the ask kitten to use the new API 2018-04-11 14:40:58 +05:30
Kovid Goyal
75b5130d96 Allow the URL hints kitten to insert the selected URL into the terminal 2018-04-11 13:22:07 +05:30
Kovid Goyal
2cf8c6aea7 Refactor the kittens framework
Make it possible to perform arbitrary actions with the kittens output
and also allow running kittens from standalone python files.
2018-04-11 13:09:31 +05:30
Kovid Goyal
5755ba72b1 Fix #449 2018-04-09 20:13:13 +05:30
Kovid Goyal
785937ecfc Remove unnecessary function calls
glfwSetInputMode is a no-op if the passed in mode is the same as the
current mode anyway.
2018-04-09 19:40:14 +05:30
Kovid Goyal
955ead05be Make the command to copy over terminfo files a one liner 2018-04-09 16:45:11 +05:30
Kovid Goyal
3830261384 Merge branch 'patch-2' of https://github.com/maximbaz/kitty 2018-04-09 16:38:42 +05:30
Maxim Baz
662ab0df45 Allow background opacity for values in range [0.91, 0.99] 2018-04-09 12:59:17 +02:00
Kovid Goyal
1ce50fb6f0 Also clean-up selection extend for line select 2018-04-09 10:43:05 +05:30
Kovid Goyal
74dcf8800d ... 2018-04-09 09:51:51 +05:30
Kovid Goyal
e90df0ca95 When double-clicking select words that continue onto next/prev line as well. Fixes #444 2018-04-09 09:47:18 +05:30
Kovid Goyal
49c375ae0f Fix #441 2018-04-08 09:05:31 +05:30
Kovid Goyal
6c4d1a4951 Merge branch 'unicode-fix' of https://github.com/epsilon-phase/kitty
Fixes #442
2018-04-08 08:54:53 +05:30
alexander white
f99e368831 Fixed crash on tab-completing without any search results. 2018-04-07 14:10:16 -04:00
Kovid Goyal
8468ad60d5 ... 2018-04-06 20:55:47 +05:30
Kovid Goyal
a1ce29aab9 Update CHANGELOG 2018-04-06 20:53:29 +05:30
Kovid Goyal
20b5084904 Update glfw
Fixes #439
2018-04-06 20:18:03 +05:30
Kovid Goyal
2fde0ea86b Add some documentation for the kitty shell 2018-04-06 15:06:28 +05:30
Kovid Goyal
b64bceac7f Add a keyboard shortcut to open the kitty shell 2018-04-06 14:59:13 +05:30
Kovid Goyal
a97174a350 Add basic command name and option name completion to the shell 2018-04-06 14:07:14 +05:30
Kovid Goyal
6141c9fdda Implement a command shell for kitty
Run it with:

kitty @
2018-04-06 12:11:37 +05:30
Kovid Goyal
a32e010f89 Move the remote commands into their own module
Separates the commands from the UI for running them.
2018-04-06 10:38:26 +05:30
Kovid Goyal
4847fb52cc Updates to README 2018-04-06 08:16:14 +05:30
Kovid Goyal
58e33e2512 URL hints: Exclude trailing punctuation from URLs 2018-04-04 08:53:12 +05:30
Kovid Goyal
584b59c7a1 Shorter filtering code 2018-04-03 10:19:21 +05:30
Kovid Goyal
2e657f0020 Have safe_remove remove all instances 2018-04-03 10:15:58 +05:30
Kovid Goyal
ae112294c8 Fix compilation on Travis 2018-04-03 10:13:18 +05:30
Kovid Goyal
8b4c7ea23f Pass CFLAGS to linker as well
Fixes #435
2018-04-03 10:03:03 +05:30
Kovid Goyal
83e225a50f ... 2018-04-02 15:30:14 +05:30
Kovid Goyal
6256b9027a Add a --reset option to @ set-colors 2018-04-02 11:14:54 +05:30
Kovid Goyal
1fd84612a8 Add a remote control command to change colors
Also add a FAQ entry about changing colors in a running kitty instance.
I'm tired of all the bug reports asking for this feature. Apparently,
people find it hard to google for the existing escape codes based
solution.
2018-04-02 10:45:52 +05:30
Kovid Goyal
2efa83bc4d Use git rev-parse instead, more robust 2018-04-01 20:33:12 +05:30
Kovid Goyal
fa064bf9cf When kitty is built from a git checkout show the current revision in --debug-config 2018-04-01 20:27:31 +05:30
Kovid Goyal
325d2bc14e Improve mouse selection for windows with padding
Moving the mouse into the padding area now acts as if the mouse is over
the nearest cell. Fixes #430

Note that this only applies to padding defined via the
window_padding_width option, not any leftover space from the window size
not being an exact multiple of the cell size.
2018-04-01 18:06:27 +05:30
Kovid Goyal
90a4b4f859 URL hints kitten: Open the URL from within the kitty process. This ensures that the behavior of the program used to open the URL is the same as when ctrl+shift clicking URLs. Fixes #424 2018-04-01 16:48:07 +05:30
Kovid Goyal
ce0db16479 Fix using kitty --single-instance to open a new window in a running kitty instance, not respecting the --directory flag. Fixes #429 2018-04-01 12:39:33 +05:30
Kovid Goyal
43ce3ce4b0 Forgot to add _replace() method to new Options class 2018-04-01 12:33:18 +05:30
Kovid Goyal
910cebeeb5 Allow setting all 256 colors in the config file 2018-04-01 12:24:00 +05:30
Kovid Goyal
1c78633d1a Add an "include" directive for the config files to read multiple config files. 2018-04-01 10:19:21 +05:30
Kovid Goyal
530fd61125 Add support for multi-key shortcuts
Fixes #338
2018-03-31 12:21:37 +05:30
Kovid Goyal
9edfafcac2 Update glfw 2018-03-31 12:21:37 +05:30
Kovid Goyal
677c47b9dd Possible fix for handling unknown modifiers on Xkb systems 2018-03-31 12:21:37 +05:30
Kovid Goyal
b59d7dda11 Import glfw from upstream, key handling on macOS works again 2018-03-31 12:21:37 +05:30
Kovid Goyal
7a3534baf8 Make ascii control char test nicer 2018-03-31 12:21:37 +05:30
Kovid Goyal
3bfcadcd81 Install newer libxkbcommon on Travis 2018-03-31 12:21:37 +05:30
Kovid Goyal
529bb5c6a0 We need xkbcommon >= 0.5 2018-03-31 12:21:36 +05:30
Kovid Goyal
caf9a12b34 Update glfw from upstream
libxkbcommon based keyboard handling now works (at least the basic
stuff, haven't tested compose/mapnotify etc.)
2018-03-31 12:21:36 +05:30
Kovid Goyal
da8e2d3d4a Adjust keys callback for new glfw keyboard API 2018-03-31 12:21:36 +05:30
Kovid Goyal
582500d3e9 Basic changes for new glfw keyboard API 2018-03-31 12:21:36 +05:30
Kovid Goyal
7926a140b8 version 0.8.4 2018-03-31 12:16:35 +05:30
Kovid Goyal
023487a1d7 Merge branch 'reduce-iconset' of https://github.com/justinnhli/kitty 2018-03-31 11:43:41 +05:30
Justin Li
71f84b1023 reduce icon set size to fit iconutil limits 2018-03-30 22:57:34 -07:00
Kovid Goyal
ce85382c68 Revert the change to use rendered bitmap width to calculate cell width
Instead if the bitmap is up to two pixels wider than the cell width
simply crop on the right. Fixes #352
2018-03-30 18:54:15 +05:30
Kovid Goyal
94d248d812 Fix presence of XDG_CONFIG_DIRS env var preventing kitty from starting up due to a silly typo
Fixes #421
2018-03-29 17:51:14 +05:30
Kovid Goyal
62db7639a8 version 0.8.3 2018-03-29 09:56:44 +05:30
Kovid Goyal
80f3fe6e9f ... 2018-03-29 00:05:37 +05:30
Kovid Goyal
9ba2c4b397 Update glfw from upstream 2018-03-28 19:57:01 +05:30
Kovid Goyal
bfa53d4b45 Update glfw from upstream
Includes proper fix for Wayland key repeat
2018-03-28 15:26:56 +05:30
Kovid Goyal
69f3ceb903 Merge glfw upstream changes to build on Wayland on FreeBSD 2018-03-28 09:47:16 +05:30
Kovid Goyal
d0f839bbd8 Fix audible bell not working under Wayland 2018-03-27 19:45:19 +05:30
Kovid Goyal
48538c0e25 Update bundled glfw 2018-03-27 15:07:25 +05:30
Kovid Goyal
02ab22f08c Update CHANGELOG 2018-03-27 12:15:25 +05:30
Kovid Goyal
b3b216a6dc ... 2018-03-27 12:09:18 +05:30
Kovid Goyal
431564c8bc Linux: When calculating cell widths use the actual rendered bitmap width in addition to the horizontal advance reported by FreeType.
The horizontal advance is insufficient for some fonts at some point
sizes. So although rendering the characters is slower, we do it
to ensure cell width is correct. Fixes #352
2018-03-27 12:04:38 +05:30
Kovid Goyal
57f1bbb71a Make cwd check more robust
Apparently getcwd() raises an exception on macOS when the cwd does not
exist.
2018-03-27 10:54:49 +05:30
Kovid Goyal
faac8649e4 Fix a regression that broke visual bell and invert screen colors escape code. Fixes #419 2018-03-27 10:46:26 +05:30
Kovid Goyal
1e6a41186f Move check for NULL boss into call_boss()
Negligible perf impact and makes it safe to use in different contexts
2018-03-27 09:56:29 +05:30
Kovid Goyal
eddee67130 Fix error when testing update_selection because not boss is defined 2018-03-27 09:54:14 +05:30
Kovid Goyal
9b834014e9 Merge branch 'zero-length-title' of https://github.com/jamessan/kitty 2018-03-27 09:41:33 +05:30
James McCoy
a467e79144 Stop setting title to ";" when 0-length OSC 2 is received
Various programs send a 0-length title if no title is configured (e.g.,
(n)vim with 'title' unset, or mutt without ts_enabled).  When this
happens, kitty is mis-parsing the data and setting the title to ";".
2018-03-26 21:30:45 -04:00
Kovid Goyal
90cc785b55 Allow double-click and triple-click + drag to extend selections word at a time or line at a time. Fixes #400 2018-03-26 16:48:28 +05:30
Kovid Goyal
09710425ab Move rectangle_select onto the selection object 2018-03-26 12:37:36 +05:30
Kovid Goyal
dd36727adf Show macOs version for --debug-config 2018-03-25 12:47:47 +05:30
Kovid Goyal
47b665a624 Prettyify --debug-config output 2018-03-25 12:36:27 +05:30
Kovid Goyal
7011329761 Add a --debug-config option to provide diagnostic info about the system and kitty 2018-03-25 12:30:56 +05:30
Kovid Goyal
ea24c23fde Allow specifying a system wide kitty config file
The system config is merged with any user specific config files, with
options in the user specific config files having higher priority.

Also follow the full XDG spec for finding config files, including
XDG_CONFIG_DIRS.
2018-03-25 11:47:11 +05:30
Kovid Goyal
be8f2b8106 Create a shortcut to set the tab title
No longer need to use remote control just to change tab titles.
2018-03-25 09:55:02 +05:30
Kovid Goyal
71db024a3b Add history based completion to the ask kitten 2018-03-24 11:49:34 +05:30
Kovid Goyal
3790af6897 Basic ask kitten 2018-03-23 18:03:43 +05:30
Kovid Goyal
dc6ef63ddd Merge branch 'oS' of https://github.com/jubalh/kitty 2018-03-23 15:33:40 +05:30
Michael Vetter
ef762f7a2b Add link to openSUSE package 2018-03-23 10:45:42 +01:00
Kovid Goyal
4ed646e062 Merge branch 'tic2' of https://github.com/jubalh/kitty 2018-03-21 21:17:46 +05:30
Michael Vetter
afbd843388 Mention tic as dependency 2018-03-21 16:42:44 +01:00
Kovid Goyal
18c3598ef2 Merge branch 'desktop' of https://github.com/jubalh/kitty 2018-03-21 20:43:50 +05:30
Michael Vetter
1c2623e609 Have more detailed desktop file category
Let's use the 'TerminalEmulator' category according to the spec:
https://specifications.freedesktop.org/menu-spec/menu-spec-1.0.html
2018-03-21 11:28:57 +01:00
Kovid Goyal
e232863028 Also recognize unicode aliases for macOS modifiers 2018-03-20 15:10:29 +05:30
Kovid Goyal
a89309153d Merge branch 'patch-1' of https://github.com/sle-odoo/kitty 2018-03-18 17:59:27 +05:30
Simon Lejeune
fee7c8e573 update dependencies to run make app
while imagemagick is defined as an optional dependency in the installation section, its presence is indeed mandatory to successfully run `make app` as seen at https://github.com/kovidgoyal/kitty/blob/master/logo/make.py#L27
2018-03-18 10:56:40 +01:00
Kovid Goyal
1a52127c55 version 0.8.2 2018-03-17 17:45:57 +05:30
Kovid Goyal
4920431c3a Update CHANGELOG 2018-03-17 17:40:41 +05:30
Kovid Goyal
773f26b61d Fix screen not being refreshed immediately after moving a window. Fixes #397 2018-03-16 22:00:57 +05:30
Kovid Goyal
16e77d7329 Replace use of PyObject_Str in a couple more places 2018-03-16 21:26:46 +05:30
Kovid Goyal
2cec0908a5 Fix a segfault when getting the contents of the scrollback buffer as text
Fixes #398
2018-03-16 21:10:48 +05:30
Kovid Goyal
b3ef62c188 Merge branch 'i386-fixes' of https://github.com/oleastre/kitty 2018-03-16 20:42:57 +05:30
Olivier Samyn
3385f6fe67 Avoid comparison between signed and unsigned compilation error (32bit) 2018-03-16 09:31:37 +01:00
Kovid Goyal
0170bf636b ... 2018-03-15 20:58:26 +05:30
Kovid Goyal
b2561fd61e Add a facility to specify command line arguments when running kitty from the GUI on macOS
Fixes #395
2018-03-15 20:57:08 +05:30
Kovid Goyal
64b48945d4 Add a focus-tab remote command
Fixes #394
2018-03-15 17:41:49 +05:30
Kovid Goyal
bbfd463357 Dont show a useless traceback when failing to match in a remote command
Fixes #393
2018-03-15 15:45:12 +05:30
Kovid Goyal
2179dab8ed Re-organize kitty.conf into sections
Sections are de-limited by vim folds
2018-03-15 13:07:02 +05:30
Kovid Goyal
801ead7f7f Add ntoe to readme about right click to extend selection 2018-03-15 12:20:46 +05:30
Kovid Goyal
9e4834d58b Right click should only extend selection if there is an existing selection 2018-03-15 12:19:45 +05:30
Kovid Goyal
f38e089de0 Merge branch 'extend-selection' of https://github.com/vsajko/kitty 2018-03-15 12:07:23 +05:30
Kovid Goyal
b252504306 ... 2018-03-15 11:31:37 +05:30
Kovid Goyal
81bcdaf48a Dont show [options] if there are no options in usage message 2018-03-15 11:30:31 +05:30
Kovid Goyal
44f654ff86 Cleanup remote_control.py 2018-03-15 11:26:22 +05:30
Kovid Goyal
84cfa9382f Add argspecs for the remote control command help text 2018-03-15 11:21:13 +05:30
Kovid Goyal
c6da374aa9 Add a configurable keyboard shortcut and remote command to set the font size to a specific value
Fixes #388
2018-03-15 11:15:35 +05:30
Vedran Sajko
d9fe0b2540 Extend selection with right click 2018-03-15 00:09:33 +01:00
Kovid Goyal
5a905dfe7f Add kitty.app to .gitignore
Also have make clean clean linux-package and kitty.app when running
manually.
2018-03-14 12:10:15 +05:30
Kovid Goyal
09b6d6e62b README cleanups 2018-03-14 08:01:10 +05:30
Kovid Goyal
6aa26cf75f Improve the wording for close_on_child_death option 2018-03-13 09:12:06 +05:30
Kovid Goyal
bae8915881 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.
Merge branch 'master' of https://github.com/josswright/kitty
2018-03-13 09:08:45 +05:30
Kovid Goyal
9c46de319e MacOS: Fix restoring of window size not correct on Retina screens
Fixes #382
2018-03-13 08:19:15 +05:30
Joss Wright
994eecadb7 Adds close_on_child_death option for disowned processes. 2018-03-12 21:02:37 +00:00
Kovid Goyal
f7f9ceec34 Consolidate signal handling
Move the signal handler for SIGCHLD into child-monitor.c
Makes for cleaner code.
2018-03-12 21:53:02 +05:30
Kovid Goyal
527255e3a1 Fix compilation with gcc 8
Apparently some nitwit Linux distros have made an unreleased compiler
their default compiler. Fixes #376

Fixes various new warnings that GCC 8 issues
2018-03-12 10:38:12 +05:30
Kovid Goyal
0333da991d ... 2018-03-12 08:11:43 +05:30
Kovid Goyal
2bf8e1b1a3 Allow the name of the lib directory in linux-package to be configurable. Fixes #372 2018-03-12 08:10:53 +05:30
Kovid Goyal
ffc717717c Oops 2018-03-12 07:53:31 +05:30
Kovid Goyal
2c8414db5d Be a bit more specific 2018-03-12 07:52:08 +05:30
Kovid Goyal
32473d7dc7 make clean fallsback to manual cleaning when not in a git checkout
Fixes #379
2018-03-12 07:50:54 +05:30
Kovid Goyal
079f10eebd ... 2018-03-11 22:31:19 +05:30
Kovid Goyal
ae1f305a81 Add kittens to the LOC script 2018-03-11 12:03:23 +05:30
Kovid Goyal
0b1f5209d5 Use CFLAGS and LDFLAGS env vars when building the launcher as well
Fixes #374
2018-03-11 08:12:17 +05:30
Kovid Goyal
fc9980903d Make layout names case-insensitive for enabled_layouts in sessions as well 2018-03-10 23:42:06 +05:30
Kovid Goyal
ad87131926 A bit of documentation for goto_layout 2018-03-10 23:38:18 +05:30
Kovid Goyal
4dfe1b664f Add configurable keyboard shortcuts to switch to a specific layout
Fixes #369
2018-03-10 23:34:47 +05:30
Kovid Goyal
d7b2ac97b5 ... 2018-03-09 21:04:20 +05:30
Kovid Goyal
fd9dac4ca4 Add a small delay when opening the editor with the config file to workaround bugs in vim that cause it to ignore SIGWINCH if it happens early during startup. 2018-03-09 21:03:42 +05:30
Kovid Goyal
8ca9348b07 ... 2018-03-09 18:10:17 +05:30
Kovid Goyal
5cc18fdb4b ... 2018-03-09 18:02:06 +05:30
Kovid Goyal
51b6b325b9 Add a keyboard shortcut to edit the kitty config file easily
Fixes #366
2018-03-09 17:57:09 +05:30
Kovid Goyal
b3cc6f3802 ... 2018-03-09 17:49:31 +05:30
Kovid Goyal
1295094715 README cleanups 2018-03-09 11:00:50 +05:30
Kovid Goyal
af85f58e3d version 0.8.1 2018-03-09 09:21:50 +05:30
Kovid Goyal
3b34821c86 Report unhandled exceptions during loop initialization in the unicode input kitten as well 2018-03-08 13:57:13 +05:30
Kovid Goyal
eaea0b16b5 Fix URL hints not working on macOS
Fixes #363
2018-03-08 13:38:16 +05:30
Kovid Goyal
964ffadc8e DRYer 2018-03-08 13:37:56 +05:30
Kovid Goyal
1b84064e5f ... 2018-03-08 13:01:35 +05:30
Kovid Goyal
dd128a99ac Report unahndled exceptions when starting loop in URL hints kitten 2018-03-08 12:58:54 +05:30
Kovid Goyal
bc829a9712 Use a dedicated static variable for tracking window system initialization 2018-03-06 13:55:06 +05:30
Kovid Goyal
3459c55ebf Add section on reporting bugs to CONTRIBUTING 2018-03-05 21:03:01 +05:30
Kovid Goyal
a631d950e1 Add a guide to contributing 2018-03-05 20:57:57 +05:30
Kovid Goyal
f35e2d76ad Add a note about using --login on macOS when changing the default shell 2018-03-05 14:54:20 +05:30
Kovid Goyal
8db838da9f macOS: Fix shell not starting in login mode on some computers. Fixes #247 2018-03-05 13:30:09 +05:30
Kovid Goyal
7254a880dc Guard against print failure 2018-03-05 13:29:26 +05:30
Kovid Goyal
a0df5b970b Report unhandled exceptions via log_error() as well 2018-03-05 10:28:33 +05:30
Kovid Goyal
54ed08c24e Ensure the extra glyphs array is always re-initialized 2018-03-04 23:36:39 +05:30
Kovid Goyal
97e1ecabdc Render private use unicode characters that are followed by a space as two cell ligatures. Fixes #357 2018-03-04 22:58:24 +05:30
Kovid Goyal
e716f6d35e Replace deprecated ASL with os_log on macOS 2018-03-04 12:28:06 +05:30
Kovid Goyal
c7506496da Unify error logging between python and C 2018-03-04 11:55:47 +05:30
Kovid Goyal
1e3d3df9b8 Remove unused code 2018-03-04 10:59:12 +05:30
Kovid Goyal
ed75ad0d41 Log a timestamp as well when printing error messages from python 2018-03-04 10:44:28 +05:30
Kovid Goyal
fd6159c0b4 Redirect stderr/stdout to console.app when launched via launchd on macOS 2018-03-04 10:17:43 +05:30
Kovid Goyal
976c322b83 Print a couple of errors to stderr instead of stdout 2018-03-04 08:58:36 +05:30
Kovid Goyal
28c1935800 Fix Unicode emoji presentation variant selector causing new fallback font to always be loaded 2018-03-03 21:32:43 +05:30
Kovid Goyal
1edf1524f8 Update bundled glfw 2018-03-03 11:21:09 +05:30
Kovid Goyal
0dfe101c0f brew's python3 handling seems to have changed 2018-03-03 11:09:07 +05:30
Kovid Goyal
a571821f73 Fix compilation on Travis (I hope) 2018-03-03 10:56:07 +05:30
Kovid Goyal
f8e6f87984 Dont use MSG_NOSIGNAL on Apple 2018-03-03 10:51:29 +05:30
Kovid Goyal
c7338bca6f Document how to control kitty from programs not running in a kitty window 2018-03-03 10:38:25 +05:30
Kovid Goyal
15f07f57bf Finish remote control via socket implementation 2018-03-03 10:32:49 +05:30
Kovid Goyal
ec989a45b5 ... 2018-03-03 08:55:05 +05:30
Kovid Goyal
e8286c50b2 Implement sending responses to peers 2018-03-02 12:35:32 +05:30
Kovid Goyal
bfbbe3db14 mutex remove_poll_fd()
It modifies talk_data.fds which can also be modified from the main
thread when adding a writer.
2018-03-02 11:13:47 +05:30
Kovid Goyal
6214a87522 Correct error handling for pthread_join 2018-03-02 11:08:11 +05:30
Kovid Goyal
743ff719c8 join the talk thread on exit
Also shutdown reading on the control socket after message received.
2018-03-02 11:02:18 +05:30
Kovid Goyal
b491eacb9c Ensure the moitor threads are shut down on exit 2018-03-02 10:32:12 +05:30
Kovid Goyal
5bfc89e13f Implement listening on the --listen-on socket 2018-03-02 10:26:19 +05:30
Kovid Goyal
52d2b7b09e Implement --listen-on option 2018-03-02 09:08:58 +05:30
Kovid Goyal
19e59d9575 Start refactoring peer message handling to allow multiple listening sockets and responses 2018-03-02 09:08:58 +05:30
Kovid Goyal
6fe4baf1de Move pthread includes into separate header 2018-03-02 09:08:58 +05:30
Kovid Goyal
bc21ec7551 Have kitty @ use /dev/tty instead of stdout/stdin
Allows redirecting those streams, while still being able to send
messages. Downside is that if user runs it outside kitty there wont
be any useful error message.
2018-03-02 09:08:58 +05:30
Kovid Goyal
aad4a1fd6a Dont fail for non-UTF-8 encoded config files, simply replace the undecodeable chars instead 2018-03-02 08:49:27 +05:30
Kovid Goyal
721595ef04 Ensure config file is always opened with UTF-8 2018-03-02 08:47:13 +05:30
Kovid Goyal
139838c717 Option to print debug information about font fallback 2018-02-28 21:43:40 +05:30
Kovid Goyal
c93238d8c9 Some documentation for the unicode input kitten 2018-02-28 00:29:11 +05:30
Kovid Goyal
5ee1fbe07d Copy the launcher script into linux-package
The launcher script is mostly useless on linux, but is needed
when using kitty.app built from source and might be useful on linux as
well, if running kitty without putting it on PATH.
2018-02-28 00:25:57 +05:30
Kovid Goyal
ee1287211d Typo in makefile app target 2018-02-27 21:06:42 +05:30
Kovid Goyal
94e8be6b1c ... 2018-02-27 20:29:08 +05:30
Kovid Goyal
053ba7f082 Nicer error message when user tries to run logo/make.py with missing tools 2018-02-27 19:56:52 +05:30
Kovid Goyal
e8f3f00792 Handle cols being zero 2018-02-27 18:48:48 +05:30
Kovid Goyal
740a89b77f Treat Ctrl+Alt+letter as Esc,Ctrl+letter
Follows the behavior of some other terminals and is better than
generating nothing, since apparently there are some terminal programs
that use these keys. Fixes #345
2018-02-27 08:46:09 +05:30
Kovid Goyal
1fe936dbbc Fix off by one when restoring cursor position after resize. Fixes #344 2018-02-25 09:15:48 +05:30
Kovid Goyal
11e85ac703 Swapped unicode input name and hex mode prompt strings 2018-02-24 14:51:49 +05:30
104 changed files with 5864 additions and 4214 deletions

1
.gitignore vendored
View File

@@ -4,6 +4,7 @@
tags
build
linux-package
kitty.app
logo/*.iconset
compile_commands.json
glad/out

View File

@@ -20,8 +20,6 @@ matrix:
- libxcursor-dev
- libunistring-dev
- libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev
- python3-pil
@@ -44,8 +42,6 @@ matrix:
- libxcursor-dev
- libunistring-dev
- libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev
- python3-pil
@@ -68,8 +64,6 @@ matrix:
- libxcursor-dev
- libunistring-dev
- libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev
- python3-pil
- clang
@@ -94,7 +88,7 @@ install: |
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
if [[ "$USE_BREW" == "1" ]]; then
brew update;
brew install python3;
brew upgrade python;
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype;
else
mkdir -p $SW;
@@ -102,6 +96,12 @@ install: |
fi
else
pushd /tmp
wget http://xkbcommon.org/download/libxkbcommon-0.6.1.tar.xz
tar xf libxkbcommon-0.6.1.tar.xz
cd libxkbcommon-0.6.1
./configure --prefix=$HOME/harfbuzz --disable-dependency-tracking --disable-static
make -j2
make install
wget https://github.com/behdad/harfbuzz/releases/download/1.6.3/harfbuzz-1.6.3.tar.bz2
tar xf harfbuzz-1.6.3.tar.bz2
cd harfbuzz-1.6.3

View File

@@ -3,6 +3,139 @@ Changelog
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
version 0.9.0 [2018-04-15]
------------------------------
- A new kitty command shell to allow controlling kitty via commands. Press
`ctrl+shift+escape` to run the shell.
- The hints kitten has become much more powerful. Now in addition to URLs you
can use it to select word, paths, filenames, lines, etc. from the screen.
These can be inserted into the terminal, copied to clipboard or sent to
external programs.
- Linux: Switch to libxkbcommon for keyboard handling. It allows kitty to
support XCompose and dead keys and also react to keyboard remapping/layout
change without needing a restart.
- Add support for multiple-key-sequence shortcuts
- A new remote control command `set-colors` to change the current and/or
configured colors.
- When double-clicking to select a word, select words that continue onto the
next/prev line as well.
- Add an `include` directive for the config files to read multiple config files
- Improve mouse selection for windows with padding. Moving the mouse into the
padding area now acts as if the mouse is over the nearest cell.
- Allow setting all 256 terminal colors in the config file
- Fix using `kitty --single-instance` to open a new window in a running kitty
instance, not respecting the `--directory` flag
- URL hints: Exclude trailing punctuation from URLs
- URL hints: Launch the browser from the kitty parent process rather than the
hints kitten. Fixes launching on some systems where xdg-open doesn't like
being run from a kitten.
- Allow using rectangle select mode by pressing shift in addition to the
rectangle select modifiers even when the terminal program has grabbed the
mouse.
version 0.8.4 [2018-03-31]
-----------------------------
- Fix presence of XDG_CONFIG_DIRS and absence of XDG_CONFIG_HOME preventing
kitty from starting
- Revert change in last release to cell width calculation. Instead just clip
the right edges of characters that overflow the cell by at most two pixels
version 0.8.3 [2018-03-29]
-----------------------------
- Fix a regression that broke the visual bell and invert screen colors escape
code
- Allow double-click and triple-click + drag to extend selections word at a
time or line at a time
- Add a keyboard shortcut to set the tab title
- Fix setting window title to empty via OSC escape code not working correctly
- Linux: Fix cell width calculation incorrect for some fonts (cell widths are
now calculated by actually rendering bitmaps, which is slower but more
accurate)
- Allow specifying a system wide kitty config file, for all users
- Add a --debug-config command line flag to output data about the system and
kitty configuration.
- Wayland: Fix auto-repeat of keys not working
version 0.8.2 [2018-03-17]
-----------------------------
- Allow extending existing selections by right clicking
- Add a configurable keyboard shortcut and remote command to set the font size to a specific value
- Add an option to have kitty close the window when the main processes running in it exits, even if there are still background processes writing to that terminal
- Add configurable keyboard shortcuts to switch to a specific layout
- Add a keyboard shortcut to edit the kitty config file easily
- macOS: Fix restoring of window size not correct on Retina screens
- macOS: Add a facility to specify command line arguments when running kitty from the GUI
- Add a focus-tab remote command
- Fix screen not being refreshed immediately after moving a window.
- Fix a crash when getting the contents of the scrollback buffer as text
version 0.8.1 [2018-03-09]
-----------------------------
- Extend kitty's remote control feature to work over both UNIX and TCP sockets,
so now you can control kitty from across the internet, if you want to.
- Render private use unicode characters that are followed by a space as a two
character ligature. This fixes rendering for applications that misuse
private-use characters to display square symbols.
- Fix Unicode emoji presentation variant selector causing new a fallback font
instance to be created
- Fix a rare error that prevented the Unicode input kitten from working
sometimes
- Allow using Ctrl+Alt+letter in legacy keyboard modes by outputting them as Ctrl+letter and Alt+letter.
This matches other terminals' behavior.
- Fix cursor position off-by-one on horizontal axis when resizing the terminal
- Wayland: Fix auto-repeat of keys not working
- Wayland: Add support for window decorations provided by the Wayland shell
- macOS: Fix URL hints not working
- macOS: Fix shell not starting in login mode on some computers
- macOS: Output errors into console.app when running as a bundle
version 0.8.0 [2018-02-24]
-----------------------------

25
CONTRIBUTING.md Normal file
View 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.

View File

@@ -31,4 +31,4 @@ logo/kitty.iconset/icon_256x256.png: logo/kitty.svg logo/make.py
rendered_logo: logo/kitty.iconset/icon_256x256.png
app: rendered_logo
python3 setup.py app $(VVAL)
python3 setup.py kitty.app $(VVAL)

View File

@@ -6,12 +6,14 @@
:sc_close_window: pass:quotes[`ctrl+shift+w`]
:sc_copy_to_clipboard: pass:quotes[`ctrl+shift+c`]
:sc_decrease_font_size: pass:quotes[`ctrl+shift+minus`]
:sc_edit_config_file: pass:quotes[`ctrl+shift+f2`]
:sc_eighth_window: pass:quotes[`ctrl+shift+8`]
:sc_fifth_window: pass:quotes[`ctrl+shift+5`]
:sc_first_window: pass:quotes[`ctrl+shift+1`]
:sc_fourth_window: pass:quotes[`ctrl+shift+4`]
:sc_increase_font_size: pass:quotes[`ctrl+shift+equal`]
:sc_input_unicode_character: pass:quotes[`ctrl+shift+u`]
:sc_kitty_shell_window: pass:quotes[`ctrl+shift+escape`]
:sc_move_tab_backward: pass:quotes[`ctrl+shift+,`]
:sc_move_tab_forward: pass:quotes[`ctrl+shift+.`]
:sc_move_window_backward: pass:quotes[`ctrl+shift+b`]
@@ -30,7 +32,11 @@
:sc_previous_tab: pass:quotes[`ctrl+shift+left`]
:sc_previous_window: pass:quotes[`ctrl+shift+[`]
:sc_restore_font_size: pass:quotes[`ctrl+shift+backspace`]
:sc_run_simple_kitten_text_url_hints: pass:quotes[`ctrl+shift+e`]
:sc_run_kitten_text_hints: pass:quotes[`ctrl+shift+e`]
:sc_run_kitten_text_hints_type_line_program: pass:quotes[`ctrl+shift+p → l`]
:sc_run_kitten_text_hints_type_path: pass:quotes[`ctrl+shift+p → shift+f`]
:sc_run_kitten_text_hints_type_path_program: pass:quotes[`ctrl+shift+p → f`]
:sc_run_kitten_text_hints_type_word_program: pass:quotes[`ctrl+shift+p → w`]
:sc_scroll_end: pass:quotes[`ctrl+shift+end`]
:sc_scroll_home: pass:quotes[`ctrl+shift+home`]
:sc_scroll_line_down: pass:quotes[`ctrl+shift+down` or `ctrl+shift+j`]
@@ -38,6 +44,7 @@
:sc_scroll_page_down: pass:quotes[`ctrl+shift+page_down`]
:sc_scroll_page_up: pass:quotes[`ctrl+shift+page_up`]
:sc_second_window: pass:quotes[`ctrl+shift+2`]
:sc_set_tab_title: pass:quotes[`ctrl+shift+alt+t`]
:sc_seventh_window: pass:quotes[`ctrl+shift+7`]
:sc_show_scrollback: pass:quotes[`ctrl+shift+h`]
:sc_sixth_window: pass:quotes[`ctrl+shift+6`]
@@ -66,7 +73,7 @@ link:#layouts[layouts] without needing to use an extra program like tmux
* 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].
For example, they are used for link:#unicode-input[Unicode input] and link:#url-hints[Hints].
* Supports link:#startup-sessions[startup sessions] which allow you to specify the window/tab layout,
working directories and programs to run on startup.
@@ -101,7 +108,7 @@ the following dependencies are installed first.
* ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal)
* gcc or clang (required only for building)
* pkg-config (required only for building)
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `xinerama` and `xkbcommon-x11`, if they are not already installed by your distro.
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `xinerama`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
=== Install and run from source
@@ -131,6 +138,8 @@ python3 .
* Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty
* openSUSE: https://build.opensuse.org/package/show/X11:terminals/kitty
=== macOS packages
kitty is available as a macOS `dmg` file for easy installation from the
@@ -199,6 +208,7 @@ windows are:
|Next layout | {sc_next_layout}
|Move tab forward | {sc_move_tab_forward}
|Move tab backward | {sc_move_tab_backward}
|Set tab title | {sc_set_tab_title}
|===
@@ -234,6 +244,8 @@ windows are:
|Input unicode character | {sc_input_unicode_character}
|Click URL using the keyboard | {sc_run_simple_kitten_text_url_hints}
|Pass current selection to program | {sc_pass_selection_to_program}
|Edit kitty config file | {sc_edit_config_file}
|Open a kitty shell | {sc_kitty_shell}
|===
@@ -241,10 +253,11 @@ windows are:
You can control kitty from the command line/scripts by sending it messages.
You can tell kitty to open/close/rename tabs and windows. You can even send
arbitrary input/text to any specified window. Messages can be sent using
`kitty @`. Note that you must set `allow_remote_control yes` in your
kitty.conf to use this feature. It even works over SSH connections. This
feature is best illustrated with a link:remote-control.asciidoc[tutorial].
arbitrary input/text to any specified window. Messages can be sent using `kitty
@`. Note that you must set `allow_remote_control yes` in your
link:kitty/kitty.conf[kitty.conf] to use this feature. It even works over SSH
connections. This feature is best illustrated with a
link:remote-control.asciidoc[tutorial].
== The scrollback buffer
@@ -267,17 +280,38 @@ Press {sc_input_unicode_character} to start the unicode input widget, shown belo
image::unicode.png?raw=true[Unicode input widget, showing selection of unicode characters by name]
== URL hints
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.
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.
In Name mode you instead type words from the character name and use the arrow
keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you dont like to use arrow
keys.
== Hints
kitty has a _hints mode_ to select and act on arbitrary text snippets currently
visible on the screen. For example, you can press {sc_run_kitten_text_hints}
to choose any URL visible on the screen and then open it using your system
browser.
image::hints_mode.png?raw=true[URL hints mode]
Similarly, you can press {sc_run_kitten_text_hints_type_path_program} to
select anything that looks like a path or filename and then insert it into the
terminal, very useful for picking files from the output of a git or ls command and
adding them to the command line for the next command.
The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, use: `kitty +kitten hints --help`.
== Miscellaneous features
You can double click to select a word and triple click to select a line. You can also
hold down `ctrl+shift` and click on a URL to open it in a browser.
* You can also hold down `ctrl+shift` and click on a URL to open it in a browser.
* You can double click to select a word and triple click to select a line.
* You can right click to extend a previous selection
== Layouts
@@ -290,7 +324,9 @@ Currently, there are five layouts available,
* Horizontal -- All windows are shown side-by-side
* Vertical -- All windows are shown one below the other
You can switch between layouts using the {sc_next_layout} key combination.
You can switch between layouts using the {sc_next_layout} key combination. You can
also create shortcuts to select particular layouts, and choose which layouts
you want to enable/disable, see link:kitty/kitty.conf[kitty.conf] for examples.
Writing a new layout only requires about fifty lines of code, so if there is
some layout you want, take a look at link:kitty/layout.py[layout.py] and submit
@@ -299,13 +335,18 @@ a pull request!
== Configuration
kitty is highly customizable, everything from keyboard shortcuts, to
painting frames-per-second. See the heavily commented
link:kitty/kitty.conf[default config file]. By default kitty looks for a
config file in the OS config directory (usually
`~/.config/kitty/kitty.conf` on Linux and
kitty is highly customizable, everything from keyboard shortcuts, to painting
frames-per-second. See the heavily commented link:kitty/kitty.conf[default
config file] for an overview of all customization possibilities.
By default kitty looks for a config file in the OS config directories (usually
`~/.config/kitty/kitty.conf` and additionally
`~/Library/Preferences/kitty/kitty.conf` on macOS) but you can pass a specific
path via the `--config` option.
path via the `--config` option or use the `KITTY_CONFIG_DIRECTORY` environment
variable. See the help for the `--config` option in `kitty --help` for full
details. You can also dump the current configuration using the `--debug-config`
option.
== Startup Sessions
@@ -422,6 +463,8 @@ installs the terminfo file and `kitty` that installs the main program.
This allows users to install the terminfo file on servers into which they ssh,
without needing to install all of kitty.
You also need `tic` to compile the terminfo files, it is usually found in the development package of `ncurses`
This applies to creating packages for kitty for macOS package managers such as
brew or MacPorts as well.
@@ -439,18 +482,16 @@ let the character overflow into neighboring cells, which is fine if the
neighboring cell is empty, but looks terrible if it is not.
Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
use unicode characters from the private use area to represent symbols. Often
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 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.
standard, kitty renders them either smaller or truncated. The exception is if
these characters are followed by a space or empty cell in which case kitty
makes use of the extra cell to render them in two cells.
=== How do I build kitty.app on macOS?
Install `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
And run:
```
@@ -481,19 +522,42 @@ 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?
=== 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:
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
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
....
Really, the correct solution for this is to convince the OpenSSH maintainers to have ssh do this
automatically when connecting to a server.
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 change the colors in a running kitty instance?
You can either use the
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands[OSC
terminal escape codes] to set colors or you can enable link:remote-control.asciidoc[remote control]
for kitty and use `kitty @ set-colors --help`.
=== How do I specify command line options for kitty on macOS?
Apple does not want you to use command line options with GUI applications. To
workaround that limitation, kitty will read command line options from the file
`<kitty config dir>/macos-launch-services-cmdline` when it is launched
from the GUI, i.e. by clicking the kitty application icon or using `open -a kitty`.
Note that this file is *only read* when running via the GUI.
You can, of course, also run kitty from a terminal with command line options, using:
`/Applications/kitty.app/Contents/MacOS/kitty`.
And within kitty itself, you can always run kitty using just `kitty` as it
cleverly adds itself to the PATH.
== Resources on terminal behavior

View File

@@ -31,6 +31,13 @@ def launch(args):
runpy.run_path(args[1], run_name='__main__')
def run_kitten(args):
kitten = args[1]
sys.argv = args[1:]
from kittens.runner import run_kitten
run_kitten(kitten)
def namespaced(args):
func = namespaced_entry_points[args[1]]
func(args[1:])
@@ -42,6 +49,7 @@ entry_points = {
'list-fonts': list_fonts,
'runpy': runpy,
'launch': launch,
'kitten': run_kitten,
'@': remote_control,
'+': namespaced,

View File

@@ -1,2 +1,2 @@
#!/bin/bash
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h\nkittens/unicode_input/names.h') kitty kittens

View File

@@ -69,6 +69,7 @@ static void changeToResourcesDirectory(void)
static void createKeyTables(void)
{
int scancode;
_glfw.ns.debug_keyboard = getenv("GLFW_DEBUG_KEYBOARD") != NULL;
memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes));
@@ -371,4 +372,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif
;
}

View File

@@ -355,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);

View File

@@ -114,6 +114,8 @@ typedef struct _GLFWlibraryNS
id listener;
char keyName[64];
char text[256];
GLFWbool debug_keyboard;
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
char* clipboardString;
@@ -166,4 +168,3 @@ void _glfwInitTimerNS(void);
void _glfwPollMonitorsNS(void);
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);

View File

@@ -183,10 +183,112 @@ static int translateFlags(NSUInteger flags)
return mods;
}
#define debug_key(...) if (_glfw.ns.debug_keyboard) NSLog(__VA_ARGS__)
static inline const char*
format_mods(int mods) {
static char buf[128];
char *p = buf, *s;
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x)
pr("mods: ");
s = p;
if (mods & GLFW_MOD_CONTROL) pr("ctrl+");
if (mods & GLFW_MOD_ALT) pr("alt+");
if (mods & GLFW_MOD_SHIFT) pr("shift+");
if (mods & GLFW_MOD_SUPER) pr("super+");
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
if (p == s) pr("none");
else p--;
pr(" ");
#undef pr
return buf;
}
static inline const char*
format_text(const char *src) {
static char buf[256];
char *p = buf;
if (!src[0]) return "<none>";
while (*src) {
p += snprintf(p, sizeof(buf) - (p - buf), "%x ", (unsigned char)*(src++));
}
if (p != buf) *(--p) = 0;
return buf;
}
static const char*
safe_name_for_scancode(unsigned int scancode) {
const char *ans = _glfwPlatformGetScancodeName(scancode);
if (!ans) return "<noname>";
if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>";
return ans;
}
// Translates a macOS keycode to a GLFW keycode
//
static int translateKey(unsigned int key)
static int translateKey(unsigned int key, GLFWbool apply_keymap)
{
if (apply_keymap) {
// Look for the effective key name after applying any keyboard layouts/mappings
const char *name = _glfwPlatformGetScancodeName(key);
if (name && name[1] == 0) {
// Single letter key name
switch(name[0]) {
#define K(ch, name) case ch: return GLFW_KEY_##name
K('A', A); K('a', A);
K('B', B); K('b', B);
K('C', C); K('c', C);
K('D', D); K('d', D);
K('E', E); K('e', E);
K('F', F); K('f', F);
K('G', G); K('g', G);
K('H', H); K('h', H);
K('I', I); K('i', I);
K('J', J); K('j', J);
K('K', K); K('k', K);
K('L', L); K('l', L);
K('M', M); K('m', M);
K('N', N); K('n', N);
K('O', O); K('o', O);
K('P', P); K('p', P);
K('Q', Q); K('q', Q);
K('R', R); K('r', R);
K('S', S); K('s', S);
K('T', T); K('t', T);
K('U', U); K('u', U);
K('V', V); K('v', V);
K('W', W); K('w', W);
K('X', X); K('x', X);
K('Y', Y); K('y', Y);
K('Z', Z); K('z', Z);
K('0', 0);
K('1', 1);
K('2', 2);
K('3', 3);
K('5', 5);
K('6', 6);
K('7', 7);
K('8', 8);
K('9', 9);
K('\'', APOSTROPHE);
K(',', COMMA);
K('.', PERIOD);
K('/', SLASH);
K('-', MINUS);
K('=', EQUAL);
K(';', SEMICOLON);
K('[', LEFT_BRACKET);
K(']', RIGHT_BRACKET);
K('`', GRAVE_ACCENT);
K('\\', BACKSLASH);
#undef K
default:
break;
}
}
}
if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0]))
return GLFW_KEY_UNKNOWN;
@@ -582,6 +684,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
window->ns.xscale = xscale;
window->ns.yscale = yscale;
_glfwInputWindowContentScale(window, xscale, yscale);
if (window->ns.layer)
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
}
}
@@ -616,12 +721,17 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (void)keyDown:(NSEvent *)event
{
const int key = translateKey([event keyCode]);
const unsigned int scancode = [event keyCode];
const int key = translateKey(scancode, GLFW_TRUE);
const int mods = translateFlags([event modifierFlags]);
_glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
_glfw.ns.text[0] = 0;
// this will call insertText with the text for this event, if any
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
if ((1 <= _glfw.ns.text[0] && _glfw.ns.text[0] <= 31) || (unsigned)_glfw.ns.text[0] == 127) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
debug_key(@"scancode: 0x%x (%s) mods: %stext: %s glfw_key: %s\n",
scancode, safe_name_for_scancode(scancode), format_mods(mods),
format_text(_glfw.ns.text), _glfwGetKeyName(key));
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
}
- (void)flagsChanged:(NSEvent *)event
@@ -629,7 +739,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
int action;
const unsigned int modifierFlags =
[event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
const int key = translateKey([event keyCode]);
const int key = translateKey([event keyCode], GLFW_FALSE);
const int mods = translateFlags(modifierFlags);
const NSUInteger keyFlag = translateKeyToModifierFlag(key);
@@ -643,14 +753,14 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
else
action = GLFW_RELEASE;
_glfwInputKey(window, key, [event keyCode], action, mods);
_glfwInputKeyboard(window, key, [event keyCode], action, mods, "", 0);
}
- (void)keyUp:(NSEvent *)event
{
const int key = translateKey([event keyCode]);
const int key = translateKey([event keyCode], GLFW_TRUE);
const int mods = translateFlags([event modifierFlags]);
_glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods);
_glfwInputKeyboard(window, key, [event keyCode], GLFW_RELEASE, mods, "", 0);
}
- (void)scrollWheel:(NSEvent *)event
@@ -785,25 +895,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
NSString* characters;
NSEvent* event = [NSApp currentEvent];
const int mods = translateFlags([event modifierFlags]);
const int plain = !(mods & GLFW_MOD_SUPER);
if ([string isKindOfClass:[NSAttributedString class]])
characters = [string string];
else
characters = (NSString*) string;
NSUInteger i, length = [characters length];
for (i = 0; i < length; i++)
{
const unichar codepoint = [characters characterAtIndex:i];
if ((codepoint & 0xff00) == 0xf700)
continue;
_glfwInputChar(window, codepoint, mods, plain);
}
snprintf(_glfw.ns.text, sizeof(_glfw.ns.text), "%s", [characters UTF8String]);
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
}
- (void)doCommandBySelector:(SEL)selector
@@ -1358,7 +1455,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
[NSApp requestUserAttention:NSInformationalRequest];
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
NSBeep();
return GLFW_TRUE;
@@ -1534,6 +1631,9 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
void _glfwPlatformPollEvents(void)
{
if (!initializeAppKit())
return;
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
@@ -1868,6 +1968,7 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
[window->ns.view setWantsLayer:YES];
memset(&sci, 0, sizeof(sci));
@@ -1899,4 +2000,3 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
return window->ns.object;
}

View File

@@ -10,6 +10,10 @@ import sys
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat
is_freebsd = 'freebsd' in _plat
is_netbsd = 'netbsd' in _plat
is_dragonflybsd = 'dragonfly' in _plat
is_bsd = is_freebsd or is_netbsd or is_dragonflybsd
base = os.path.dirname(os.path.abspath(__file__))
@@ -42,6 +46,9 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
ans.sources = sinfo['common']['sources'] + sinfo[module]['sources']
ans.all_headers = [x for x in os.listdir(base) if x.endswith('.h')]
if module in ('x11', 'wayland'):
at_least_version('xkbcommon', 0, 5)
if module == 'x11':
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11'.split():
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
@@ -100,13 +107,14 @@ def collect_source_information():
'common': dict(extract_sources('common')),
'wayland_protocols': wayland_protocols,
}
joystick = 'null' if is_bsd else 'linux'
for group in 'cocoa win32 x11 wayland osmesa'.split():
m = re.search('_GLFW_' + group.upper(), raw)
ans[group] = dict(extract_sources('glfw', m.start()))
if group == 'x11':
ans[group]['headers'].append('linux_joystick.h')
ans[group]['sources'].append('linux_joystick.c')
elif group == 'wayland':
if group in ('x11', 'wayland'):
ans[group]['headers'].append('{}_joystick.h'.format(joystick))
ans[group]['sources'].append('{}_joystick.c'.format(joystick))
if group == 'wayland':
ans[group]['protocols'] = p = []
for m in re.finditer(r'WAYLAND_PROTOCOLS_PKGDATADIR\}/(.+?)"?$', raw, flags=re.M):
p.append(m.group(1))

202
glfw/glfw3.h vendored
View File

@@ -1375,9 +1375,15 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
*/
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
/*! @brief The function signature for keyboard key callbacks.
/*! @brief The function signature for key callbacks.
*
* This is the function signature for keyboard key callback functions.
* This is the function signature for key callback functions.
* The semantics of this function are that the key that is interacted with on the
* keyboard is reported, and the text, if any generated by the key is reported.
* So, for example, if on a US-ASCII keyboard the user presses Shift+= GLFW
* will report the text "+" and the key as GLFW_KEY_EQUAL. The reported key takes into
* account any current keyboard maps defined in the OS. So with a dvorak mapping, pressing
* the "s" key will generate text "o" and GLFW_KEY_O.
*
* @param[in] window The window that received the event.
* @param[in] key The [keyboard key](@ref keys) that was pressed or released.
@@ -1385,56 +1391,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
* @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
* @param[in] reserved Reserved for future use.
*
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
* than the unshifted key. So for example, if ISO_Shift_Level_5 is used to
* convert the key A into UP GLFW will report the key as UP with no modifiers.
*
* @sa @ref input_key
* @sa @ref glfwSetKeyCallback
* @sa @ref glfwSetKeyboardCallback
*
* @since Added in version 1.0.
* @glfw3 Added window handle, scancode and modifier mask parameters.
* @since Added in version 4.0.
*
* @ingroup input
*/
typedef void (* GLFWkeyfun)(GLFWwindow*,int,int,int,int);
/*! @brief The function signature for Unicode character callbacks.
*
* This is the function signature for Unicode character callback functions.
*
* @param[in] window The window that received the event.
* @param[in] codepoint The Unicode code point of the character.
*
* @sa @ref input_char
* @sa @ref glfwSetCharCallback
*
* @since Added in version 2.4.
* @glfw3 Added window handle parameter.
*
* @ingroup input
*/
typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int);
/*! @brief The function signature for Unicode character with modifiers
* callbacks.
*
* This is the function signature for Unicode character with modifiers callback
* functions. It is called for each input character, regardless of what
* modifier keys are held down.
*
* @param[in] window The window that received the event.
* @param[in] codepoint The Unicode code point of the character.
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
*
* @sa @ref input_char
* @sa @ref glfwSetCharModsCallback
*
* @deprecated Scheduled for removal in version 4.0.
*
* @since Added in version 3.1.
*
* @ingroup input
*/
typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int);
typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, int);
/*! @brief The function signature for file drop callbacks.
*
@@ -2453,10 +2425,11 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
* GLFW_X11_CLASS_NAME and @ref GLFW_X11_INSTANCE_NAME window hints to override
* this.
*
* @remark @wayland The window frame is currently unimplemented, as if
* [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`.
* A compositor can still emit close, resize or maximize events, using for
* example a keybind mechanism.
* @remark @wayland The window frame is currently very simple, only allowing
* window resize or move. A compositor can still emit close, maximize or
* fullscreen events, using for example a keybind mechanism. Additionally,
* the wp_viewporter protocol is required for this feature, otherwise the
* window will not be decorated.
*
* @remark @wayland A full screen window will not attempt to change the mode,
* no matter what the requested size or refresh rate.
@@ -2898,10 +2871,6 @@ GLFWAPI void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height)
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @remark @wayland The window frame is currently unimplemented, as if
* [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`,
* so the returned values will always be zero.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_size
@@ -3191,13 +3160,9 @@ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* window);
/*! @brief Sounds an audible bell associated with the window
*
* This function sounds an audible bell, on platforms where it is
* supported. Currently (macOS, Windows and X11).
* supported. Currently (macOS, Windows, X11 and Wayland).
*
* @param[in] window The window with which the bell is associated.
* @param[in] param The meaning of this parameter is platform dependent. On
* X11 it corresponds to the percentage controlling bell volume (see man
* XBell). On Windows it is the type of sound to make, see the MSDN docs for
* MessageBeep. On macOS, it is ignored.
* @return GLFW_TRUE if the bell succeeded otherwise GLFW_FALSE
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
@@ -3212,7 +3177,7 @@ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* window);
*
* @ingroup window
*/
GLFWAPI int glfwWindowBell(GLFWwindow* window, int64_t param);
GLFWAPI int glfwWindowBell(GLFWwindow* window);
/*! @brief Returns the monitor that the window uses for full screen mode.
@@ -4239,121 +4204,11 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor);
*/
GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
/*! @brief Sets the key callback.
*
* This function sets the key callback of the specified window, which is called
* when a key is pressed, repeated or released.
*
* The key functions deal with physical keys, with layout independent
* [key tokens](@ref keys) named after their values in the standard US keyboard
* layout. If you want to input text, use the
* [character callback](@ref glfwSetCharCallback) instead.
*
* When a window loses input focus, it will generate synthetic key release
* events for all pressed keys. You can tell these events from user-generated
* events by the fact that the synthetic ones are generated after the focus
* loss event has been processed, i.e. after the
* [window focus callback](@ref glfwSetWindowFocusCallback) has been called.
*
* The scancode of a key is specific to that platform or sometimes even to that
* machine. Scancodes are intended to allow users to bind keys that don't have
* a GLFW key token. Such keys have `key` set to `GLFW_KEY_UNKNOWN`, their
* state is not saved and so it cannot be queried with @ref glfwGetKey.
*
* Sometimes GLFW needs to generate synthetic key events, in which case the
* scancode may be zero.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new key callback, or `NULL` to remove the currently
* set callback.
* @return The previously set callback, or `NULL` if no callback was set or the
* library had not been [initialized](@ref intro_init).
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_key
*
* @since Added in version 1.0.
* @glfw3 Added window handle parameter and return value.
/*! @brief Sets the callback for handling keyboard events.
*
* @ingroup input
*/
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* window, GLFWkeyfun cbfun);
/*! @brief Sets the Unicode character callback.
*
* This function sets the character callback of the specified window, which is
* called when a Unicode character is input.
*
* The character callback is intended for Unicode text input. As it deals with
* characters, it is keyboard layout dependent, whereas the
* [key callback](@ref glfwSetKeyCallback) is not. Characters do not map 1:1
* to physical keys, as a key may produce zero, one or more characters. If you
* want to know whether a specific physical key was pressed or released, see
* the key callback instead.
*
* The character callback behaves as system text input normally does and will
* not be called if modifier keys are held down that would prevent normal text
* input on that platform, for example a Super (Command) key on macOS or Alt key
* on Windows. There is a
* [character with modifiers callback](@ref glfwSetCharModsCallback) that
* receives these events.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or the
* library had not been [initialized](@ref intro_init).
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in version 2.4.
* @glfw3 Added window handle parameter and return value.
*
* @ingroup input
*/
GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun);
/*! @brief Sets the Unicode character with modifiers callback.
*
* This function sets the character with modifiers callback of the specified
* window, which is called when a Unicode character is input regardless of what
* modifier keys are used.
*
* The character with modifiers callback is intended for implementing custom
* Unicode character input. For regular Unicode text input, see the
* [character callback](@ref glfwSetCharCallback). Like the character
* callback, the character with modifiers callback deals with characters and is
* keyboard layout dependent. Characters do not map 1:1 to physical keys, as
* a key may produce zero, one or more characters. If you want to know whether
* a specific physical key was pressed or released, see the
* [key callback](@ref glfwSetKeyCallback) instead.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or an
* [error](@ref error_handling) occurred.
*
* @deprecated Scheduled for removal in version 4.0.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in version 3.1.
*
* @ingroup input
*/
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun);
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
/*! @brief Sets the mouse button callback.
*
@@ -4904,7 +4759,7 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
*
* Not all devices have all the buttons or axes provided by @ref
* GLFWgamepadstate. Unavailable buttons and axes will always report
* `GLFW_RELEASE` and 1.0 respectively.
* `GLFW_RELEASE` and 0.0 respectively.
*
* @param[in] jid The [joystick](@ref joysticks) to query.
* @param[out] state The gamepad input state of the joystick.
@@ -5549,4 +5404,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window
#endif
#endif /* _glfw3_h_ */

272
glfw/input.c vendored
View File

@@ -62,11 +62,11 @@ static _GLFWmapping* findMapping(const char* guid)
static GLFWbool isValidElementForJoystick(const _GLFWmapelement* e,
const _GLFWjoystick* js)
{
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->value >> 4) >= js->hatCount)
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->index >> 4) >= js->hatCount)
return GLFW_FALSE;
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->value >= js->buttonCount)
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->index >= js->buttonCount)
return GLFW_FALSE;
else if (e->type == _GLFW_JOYSTICK_AXIS && e->value >= js->axisCount)
else if (e->type == _GLFW_JOYSTICK_AXIS && e->index >= js->axisCount)
return GLFW_FALSE;
return GLFW_TRUE;
@@ -167,6 +167,10 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
while (*c)
{
// TODO: Implement output modifiers
if (*c == '+' || *c == '-')
return GLFW_FALSE;
for (i = 0; i < sizeof(fields) / sizeof(fields[0]); i++)
{
length = strlen(fields[i].name);
@@ -177,23 +181,50 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
if (fields[i].element)
{
_GLFWmapelement* e = fields[i].element;
int8_t minimum = -1;
int8_t maximum = 1;
if (*c == '+')
{
minimum = 0;
c += 1;
}
else if (*c == '-')
{
maximum = 0;
c += 1;
}
if (*c == 'a')
fields[i].element->type = _GLFW_JOYSTICK_AXIS;
e->type = _GLFW_JOYSTICK_AXIS;
else if (*c == 'b')
fields[i].element->type = _GLFW_JOYSTICK_BUTTON;
e->type = _GLFW_JOYSTICK_BUTTON;
else if (*c == 'h')
fields[i].element->type = _GLFW_JOYSTICK_HATBIT;
e->type = _GLFW_JOYSTICK_HATBIT;
else
break;
if (fields[i].element->type == _GLFW_JOYSTICK_HATBIT)
if (e->type == _GLFW_JOYSTICK_HATBIT)
{
const unsigned long hat = strtoul(c + 1, (char**) &c, 10);
const unsigned long bit = strtoul(c + 1, (char**) &c, 10);
fields[i].element->value = (uint8_t) ((hat << 4) | bit);
e->index = (uint8_t) ((hat << 4) | bit);
}
else
fields[i].element->value = (uint8_t) strtoul(c + 1, (char**) &c, 10);
e->index = (uint8_t) strtoul(c + 1, (char**) &c, 10);
if (e->type == _GLFW_JOYSTICK_AXIS)
{
e->axisScale = 2 / (maximum - minimum);
e->axisOffset = -(maximum + minimum);
if (*c == '~')
{
e->axisScale = -e->axisScale;
e->axisOffset = -e->axisOffset;
}
}
}
else
{
@@ -224,9 +255,9 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
// Notifies shared code of a physical key event
// Notifies shared code of a key event
//
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods)
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state)
{
if (key >= 0 && key <= GLFW_KEY_LAST)
{
@@ -247,31 +278,10 @@ void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int m
action = GLFW_REPEAT;
}
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.key)
window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods);
}
// Notifies shared code of a Unicode codepoint input event
// The 'plain' parameter determines whether to emit a regular character event
//
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain)
{
if (codepoint < 32 || (codepoint > 126 && codepoint < 160))
return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.charmods)
window->callbacks.charmods((GLFWwindow*) window, codepoint, mods);
if (plain)
{
if (window->callbacks.character)
window->callbacks.character((GLFWwindow*) window, codepoint);
if (window->callbacks.keyboard) {
if (!window->lockKeyMods) mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
window->callbacks.keyboard((GLFWwindow*) window, key, scancode, action, mods, text, state);
}
}
@@ -423,6 +433,137 @@ void _glfwFreeJoystick(_GLFWjoystick* js)
memset(js, 0, sizeof(_GLFWjoystick));
}
const char* _glfwGetKeyName(int key)
{
switch (key)
{
// Printable keys
case GLFW_KEY_A: return "A";
case GLFW_KEY_B: return "B";
case GLFW_KEY_C: return "C";
case GLFW_KEY_D: return "D";
case GLFW_KEY_E: return "E";
case GLFW_KEY_F: return "F";
case GLFW_KEY_G: return "G";
case GLFW_KEY_H: return "H";
case GLFW_KEY_I: return "I";
case GLFW_KEY_J: return "J";
case GLFW_KEY_K: return "K";
case GLFW_KEY_L: return "L";
case GLFW_KEY_M: return "M";
case GLFW_KEY_N: return "N";
case GLFW_KEY_O: return "O";
case GLFW_KEY_P: return "P";
case GLFW_KEY_Q: return "Q";
case GLFW_KEY_R: return "R";
case GLFW_KEY_S: return "S";
case GLFW_KEY_T: return "T";
case GLFW_KEY_U: return "U";
case GLFW_KEY_V: return "V";
case GLFW_KEY_W: return "W";
case GLFW_KEY_X: return "X";
case GLFW_KEY_Y: return "Y";
case GLFW_KEY_Z: return "Z";
case GLFW_KEY_1: return "1";
case GLFW_KEY_2: return "2";
case GLFW_KEY_3: return "3";
case GLFW_KEY_4: return "4";
case GLFW_KEY_5: return "5";
case GLFW_KEY_6: return "6";
case GLFW_KEY_7: return "7";
case GLFW_KEY_8: return "8";
case GLFW_KEY_9: return "9";
case GLFW_KEY_0: return "0";
case GLFW_KEY_SPACE: return "SPACE";
case GLFW_KEY_MINUS: return "MINUS";
case GLFW_KEY_EQUAL: return "EQUAL";
case GLFW_KEY_LEFT_BRACKET: return "LEFT BRACKET";
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT BRACKET";
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
case GLFW_KEY_SEMICOLON: return "SEMICOLON";
case GLFW_KEY_APOSTROPHE: return "APOSTROPHE";
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE ACCENT";
case GLFW_KEY_COMMA: return "COMMA";
case GLFW_KEY_PERIOD: return "PERIOD";
case GLFW_KEY_SLASH: return "SLASH";
case GLFW_KEY_WORLD_1: return "WORLD 1";
case GLFW_KEY_WORLD_2: return "WORLD 2";
// Function keys
case GLFW_KEY_ESCAPE: return "ESCAPE";
case GLFW_KEY_F1: return "F1";
case GLFW_KEY_F2: return "F2";
case GLFW_KEY_F3: return "F3";
case GLFW_KEY_F4: return "F4";
case GLFW_KEY_F5: return "F5";
case GLFW_KEY_F6: return "F6";
case GLFW_KEY_F7: return "F7";
case GLFW_KEY_F8: return "F8";
case GLFW_KEY_F9: return "F9";
case GLFW_KEY_F10: return "F10";
case GLFW_KEY_F11: return "F11";
case GLFW_KEY_F12: return "F12";
case GLFW_KEY_F13: return "F13";
case GLFW_KEY_F14: return "F14";
case GLFW_KEY_F15: return "F15";
case GLFW_KEY_F16: return "F16";
case GLFW_KEY_F17: return "F17";
case GLFW_KEY_F18: return "F18";
case GLFW_KEY_F19: return "F19";
case GLFW_KEY_F20: return "F20";
case GLFW_KEY_F21: return "F21";
case GLFW_KEY_F22: return "F22";
case GLFW_KEY_F23: return "F23";
case GLFW_KEY_F24: return "F24";
case GLFW_KEY_F25: return "F25";
case GLFW_KEY_UP: return "UP";
case GLFW_KEY_DOWN: return "DOWN";
case GLFW_KEY_LEFT: return "LEFT";
case GLFW_KEY_RIGHT: return "RIGHT";
case GLFW_KEY_LEFT_SHIFT: return "LEFT SHIFT";
case GLFW_KEY_RIGHT_SHIFT: return "RIGHT SHIFT";
case GLFW_KEY_LEFT_CONTROL: return "LEFT CONTROL";
case GLFW_KEY_RIGHT_CONTROL: return "RIGHT CONTROL";
case GLFW_KEY_LEFT_ALT: return "LEFT ALT";
case GLFW_KEY_RIGHT_ALT: return "RIGHT ALT";
case GLFW_KEY_TAB: return "TAB";
case GLFW_KEY_ENTER: return "ENTER";
case GLFW_KEY_BACKSPACE: return "BACKSPACE";
case GLFW_KEY_INSERT: return "INSERT";
case GLFW_KEY_DELETE: return "DELETE";
case GLFW_KEY_PAGE_UP: return "PAGE UP";
case GLFW_KEY_PAGE_DOWN: return "PAGE DOWN";
case GLFW_KEY_HOME: return "HOME";
case GLFW_KEY_END: return "END";
case GLFW_KEY_KP_0: return "KEYPAD 0";
case GLFW_KEY_KP_1: return "KEYPAD 1";
case GLFW_KEY_KP_2: return "KEYPAD 2";
case GLFW_KEY_KP_3: return "KEYPAD 3";
case GLFW_KEY_KP_4: return "KEYPAD 4";
case GLFW_KEY_KP_5: return "KEYPAD 5";
case GLFW_KEY_KP_6: return "KEYPAD 6";
case GLFW_KEY_KP_7: return "KEYPAD 7";
case GLFW_KEY_KP_8: return "KEYPAD 8";
case GLFW_KEY_KP_9: return "KEYPAD 9";
case GLFW_KEY_KP_DIVIDE: return "KEYPAD DIVIDE";
case GLFW_KEY_KP_MULTIPLY: return "KEYPAD MULTPLY";
case GLFW_KEY_KP_SUBTRACT: return "KEYPAD SUBTRACT";
case GLFW_KEY_KP_ADD: return "KEYPAD ADD";
case GLFW_KEY_KP_DECIMAL: return "KEYPAD DECIMAL";
case GLFW_KEY_KP_EQUAL: return "KEYPAD EQUAL";
case GLFW_KEY_KP_ENTER: return "KEYPAD ENTER";
case GLFW_KEY_PRINT_SCREEN: return "PRINT SCREEN";
case GLFW_KEY_NUM_LOCK: return "NUM LOCK";
case GLFW_KEY_CAPS_LOCK: return "CAPS LOCK";
case GLFW_KEY_SCROLL_LOCK: return "SCROLL LOCK";
case GLFW_KEY_PAUSE: return "PAUSE";
case GLFW_KEY_LEFT_SUPER: return "LEFT SUPER";
case GLFW_KEY_RIGHT_SUPER: return "RIGHT SUPER";
case GLFW_KEY_MENU: return "MENU";
default: return "UNKNOWN";
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW public API //////
@@ -479,7 +620,9 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
&window->virtualCursorPosX,
&window->virtualCursorPosY);
#ifdef __APPLE__
if (_glfwPlatformWindowFocused(window))
#endif
_glfwPlatformSetCursorMode(window, value);
}
else if (mode == GLFW_STICKY_KEYS)
@@ -760,33 +903,13 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
_glfwPlatformSetCursor(window, cursor);
}
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboardfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.key, cbfun);
return cbfun;
}
GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* handle, GLFWcharfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.character, cbfun);
return cbfun;
}
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmodsfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.charmods, cbfun);
_GLFW_SWAP_POINTERS(window->callbacks.keyboard, cbfun);
return cbfun;
}
@@ -1188,35 +1311,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 (fabsf(js->axes[js->mapping->buttons[i].value]) > 0.5f)
const float value = js->axes[e->index] * e->axisScale + e->axisOffset;
if (value > 0.f)
state->buttons[i] = GLFW_PRESS;
}
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
else if (e->type == _GLFW_JOYSTICK_HATBIT)
{
const unsigned int hat = js->mapping->buttons[i].value >> 4;
const unsigned int bit = js->mapping->buttons[i].value & 0xf;
const unsigned int hat = e->index >> 4;
const unsigned int bit = e->index & 0xf;
if (js->hats[hat] & bit)
state->buttons[i] = GLFW_PRESS;
}
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_BUTTON)
state->buttons[i] = js->buttons[js->mapping->buttons[i].value];
else if (e->type == _GLFW_JOYSTICK_BUTTON)
state->buttons[i] = js->buttons[e->index];
}
for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++)
{
if (js->mapping->axes[i].type == _GLFW_JOYSTICK_AXIS)
state->axes[i] = js->axes[js->mapping->axes[i].value];
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
const _GLFWmapelement* e = js->mapping->axes + i;
if (e->type == _GLFW_JOYSTICK_AXIS)
{
const unsigned int hat = js->mapping->axes[i].value >> 4;
const unsigned int bit = js->mapping->axes[i].value & 0xf;
const float value = js->axes[e->index] * e->axisScale + e->axisOffset;
state->axes[i] = fminf(fmaxf(value, -1.f), 1.f);
}
else if (e->type == _GLFW_JOYSTICK_HATBIT)
{
const unsigned int hat = e->index >> 4;
const unsigned int bit = e->index & 0xf;
if (js->hats[hat] & bit)
state->axes[i] = 1.f;
}
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_BUTTON)
state->axes[i] = (float) js->buttons[js->mapping->axes[i].value];
else if (e->type == _GLFW_JOYSTICK_BUTTON)
state->axes[i] = (float) js->buttons[e->index];
}
return GLFW_TRUE;
@@ -1268,4 +1397,3 @@ GLFWAPI uint64_t glfwGetTimerFrequency(void)
_GLFW_REQUIRE_INIT_OR_RETURN(0);
return _glfwPlatformGetTimerFrequency();
}

20
glfw/internal.h vendored
View File

@@ -128,7 +128,7 @@ typedef enum VkStructureType
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000053000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkStructureType;
@@ -407,9 +407,7 @@ struct _GLFWwindow
GLFWcursorposfun cursorPos;
GLFWcursorenterfun cursorEnter;
GLFWscrollfun scroll;
GLFWkeyfun key;
GLFWcharfun character;
GLFWcharmodsfun charmods;
GLFWkeyboardfun keyboard;
GLFWdropfun drop;
} callbacks;
@@ -456,7 +454,9 @@ struct _GLFWcursor
struct _GLFWmapelement
{
uint8_t type;
uint8_t value;
uint8_t index;
int8_t axisScale;
int8_t axisOffset;
};
// Gamepad mapping structure
@@ -605,6 +605,7 @@ 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);
@@ -650,7 +651,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
void _glfwPlatformShowWindow(_GLFWwindow* window);
void _glfwPlatformHideWindow(_GLFWwindow* window);
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t);
int _glfwPlatformWindowBell(_GLFWwindow* window);
void _glfwPlatformFocusWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor,
int xpos, int ypos, int width, int height,
@@ -708,10 +709,7 @@ void _glfwInputWindowDamage(_GLFWwindow* window);
void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKey(_GLFWwindow* window,
int key, int scancode, int action, int mods);
void _glfwInputChar(_GLFWwindow* window,
unsigned int codepoint, int mods, GLFWbool plain);
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
@@ -760,10 +758,10 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name,
int buttonCount,
int hatCount);
void _glfwFreeJoystick(_GLFWjoystick* js);
const char* _glfwGetKeyName(int key);
GLFWbool _glfwInitVulkan(int mode);
void _glfwTerminateVulkan(void);
const char* _glfwGetVulkanResultString(VkResult result);
char* _glfw_strdup(const char* source);

624
glfw/mappings.h vendored
View File

@@ -60,243 +60,411 @@
const char* _glfwDefaultMappings[] =
{
"02200090000000000000504944564944,8Bitdo NES30 PRO USB,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
"20380900000000000000504944564944,8Bitdo NES30 PRO Wireless,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
"10280900000000000000504944564944,8Bitdo SFC30 GamePad,a:b1,b:b0,y:b3,x:b4,start:b11,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,platform:Windows,",
"8f0e1200000000000000504944564944,Acme,platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"c0111352000000000000504944564944,Battalife Joystick,platform:Windows,x:b4,a:b6,b:b7,y:b5,back:b2,start:b3,leftshoulder:b0,rightshoulder:b1,leftx:a0,lefty:a1,",
"d81d0b00000000000000504944564944,BUFFALO BSGP1601 Series ,platform:Windows,x:b4,a:b5,b:b3,y:b2,back:b12,start:b13,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b6,rightshoulder:b9,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"5e048e02000000000000504944564944,Controller (XBOX 360 For Windows),platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,righttrigger:a2,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"4f0423b3000000000000504944564944,Dual Trigger 3-in-1,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"341a0108000000000000504944564944,EXEQ RF USB Gamepad 8206,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,leftstick:b8,rightstick:b7,back:b8,start:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows,",
"0d0f8500000000000000504944564944,Fighting Commander 2016 PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f5f00000000000000504944564944,Fighting Commander 4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f5e00000000000000504944564944,Fighting Commander 4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a4,righttrigger:a3,platform:Windows,",
"0d0f8400000000000000504944564944,Fighting Commander 5,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f8700000000000000504944564944,Fighting Stick mini 4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f8800000000000000504944564944,Fighting Stick mini 4,a:b1,b:b2,x:b0,y:b3,back:b9,guide:b12,start:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f2700000000000000504944564944,FIGHTING STICK V3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"79000600000000000000504944564944,G-Shark GS-GP702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"28040140000000000000504944564944,GamePad Pro USB,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7,",
"ffff0000000000000000504944564944,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"6d0416c2000000000000504944564944,Generic DirectInput Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"45130010000000000000504944564944,Generic USB Joystick,a:b0,b:b1,x:b2,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f4900000000000000504944564944,Hatsune Miku Sho Controller,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"d8140862000000000000504944564944,HitBox Edition Cthulhu+,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b5,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b4,righttrigger:b6,platform:Windows,",
"0d0f4000000000000000504944564944,Hori Fighting Stick Mini 3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b5,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b4,righttrigger:b6,platform:Windows,",
"0d0f6e00000000000000504944564944,HORIPAD 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f4d00000000000000504944564944,HORIPAD3 A,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"25090017000000000000504944564944,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b5,rightshoulder:b7,leftx:a0,lefty:a1,lefttrigger:b4,righttrigger:b6,platform:Windows,",
"d81d0f00000000000000504944564944,iBUFFALO BSGP1204 Series,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"d81d1000000000000000504944564944,iBUFFALO BSGP1204P Series,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"83056020000000000000504944564944,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,y:b2,x:b3,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Windows,",
"6f0e2401000000000000504944564944,INJUSTICE FightStick for PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Windows",
"49190204000000000000504944564944,Ipega PG-9023,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Windows,",
"6d0419c2000000000000504944564944,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"6d0418c2000000000000504944564944,Logitech RumblePad 2 USB,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"38075032000000000000504944564944,Mad Catz FightPad PRO PS3,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"38075082000000000000504944564944,Mad Catz FightPad PRO PS4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b13,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
"38078433000000000000504944564944,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"38078483000000000000504944564944,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:b6,platform:Windows,",
"38078134000000000000504944564944,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b7,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b4,platform:Windows,",
"38078184000000000000504944564944,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a4,righttrigger:b7,platform:Windows,",
"38078034000000000000504944564944,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"38078084000000000000504944564944,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
"38078532000000000000504944564944,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"38073888000000000000504944564944,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"38071888000000000000504944564944,MadCatz SFIV FightStick PS3,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b7,righttrigger:b6,platform:Windows,",
"03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"25090128000000000000504944564944,Mayflash Arcade Stick,a:b1,b:b2,x:b5,y:b6,back:b8,start:b9,leftshoulder:b0,rightshoulder:b3,leftx:a0,lefty:a1,rightx:h0.4,righty:h0.0,lefttrigger:b4,righttrigger:b7,platform:Windows,",
"79004318000000000000504944564944,Mayflash GameCube Controller Adapter,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b0,start:b9,guide:b0,leftshoulder:b4,rightshoulder:b7,leftstick:b0,rightstick:b0,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
"8f0e1030000000000000504944564944,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,x:b3,y:b4,start:b9,leftshoulder:b6,rightshoulder:b2,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b5,righttrigger:b7,platform:Windows,",
"2509e803000000000000504944564944,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"79000018000000000000504944564944,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"8f0e0d31000000000000504944564944,Multilaser JS071 USB,platform:Windows,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
"100801e5000000000000504944564944,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Windows,",
"bd1215d0000000000000504944564944,Nintendo Retrolink USB Super SNES Classic Controller,y:b0,b:b1,a:b2,x:b3,leftshoulder:b4,rightshoulder:b5,start:b9,back:b8,leftx:a0,lefty:a1,platform:Windows,",
"4b12014d000000000000504944564944,NYKO AIRFLO,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftstick:a0,rightstick:a2,leftshoulder:a3,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:h0.6,lefty:h0.12,rightx:h0.9,righty:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"36280100000000000000504944564944,OUYA Controller,platform:Windows,a:b0,b:b3,y:b2,x:b1,start:b14,guide:b15,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b8,dpleft:b10,dpdown:b9,dpright:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b12,righttrigger:b13,",
"120cf60e000000000000504944564944,P4 Wired Gamepad,a:b1,b:b2,x:b0,y:b3,back:b12,guide:b8,start:b9,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:h0.0,lefttrigger:b7,righttrigger:b6,platform:Windows,",
"8f0e0300000000000000504944564944,Piranha xtreme,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"d6206dca000000000000504944564944,PowerA Pro Ex,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"10080100000000000000504944564944,PS1 USB,platform:Windows,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
"10080300000000000000504944564944,PS2 USB,platform:Windows,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a4,righty:a2,lefttrigger:b4,righttrigger:b5,",
"88880803000000000000504944564944,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,",
"4c056802000000000000504944564944,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,",
"25090500000000000000504944564944,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,",
"10008200000000000000504944564944,PS360+ v1.66,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"4c05c405000000000000504944564944,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"300f0011000000000000504944564944,QanBa Arcade JoyStick 1008,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b10,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,",
"300f1611000000000000504944564944,QanBa Arcade JoyStick 4018,a:b1,b:b2,x:b0,y:b3,back:b10,guide:b9,start:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"222c0020000000000000504944564944,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:a3,righttrigger:a4,platform:Windows,",
"300f1210000000000000504944564944,QanBa Joystick Plus,a:b0,b:b1,x:b2,y:b3,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,platform:Windows,",
"341a0104000000000000504944564944,QanBa Joystick Q4RAF,a:b5,b:b6,x:b1,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b0,rightshoulder:b3,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,lefttrigger:b4,righttrigger:b7,platform:Windows,",
"222c0223000000000000504944564944,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"222c0023000000000000504944564944,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,x:b0,y:b3,back:b13,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
"0d0f1100000000000000504944564944,REAL ARCADE PRO.3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f8b00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"0d0f8a00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
"0d0f6b00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"0d0f6a00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
"0d0f7000000000000000504944564944,REAL ARCADE PRO.4 VLX,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"0d0f2200000000000000504944564944,REAL ARCADE Pro.V3,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"00f00300000000000000504944564944,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows,",
"00f0f100000000000000504944564944,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows,",
"6f0e1e01000000000000504944564944,Rock Candy Gamepad for PS3,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
"300f1201000000000000504944564944,Saitek Dual Analog Pad,a:b2,b:b3,x:b0,y:b1,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b5,righttrigger:b7,platform:Windows,",
"a3060cff000000000000504944564944,Saitek P2500,a:b2,b:b3,y:b1,x:b0,start:b4,guide:b10,back:b5,leftstick:b8,rightstick:b9,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows,",
"300f1001000000000000504944564944,Saitek P480 Rumble Pad,a:b2,b:b3,x:b0,y:b1,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b5,righttrigger:b7,platform:Windows,",
"9b280500000000000000504944564944,Saturn_Adapter_2.0,a:b1,b:b2,x:b0,y:b3,start:b9,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,lefttrigger:b4,righttrigger:b5,platform:Windows,",
"79001100000000000000504944564944,Sega Saturn Gamepad,a:b1,b:b2,x:b4,y:b5,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a4,lefttrigger:b3,righttrigger:b0,platform:Windows,",
"4c05cc09000000000000504944564944,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
"4c05a00b000000000000504944564944,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
"ff113133000000000000504944564944,SVEN X-PAD,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b5,back:b4,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b8,righttrigger:b9,",
"4f0415b3000000000000504944564944,Thrustmaster Dual Analog 3.2,platform:Windows,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"4f0400b3000000000000504944564944,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Windows,",
"66660488000000000000504944564944,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:b12,dpdown:b14,dpleft:b15,dpright:b13,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,platform:Windows,",
"38076652000000000000504944564944,UnKnown,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"63252305000000000000504944564944,USB Vibration Joystick (BM),platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"79001b18000000000000504944564944,Venom Arcade Joystick,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
"10280000000000000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,",
"79000000000000000600000000000000,G-Shark GP-702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Mac OS X,",
"AD1B00000000000001F9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,",
"0d0f0000000000004d00000000000000,HORI Gem Pad 3,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
"0d0f0000000000006600000000000000,HORIPAD FPS PLUS 4,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:a4,",
"83050000000000006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X,",
"6d0400000000000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"6d0400000000000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"6d040000000000001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"6d0400000000000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"2509000000000000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Mac OS X,",
"79000000000000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,x:b0,y:b12,back:b32,start:b36,leftstick:b40,rightstick:b44,leftshoulder:b16,rightshoulder:b20,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a4,rightx:a8,righty:a12,lefttrigger:b24,righttrigger:b28,platform:Mac OS X,",
"d814000000000000cecf000000000000,MC Cthulhu,platform:Mac OS X,leftx:,lefty:,rightx:,righty:,lefttrigger:b6,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,righttrigger:b7,",
"8f0e0000000000000300000000000000,Piranha xtreme,platform:Mac OS X,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"4c050000000000006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,",
"4c05000000000000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"891600000000000000fd000000000000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b8,guide:b10,back:b9,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b11,dpleft:b13,dpdown:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Mac OS X,",
"79000000000000001100000000000000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a3,lefty:a4,platform:Mac OS X,",
"81170000000000007e05000000000000,Sega Saturn,x:b0,a:b2,b:b4,y:b6,start:b13,dpleft:b15,dpdown:b16,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,lefttrigger:b10,rightshoulder:b9,righttrigger:a4,righttrigger:b11,leftx:a0,lefty:a2,platform:Mac OS X,",
"b4040000000000000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,x:b3,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,",
"351200000000000021ab000000000000,SFC30 Joystick,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,",
"4c05000000000000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X,",
"4c05000000000000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X,",
"4f0400000000000015b3000000000000,Thrustmaster Dual Analog 3.2,platform:Mac OS X,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"4f0400000000000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Mac OS X,",
"bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X,",
"10080000000000000100000000000000,Twin USB Joystick,a:b4,b:b2,x:b6,y:b0,back:b16,start:b18,leftstick:b20,rightstick:b22,leftshoulder:b12,rightshoulder:b14,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a2,rightx:a6,righty:a4,lefttrigger:b8,righttrigger:b10,platform:Mac OS X,",
"050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,y:b9,x:b10,start:b6,guide:b8,back:b7,dpup:b2,dpleft:b0,dpdown:b3,dpright:b1,leftx:a0,lefty:a1,lefttrigger:b12,righttrigger:,leftshoulder:b11,platform:Mac OS X,",
"050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,x:b18,y:b17,back:b7,guide:b8,start:b6,leftstick:b23,rightstick:b24,leftshoulder:b19,rightshoulder:b20,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b21,righttrigger:b22,platform:Mac OS X,",
"5e040000000000008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"5e04000000000000dd02000000000000,Xbox One Wired Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"5e04000000000000ea02000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"5e04000000000000e002000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b10,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"05000000102800000900000000010000,8Bitdo SFC30 GamePad,platform:Linux,x:b4,a:b1,b:b0,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
"05000000a00500003232000001000000,8Bitdo Zero GamePad,platform:Linux,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
"030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux,",
"03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000006f0e00003001000001010000,EA Sports PS3 Controller,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
"03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,y:b3,x:b0,start:b9,guide:,back:,leftstick:,rightstick:,leftshoulder:,dpleft:b15,dpdown:b14,dpright:b13,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,rightshoulder:b7,dpup:b12,platform:Linux,",
"03000000260900008888000000010000,GameCube {WiseGroup USB box},a:b0,b:b2,y:b3,x:b1,start:b7,leftshoulder:,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,rightstick:,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Linux,",
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"030000006f0e00001f01000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000006f0e00001304000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:a0,rightstick:a3,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"03000000280400000140000000010000,Gravis GamePad Pro USB ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,",
"030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"03000000ff1100004133000010010000,GreenAsia Inc.USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"06000000adde0000efbe000002010000,Hidromancer Game Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,leftx:a0,lefty:a1,",
"03000000c9110000f055000011010000,HJC Game GAMEPAD,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b11,rightshoulder:b5,rightx:a2,start:b9,righty:a3,dpleft:h0.8,lefttrigger:b6,x:b2,dpup:h0.1,back:b8,leftstick:b10,leftshoulder:b4,y:b3,a:b0,dpright:h0.2,righttrigger:b7,b:b1,platform:Linux,",
"030000000d0f00000d00000000010000,hori,platform:Linux,a:b0,b:b6,y:b2,x:b1,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,start:b9,guide:b10,back:b8,leftshoulder:b3,rightshoulder:b7,leftx:b4,lefty:b5,",
"030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7",
"030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,",
"03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
"03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,",
"03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),platform:Linux,a:b3,b:b4,y:b1,x:b0,start:b7,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,",
"030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b10,guide:b12,start:b11,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
"03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
"030000006f0e00000103000000020000,Logic3 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006d04000016c2000011010000,Logitech F310 Gamepad (DInput),x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux,",
"03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,",
"03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,",
"03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,",
"03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,",
"03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,",
"030000008f0e00001200000000000000,Acme,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,",
"03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,",
"030000006b1400000055000000000000,bigben ps3padstreetnew,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
"03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,",
"03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,",
"03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,",
"03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
"03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"030000004f04000023b3000000000000,Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,",
"030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,",
"03000000790000000600000000000000,G-Shark GS-GP702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,",
"03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,",
"030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"03000000451300000010000000000000,Generic USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00004d00000000000000,HORIPAD3 A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,",
"030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,",
"03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,",
"03000000b50700001403000000000000,IMPACT BLACK,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
"030000006f0e00002401000000000000,INJUSTICE FightStick for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,",
"030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,",
"030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008433000000000000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008483000000000000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b6,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000008305000031b0000000000000,MaxfireBlaze3,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,",
"03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
"03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
"030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,",
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,",
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,",
"03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,",
"030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,leftx:h0.6,lefty:h0.12,rightshoulder:b5,rightstick:a2,righttrigger:b7,rightx:h0.9,righty:h0.4,start:b9,x:b2,y:b3,platform:Windows,",
"03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,",
"03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,",
"030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
"03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000008f0e00007530000000000000,PS (R) Gamepad,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,",
"03000000100800000100000000000000,PS1 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
"03000000100800000300000000000000,PS2 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
"03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,",
"030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,",
"03000000250900000500000000000000,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,",
"03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,",
"03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,",
"03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,",
"03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,",
"03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,",
"03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,",
"030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,",
"0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,",
"0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,",
"030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"030000006f0e00001e01000000000000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
"030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
"03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
"03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
"03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,platform:Windows,",
"03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
"03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
"03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
"03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,",
"03000000300f00001101000000000000,saitek rumble pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
"0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,",
"030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,",
"03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,",
"030000008f0e00000800000000000000,SpeedLink Strike FX Wireless,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,",
"03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
"030000004f04000015b3000000000000,Thrustmaster Dual Analog 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,",
"030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,",
"030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,",
"03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,",
"03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
"03000000380700006652000000000000,UnKnown,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
"03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
"03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
"03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
"03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,",
"03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,",
"03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,",
"03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,",
"03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,",
"03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,",
"030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,",
"03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,",
"03000000790000000600000000000000,G-Shark GP-702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Mac OS X,",
"03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,",
"030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,",
"03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,",
"03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,",
"030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
"03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,",
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,",
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,",
"03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,",
"030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,",
"030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,",
"030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,",
"030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,",
"030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
"03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
"030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,",
"03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,",
"030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,",
"030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"030000005e0400008e02000001000000,Steam Virtual GamePad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,",
"03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,",
"03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,",
"03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,",
"03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,",
"030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,",
"030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,",
"03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
"03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
"03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,",
"050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,",
"050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,",
"030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,",
"030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,",
"030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
"030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,",
"03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,",
"03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
"05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
"03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
"05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
"03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
"05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,",
"05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,",
"05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,",
"05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,",
"030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
"05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,",
"03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,",
"03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,",
"03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,",
"03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,",
"03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,",
"03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,",
"030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,",
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000006f0e00001f01000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
"030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,",
"030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,",
"030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
"0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,",
"03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,",
"030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,",
"03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,",
"050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,",
"03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,",
"030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,",
"03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,",
"03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
"030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000006d04000016c2000010010000,Logitech Logitech Dual Action,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"030000006d04000018c2000010010000,Logitech Logitech RumblePad 2 USB,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,y:b4,x:b3,start:b8,guide:b5,back:b2,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b9,righttrigger:b10,platform:Linux,",
"05000000380700006652000025010000,Mad Catz C.T.R.L.R ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,lefttrigger:a2,righttrigger:a5,",
"03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,",
"03000000780000000600000010010000,Microntek USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftx:a0,lefty:a1,",
"030000005e0400008e02000004010000,Microsoft X-Box 360 pad,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
"030000005e0400008e02000062230000,Microsoft X-Box 360 pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000005e040000d102000001010000,Microsoft X-Box One pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000005e040000dd02000003020000,Microsoft X-Box One pad v2,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux,",
"030000005e0400008502000000010000,Microsoft X-Box pad (Japan),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"05000000d6200000ad0d000001000000,Moga Pro,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,",
"030000001008000001e5000010010000,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Linux,",
"050000007e0500003003000001000000,Nintendo Wii U Pro Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,start:b9,guide:b10,leftshoulder:b4,rightshoulder:b5,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16,",
"05000000010000000100000003000000,Nintendo Wiimote,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
"03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Linux,",
"05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
"05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
"03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,",
"030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,platform:Linux,",
"030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,",
"05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,",
"03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700008433000011010000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700008483000011010000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,",
"03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,",
"030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,",
"030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,",
"05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,",
"030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,",
"050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,",
"05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,",
"03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
"03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,",
"05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,",
"05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,",
"03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,",
"030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,",
"030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
"050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
"030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
"060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,",
"050000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,",
"05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,",
"030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,platform:Linux,x:b1,a:b0,b:b4,y:b5,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
"030000008916000001fd000024010000,Razer Onza Classic Edition,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:b11,dpdown:b14,dpright:b12,dpup:b13,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
"03000000790000001100000010010000,RetroLink Saturn Classic Controller,platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
"0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux,",
"0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux,",
"030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
"030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,platform:Linux,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,lefttrigger:a2,righttrigger:a5,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Linux,",
"03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux,",
"03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,y:b1,x:b0,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b6,righttrigger:b7,platform:Linux,",
"03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,platform:Linux,a:b12,b:b10,x:b13,y:b11,back:b4,start:b5,leftstick:b14,rightstick:b15,leftshoulder:b9,rightshoulder:b8,dpup:b0,dpdown:b2,dpleft:b3,dpright:b1,leftx:a1,lefty:a0,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
"030000004c050000c405000011810000,Sony DualShock 4,a:b0,b:b1,y:b2,x:b3,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
"030000004c050000c405000011010000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Linux,",
"050000004c050000cc09000000810000,Sony DualShock 4 (CUH-ZCT2U) (Bluetooth),platform:Linux,a:b0,b:b1,y:b2,x:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,guide:b10,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
"030000004c050000cc09000011810000,Sony DualShock 4 (CUH-ZCT2U) (USB),platform:Linux,a:b0,b:b1,y:b2,x:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,guide:b10,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
"050000004c050000c405000000010000,Sony DualShock 4 BT,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000004c050000cc09000011010000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,",
"050000004c050000cc09000000010000,Sony DualShock 4 V2 BT,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,",
"030000004c050000a00b000011010000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,",
"03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,platform:Linux,a:b2,b:b1,y:b0,x:b3,start:b8,back:b9,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,",
"030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
"03000000666600000488000000010000,Super Joy Box 5 Pro,platform:Linux,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,dpup:b12,dpleft:b15,dpdown:b14,dpright:b13,",
"030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,y:b3,x:b1,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux,",
"030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,platform:Linux,a:b0,b:b2,x:b1,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,",
"030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
"030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux,",
"030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,",
"030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
"03000000bd12000015d0000010010000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,",
"03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,platform:Linux,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7,",
"03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,y:b0,x:b3,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux,",
"03000000100800000300000010010000,USB Gamepad,platform:Linux,a:b2,b:b1,x:b3,y:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,",
"03000000de280000ff11000001000000,Valve Streaming Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"05000000ac0500003232000001000000,VR-BOX,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,",
"060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
"05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
"050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
"030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,",
"030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,",
"030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
"050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
"0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,",
"0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,",
"0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,",
"030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,",
"03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,",
"03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,",
"03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,",
"03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,",
"03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,",
"03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,",
"03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,",
"03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,",
"030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
"03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
"05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
"03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
"03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,",
"030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,",
"030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,",
"030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,",
"030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
"03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,",
"03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,",
"03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
"03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
"03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,",
"05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,",
"030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
"030000005e040000a102000000010000,X360 Wireless Controller,platform:Linux,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
"0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b7,back:b6,guide:b8,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,lefttrigger:a5,righttrigger:a4,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
"030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,",
"0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
"050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
"03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,platform:Linux,y:b0,x:b1,b:b3,a:b4,leftshoulder:b2,rightshoulder:b5,back:b6,start:b7,guide:b9,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftx:a0,lefty:a1,",
"050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,",
"03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
"05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,",
"03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,",
"03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
"64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,",
"61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,",
"4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,",
"37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,",
"35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,",
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,",
"5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,",
"34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,",
"4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,",
"4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,",
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,",
"78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
"78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",

2
glfw/monitor.c vendored
View File

@@ -177,6 +177,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor)
if (monitor == NULL)
return;
_glfwPlatformFreeMonitor(monitor);
_glfwFreeGammaArrays(&monitor->originalRamp);
_glfwFreeGammaArrays(&monitor->currentRamp);

View File

@@ -175,7 +175,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
// Info.plist for unbundled applications
// HACK: This assumes that NSOpenGLPixelFormat will remain
// a straightforward wrapper of its CGL counterpart
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 100800
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
addAttrib(kCGLPFASupportsAutomaticGraphicsSwitching);
#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/
}

4
glfw/null_monitor.c vendored
View File

@@ -32,6 +32,10 @@
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
{
}
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
{
}

3
glfw/null_window.c vendored
View File

@@ -205,7 +205,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
{
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
return GLFW_FALSE;
}
@@ -323,4 +323,3 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
// This seems like the most appropriate error to return here
return VK_ERROR_INITIALIZATION_FAILED;
}

View File

@@ -55,15 +55,16 @@
"wayland": {
"headers": [
"wl_platform.h",
"linux_joystick.h",
"posix_time.h",
"posix_thread.h",
"xkb_unicode.h",
"xkb_glfw.h",
"egl_context.h",
"osmesa_context.h"
"osmesa_context.h",
"linux_joystick.h"
],
"protocols": [
"stable/xdg-shell/xdg-shell.xml",
"stable/viewporter/viewporter.xml",
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
@@ -72,12 +73,12 @@
"wl_init.c",
"wl_monitor.c",
"wl_window.c",
"linux_joystick.c",
"posix_time.c",
"posix_thread.c",
"xkb_unicode.c",
"xkb_glfw.c",
"egl_context.c",
"osmesa_context.c"
"osmesa_context.c",
"linux_joystick.c"
]
},
"wayland_protocols": [
@@ -107,7 +108,7 @@
"x11": {
"headers": [
"x11_platform.h",
"xkb_unicode.h",
"xkb_glfw.h",
"posix_time.h",
"posix_thread.h",
"glx_context.h",
@@ -119,7 +120,7 @@
"x11_init.c",
"x11_monitor.c",
"x11_window.c",
"xkb_unicode.c",
"xkb_glfw.c",
"posix_time.c",
"posix_thread.c",
"glx_context.c",

2
glfw/vulkan.c vendored
View File

@@ -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

View File

@@ -334,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;

5
glfw/win32_window.c vendored
View File

@@ -1474,9 +1474,9 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
FlashWindow(window->win32.handle, TRUE);
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
return MessageBeep(0xFFFFFFFF & param) ? GLFW_TRUE : GLFW_FALSE;
return MessageBeep(0xFFFFFFFF) ? GLFW_TRUE : GLFW_FALSE;
}
void _glfwPlatformFocusWindow(_GLFWwindow* window)
@@ -2014,4 +2014,3 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return window->win32.handle;
}

7
glfw/window.c vendored
View File

@@ -54,7 +54,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
if (window->keys[key] == GLFW_PRESS)
{
const int scancode = _glfwPlatformGetKeyScancode(key);
_glfwInputKey(window, key, scancode, GLFW_RELEASE, 0);
_glfwInputKeyboard(window, key, scancode, GLFW_RELEASE, 0, "", 0);
}
}
@@ -768,14 +768,14 @@ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* handle)
_glfwPlatformRequestWindowAttention(window);
}
GLFWAPI int glfwWindowBell(GLFWwindow* handle, int64_t param)
GLFWAPI int glfwWindowBell(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
return _glfwPlatformWindowBell(window, param);
return _glfwPlatformWindowBell(window);
}
GLFWAPI void glfwHideWindow(GLFWwindow* handle)
@@ -1106,4 +1106,3 @@ GLFWAPI void glfwPostEmptyEvent(void)
_glfwPlatformPostEmptyEvent();
}

640
glfw/wl_init.c vendored
View File

@@ -41,6 +41,39 @@ static inline int min(int n1, int n2)
return n1 < n2 ? n1 : n2;
}
static _GLFWwindow* findWindowFromDecorationSurface(struct wl_surface* surface, int* which)
{
int focus;
_GLFWwindow* window = _glfw.windowListHead;
if (!which)
which = &focus;
while (window)
{
if (surface == window->wl.decorations.top.surface)
{
*which = topDecoration;
break;
}
if (surface == window->wl.decorations.left.surface)
{
*which = leftDecoration;
break;
}
if (surface == window->wl.decorations.right.surface)
{
*which = rightDecoration;
break;
}
if (surface == window->wl.decorations.bottom.surface)
{
*which = bottomDecoration;
break;
}
window = window->next;
}
return window;
}
static void pointerHandleEnter(void* data,
struct wl_pointer* pointer,
uint32_t serial,
@@ -52,8 +85,16 @@ static void pointerHandleEnter(void* data,
if (!surface)
return;
int focus = 0;
_GLFWwindow* window = wl_surface_get_user_data(surface);
if (!window)
{
window = findWindowFromDecorationSurface(surface, &focus);
if (!window)
return;
}
window->wl.decorations.focus = focus;
_glfw.wl.pointerSerial = serial;
_glfw.wl.pointerFocus = window;
@@ -80,6 +121,39 @@ static void pointerHandleLeave(void* data,
_glfwInputCursorEnter(window, GLFW_FALSE);
}
static void setCursor(const char* name)
{
struct wl_buffer* buffer;
struct wl_cursor* cursor;
struct wl_cursor_image* image;
struct wl_surface* surface = _glfw.wl.cursorSurface;
cursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
name);
if (!cursor)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Standard cursor not found");
return;
}
image = cursor->images[0];
if (!image)
return;
buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
image->hotspot_x,
image->hotspot_y);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
image->width, image->height);
wl_surface_commit(surface);
}
static void pointerHandleMotion(void* data,
struct wl_pointer* pointer,
uint32_t time,
@@ -87,6 +161,7 @@ static void pointerHandleMotion(void* data,
wl_fixed_t sy)
{
_GLFWwindow* window = _glfw.wl.pointerFocus;
const char* cursorName;
if (!window)
return;
@@ -99,13 +174,47 @@ static void pointerHandleMotion(void* data,
window->wl.cursorPosY = wl_fixed_to_double(sy);
}
_glfwInputCursorPos(window,
wl_fixed_to_double(sx),
wl_fixed_to_double(sy));
switch (window->wl.decorations.focus)
{
case mainWindow:
_glfwInputCursorPos(window,
wl_fixed_to_double(sx),
wl_fixed_to_double(sy));
return;
case topDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
cursorName = "n-resize";
else
cursorName = "left_ptr";
break;
case leftDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
cursorName = "nw-resize";
else
cursorName = "w-resize";
break;
case rightDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
cursorName = "ne-resize";
else
cursorName = "e-resize";
break;
case bottomDecoration:
if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH)
cursorName = "sw-resize";
else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH)
cursorName = "se-resize";
else
cursorName = "s-resize";
break;
default:
assert(0);
}
setCursor(cursorName);
}
static void pointerHandleButton(void* data,
struct wl_pointer* wl_pointer,
struct wl_pointer* pointer,
uint32_t serial,
uint32_t time,
uint32_t button,
@@ -114,8 +223,76 @@ static void pointerHandleButton(void* data,
_GLFWwindow* window = _glfw.wl.pointerFocus;
int glfwButton;
// Both xdg-shell and wl_shell use the same values.
uint32_t edges = WL_SHELL_SURFACE_RESIZE_NONE;
if (!window)
return;
if (button == BTN_LEFT)
{
switch (window->wl.decorations.focus)
{
case mainWindow:
break;
case topDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
edges = WL_SHELL_SURFACE_RESIZE_TOP;
else
{
if (window->wl.xdg.toplevel)
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
else
wl_shell_surface_move(window->wl.shellSurface, _glfw.wl.seat, serial);
}
break;
case leftDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
edges = WL_SHELL_SURFACE_RESIZE_TOP_LEFT;
else
edges = WL_SHELL_SURFACE_RESIZE_LEFT;
break;
case rightDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
edges = WL_SHELL_SURFACE_RESIZE_TOP_RIGHT;
else
edges = WL_SHELL_SURFACE_RESIZE_RIGHT;
break;
case bottomDecoration:
if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH)
edges = WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT;
else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH)
edges = WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT;
else
edges = WL_SHELL_SURFACE_RESIZE_BOTTOM;
break;
default:
assert(0);
}
if (edges != WL_SHELL_SURFACE_RESIZE_NONE)
{
if (window->wl.xdg.toplevel)
xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat,
serial, edges);
else
wl_shell_surface_resize(window->wl.shellSurface, _glfw.wl.seat,
serial, edges);
}
}
else if (button == BTN_RIGHT)
{
if (window->wl.decorations.focus != mainWindow && window->wl.xdg.toplevel)
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
window->wl.cursorPosX,
window->wl.cursorPosY);
return;
}
}
// Dont pass the button to the user if it was related to a decoration.
if (window->wl.decorations.focus != mainWindow)
return;
_glfw.wl.pointerSerial = serial;
@@ -132,7 +309,7 @@ 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)
@@ -172,16 +349,7 @@ static void keyboardHandleKeymap(void* data,
int fd,
uint32_t size)
{
struct xkb_keymap* keymap;
struct xkb_state* state;
#ifdef HAVE_XKBCOMMON_COMPOSE_H
struct xkb_compose_table* composeTable;
struct xkb_compose_state* composeState;
#endif
char* mapStr;
const char* locale;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
{
@@ -194,78 +362,10 @@ static void keyboardHandleKeymap(void* data,
close(fd);
return;
}
keymap = xkb_keymap_new_from_string(_glfw.wl.xkb.context,
mapStr,
XKB_KEYMAP_FORMAT_TEXT_V1,
0);
glfw_xkb_compile_keymap(&_glfw.wl.xkb, mapStr);
munmap(mapStr, size);
close(fd);
if (!keymap)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to compile keymap");
return;
}
state = xkb_state_new(keymap);
if (!state)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create XKB state");
xkb_keymap_unref(keymap);
return;
}
// Look up the preferred locale, falling back to "C" as default.
locale = getenv("LC_ALL");
if (!locale)
locale = getenv("LC_CTYPE");
if (!locale)
locale = getenv("LANG");
if (!locale)
locale = "C";
#ifdef HAVE_XKBCOMMON_COMPOSE_H
composeTable =
xkb_compose_table_new_from_locale(_glfw.wl.xkb.context, locale,
XKB_COMPOSE_COMPILE_NO_FLAGS);
if (composeTable)
{
composeState =
xkb_compose_state_new(composeTable, XKB_COMPOSE_STATE_NO_FLAGS);
xkb_compose_table_unref(composeTable);
if (composeState)
_glfw.wl.xkb.composeState = composeState;
else
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create XKB compose state");
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create XKB compose table");
}
#endif
xkb_keymap_unref(_glfw.wl.xkb.keymap);
xkb_state_unref(_glfw.wl.xkb.state);
_glfw.wl.xkb.keymap = keymap;
_glfw.wl.xkb.state = state;
_glfw.wl.xkb.controlMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Control");
_glfw.wl.xkb.altMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod1");
_glfw.wl.xkb.shiftMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift");
_glfw.wl.xkb.superMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4");
_glfw.wl.xkb.capsLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock");
_glfw.wl.xkb.numLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2");
}
static void keyboardHandleEnter(void* data,
@@ -279,6 +379,12 @@ static void keyboardHandleEnter(void* data,
return;
_GLFWwindow* window = wl_surface_get_user_data(surface);
if (!window)
{
window = findWindowFromDecorationSurface(surface, NULL);
if (!window)
return;
}
_glfw.wl.keyboardFocus = window;
_glfwInputWindowFocus(window, GLFW_TRUE);
@@ -298,63 +404,6 @@ static void keyboardHandleLeave(void* data,
_glfwInputWindowFocus(window, GLFW_FALSE);
}
static int toGLFWKeyCode(uint32_t key)
{
if (key < sizeof(_glfw.wl.keycodes) / sizeof(_glfw.wl.keycodes[0]))
return _glfw.wl.keycodes[key];
return GLFW_KEY_UNKNOWN;
}
#ifdef HAVE_XKBCOMMON_COMPOSE_H
static xkb_keysym_t composeSymbol(xkb_keysym_t sym)
{
if (sym == XKB_KEY_NoSymbol || !_glfw.wl.xkb.composeState)
return sym;
if (xkb_compose_state_feed(_glfw.wl.xkb.composeState, sym)
!= XKB_COMPOSE_FEED_ACCEPTED)
return sym;
switch (xkb_compose_state_get_status(_glfw.wl.xkb.composeState))
{
case XKB_COMPOSE_COMPOSED:
return xkb_compose_state_get_one_sym(_glfw.wl.xkb.composeState);
case XKB_COMPOSE_COMPOSING:
case XKB_COMPOSE_CANCELLED:
return XKB_KEY_NoSymbol;
case XKB_COMPOSE_NOTHING:
default:
return sym;
}
}
#endif
static void inputChar(_GLFWwindow* window, uint32_t key)
{
uint32_t code, numSyms;
long cp;
const xkb_keysym_t *syms;
xkb_keysym_t sym;
code = key + 8;
numSyms = xkb_state_key_get_syms(_glfw.wl.xkb.state, code, &syms);
if (numSyms == 1)
{
#ifdef HAVE_XKBCOMMON_COMPOSE_H
sym = composeSymbol(syms[0]);
#else
sym = syms[0];
#endif
cp = _glfwKeySym2Unicode(sym);
if (cp != -1)
{
const int mods = _glfw.wl.xkb.modifiers;
const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT));
_glfwInputChar(window, cp, mods, plain);
}
}
}
static void keyboardHandleKey(void* data,
struct wl_keyboard* keyboard,
uint32_t serial,
@@ -362,22 +411,19 @@ static void keyboardHandleKey(void* data,
uint32_t key,
uint32_t state)
{
int keyCode;
int action;
_GLFWwindow* window = _glfw.wl.keyboardFocus;
if (!window)
return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
_glfw.wl.keyRepeatInfo.nextRepeatAt = 0;
keyCode = toGLFWKeyCode(key);
action = state == WL_KEYBOARD_KEY_STATE_PRESSED
? GLFW_PRESS : GLFW_RELEASE;
_glfwInputKey(window, keyCode, key, action,
_glfw.wl.xkb.modifiers);
if (action == GLFW_PRESS)
inputChar(window, key);
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
{
_glfw.wl.keyRepeatInfo.key = key;
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
}
}
static void keyboardHandleModifiers(void* data,
@@ -388,38 +434,19 @@ static void keyboardHandleModifiers(void* data,
uint32_t modsLocked,
uint32_t group)
{
xkb_mod_mask_t mask;
unsigned int modifiers = 0;
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
}
if (!_glfw.wl.xkb.keymap)
static void keyboardHandleRepeatInfo(void* data,
struct wl_keyboard* keyboard,
int32_t rate,
int32_t delay)
{
if (keyboard != _glfw.wl.keyboard)
return;
xkb_state_update_mask(_glfw.wl.xkb.state,
modsDepressed,
modsLatched,
modsLocked,
0,
0,
group);
mask = xkb_state_serialize_mods(_glfw.wl.xkb.state,
XKB_STATE_MODS_DEPRESSED |
XKB_STATE_LAYOUT_DEPRESSED |
XKB_STATE_MODS_LATCHED |
XKB_STATE_LAYOUT_LATCHED);
if (mask & _glfw.wl.xkb.controlMask)
modifiers |= GLFW_MOD_CONTROL;
if (mask & _glfw.wl.xkb.altMask)
modifiers |= GLFW_MOD_ALT;
if (mask & _glfw.wl.xkb.shiftMask)
modifiers |= GLFW_MOD_SHIFT;
if (mask & _glfw.wl.xkb.superMask)
modifiers |= GLFW_MOD_SUPER;
if (mask & _glfw.wl.xkb.capsLockMask)
modifiers |= GLFW_MOD_CAPS_LOCK;
if (mask & _glfw.wl.xkb.numLockMask)
modifiers |= GLFW_MOD_NUM_LOCK;
_glfw.wl.xkb.modifiers = modifiers;
_glfw.wl.keyboardRepeatRate = rate;
_glfw.wl.keyboardRepeatDelay = delay;
}
static const struct wl_keyboard_listener keyboardListener = {
@@ -428,6 +455,7 @@ static const struct wl_keyboard_listener keyboardListener = {
keyboardHandleLeave,
keyboardHandleKey,
keyboardHandleModifiers,
keyboardHandleRepeatInfo,
};
static void seatHandleCapabilities(void* data,
@@ -457,8 +485,15 @@ static void seatHandleCapabilities(void* data,
}
}
static void seatHandleName(void* data,
struct wl_seat* seat,
const char* name)
{
}
static const struct wl_seat_listener seatListener = {
seatHandleCapabilities
seatHandleCapabilities,
seatHandleName,
};
static void wmBaseHandlePing(void* data,
@@ -485,6 +520,11 @@ static void registryHandleGlobal(void* data,
wl_registry_bind(registry, name, &wl_compositor_interface,
_glfw.wl.compositorVersion);
}
else if (strcmp(interface, "wl_subcompositor") == 0)
{
_glfw.wl.subcompositor =
wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
}
else if (strcmp(interface, "wl_shm") == 0)
{
_glfw.wl.shm =
@@ -503,8 +543,10 @@ static void registryHandleGlobal(void* data,
{
if (!_glfw.wl.seat)
{
_glfw.wl.seatVersion = min(4, version);
_glfw.wl.seat =
wl_registry_bind(registry, name, &wl_seat_interface, 1);
wl_registry_bind(registry, name, &wl_seat_interface,
_glfw.wl.seatVersion);
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
}
}
@@ -514,6 +556,11 @@ static void registryHandleGlobal(void* data,
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
}
else if (strcmp(interface, "wp_viewporter") == 0)
{
_glfw.wl.viewporter =
wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
}
else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0)
{
_glfw.wl.relativePointerManager =
@@ -541,6 +588,18 @@ static void registryHandleGlobalRemove(void *data,
struct wl_registry *registry,
uint32_t name)
{
int i;
_GLFWmonitor* monitor;
for (i = 0; i < _glfw.monitorCount; ++i)
{
monitor = _glfw.monitors[i];
if (monitor->wl.name == name)
{
_glfwInputMonitor(monitor, GLFW_DISCONNECTED, 0);
return;
}
}
}
@@ -549,140 +608,6 @@ static const struct wl_registry_listener registryListener = {
registryHandleGlobalRemove
};
// Create key code translation tables
//
static void createKeyTables(void)
{
int scancode;
memset(_glfw.wl.keycodes, -1, sizeof(_glfw.wl.keycodes));
memset(_glfw.wl.scancodes, -1, sizeof(_glfw.wl.scancodes));
_glfw.wl.keycodes[KEY_GRAVE] = GLFW_KEY_GRAVE_ACCENT;
_glfw.wl.keycodes[KEY_1] = GLFW_KEY_1;
_glfw.wl.keycodes[KEY_2] = GLFW_KEY_2;
_glfw.wl.keycodes[KEY_3] = GLFW_KEY_3;
_glfw.wl.keycodes[KEY_4] = GLFW_KEY_4;
_glfw.wl.keycodes[KEY_5] = GLFW_KEY_5;
_glfw.wl.keycodes[KEY_6] = GLFW_KEY_6;
_glfw.wl.keycodes[KEY_7] = GLFW_KEY_7;
_glfw.wl.keycodes[KEY_8] = GLFW_KEY_8;
_glfw.wl.keycodes[KEY_9] = GLFW_KEY_9;
_glfw.wl.keycodes[KEY_0] = GLFW_KEY_0;
_glfw.wl.keycodes[KEY_SPACE] = GLFW_KEY_SPACE;
_glfw.wl.keycodes[KEY_MINUS] = GLFW_KEY_MINUS;
_glfw.wl.keycodes[KEY_EQUAL] = GLFW_KEY_EQUAL;
_glfw.wl.keycodes[KEY_Q] = GLFW_KEY_Q;
_glfw.wl.keycodes[KEY_W] = GLFW_KEY_W;
_glfw.wl.keycodes[KEY_E] = GLFW_KEY_E;
_glfw.wl.keycodes[KEY_R] = GLFW_KEY_R;
_glfw.wl.keycodes[KEY_T] = GLFW_KEY_T;
_glfw.wl.keycodes[KEY_Y] = GLFW_KEY_Y;
_glfw.wl.keycodes[KEY_U] = GLFW_KEY_U;
_glfw.wl.keycodes[KEY_I] = GLFW_KEY_I;
_glfw.wl.keycodes[KEY_O] = GLFW_KEY_O;
_glfw.wl.keycodes[KEY_P] = GLFW_KEY_P;
_glfw.wl.keycodes[KEY_LEFTBRACE] = GLFW_KEY_LEFT_BRACKET;
_glfw.wl.keycodes[KEY_RIGHTBRACE] = GLFW_KEY_RIGHT_BRACKET;
_glfw.wl.keycodes[KEY_A] = GLFW_KEY_A;
_glfw.wl.keycodes[KEY_S] = GLFW_KEY_S;
_glfw.wl.keycodes[KEY_D] = GLFW_KEY_D;
_glfw.wl.keycodes[KEY_F] = GLFW_KEY_F;
_glfw.wl.keycodes[KEY_G] = GLFW_KEY_G;
_glfw.wl.keycodes[KEY_H] = GLFW_KEY_H;
_glfw.wl.keycodes[KEY_J] = GLFW_KEY_J;
_glfw.wl.keycodes[KEY_K] = GLFW_KEY_K;
_glfw.wl.keycodes[KEY_L] = GLFW_KEY_L;
_glfw.wl.keycodes[KEY_SEMICOLON] = GLFW_KEY_SEMICOLON;
_glfw.wl.keycodes[KEY_APOSTROPHE] = GLFW_KEY_APOSTROPHE;
_glfw.wl.keycodes[KEY_Z] = GLFW_KEY_Z;
_glfw.wl.keycodes[KEY_X] = GLFW_KEY_X;
_glfw.wl.keycodes[KEY_C] = GLFW_KEY_C;
_glfw.wl.keycodes[KEY_V] = GLFW_KEY_V;
_glfw.wl.keycodes[KEY_B] = GLFW_KEY_B;
_glfw.wl.keycodes[KEY_N] = GLFW_KEY_N;
_glfw.wl.keycodes[KEY_M] = GLFW_KEY_M;
_glfw.wl.keycodes[KEY_COMMA] = GLFW_KEY_COMMA;
_glfw.wl.keycodes[KEY_DOT] = GLFW_KEY_PERIOD;
_glfw.wl.keycodes[KEY_SLASH] = GLFW_KEY_SLASH;
_glfw.wl.keycodes[KEY_BACKSLASH] = GLFW_KEY_BACKSLASH;
_glfw.wl.keycodes[KEY_ESC] = GLFW_KEY_ESCAPE;
_glfw.wl.keycodes[KEY_TAB] = GLFW_KEY_TAB;
_glfw.wl.keycodes[KEY_LEFTSHIFT] = GLFW_KEY_LEFT_SHIFT;
_glfw.wl.keycodes[KEY_RIGHTSHIFT] = GLFW_KEY_RIGHT_SHIFT;
_glfw.wl.keycodes[KEY_LEFTCTRL] = GLFW_KEY_LEFT_CONTROL;
_glfw.wl.keycodes[KEY_RIGHTCTRL] = GLFW_KEY_RIGHT_CONTROL;
_glfw.wl.keycodes[KEY_LEFTALT] = GLFW_KEY_LEFT_ALT;
_glfw.wl.keycodes[KEY_RIGHTALT] = GLFW_KEY_RIGHT_ALT;
_glfw.wl.keycodes[KEY_LEFTMETA] = GLFW_KEY_LEFT_SUPER;
_glfw.wl.keycodes[KEY_RIGHTMETA] = GLFW_KEY_RIGHT_SUPER;
_glfw.wl.keycodes[KEY_MENU] = GLFW_KEY_MENU;
_glfw.wl.keycodes[KEY_NUMLOCK] = GLFW_KEY_NUM_LOCK;
_glfw.wl.keycodes[KEY_CAPSLOCK] = GLFW_KEY_CAPS_LOCK;
_glfw.wl.keycodes[KEY_PRINT] = GLFW_KEY_PRINT_SCREEN;
_glfw.wl.keycodes[KEY_SCROLLLOCK] = GLFW_KEY_SCROLL_LOCK;
_glfw.wl.keycodes[KEY_PAUSE] = GLFW_KEY_PAUSE;
_glfw.wl.keycodes[KEY_DELETE] = GLFW_KEY_DELETE;
_glfw.wl.keycodes[KEY_BACKSPACE] = GLFW_KEY_BACKSPACE;
_glfw.wl.keycodes[KEY_ENTER] = GLFW_KEY_ENTER;
_glfw.wl.keycodes[KEY_HOME] = GLFW_KEY_HOME;
_glfw.wl.keycodes[KEY_END] = GLFW_KEY_END;
_glfw.wl.keycodes[KEY_PAGEUP] = GLFW_KEY_PAGE_UP;
_glfw.wl.keycodes[KEY_PAGEDOWN] = GLFW_KEY_PAGE_DOWN;
_glfw.wl.keycodes[KEY_INSERT] = GLFW_KEY_INSERT;
_glfw.wl.keycodes[KEY_LEFT] = GLFW_KEY_LEFT;
_glfw.wl.keycodes[KEY_RIGHT] = GLFW_KEY_RIGHT;
_glfw.wl.keycodes[KEY_DOWN] = GLFW_KEY_DOWN;
_glfw.wl.keycodes[KEY_UP] = GLFW_KEY_UP;
_glfw.wl.keycodes[KEY_F1] = GLFW_KEY_F1;
_glfw.wl.keycodes[KEY_F2] = GLFW_KEY_F2;
_glfw.wl.keycodes[KEY_F3] = GLFW_KEY_F3;
_glfw.wl.keycodes[KEY_F4] = GLFW_KEY_F4;
_glfw.wl.keycodes[KEY_F5] = GLFW_KEY_F5;
_glfw.wl.keycodes[KEY_F6] = GLFW_KEY_F6;
_glfw.wl.keycodes[KEY_F7] = GLFW_KEY_F7;
_glfw.wl.keycodes[KEY_F8] = GLFW_KEY_F8;
_glfw.wl.keycodes[KEY_F9] = GLFW_KEY_F9;
_glfw.wl.keycodes[KEY_F10] = GLFW_KEY_F10;
_glfw.wl.keycodes[KEY_F11] = GLFW_KEY_F11;
_glfw.wl.keycodes[KEY_F12] = GLFW_KEY_F12;
_glfw.wl.keycodes[KEY_F13] = GLFW_KEY_F13;
_glfw.wl.keycodes[KEY_F14] = GLFW_KEY_F14;
_glfw.wl.keycodes[KEY_F15] = GLFW_KEY_F15;
_glfw.wl.keycodes[KEY_F16] = GLFW_KEY_F16;
_glfw.wl.keycodes[KEY_F17] = GLFW_KEY_F17;
_glfw.wl.keycodes[KEY_F18] = GLFW_KEY_F18;
_glfw.wl.keycodes[KEY_F19] = GLFW_KEY_F19;
_glfw.wl.keycodes[KEY_F20] = GLFW_KEY_F20;
_glfw.wl.keycodes[KEY_F21] = GLFW_KEY_F21;
_glfw.wl.keycodes[KEY_F22] = GLFW_KEY_F22;
_glfw.wl.keycodes[KEY_F23] = GLFW_KEY_F23;
_glfw.wl.keycodes[KEY_F24] = GLFW_KEY_F24;
_glfw.wl.keycodes[KEY_KPSLASH] = GLFW_KEY_KP_DIVIDE;
_glfw.wl.keycodes[KEY_KPDOT] = GLFW_KEY_KP_MULTIPLY;
_glfw.wl.keycodes[KEY_KPMINUS] = GLFW_KEY_KP_SUBTRACT;
_glfw.wl.keycodes[KEY_KPPLUS] = GLFW_KEY_KP_ADD;
_glfw.wl.keycodes[KEY_KP0] = GLFW_KEY_KP_0;
_glfw.wl.keycodes[KEY_KP1] = GLFW_KEY_KP_1;
_glfw.wl.keycodes[KEY_KP2] = GLFW_KEY_KP_2;
_glfw.wl.keycodes[KEY_KP3] = GLFW_KEY_KP_3;
_glfw.wl.keycodes[KEY_KP4] = GLFW_KEY_KP_4;
_glfw.wl.keycodes[KEY_KP5] = GLFW_KEY_KP_5;
_glfw.wl.keycodes[KEY_KP6] = GLFW_KEY_KP_6;
_glfw.wl.keycodes[KEY_KP7] = GLFW_KEY_KP_7;
_glfw.wl.keycodes[KEY_KP8] = GLFW_KEY_KP_8;
_glfw.wl.keycodes[KEY_KP9] = GLFW_KEY_KP_9;
_glfw.wl.keycodes[KEY_KPCOMMA] = GLFW_KEY_KP_DECIMAL;
_glfw.wl.keycodes[KEY_KPEQUAL] = GLFW_KEY_KP_EQUAL;
_glfw.wl.keycodes[KEY_KPENTER] = GLFW_KEY_KP_ENTER;
for (scancode = 0; scancode < 256; scancode++)
{
if (_glfw.wl.keycodes[scancode] > 0)
_glfw.wl.scancodes[_glfw.wl.keycodes[scancode]] = scancode;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
@@ -722,52 +647,6 @@ int _glfwPlatformInit(void)
_glfw.wl.egl.window_resize = (PFN_wl_egl_window_resize)
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_resize");
_glfw.wl.xkb.handle = _glfw_dlopen("libxkbcommon.so.0");
if (!_glfw.wl.xkb.handle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to open libxkbcommon");
return GLFW_FALSE;
}
_glfw.wl.xkb.context_new = (PFN_xkb_context_new)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_new");
_glfw.wl.xkb.context_unref = (PFN_xkb_context_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_unref");
_glfw.wl.xkb.keymap_new_from_string = (PFN_xkb_keymap_new_from_string)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_new_from_string");
_glfw.wl.xkb.keymap_unref = (PFN_xkb_keymap_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_unref");
_glfw.wl.xkb.keymap_mod_get_index = (PFN_xkb_keymap_mod_get_index)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index");
_glfw.wl.xkb.state_new = (PFN_xkb_state_new)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_new");
_glfw.wl.xkb.state_unref = (PFN_xkb_state_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_unref");
_glfw.wl.xkb.state_key_get_syms = (PFN_xkb_state_key_get_syms)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_key_get_syms");
_glfw.wl.xkb.state_update_mask = (PFN_xkb_state_update_mask)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask");
_glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_serialize_mods");
#ifdef HAVE_XKBCOMMON_COMPOSE_H
_glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_new_from_locale");
_glfw.wl.xkb.compose_table_unref = (PFN_xkb_compose_table_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_unref");
_glfw.wl.xkb.compose_state_new = (PFN_xkb_compose_state_new)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_new");
_glfw.wl.xkb.compose_state_unref = (PFN_xkb_compose_state_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_unref");
_glfw.wl.xkb.compose_state_feed = (PFN_xkb_compose_state_feed)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_feed");
_glfw.wl.xkb.compose_state_get_status = (PFN_xkb_compose_state_get_status)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_status");
_glfw.wl.xkb.compose_state_get_one_sym = (PFN_xkb_compose_state_get_one_sym)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_one_sym");
#endif
_glfw.wl.display = wl_display_connect(NULL);
if (!_glfw.wl.display)
{
@@ -779,15 +658,7 @@ int _glfwPlatformInit(void)
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
wl_registry_add_listener(_glfw.wl.registry, &registryListener, NULL);
createKeyTables();
_glfw.wl.xkb.context = xkb_context_new(0);
if (!_glfw.wl.xkb.context)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to initialize xkb context");
return GLFW_FALSE;
}
if (!glfw_xkb_create_context(&_glfw.wl.xkb)) return GLFW_FALSE;
// Sync so we got all registry objects
wl_display_roundtrip(_glfw.wl.display);
@@ -795,8 +666,10 @@ int _glfwPlatformInit(void)
// Sync so we got all initial output events
wl_display_roundtrip(_glfw.wl.display);
#ifdef __linux__
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
#endif
_glfwInitTimerPOSIX();
@@ -818,7 +691,9 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void)
{
#ifdef __linux__
_glfwTerminateJoysticksLinux();
#endif
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
@@ -826,21 +701,7 @@ void _glfwPlatformTerminate(void)
_glfw.wl.egl.handle = NULL;
}
#ifdef HAVE_XKBCOMMON_COMPOSE_H
if (_glfw.wl.xkb.composeState)
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
#endif
if (_glfw.wl.xkb.keymap)
xkb_keymap_unref(_glfw.wl.xkb.keymap);
if (_glfw.wl.xkb.state)
xkb_state_unref(_glfw.wl.xkb.state);
if (_glfw.wl.xkb.context)
xkb_context_unref(_glfw.wl.xkb.context);
if (_glfw.wl.xkb.handle)
{
_glfw_dlclose(_glfw.wl.xkb.handle);
_glfw.wl.xkb.handle = NULL;
}
glfw_xkb_release(&_glfw.wl.xkb);
if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
@@ -852,12 +713,16 @@ void _glfwPlatformTerminate(void)
if (_glfw.wl.cursorSurface)
wl_surface_destroy(_glfw.wl.cursorSurface);
if (_glfw.wl.subcompositor)
wl_subcompositor_destroy(_glfw.wl.subcompositor);
if (_glfw.wl.compositor)
wl_compositor_destroy(_glfw.wl.compositor);
if (_glfw.wl.shm)
wl_shm_destroy(_glfw.wl.shm);
if (_glfw.wl.shell)
wl_shell_destroy(_glfw.wl.shell);
if (_glfw.wl.viewporter)
wp_viewporter_destroy(_glfw.wl.viewporter);
if (_glfw.wl.wmBase)
xdg_wm_base_destroy(_glfw.wl.wmBase);
if (_glfw.wl.pointer)
@@ -895,4 +760,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif
;
}

7
glfw/wl_monitor.c vendored
View File

@@ -136,6 +136,7 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
monitor->wl.scale = 1;
monitor->wl.output = output;
monitor->wl.name = name;
wl_output_add_listener(output, &outputListener, monitor);
}
@@ -145,6 +146,12 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
{
if (monitor->wl.output)
wl_output_destroy(monitor->wl.output);
}
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
{
if (xpos)

126
glfw/wl_platform.h vendored
View File

@@ -25,10 +25,6 @@
//========================================================================
#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#ifdef HAVE_XKBCOMMON_COMPOSE_H
#include <xkbcommon/xkbcommon-compose.h>
#endif
#include <dlfcn.h>
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
@@ -47,12 +43,17 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "posix_thread.h"
#include "posix_time.h"
#ifdef __linux__
#include "linux_joystick.h"
#include "xkb_unicode.h"
#else
#include "null_joystick.h"
#endif
#include "xkb_glfw.h"
#include "egl_context.h"
#include "osmesa_context.h"
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
@@ -100,44 +101,28 @@ typedef void (* PFN_wl_egl_window_resize)(struct wl_egl_window*, int, int, int,
#define wl_egl_window_destroy _glfw.wl.egl.window_destroy
#define wl_egl_window_resize _glfw.wl.egl.window_resize
typedef struct xkb_context* (* PFN_xkb_context_new)(enum xkb_context_flags);
typedef void (* PFN_xkb_context_unref)(struct xkb_context*);
typedef struct xkb_keymap* (* PFN_xkb_keymap_new_from_string)(struct xkb_context*, const char*, enum xkb_keymap_format, enum xkb_keymap_compile_flags);
typedef void (* PFN_xkb_keymap_unref)(struct xkb_keymap*);
typedef xkb_mod_index_t (* PFN_xkb_keymap_mod_get_index)(struct xkb_keymap*, const char*);
typedef struct xkb_state* (* PFN_xkb_state_new)(struct xkb_keymap*);
typedef void (* PFN_xkb_state_unref)(struct xkb_state*);
typedef int (* PFN_xkb_state_key_get_syms)(struct xkb_state*, xkb_keycode_t, const xkb_keysym_t**);
typedef enum xkb_state_component (* PFN_xkb_state_update_mask)(struct xkb_state*, xkb_mod_mask_t, xkb_mod_mask_t, xkb_mod_mask_t, xkb_layout_index_t, xkb_layout_index_t, xkb_layout_index_t);
typedef xkb_mod_mask_t (* PFN_xkb_state_serialize_mods)(struct xkb_state*, enum xkb_state_component);
#define xkb_context_new _glfw.wl.xkb.context_new
#define xkb_context_unref _glfw.wl.xkb.context_unref
#define xkb_keymap_new_from_string _glfw.wl.xkb.keymap_new_from_string
#define xkb_keymap_unref _glfw.wl.xkb.keymap_unref
#define xkb_keymap_mod_get_index _glfw.wl.xkb.keymap_mod_get_index
#define xkb_state_new _glfw.wl.xkb.state_new
#define xkb_state_unref _glfw.wl.xkb.state_unref
#define xkb_state_key_get_syms _glfw.wl.xkb.state_key_get_syms
#define xkb_state_update_mask _glfw.wl.xkb.state_update_mask
#define xkb_state_serialize_mods _glfw.wl.xkb.state_serialize_mods
#define _GLFW_DECORATION_WIDTH 4
#define _GLFW_DECORATION_TOP 24
#define _GLFW_DECORATION_VERTICAL (_GLFW_DECORATION_TOP + _GLFW_DECORATION_WIDTH)
#define _GLFW_DECORATION_HORIZONTAL (2 * _GLFW_DECORATION_WIDTH)
#ifdef HAVE_XKBCOMMON_COMPOSE_H
typedef struct xkb_compose_table* (* PFN_xkb_compose_table_new_from_locale)(struct xkb_context*, const char*, enum xkb_compose_compile_flags);
typedef void (* PFN_xkb_compose_table_unref)(struct xkb_compose_table*);
typedef struct xkb_compose_state* (* PFN_xkb_compose_state_new)(struct xkb_compose_table*, enum xkb_compose_state_flags);
typedef void (* PFN_xkb_compose_state_unref)(struct xkb_compose_state*);
typedef enum xkb_compose_feed_result (* PFN_xkb_compose_state_feed)(struct xkb_compose_state*, xkb_keysym_t);
typedef enum xkb_compose_status (* PFN_xkb_compose_state_get_status)(struct xkb_compose_state*);
typedef xkb_keysym_t (* PFN_xkb_compose_state_get_one_sym)(struct xkb_compose_state*);
#define xkb_compose_table_new_from_locale _glfw.wl.xkb.compose_table_new_from_locale
#define xkb_compose_table_unref _glfw.wl.xkb.compose_table_unref
#define xkb_compose_state_new _glfw.wl.xkb.compose_state_new
#define xkb_compose_state_unref _glfw.wl.xkb.compose_state_unref
#define xkb_compose_state_feed _glfw.wl.xkb.compose_state_feed
#define xkb_compose_state_get_status _glfw.wl.xkb.compose_state_get_status
#define xkb_compose_state_get_one_sym _glfw.wl.xkb.compose_state_get_one_sym
#endif
typedef enum _GLFWdecorationSideWayland
{
mainWindow,
topDecoration,
leftDecoration,
rightDecoration,
bottomDecoration,
} _GLFWdecorationSideWayland;
typedef struct _GLFWdecorationWayland
{
struct wl_surface* surface;
struct wl_subsurface* subsurface;
struct wp_viewport* viewport;
} _GLFWdecorationWayland;
// Wayland-specific per-window data
//
@@ -180,6 +165,12 @@ typedef struct _GLFWwindowWayland
// This is a hack to prevent auto-iconification on creation.
GLFWbool justCreated;
struct {
struct wl_buffer* buffer;
_GLFWdecorationWayland top, left, right, bottom;
int focus;
} decorations;
} _GLFWwindowWayland;
// Wayland-specific global data
@@ -189,64 +180,33 @@ typedef struct _GLFWlibraryWayland
struct wl_display* display;
struct wl_registry* registry;
struct wl_compositor* compositor;
struct wl_subcompositor* subcompositor;
struct wl_shell* shell;
struct wl_shm* shm;
struct wl_seat* seat;
struct wl_pointer* pointer;
struct wl_keyboard* keyboard;
struct xdg_wm_base* wmBase;
struct wp_viewporter* viewporter;
struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints;
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
int compositorVersion;
int seatVersion;
struct wl_cursor_theme* cursorTheme;
struct wl_surface* cursorSurface;
uint32_t pointerSerial;
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
int32_t keyboardRepeatRate;
int32_t keyboardRepeatDelay;
struct {
void* handle;
struct xkb_context* context;
struct xkb_keymap* keymap;
struct xkb_state* state;
#ifdef HAVE_XKBCOMMON_COMPOSE_H
struct xkb_compose_state* composeState;
#endif
xkb_mod_mask_t controlMask;
xkb_mod_mask_t altMask;
xkb_mod_mask_t shiftMask;
xkb_mod_mask_t superMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
unsigned int modifiers;
PFN_xkb_context_new context_new;
PFN_xkb_context_unref context_unref;
PFN_xkb_keymap_new_from_string keymap_new_from_string;
PFN_xkb_keymap_unref keymap_unref;
PFN_xkb_keymap_mod_get_index keymap_mod_get_index;
PFN_xkb_state_new state_new;
PFN_xkb_state_unref state_unref;
PFN_xkb_state_key_get_syms state_key_get_syms;
PFN_xkb_state_update_mask state_update_mask;
PFN_xkb_state_serialize_mods state_serialize_mods;
#ifdef HAVE_XKBCOMMON_COMPOSE_H
PFN_xkb_compose_table_new_from_locale compose_table_new_from_locale;
PFN_xkb_compose_table_unref compose_table_unref;
PFN_xkb_compose_state_new compose_state_new;
PFN_xkb_compose_state_unref compose_state_unref;
PFN_xkb_compose_state_feed compose_state_feed;
PFN_xkb_compose_state_get_status compose_state_get_status;
PFN_xkb_compose_state_get_one_sym compose_state_get_one_sym;
#endif
} xkb;
uint32_t key;
double nextRepeatAt;
_GLFWwindow* keyboardFocus;
} keyRepeatInfo;
_GLFWXKBData xkb;
_GLFWwindow* pointerFocus;
_GLFWwindow* keyboardFocus;
@@ -275,6 +235,7 @@ typedef struct _GLFWlibraryWayland
typedef struct _GLFWmonitorWayland
{
struct wl_output* output;
int name;
int currentMode;
int x;
@@ -295,4 +256,3 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version);

559
glfw/wl_window.c vendored
View File

@@ -57,6 +57,16 @@ static void handleConfigure(void* data,
if (!window->monitor)
{
if (_glfw.wl.viewporter && window->decorated)
{
width -= _GLFW_DECORATION_HORIZONTAL;
height -= _GLFW_DECORATION_VERTICAL;
}
if (width < 1)
width = 1;
if (height < 1)
height = 1;
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
{
aspectRatio = (float)width / (float)height;
@@ -94,9 +104,265 @@ static const struct wl_shell_surface_listener shellSurfaceListener = {
handlePopupDone
};
static int
createTmpfileCloexec(char* tmpname)
{
int fd;
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
return fd;
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
* CLOEXEC. The file is immediately suitable for mmap()'ing
* the given size at offset zero.
*
* The file should not have a permanent backing store like a disk,
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
*
* The file name is deleted from the file system.
*
* The file is suitable for buffer sharing between processes by
* transmitting the file descriptor over Unix sockets using the
* SCM_RIGHTS methods.
*
* posix_fallocate() is used to guarantee that disk space is available
* for the file at the given size. If disk space is insufficent, errno
* is set to ENOSPC. If posix_fallocate() is not supported, program may
* receive SIGBUS on accessing mmap()'ed file contents instead.
*/
static int
createAnonymousFile(off_t size)
{
static const char template[] = "/glfw-shared-XXXXXX";
const char* path;
char* name;
int fd;
int ret;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}
name = calloc(strlen(path) + sizeof(template), 1);
strcpy(name, path);
strcat(name, template);
fd = createTmpfileCloexec(name);
free(name);
if (fd < 0)
return -1;
ret = posix_fallocate(fd, 0, size);
if (ret != 0)
{
close(fd);
errno = ret;
return -1;
}
return fd;
}
static struct wl_buffer* createShmBuffer(const GLFWimage* image)
{
struct wl_shm_pool* pool;
struct wl_buffer* buffer;
int stride = image->width * 4;
int length = image->width * image->height * 4;
void* data;
int fd, i;
fd = createAnonymousFile(length);
if (fd < 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %m",
length);
return GLFW_FALSE;
}
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: mmap failed: %m");
close(fd);
return GLFW_FALSE;
}
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
close(fd);
unsigned char* source = (unsigned char*) image->pixels;
unsigned char* target = data;
for (i = 0; i < image->width * image->height; i++, source += 4)
{
unsigned int alpha = source[3];
*target++ = (unsigned char) ((source[2] * alpha) / 255);
*target++ = (unsigned char) ((source[1] * alpha) / 255);
*target++ = (unsigned char) ((source[0] * alpha) / 255);
*target++ = (unsigned char) alpha;
}
buffer =
wl_shm_pool_create_buffer(pool, 0,
image->width,
image->height,
stride, WL_SHM_FORMAT_ARGB8888);
munmap(data, length);
wl_shm_pool_destroy(pool);
return buffer;
}
static void createDecoration(_GLFWdecorationWayland* decoration,
struct wl_surface* parent,
struct wl_buffer* buffer, GLFWbool opaque,
int x, int y,
int width, int height)
{
struct wl_region* region;
decoration->surface = wl_compositor_create_surface(_glfw.wl.compositor);
decoration->subsurface =
wl_subcompositor_get_subsurface(_glfw.wl.subcompositor,
decoration->surface, parent);
wl_subsurface_set_position(decoration->subsurface, x, y);
decoration->viewport = wp_viewporter_get_viewport(_glfw.wl.viewporter,
decoration->surface);
wp_viewport_set_destination(decoration->viewport, width, height);
wl_surface_attach(decoration->surface, buffer, 0, 0);
if (opaque)
{
region = wl_compositor_create_region(_glfw.wl.compositor);
wl_region_add(region, 0, 0, width, height);
wl_surface_set_opaque_region(decoration->surface, region);
wl_surface_commit(decoration->surface);
wl_region_destroy(region);
}
else
wl_surface_commit(decoration->surface);
}
static void createDecorations(_GLFWwindow* window)
{
unsigned char data[] = { 224, 224, 224, 255 };
const GLFWimage image = { 1, 1, data };
GLFWbool opaque = (data[3] == 255);
if (!_glfw.wl.viewporter)
return;
if (!window->wl.decorations.buffer)
window->wl.decorations.buffer = createShmBuffer(&image);
createDecoration(&window->wl.decorations.top, window->wl.surface,
window->wl.decorations.buffer, opaque,
0, -_GLFW_DECORATION_TOP,
window->wl.width, _GLFW_DECORATION_TOP);
createDecoration(&window->wl.decorations.left, window->wl.surface,
window->wl.decorations.buffer, opaque,
-_GLFW_DECORATION_WIDTH, -_GLFW_DECORATION_TOP,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
createDecoration(&window->wl.decorations.right, window->wl.surface,
window->wl.decorations.buffer, opaque,
window->wl.width, -_GLFW_DECORATION_TOP,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
createDecoration(&window->wl.decorations.bottom, window->wl.surface,
window->wl.decorations.buffer, opaque,
-_GLFW_DECORATION_WIDTH, window->wl.height,
window->wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
}
static void destroyDecoration(_GLFWdecorationWayland* decoration)
{
if (decoration->surface)
wl_surface_destroy(decoration->surface);
if (decoration->subsurface)
wl_subsurface_destroy(decoration->subsurface);
if (decoration->viewport)
wp_viewport_destroy(decoration->viewport);
decoration->surface = NULL;
decoration->subsurface = NULL;
decoration->viewport = NULL;
}
static void destroyDecorations(_GLFWwindow* window)
{
destroyDecoration(&window->wl.decorations.top);
destroyDecoration(&window->wl.decorations.left);
destroyDecoration(&window->wl.decorations.right);
destroyDecoration(&window->wl.decorations.bottom);
}
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
{
struct wl_region* region;
region = wl_compositor_create_region(_glfw.wl.compositor);
if (!region)
return;
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
wl_surface_set_opaque_region(window->wl.surface, region);
wl_surface_commit(window->wl.surface);
wl_region_destroy(region);
}
static void resizeWindow(_GLFWwindow* window)
{
int scale = window->wl.scale;
int scaledWidth = window->wl.width * scale;
int scaledHeight = window->wl.height * scale;
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent)
setOpaqueRegion(window);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
_glfwInputWindowContentScale(window, scale, scale);
if (!window->wl.decorations.top.surface)
return;
// Top decoration.
wp_viewport_set_destination(window->wl.decorations.top.viewport,
window->wl.width, _GLFW_DECORATION_TOP);
wl_surface_commit(window->wl.decorations.top.surface);
// Left decoration.
wp_viewport_set_destination(window->wl.decorations.left.viewport,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
wl_surface_commit(window->wl.decorations.left.surface);
// Right decoration.
wl_subsurface_set_position(window->wl.decorations.right.subsurface,
window->wl.width, -_GLFW_DECORATION_TOP);
wp_viewport_set_destination(window->wl.decorations.right.viewport,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
wl_surface_commit(window->wl.decorations.right.surface);
// Bottom decoration.
wl_subsurface_set_position(window->wl.decorations.bottom.subsurface,
-_GLFW_DECORATION_WIDTH, window->wl.height);
wp_viewport_set_destination(window->wl.decorations.bottom.viewport,
window->wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
wl_surface_commit(window->wl.decorations.bottom.surface);
}
static void checkScaleChange(_GLFWwindow* window)
{
int scaledWidth, scaledHeight;
int scale = 1;
int i;
int monitorScale;
@@ -117,12 +383,8 @@ static void checkScaleChange(_GLFWwindow* window)
if (scale != window->wl.scale)
{
window->wl.scale = scale;
scaledWidth = window->wl.width * scale;
scaledHeight = window->wl.height * scale;
wl_surface_set_buffer_scale(window->wl.surface, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
_glfwInputWindowContentScale(window, scale, scale);
resizeWindow(window);
}
}
@@ -172,21 +434,6 @@ static const struct wl_surface_listener surfaceListener = {
handleLeave
};
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
{
struct wl_region* region;
region = wl_compositor_create_region(_glfw.wl.compositor);
if (!region)
return;
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
wl_surface_set_opaque_region(window->wl.surface, region);
wl_surface_commit(window->wl.surface);
wl_region_destroy(region);
}
static void setIdleInhibitor(_GLFWwindow* window, GLFWbool enable)
{
if (enable && !window->wl.idleInhibitor && _glfw.wl.idleInhibitManager)
@@ -231,9 +478,32 @@ static GLFWbool createSurface(_GLFWwindow* window,
if (!window->wl.transparent)
setOpaqueRegion(window);
if (window->decorated && !window->monitor)
createDecorations(window);
return GLFW_TRUE;
}
static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refreshRate)
{
if (window->wl.xdg.toplevel)
{
xdg_toplevel_set_fullscreen(
window->wl.xdg.toplevel,
monitor->wl.output);
}
else if (window->wl.shellSurface)
{
wl_shell_surface_set_fullscreen(
window->wl.shellSurface,
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
refreshRate * 1000, // Convert Hz to mHz.
monitor->wl.output);
}
setIdleInhibitor(window, GLFW_TRUE);
destroyDecorations(window);
}
static GLFWbool createShellSurface(_GLFWwindow* window)
{
if (!_glfw.wl.shell)
@@ -261,12 +531,7 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
if (window->monitor)
{
wl_shell_surface_set_fullscreen(
window->wl.shellSurface,
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
0,
window->monitor->wl.output);
setIdleInhibitor(window, GLFW_TRUE);
setFullscreen(window, window->monitor, 0);
}
else if (window->wl.maximized)
{
@@ -424,25 +689,32 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
return GLFW_TRUE;
}
static void
dispatchPendingKeyRepeats() {
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
double now = glfwGetTime();
while (_glfw.wl.keyRepeatInfo.nextRepeatAt <= now) {
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
_glfw.wl.keyRepeatInfo.nextRepeatAt += 1.0 / _glfw.wl.keyboardRepeatRate;
now = glfwGetTime();
}
}
static int
createTmpfileCloexec(char* tmpname)
{
int fd;
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
return fd;
adjustTimeoutForKeyRepeat(int timeout) {
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return timeout;
double now = glfwGetTime();
if (timeout < 0 || now + timeout / 1000. > _glfw.wl.keyRepeatInfo.nextRepeatAt) {
timeout = _glfw.wl.keyRepeatInfo.nextRepeatAt <= now ? 0 : ( (_glfw.wl.keyRepeatInfo.nextRepeatAt - now) * 1000 + 1 );
}
return timeout;
}
static void
handleEvents(int timeout)
{
struct wl_display* display = _glfw.wl.display;
struct pollfd fds[] = {
{ wl_display_get_fd(display), POLLIN },
};
struct pollfd pfd = { wl_display_get_fd(display), POLLIN };
while (wl_display_prepare_read(display) != 0)
wl_display_dispatch_pending(display);
@@ -462,71 +734,27 @@ handleEvents(int timeout)
return;
}
if (poll(fds, 1, timeout) > 0)
dispatchPendingKeyRepeats();
timeout = adjustTimeoutForKeyRepeat(timeout);
if (poll(&pfd, 1, timeout) > 0)
{
wl_display_read_events(display);
wl_display_dispatch_pending(display);
if (pfd.revents & POLLIN)
{
wl_display_read_events(display);
wl_display_dispatch_pending(display);
}
else
{
wl_display_cancel_read(display);
}
}
else
{
wl_display_cancel_read(display);
}
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
* CLOEXEC. The file is immediately suitable for mmap()'ing
* the given size at offset zero.
*
* The file should not have a permanent backing store like a disk,
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
*
* The file name is deleted from the file system.
*
* The file is suitable for buffer sharing between processes by
* transmitting the file descriptor over Unix sockets using the
* SCM_RIGHTS methods.
*
* posix_fallocate() is used to guarantee that disk space is available
* for the file at the given size. If disk space is insufficent, errno
* is set to ENOSPC. If posix_fallocate() is not supported, program may
* receive SIGBUS on accessing mmap()'ed file contents instead.
*/
int
createAnonymousFile(off_t size)
{
static const char template[] = "/glfw-shared-XXXXXX";
const char* path;
char* name;
int fd;
int ret;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}
name = calloc(strlen(path) + sizeof(template), 1);
strcpy(name, path);
strcat(name, template);
fd = createTmpfileCloexec(name);
free(name);
if (fd < 0)
return -1;
ret = posix_fallocate(fd, 0, size);
if (ret != 0)
{
close(fd);
errno = ret;
return -1;
}
return fd;
dispatchPendingKeyRepeats();
}
// Translates a GLFW standard cursor to a theme cursor name
@@ -639,6 +867,10 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->context.destroy)
window->context.destroy(window);
destroyDecorations(window);
if (window->wl.decorations.buffer)
wl_buffer_destroy(window->wl.decorations.buffer);
if (window->wl.native)
wl_egl_window_destroy(window->wl.native);
@@ -703,14 +935,9 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
int scaledWidth = width * window->wl.scale;
int scaledHeight = height * window->wl.scale;
window->wl.width = width;
window->wl.height = height;
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent)
setOpaqueRegion(window);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
resizeWindow(window);
}
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
@@ -754,8 +981,17 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* left, int* top,
int* right, int* bottom)
{
// TODO: will need a proper implementation once decorations are
// implemented, but for now just leave everything as 0.
if (window->decorated && !window->monitor)
{
if (top)
*top = _GLFW_DECORATION_TOP;
if (left)
*left = _GLFW_DECORATION_WIDTH;
if (right)
*right = _GLFW_DECORATION_WIDTH;
if (bottom)
*bottom = _GLFW_DECORATION_WIDTH;
}
}
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
@@ -851,11 +1087,15 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
"Wayland: Window attention request not implemented yet");
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Window bell request not implemented yet");
// TODO: Use an actual Wayland API to implement this when one becomes available
int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC);
if (fd > -1) {
int ret = write(fd, "\x07", 1) == 1 ? GLFW_TRUE : GLFW_FALSE;
close(fd);
return ret;
}
return GLFW_FALSE;
}
@@ -871,35 +1111,20 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
int width, int height,
int refreshRate)
{
if (window->wl.xdg.toplevel)
if (monitor)
{
if (monitor)
{
xdg_toplevel_set_fullscreen(
window->wl.xdg.toplevel,
monitor->wl.output);
}
else
{
setFullscreen(window, monitor, refreshRate);
}
else
{
if (window->wl.xdg.toplevel)
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
}
}
else if (window->wl.shellSurface)
{
if (monitor)
{
wl_shell_surface_set_fullscreen(
window->wl.shellSurface,
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
refreshRate * 1000, // Convert Hz to mHz.
monitor->wl.output);
}
else
{
else if (window->wl.shellSurface)
wl_shell_surface_set_toplevel(window->wl.shellSurface);
}
setIdleInhibitor(window, GLFW_FALSE);
if (window->decorated)
createDecorations(window);
}
setIdleInhibitor(window, monitor ? GLFW_TRUE : GLFW_FALSE);
_glfwInputWindowMonitor(window, monitor);
}
@@ -944,9 +1169,13 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Window attribute setting not implemented yet");
if (!window->monitor)
{
if (enabled)
createDecorations(window);
else
destroyDecorations(window);
}
}
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled)
@@ -1013,66 +1242,19 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
const char* _glfwPlatformGetScancodeName(int scancode)
{
// TODO
return NULL;
return glfw_xkb_keysym_name(scancode);
}
int _glfwPlatformGetKeyScancode(int key)
{
return _glfw.wl.scancodes[key];
return glfw_xkb_sym_for_key(key);
}
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
{
struct wl_shm_pool* pool;
int stride = image->width * 4;
int length = image->width * image->height * 4;
void* data;
int fd, i;
fd = createAnonymousFile(length);
if (fd < 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %m",
length);
return GLFW_FALSE;
}
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cursor mmap failed: %m");
close(fd);
return GLFW_FALSE;
}
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
close(fd);
unsigned char* source = (unsigned char*) image->pixels;
unsigned char* target = data;
for (i = 0; i < image->width * image->height; i++, source += 4)
{
unsigned int alpha = source[3];
*target++ = (unsigned char) ((source[2] * alpha) / 255);
*target++ = (unsigned char) ((source[1] * alpha) / 255);
*target++ = (unsigned char) ((source[0] * alpha) / 255);
*target++ = (unsigned char) alpha;
}
cursor->wl.buffer =
wl_shm_pool_create_buffer(pool, 0,
image->width,
image->height,
stride, WL_SHM_FORMAT_ARGB8888);
munmap(data, length);
wl_shm_pool_destroy(pool);
cursor->wl.buffer = createShmBuffer(image);
cursor->wl.width = image->width;
cursor->wl.height = image->height;
cursor->wl.xhot = xhot;
@@ -1376,4 +1558,3 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return window->wl.surface;
}

404
glfw/x11_init.c vendored
View File

@@ -36,319 +36,6 @@
#include <locale.h>
// Translate an X11 key code to a GLFW key code.
//
static int translateKeyCode(int scancode)
{
int keySym;
// Valid key code range is [8,255], according to the Xlib manual
if (scancode < 8 || scancode > 255)
return GLFW_KEY_UNKNOWN;
if (_glfw.x11.xkb.available)
{
// Try secondary keysym, for numeric keypad keys
// Note: This way we always force "NumLock = ON", which is intentional
// since the returned key code should correspond to a physical
// location.
keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 1);
switch (keySym)
{
case XK_KP_0: return GLFW_KEY_KP_0;
case XK_KP_1: return GLFW_KEY_KP_1;
case XK_KP_2: return GLFW_KEY_KP_2;
case XK_KP_3: return GLFW_KEY_KP_3;
case XK_KP_4: return GLFW_KEY_KP_4;
case XK_KP_5: return GLFW_KEY_KP_5;
case XK_KP_6: return GLFW_KEY_KP_6;
case XK_KP_7: return GLFW_KEY_KP_7;
case XK_KP_8: return GLFW_KEY_KP_8;
case XK_KP_9: return GLFW_KEY_KP_9;
case XK_KP_Separator:
case XK_KP_Decimal: return GLFW_KEY_KP_DECIMAL;
case XK_KP_Equal: return GLFW_KEY_KP_EQUAL;
case XK_KP_Enter: return GLFW_KEY_KP_ENTER;
default: break;
}
// Now try primary keysym for function keys (non-printable keys)
// These should not depend on the current keyboard layout
keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0);
}
else
{
int dummy;
KeySym* keySyms;
keySyms = XGetKeyboardMapping(_glfw.x11.display, scancode, 1, &dummy);
keySym = keySyms[0];
XFree(keySyms);
}
switch (keySym)
{
case XK_Escape: return GLFW_KEY_ESCAPE;
case XK_Tab: return GLFW_KEY_TAB;
case XK_Shift_L: return GLFW_KEY_LEFT_SHIFT;
case XK_Shift_R: return GLFW_KEY_RIGHT_SHIFT;
case XK_Control_L: return GLFW_KEY_LEFT_CONTROL;
case XK_Control_R: return GLFW_KEY_RIGHT_CONTROL;
case XK_Meta_L:
case XK_Alt_L: return GLFW_KEY_LEFT_ALT;
case XK_Mode_switch: // Mapped to Alt_R on many keyboards
case XK_ISO_Level3_Shift: // AltGr on at least some machines
case XK_Meta_R:
case XK_Alt_R: return GLFW_KEY_RIGHT_ALT;
case XK_Super_L: return GLFW_KEY_LEFT_SUPER;
case XK_Super_R: return GLFW_KEY_RIGHT_SUPER;
case XK_Menu: return GLFW_KEY_MENU;
case XK_Num_Lock: return GLFW_KEY_NUM_LOCK;
case XK_Caps_Lock: return GLFW_KEY_CAPS_LOCK;
case XK_Print: return GLFW_KEY_PRINT_SCREEN;
case XK_Scroll_Lock: return GLFW_KEY_SCROLL_LOCK;
case XK_Pause: return GLFW_KEY_PAUSE;
case XK_Delete: return GLFW_KEY_DELETE;
case XK_BackSpace: return GLFW_KEY_BACKSPACE;
case XK_Return: return GLFW_KEY_ENTER;
case XK_Home: return GLFW_KEY_HOME;
case XK_End: return GLFW_KEY_END;
case XK_Page_Up: return GLFW_KEY_PAGE_UP;
case XK_Page_Down: return GLFW_KEY_PAGE_DOWN;
case XK_Insert: return GLFW_KEY_INSERT;
case XK_Left: return GLFW_KEY_LEFT;
case XK_Right: return GLFW_KEY_RIGHT;
case XK_Down: return GLFW_KEY_DOWN;
case XK_Up: return GLFW_KEY_UP;
case XK_F1: return GLFW_KEY_F1;
case XK_F2: return GLFW_KEY_F2;
case XK_F3: return GLFW_KEY_F3;
case XK_F4: return GLFW_KEY_F4;
case XK_F5: return GLFW_KEY_F5;
case XK_F6: return GLFW_KEY_F6;
case XK_F7: return GLFW_KEY_F7;
case XK_F8: return GLFW_KEY_F8;
case XK_F9: return GLFW_KEY_F9;
case XK_F10: return GLFW_KEY_F10;
case XK_F11: return GLFW_KEY_F11;
case XK_F12: return GLFW_KEY_F12;
case XK_F13: return GLFW_KEY_F13;
case XK_F14: return GLFW_KEY_F14;
case XK_F15: return GLFW_KEY_F15;
case XK_F16: return GLFW_KEY_F16;
case XK_F17: return GLFW_KEY_F17;
case XK_F18: return GLFW_KEY_F18;
case XK_F19: return GLFW_KEY_F19;
case XK_F20: return GLFW_KEY_F20;
case XK_F21: return GLFW_KEY_F21;
case XK_F22: return GLFW_KEY_F22;
case XK_F23: return GLFW_KEY_F23;
case XK_F24: return GLFW_KEY_F24;
case XK_F25: return GLFW_KEY_F25;
// Numeric keypad
case XK_KP_Divide: return GLFW_KEY_KP_DIVIDE;
case XK_KP_Multiply: return GLFW_KEY_KP_MULTIPLY;
case XK_KP_Subtract: return GLFW_KEY_KP_SUBTRACT;
case XK_KP_Add: return GLFW_KEY_KP_ADD;
// These should have been detected in secondary keysym test above!
case XK_KP_Insert: return GLFW_KEY_KP_0;
case XK_KP_End: return GLFW_KEY_KP_1;
case XK_KP_Down: return GLFW_KEY_KP_2;
case XK_KP_Page_Down: return GLFW_KEY_KP_3;
case XK_KP_Left: return GLFW_KEY_KP_4;
case XK_KP_Right: return GLFW_KEY_KP_6;
case XK_KP_Home: return GLFW_KEY_KP_7;
case XK_KP_Up: return GLFW_KEY_KP_8;
case XK_KP_Page_Up: return GLFW_KEY_KP_9;
case XK_KP_Delete: return GLFW_KEY_KP_DECIMAL;
case XK_KP_Equal: return GLFW_KEY_KP_EQUAL;
case XK_KP_Enter: return GLFW_KEY_KP_ENTER;
// Last resort: Check for printable keys (should not happen if the XKB
// extension is available). This will give a layout dependent mapping
// (which is wrong, and we may miss some keys, especially on non-US
// keyboards), but it's better than nothing...
case XK_a: return GLFW_KEY_A;
case XK_b: return GLFW_KEY_B;
case XK_c: return GLFW_KEY_C;
case XK_d: return GLFW_KEY_D;
case XK_e: return GLFW_KEY_E;
case XK_f: return GLFW_KEY_F;
case XK_g: return GLFW_KEY_G;
case XK_h: return GLFW_KEY_H;
case XK_i: return GLFW_KEY_I;
case XK_j: return GLFW_KEY_J;
case XK_k: return GLFW_KEY_K;
case XK_l: return GLFW_KEY_L;
case XK_m: return GLFW_KEY_M;
case XK_n: return GLFW_KEY_N;
case XK_o: return GLFW_KEY_O;
case XK_p: return GLFW_KEY_P;
case XK_q: return GLFW_KEY_Q;
case XK_r: return GLFW_KEY_R;
case XK_s: return GLFW_KEY_S;
case XK_t: return GLFW_KEY_T;
case XK_u: return GLFW_KEY_U;
case XK_v: return GLFW_KEY_V;
case XK_w: return GLFW_KEY_W;
case XK_x: return GLFW_KEY_X;
case XK_y: return GLFW_KEY_Y;
case XK_z: return GLFW_KEY_Z;
case XK_1: return GLFW_KEY_1;
case XK_2: return GLFW_KEY_2;
case XK_3: return GLFW_KEY_3;
case XK_4: return GLFW_KEY_4;
case XK_5: return GLFW_KEY_5;
case XK_6: return GLFW_KEY_6;
case XK_7: return GLFW_KEY_7;
case XK_8: return GLFW_KEY_8;
case XK_9: return GLFW_KEY_9;
case XK_0: return GLFW_KEY_0;
case XK_space: return GLFW_KEY_SPACE;
case XK_minus: return GLFW_KEY_MINUS;
case XK_equal: return GLFW_KEY_EQUAL;
case XK_bracketleft: return GLFW_KEY_LEFT_BRACKET;
case XK_bracketright: return GLFW_KEY_RIGHT_BRACKET;
case XK_backslash: return GLFW_KEY_BACKSLASH;
case XK_semicolon: return GLFW_KEY_SEMICOLON;
case XK_apostrophe: return GLFW_KEY_APOSTROPHE;
case XK_grave: return GLFW_KEY_GRAVE_ACCENT;
case XK_comma: return GLFW_KEY_COMMA;
case XK_period: return GLFW_KEY_PERIOD;
case XK_slash: return GLFW_KEY_SLASH;
case XK_less: return GLFW_KEY_WORLD_1; // At least in some layouts...
default: break;
}
// No matching translation was found
return GLFW_KEY_UNKNOWN;
}
// Create key code translation tables
//
static void createKeyTables(void)
{
int scancode, key;
memset(_glfw.x11.keycodes, -1, sizeof(_glfw.x11.keycodes));
memset(_glfw.x11.scancodes, -1, sizeof(_glfw.x11.scancodes));
if (_glfw.x11.xkb.available)
{
// Use XKB to determine physical key locations independently of the
// current keyboard layout
char name[XkbKeyNameLength + 1];
XkbDescPtr desc = XkbGetMap(_glfw.x11.display, 0, XkbUseCoreKbd);
XkbGetNames(_glfw.x11.display, XkbKeyNamesMask, desc);
// Find the X11 key code -> GLFW key code mapping
for (scancode = desc->min_key_code; scancode <= desc->max_key_code; scancode++)
{
memcpy(name, desc->names->keys[scancode].name, XkbKeyNameLength);
name[XkbKeyNameLength] = '\0';
// Map the key name to a GLFW key code. Note: We only map printable
// keys here, and we use the US keyboard layout. The rest of the
// keys (function keys) are mapped using traditional KeySym
// translations.
if (strcmp(name, "TLDE") == 0) key = GLFW_KEY_GRAVE_ACCENT;
else if (strcmp(name, "AE01") == 0) key = GLFW_KEY_1;
else if (strcmp(name, "AE02") == 0) key = GLFW_KEY_2;
else if (strcmp(name, "AE03") == 0) key = GLFW_KEY_3;
else if (strcmp(name, "AE04") == 0) key = GLFW_KEY_4;
else if (strcmp(name, "AE05") == 0) key = GLFW_KEY_5;
else if (strcmp(name, "AE06") == 0) key = GLFW_KEY_6;
else if (strcmp(name, "AE07") == 0) key = GLFW_KEY_7;
else if (strcmp(name, "AE08") == 0) key = GLFW_KEY_8;
else if (strcmp(name, "AE09") == 0) key = GLFW_KEY_9;
else if (strcmp(name, "AE10") == 0) key = GLFW_KEY_0;
else if (strcmp(name, "AE11") == 0) key = GLFW_KEY_MINUS;
else if (strcmp(name, "AE12") == 0) key = GLFW_KEY_EQUAL;
else if (strcmp(name, "AD01") == 0) key = GLFW_KEY_Q;
else if (strcmp(name, "AD02") == 0) key = GLFW_KEY_W;
else if (strcmp(name, "AD03") == 0) key = GLFW_KEY_E;
else if (strcmp(name, "AD04") == 0) key = GLFW_KEY_R;
else if (strcmp(name, "AD05") == 0) key = GLFW_KEY_T;
else if (strcmp(name, "AD06") == 0) key = GLFW_KEY_Y;
else if (strcmp(name, "AD07") == 0) key = GLFW_KEY_U;
else if (strcmp(name, "AD08") == 0) key = GLFW_KEY_I;
else if (strcmp(name, "AD09") == 0) key = GLFW_KEY_O;
else if (strcmp(name, "AD10") == 0) key = GLFW_KEY_P;
else if (strcmp(name, "AD11") == 0) key = GLFW_KEY_LEFT_BRACKET;
else if (strcmp(name, "AD12") == 0) key = GLFW_KEY_RIGHT_BRACKET;
else if (strcmp(name, "AC01") == 0) key = GLFW_KEY_A;
else if (strcmp(name, "AC02") == 0) key = GLFW_KEY_S;
else if (strcmp(name, "AC03") == 0) key = GLFW_KEY_D;
else if (strcmp(name, "AC04") == 0) key = GLFW_KEY_F;
else if (strcmp(name, "AC05") == 0) key = GLFW_KEY_G;
else if (strcmp(name, "AC06") == 0) key = GLFW_KEY_H;
else if (strcmp(name, "AC07") == 0) key = GLFW_KEY_J;
else if (strcmp(name, "AC08") == 0) key = GLFW_KEY_K;
else if (strcmp(name, "AC09") == 0) key = GLFW_KEY_L;
else if (strcmp(name, "AC10") == 0) key = GLFW_KEY_SEMICOLON;
else if (strcmp(name, "AC11") == 0) key = GLFW_KEY_APOSTROPHE;
else if (strcmp(name, "AB01") == 0) key = GLFW_KEY_Z;
else if (strcmp(name, "AB02") == 0) key = GLFW_KEY_X;
else if (strcmp(name, "AB03") == 0) key = GLFW_KEY_C;
else if (strcmp(name, "AB04") == 0) key = GLFW_KEY_V;
else if (strcmp(name, "AB05") == 0) key = GLFW_KEY_B;
else if (strcmp(name, "AB06") == 0) key = GLFW_KEY_N;
else if (strcmp(name, "AB07") == 0) key = GLFW_KEY_M;
else if (strcmp(name, "AB08") == 0) key = GLFW_KEY_COMMA;
else if (strcmp(name, "AB09") == 0) key = GLFW_KEY_PERIOD;
else if (strcmp(name, "AB10") == 0) key = GLFW_KEY_SLASH;
else if (strcmp(name, "BKSL") == 0) key = GLFW_KEY_BACKSLASH;
else if (strcmp(name, "LSGT") == 0) key = GLFW_KEY_WORLD_1;
else key = GLFW_KEY_UNKNOWN;
if ((scancode >= 0) && (scancode < 256))
_glfw.x11.keycodes[scancode] = key;
}
XkbFreeNames(desc, XkbKeyNamesMask, True);
XkbFreeKeyboard(desc, 0, True);
}
for (scancode = 0; scancode < 256; scancode++)
{
// Translate the un-translated key codes using traditional X11 KeySym
// lookups
if (_glfw.x11.keycodes[scancode] < 0)
_glfw.x11.keycodes[scancode] = translateKeyCode(scancode);
// Store the reverse translation for faster key name lookup
if (_glfw.x11.keycodes[scancode] > 0)
_glfw.x11.scancodes[_glfw.x11.keycodes[scancode]] = scancode;
}
}
// Check whether the IM has a usable style
//
static GLFWbool hasUsableInputMethodStyle(void)
{
unsigned int i;
GLFWbool found = GLFW_FALSE;
XIMStyles* styles = NULL;
if (XGetIMValues(_glfw.x11.im, XNQueryInputStyle, &styles, NULL) != NULL)
return GLFW_FALSE;
for (i = 0; i < styles->count_styles; i++)
{
if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing))
{
found = GLFW_TRUE;
break;
}
}
XFree(styles);
return found;
}
// Check whether the specified atom is supported
//
static Atom getSupportedAtom(Atom* supportedAtoms,
@@ -623,33 +310,14 @@ static GLFWbool initExtensions(void)
}
}
_glfw.x11.xkb.major = 1;
_glfw.x11.xkb.minor = 0;
_glfw.x11.xkb.available =
XkbQueryExtension(_glfw.x11.display,
&_glfw.x11.xkb.majorOpcode,
&_glfw.x11.xkb.eventBase,
&_glfw.x11.xkb.errorBase,
&_glfw.x11.xkb.major,
&_glfw.x11.xkb.minor);
if (_glfw.x11.xkb.available)
{
Bool supported;
if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported))
{
if (supported)
_glfw.x11.xkb.detectable = GLFW_TRUE;
}
}
_glfw.x11.x11xcb.handle = _glfw_dlopen("libX11-xcb.so.1");
if (_glfw.x11.x11xcb.handle)
if (!_glfw.x11.x11xcb.handle)
{
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
_glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load libX11-xcb");
return GLFW_FALSE;
}
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
_glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1");
if (_glfw.x11.xrender.handle)
@@ -674,10 +342,31 @@ static GLFWbool initExtensions(void)
}
}
// Update the key code LUT
// FIXME: We should listen to XkbMapNotify events to track changes to
// the keyboard mapping.
createKeyTables();
_glfw.x11.xkb.major = 1;
_glfw.x11.xkb.minor = 0;
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
&_glfw.x11.xkb.majorOpcode,
&_glfw.x11.xkb.eventBase,
&_glfw.x11.xkb.errorBase,
&_glfw.x11.xkb.major,
&_glfw.x11.xkb.minor);
if (!_glfw.x11.xkb.available)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load Xkb extension");
return GLFW_FALSE;
}
Bool supported;
if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported))
{
if (supported)
_glfw.x11.xkb.detectable = GLFW_TRUE;
}
if (!glfw_xkb_set_x11_events_mask()) return GLFW_FALSE;
if (!glfw_xkb_create_context(&_glfw.x11.xkb)) return GLFW_FALSE;
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return GLFW_FALSE;
if (!glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL)) return GLFW_FALSE;
// Detect whether an EWMH-conformant window manager is running
detectEWMH();
@@ -896,16 +585,6 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot)
int _glfwPlatformInit(void)
{
#if !defined(X_HAVE_UTF8_STRING)
// HACK: If the current locale is "C" and the Xlib UTF-8 functions are
// unavailable, apply the environment's locale in the hope that it's
// both available and not "C"
// This is done because the "C" locale breaks wide character input,
// which is what we fall back on when UTF-8 support is missing
if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0)
setlocale(LC_CTYPE, "");
#endif
XInitThreads();
XrmInitialize();
@@ -939,21 +618,6 @@ int _glfwPlatformInit(void)
_glfw.x11.helperWindowHandle = createHelperWindow();
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
if (XSupportsLocale())
{
XSetLocaleModifiers("");
_glfw.x11.im = XOpenIM(_glfw.x11.display, 0, NULL, NULL);
if (_glfw.x11.im)
{
if (!hasUsableInputMethodStyle())
{
XCloseIM(_glfw.x11.im);
_glfw.x11.im = NULL;
}
}
}
#if defined(__linux__)
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
@@ -985,15 +649,10 @@ void _glfwPlatformTerminate(void)
_glfw.x11.hiddenCursorHandle = (Cursor) 0;
}
glfw_xkb_release(&_glfw.x11.xkb);
free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString);
if (_glfw.x11.im)
{
XCloseIM(_glfw.x11.im);
_glfw.x11.im = NULL;
}
if (_glfw.x11.display)
{
XCloseDisplay(_glfw.x11.display);
@@ -1050,4 +709,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif
;
}

4
glfw/x11_monitor.c vendored
View File

@@ -308,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)

25
glfw/x11_platform.h vendored
View File

@@ -47,6 +47,8 @@
// The XInput extension provides raw mouse motion input
#include <X11/extensions/XInput2.h>
#include "xkb_glfw.h"
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*);
@@ -96,8 +98,6 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
typedef XID xcb_window_t;
typedef XID xcb_visualid_t;
typedef struct xcb_connection_t xcb_connection_t;
typedef xcb_connection_t* (* PFN_XGetXCBConnection)(Display*);
#define XGetXCBConnection _glfw.x11.x11xcb.GetXCBConnection
@@ -151,7 +151,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
#include "posix_thread.h"
#include "posix_time.h"
#include "xkb_unicode.h"
#include "glx_context.h"
#include "egl_context.h"
#include "osmesa_context.h"
@@ -180,7 +179,6 @@ typedef struct _GLFWwindowX11
{
Colormap colormap;
Window handle;
XIC ic;
GLFWbool overrideRedirect;
GLFWbool iconified;
@@ -219,20 +217,12 @@ typedef struct _GLFWlibraryX11
Cursor hiddenCursorHandle;
// Context for mapping window XIDs to _GLFWwindow pointers
XContext context;
// XIM input method
XIM im;
// Most recent error code received by X error handler
int errorCode;
// Primary selection string (while the primary selection is owned)
char* primarySelectionString;
// Clipboard string (while the selection is owned)
char* clipboardString;
// Key name string
char keyName[5];
// X11 keycode to GLFW key LUT
short int keycodes[256];
// GLFW key to X11 keycode LUT
short int scancodes[GLFW_KEY_LAST + 1];
// Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY;
// The window whose disabled cursor mode is active
@@ -318,15 +308,7 @@ typedef struct _GLFWlibraryX11
PFN_XRRUpdateConfiguration UpdateConfiguration;
} randr;
struct {
GLFWbool available;
GLFWbool detectable;
int majorOpcode;
int eventBase;
int errorBase;
int major;
int minor;
} xkb;
_GLFWXKBData xkb;
struct {
int count;
@@ -441,4 +423,3 @@ void _glfwReleaseErrorHandlerX11(void);
void _glfwInputErrorX11(int error, const char* message);
void _glfwPushSelectionToManagerX11(void);

216
glfw/x11_window.c vendored
View File

@@ -220,17 +220,6 @@ static int translateState(int state)
return mods;
}
// Translates an X11 key code to a GLFW key token
//
static int translateKey(int scancode)
{
// Use the pre-filled LUT (see createKeyTables() in x11_init.c)
if (scancode < 0 || scancode > 255)
return GLFW_KEY_UNKNOWN;
return _glfw.x11.keycodes[scancode];
}
// Return the GLFW window corresponding to the specified X11 window
//
static _GLFWwindow* findWindowByHandle(Window handle)
@@ -497,31 +486,6 @@ static size_t encodeUTF8(char* s, unsigned int ch)
return count;
}
// Decode a Unicode code point from a UTF-8 stream
// Based on cutef8 by Jeff Bezanson (Public Domain)
//
#if defined(X_HAVE_UTF8_STRING)
static unsigned int decodeUTF8(const char** s)
{
unsigned int ch = 0, count = 0;
static const unsigned int offsets[] =
{
0x00000000u, 0x00003080u, 0x000e2080u,
0x03c82080u, 0xfa082080u, 0x82082080u
};
do
{
ch = (ch << 6) + (unsigned char) **s;
(*s)++;
count++;
} while ((**s & 0xc0) == 0x80);
assert(count <= 6);
return ch - offsets[count - 1];
}
#endif /*X_HAVE_UTF8_STRING*/
// Convert the specified Latin-1 string to UTF-8
//
static char* convertLatin1toUTF8(const char* source)
@@ -746,19 +710,6 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
}
_glfwPlatformSetWindowTitle(window, wndconfig->title);
if (_glfw.x11.im)
{
window->x11.ic = XCreateIC(_glfw.x11.im,
XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XNClientWindow,
window->x11.handle,
XNFocusWindow,
window->x11.handle,
NULL);
}
_glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos);
_glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height);
@@ -1141,16 +1092,8 @@ static void releaseMonitor(_GLFWwindow* window)
static void processEvent(XEvent *event)
{
_GLFWwindow* window = NULL;
int keycode = 0;
Bool filtered = False;
// HACK: Save scancode as some IMs clear the field in XFilterEvent
if (event->type == KeyPress || event->type == KeyRelease)
keycode = event->xkey.keycode;
if (_glfw.x11.im)
filtered = XFilterEvent(event, None);
if (_glfw.x11.randr.available)
{
if (event->type == _glfw.x11.randr.eventBase + RRNotify)
@@ -1208,6 +1151,30 @@ static void processEvent(XEvent *event)
handleSelectionRequest(event);
return;
}
else if (event->type == _glfw.x11.xkb.eventBase)
{
XkbEvent *kb_event = (XkbEvent*)event;
switch(kb_event->any.xkb_type) {
case XkbNewKeyboardNotify:
if(!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
/* fallthrough */
case XkbMapNotify:
{
glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL);
return;
}
case XkbStateNotify:
{
XkbStateNotifyEvent *state_event = (XkbStateNotifyEvent*)kb_event;
glfw_xkb_update_modifiers(
&_glfw.x11.xkb, state_event->base_mods, state_event->latched_mods,
state_event->locked_mods, state_event->base_group, state_event->latched_group, state_event->locked_group
);
return;
}
}
return;
}
window = findWindowByHandle(event->xany.window);
if (window == NULL)
@@ -1220,105 +1187,12 @@ static void processEvent(XEvent *event)
{
case KeyPress:
{
const int key = translateKey(keycode);
const int mods = translateState(event->xkey.state);
const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT));
if (window->x11.ic)
{
// HACK: Ignore duplicate key press events generated by ibus
// These have the same timestamp as the original event
// Corresponding release events are filtered out
// implicitly by the GLFW key repeat logic
if (window->x11.lastKeyTime < event->xkey.time)
{
if (keycode)
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
window->x11.lastKeyTime = event->xkey.time;
}
if (!filtered)
{
int count;
Status status;
#if defined(X_HAVE_UTF8_STRING)
char buffer[100];
char* chars = buffer;
count = Xutf8LookupString(window->x11.ic,
&event->xkey,
buffer, sizeof(buffer) - 1,
NULL, &status);
if (status == XBufferOverflow)
{
chars = calloc(count + 1, 1);
count = Xutf8LookupString(window->x11.ic,
&event->xkey,
chars, count,
NULL, &status);
}
if (status == XLookupChars || status == XLookupBoth)
{
const char* c = chars;
chars[count] = '\0';
while (c - chars < count)
_glfwInputChar(window, decodeUTF8(&c), mods, plain);
}
#else /*X_HAVE_UTF8_STRING*/
wchar_t buffer[16];
wchar_t* chars = buffer;
count = XwcLookupString(window->x11.ic,
&event->xkey,
buffer,
sizeof(buffer) / sizeof(wchar_t),
NULL,
&status);
if (status == XBufferOverflow)
{
chars = calloc(count, sizeof(wchar_t));
count = XwcLookupString(window->x11.ic,
&event->xkey,
chars, count,
NULL, &status);
}
if (status == XLookupChars || status == XLookupBoth)
{
int i;
for (i = 0; i < count; i++)
_glfwInputChar(window, chars[i], mods, plain);
}
#endif /*X_HAVE_UTF8_STRING*/
if (chars != buffer)
free(chars);
}
}
else
{
KeySym keysym;
XLookupString(&event->xkey, NULL, 0, &keysym, NULL);
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
const long character = _glfwKeySym2Unicode(keysym);
if (character != -1)
_glfwInputChar(window, character, mods, plain);
}
glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_PRESS);
return;
}
case KeyRelease:
{
const int key = translateKey(keycode);
const int mods = translateState(event->xkey.state);
if (!_glfw.x11.xkb.detectable)
{
// HACK: Key repeat events will arrive as KeyRelease/KeyPress
@@ -1332,7 +1206,7 @@ static void processEvent(XEvent *event)
if (next.type == KeyPress &&
next.xkey.window == event->xkey.window &&
next.xkey.keycode == keycode)
next.xkey.keycode == event->xkey.keycode)
{
// HACK: The time of repeat events sometimes doesn't
// match that of the press event, so add an
@@ -1351,7 +1225,7 @@ static void processEvent(XEvent *event)
}
}
_glfwInputKey(window, key, keycode, GLFW_RELEASE, mods);
glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_RELEASE);
return;
}
@@ -1735,9 +1609,6 @@ static void processEvent(XEvent *event)
return;
}
if (window->x11.ic)
XSetICFocus(window->x11.ic);
_glfwInputWindowFocus(window, GLFW_TRUE);
return;
}
@@ -1755,9 +1626,6 @@ static void processEvent(XEvent *event)
return;
}
if (window->x11.ic)
XUnsetICFocus(window->x11.ic);
if (window->monitor && window->autoIconify)
_glfwPlatformIconifyWindow(window);
@@ -1989,12 +1857,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->monitor)
releaseMonitor(window);
if (window->x11.ic)
{
XDestroyIC(window->x11.ic);
window->x11.ic = NULL;
}
if (window->context.destroy)
window->context.destroy(window);
@@ -2340,9 +2202,9 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
0, 1, 0);
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
return XkbBell(_glfw.x11.display, window->x11.handle, (int)param, (Atom)0) ? GLFW_TRUE : GLFW_FALSE;
return XkbBell(_glfw.x11.display, window->x11.handle, 100, (Atom)0) ? GLFW_TRUE : GLFW_FALSE;
}
void _glfwPlatformFocusWindow(_GLFWwindow* window)
@@ -2765,28 +2627,13 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
const char* _glfwPlatformGetScancodeName(int scancode)
{
if (!_glfw.x11.xkb.available)
return NULL;
const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0);
if (keysym == NoSymbol)
return NULL;
const long ch = _glfwKeySym2Unicode(keysym);
if (ch == -1)
return NULL;
const size_t count = encodeUTF8(_glfw.x11.keyName, (unsigned int) ch);
if (count == 0)
return NULL;
_glfw.x11.keyName[count] = '\0';
return _glfw.x11.keyName;
return glfw_xkb_keysym_name(scancode);
}
int _glfwPlatformGetKeyScancode(int key)
{
return _glfw.x11.scancodes[key];
return glfw_xkb_sym_for_key(key);
}
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
@@ -3044,4 +2891,3 @@ GLFWAPI const char* glfwGetX11SelectionString(void)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return getSelectionString(_glfw.x11.PRIMARY);
}

422
glfw/xkb_glfw.c vendored Normal file
View File

@@ -0,0 +1,422 @@
//========================================================================
// GLFW 3.3 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#include <string.h>
#include <stdlib.h>
#include "internal.h"
#include "xkb_glfw.h"
static GLFWbool debug_keyboard = GLFW_FALSE;
#define debug(...) if (debug_keyboard) printf(__VA_ARGS__);
#define map_key(key) { \
switch(key) { \
S(space, SPACE); \
S(apostrophe, APOSTROPHE); \
S(comma, COMMA); \
S(minus, MINUS); \
S(period, PERIOD); \
S(slash, SLASH); \
S(semicolon, SEMICOLON); \
S(equal, EQUAL); \
S(bracketleft, LEFT_BRACKET); \
S(backslash, BACKSLASH); \
S(bracketright, RIGHT_BRACKET); \
S(grave, GRAVE_ACCENT); \
S(Escape, ESCAPE); \
S(Return, ENTER); \
S(Tab, TAB); \
S(BackSpace, BACKSPACE); \
S(Insert, INSERT); \
S(Delete, DELETE); \
S(Right, RIGHT); \
S(Left, LEFT); \
S(Up, UP); \
S(Down, DOWN); \
S(Page_Up, PAGE_UP); \
S(Page_Down, PAGE_DOWN); \
S(Home, HOME); \
S(End, END); \
S(Caps_Lock, CAPS_LOCK); \
S(Scroll_Lock, SCROLL_LOCK); \
S(Num_Lock, NUM_LOCK); \
S(Print, PRINT_SCREEN); \
S(Pause, PAUSE); \
S(KP_Decimal, KP_DECIMAL); \
S(KP_Divide, KP_DIVIDE); \
S(KP_Multiply, KP_MULTIPLY); \
S(KP_Subtract, KP_SUBTRACT); \
S(KP_Add, KP_ADD); \
S(KP_Enter, KP_ENTER); \
S(KP_Equal, KP_EQUAL); \
F(KP_Home, HOME); \
F(KP_End, END); \
F(KP_Page_Up, PAGE_UP); \
F(KP_Page_Down, PAGE_DOWN); \
F(KP_Insert, INSERT); \
F(KP_Delete, DELETE); \
S(Shift_L, LEFT_SHIFT); \
S(Control_L, LEFT_CONTROL); \
S(Alt_L, LEFT_ALT); \
S(Super_L, LEFT_SUPER); \
S(Shift_R, RIGHT_SHIFT); \
S(Control_R, RIGHT_CONTROL); \
S(Alt_R, RIGHT_ALT); \
S(Super_R, RIGHT_SUPER); \
S(Menu, MENU); \
R(0, 9, 0, 9); \
R(a, z, A, Z); \
D(A, Z, A, Z); \
R(F1, F25, F1, F25); \
R(KP_0, KP_9, KP_0, KP_9); \
default: \
break; \
} \
}
static int
glfw_key_for_sym(xkb_keysym_t key) {
#define S(f, t) case XKB_KEY_##f: return GLFW_KEY_##t
#define F(f, t) S(f, t)
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
#define D(s, e, gs, ...) R(s, e, gs)
map_key(key)
return GLFW_KEY_UNKNOWN;
#undef F
#undef D
#undef R
#undef S
};
xkb_keysym_t
glfw_xkb_sym_for_key(int key) {
#define S(f, t) case GLFW_KEY_##t: return XKB_KEY_##f
#define F(...)
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
#define D(...)
map_key(key)
return GLFW_KEY_UNKNOWN;
#undef F
#undef D
#undef R
#undef S
}
#ifdef _GLFW_X11
GLFWbool
glfw_xkb_set_x11_events_mask(void) {
if (!XkbSelectEvents(_glfw.x11.display, XkbUseCoreKbd, XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask, XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set XKB events mask");
return GLFW_FALSE;
}
return GLFW_TRUE;
}
GLFWbool
glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
xkb->keyboard_device_id = -1;
xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display);
if (!conn) {
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection");
return GLFW_FALSE;
}
xkb->keyboard_device_id = xkb_x11_get_core_keyboard_device_id(conn);
if (xkb->keyboard_device_id == -1) {
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve core keyboard device id");
return GLFW_FALSE;
}
return GLFW_TRUE;
}
#define xkb_glfw_load_keymap(keymap, ...) {\
xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); \
if (conn) keymap = xkb_x11_keymap_new_from_device(xkb->context, conn, xkb->keyboard_device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); \
}
#define xkb_glfw_load_state(keymap, state, ...) {\
xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); \
if (conn) state = xkb_x11_state_new_from_device(keymap, conn, xkb->keyboard_device_id); \
}
#else
#define xkb_glfw_load_keymap(keymap, map_str) keymap = xkb_keymap_new_from_string(xkb->context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
#define xkb_glfw_load_state(keymap, state, ...) state = xkb_state_new(keymap);
#endif
void
glfw_xkb_release(_GLFWXKBData *xkb) {
if (xkb->composeState) {
xkb_compose_state_unref(xkb->composeState);
xkb->composeState = NULL;
}
if (xkb->keymap) {
xkb_keymap_unref(xkb->keymap);
xkb->keymap = NULL;
}
if (xkb->state) {
xkb_state_unref(xkb->state);
xkb->state = NULL;
}
if (xkb->clean_state) {
xkb_state_unref(xkb->clean_state);
xkb->clean_state = NULL;
}
if (xkb->context) {
xkb_context_unref(xkb->context);
xkb->context = NULL;
}
}
GLFWbool
glfw_xkb_create_context(_GLFWXKBData *xkb) {
xkb->context = xkb_context_new(0);
debug_keyboard = getenv("GLFW_DEBUG_KEYBOARD") != NULL;
if (!xkb->context)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Failed to initialize XKB context");
return GLFW_FALSE;
}
return GLFW_TRUE;
}
GLFWbool
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
const char* locale = NULL;
struct xkb_state* state = NULL, *clean_state = NULL;
struct xkb_keymap* keymap = NULL;
struct xkb_compose_table* compose_table = NULL;
struct xkb_compose_state* compose_state = NULL;
(void)(map_str); // not needed on X11
GLFWbool ok = GLFW_FALSE;
xkb_glfw_load_keymap(keymap, map_str);
if (!keymap) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to compile XKB keymap");
else {
xkb_glfw_load_state(keymap, state);
clean_state = xkb_state_new(keymap);
if (!state || !clean_state) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB state");
xkb_keymap_unref(keymap); keymap = NULL;
} else {
ok = GLFW_TRUE;
/* Look up the preferred locale, falling back to "C" as default. */
locale = getenv("LC_ALL");
if (!locale) locale = getenv("LC_CTYPE");
if (!locale) locale = getenv("LANG");
if (!locale) locale = "C";
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
if (!compose_table) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table");
} else {
compose_state = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
xkb_compose_table_unref(compose_table); compose_table = NULL;
if (!compose_state) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
}
}
}
}
if (keymap && state && clean_state) {
if (xkb->composeState) xkb_compose_state_unref(xkb->composeState);
xkb->composeState = compose_state;
if (xkb->keymap) xkb_keymap_unref(xkb->keymap);
xkb->keymap = keymap;
if (xkb->state) xkb_state_unref(xkb->state);
xkb->state = state;
if (xkb->clean_state) xkb_state_unref(xkb->clean_state);
xkb->clean_state = clean_state;
}
if (xkb->keymap) {
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
S(control, XKB_MOD_NAME_CTRL);
S(alt, XKB_MOD_NAME_ALT);
S(shift, XKB_MOD_NAME_SHIFT);
S(super, XKB_MOD_NAME_LOGO);
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
}
#undef S
}
return ok;
}
static inline xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
if (xkb_state_mod_index_is_active(xkb->state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
i++;
}
return ans;
}
void
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
if (!xkb->keymap) return;
xkb->modifiers = 0;
xkb_state_update_mask(xkb->state, depressed, latched, locked, base_group, latched_group, locked_group);
#define S(attr, name) if (xkb_state_mod_index_is_active(xkb->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) xkb->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->activeUnknownModifiers = active_unknown_modifiers(xkb);
}
GLFWbool
glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
#ifdef _GLFW_WAYLAND
scancode += 8;
#endif
return xkb_keymap_key_repeats(xkb->keymap, scancode);
}
static char text[256];
static inline xkb_keysym_t
compose_symbol(_GLFWXKBData *xkb, xkb_keysym_t sym) {
if (sym == XKB_KEY_NoSymbol || !xkb->composeState) return sym;
if (xkb_compose_state_feed(xkb->composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
switch (xkb_compose_state_get_status(xkb->composeState)) {
case XKB_COMPOSE_COMPOSED:
xkb_compose_state_get_utf8(xkb->composeState, text, sizeof(text));
return xkb_compose_state_get_one_sym(xkb->composeState);
case XKB_COMPOSE_COMPOSING:
case XKB_COMPOSE_CANCELLED:
return XKB_KEY_NoSymbol;
case XKB_COMPOSE_NOTHING:
default:
return sym;
}
}
const char*
glfw_xkb_keysym_name(xkb_keysym_t sym) {
static char name[256];
name[0] = 0;
xkb_keysym_get_name(sym, name, sizeof(name));
return name;
}
static inline const char*
format_mods(unsigned int mods) {
static char buf[128];
char *p = buf, *s;
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
pr("mods: ");
s = p;
if (mods & GLFW_MOD_CONTROL) pr("ctrl+");
if (mods & GLFW_MOD_ALT) pr("alt+");
if (mods & GLFW_MOD_SHIFT) pr("shift+");
if (mods & GLFW_MOD_SUPER) pr("super+");
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
if (p == s) pr("none");
else p--;
pr(" ");
#undef pr
return buf;
}
static inline const char*
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
static char buf[512];
char *p = buf, *s;
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
pr(name); pr(": ");
s = p;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
xkb_mod_mask_t m = 1 << i;
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
}
if (p == s) pr("none");
else p--;
pr(" ");
#undef pr
return buf;
}
void
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
const xkb_keysym_t *syms, *clean_syms;
xkb_keysym_t glfw_sym;
xkb_keycode_t code_for_sym = scancode;
#ifdef _GLFW_WAYLAND
code_for_sym += 8;
#endif
debug("scancode: 0x%x release: %d ", scancode, action == GLFW_RELEASE);
int num_syms = xkb_state_key_get_syms(xkb->state, code_for_sym, &syms);
int num_clean_syms = xkb_state_key_get_syms(xkb->clean_state, code_for_sym, &clean_syms);
text[0] = 0;
// According to the documentation of xkb_compose_state_feed it does not
// support multi-sym events, so we ignore them
if (num_syms != 1 || num_clean_syms != 1) {
debug("scancode: 0x%x num_syms: %d num_clean_syms: %d ignoring event\n", scancode, num_syms, num_clean_syms);
return;
}
glfw_sym = clean_syms[0];
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
const char *text_type = "composed_text";
glfw_sym = compose_symbol(xkb, syms[0]);
if (glfw_sym == XKB_KEY_NoSymbol) {
debug("compose not complete, ignoring.\n");
return;
}
debug("composed_sym: %s ", glfw_xkb_keysym_name(glfw_sym));
if (glfw_sym == syms[0]) { // composed sym is the same as non-composed sym
// Only use the clean_sym if no mods other than the mods we report
// are active (for example if ISO_Shift_Level_* mods are active
// they are not reported by GLFW so the key should be the shifted
// key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(xkb->state, code_for_sym) & xkb->activeUnknownModifiers;
if (xkb->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", xkb->activeUnknownModifiers));
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
else glfw_sym = clean_syms[0];
// xkb returns text even if alt and/or super are pressed
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & xkb->modifiers) == 0) xkb_state_key_get_utf8(xkb->state, code_for_sym, text, sizeof(text));
text_type = "text";
}
if ((1 <= text[0] && text[0] <= 31) || text[0] == 127) text[0] = 0; // dont send text for ascii control codes
if (text[0]) { debug("%s: %s ", text_type, text); }
}
int glfw_keycode = glfw_key_for_sym(glfw_sym);
debug("%sglfw_key: %s\n", format_mods(xkb->modifiers), _glfwGetKeyName(glfw_keycode));
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, xkb->modifiers, text, 0);
}

83
glfw/xkb_glfw.h vendored Normal file
View File

@@ -0,0 +1,83 @@
//========================================================================
// GLFW 3.3 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#pragma once
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-compose.h>
#ifdef _GLFW_X11
#include <xkbcommon/xkbcommon-x11.h>
#endif
typedef struct {
struct xkb_context* context;
struct xkb_keymap* keymap;
struct xkb_state* state;
struct xkb_state* clean_state;
struct xkb_compose_state* composeState;
xkb_mod_index_t controlIdx;
xkb_mod_index_t altIdx;
xkb_mod_index_t shiftIdx;
xkb_mod_index_t superIdx;
xkb_mod_index_t capsLockIdx;
xkb_mod_index_t numLockIdx;
xkb_mod_mask_t controlMask;
xkb_mod_mask_t altMask;
xkb_mod_mask_t shiftMask;
xkb_mod_mask_t superMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
xkb_mod_mask_t activeUnknownModifiers;
unsigned int modifiers;
xkb_mod_index_t unknownModifiers[256];
#ifdef _GLFW_X11
int32_t keyboard_device_id;
GLFWbool available;
GLFWbool detectable;
int majorOpcode;
int eventBase;
int errorBase;
int major;
int minor;
#endif
} _GLFWXKBData;
#ifdef _GLFW_X11
GLFWbool glfw_xkb_set_x11_events_mask(void);
GLFWbool glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb);
#endif
void glfw_xkb_release(_GLFWXKBData *xkb);
GLFWbool glfw_xkb_create_context(_GLFWXKBData *xkb);
GLFWbool glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str);
void glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group);
GLFWbool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode);
const char* glfw_xkb_keysym_name(xkb_keysym_t sym);
xkb_keysym_t glfw_xkb_sym_for_key(int key);
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action);

940
glfw/xkb_unicode.c vendored
View File

@@ -1,940 +0,0 @@
//========================================================================
// GLFW 3.3 X11 - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2002-2006 Marcus Geelnard
// Copyright (c) 2006-2016 Camilla Löwy <elmindreda@glfw.org>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#include "internal.h"
/*
* Marcus: This code was originally written by Markus G. Kuhn.
* I have made some slight changes (trimmed it down a bit from >60 KB to
* 20 KB), but the functionality is the same.
*/
/*
* This module converts keysym values into the corresponding ISO 10646
* (UCS, Unicode) values.
*
* The array keysymtab[] contains pairs of X11 keysym values for graphical
* characters and the corresponding Unicode value. The function
* _glfwKeySym2Unicode() maps a keysym onto a Unicode value using a binary
* search, therefore keysymtab[] must remain SORTED by keysym value.
*
* We allow to represent any UCS character in the range U-00000000 to
* U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
* This admittedly does not cover the entire 31-bit space of UCS, but
* it does cover all of the characters up to U-10FFFF, which can be
* represented by UTF-16, and more, and it is very unlikely that higher
* UCS codes will ever be assigned by ISO. So to get Unicode character
* U+ABCD you can directly use keysym 0x0100abcd.
*
* Original author: Markus G. Kuhn <mkuhn@acm.org>, University of
* Cambridge, April 2001
*
* Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing
* an initial draft of the mapping table.
*
*/
//************************************************************************
//**** KeySym to Unicode mapping table ****
//************************************************************************
static const struct codepair {
unsigned short keysym;
unsigned short ucs;
} keysymtab[] = {
{ 0x01a1, 0x0104 },
{ 0x01a2, 0x02d8 },
{ 0x01a3, 0x0141 },
{ 0x01a5, 0x013d },
{ 0x01a6, 0x015a },
{ 0x01a9, 0x0160 },
{ 0x01aa, 0x015e },
{ 0x01ab, 0x0164 },
{ 0x01ac, 0x0179 },
{ 0x01ae, 0x017d },
{ 0x01af, 0x017b },
{ 0x01b1, 0x0105 },
{ 0x01b2, 0x02db },
{ 0x01b3, 0x0142 },
{ 0x01b5, 0x013e },
{ 0x01b6, 0x015b },
{ 0x01b7, 0x02c7 },
{ 0x01b9, 0x0161 },
{ 0x01ba, 0x015f },
{ 0x01bb, 0x0165 },
{ 0x01bc, 0x017a },
{ 0x01bd, 0x02dd },
{ 0x01be, 0x017e },
{ 0x01bf, 0x017c },
{ 0x01c0, 0x0154 },
{ 0x01c3, 0x0102 },
{ 0x01c5, 0x0139 },
{ 0x01c6, 0x0106 },
{ 0x01c8, 0x010c },
{ 0x01ca, 0x0118 },
{ 0x01cc, 0x011a },
{ 0x01cf, 0x010e },
{ 0x01d0, 0x0110 },
{ 0x01d1, 0x0143 },
{ 0x01d2, 0x0147 },
{ 0x01d5, 0x0150 },
{ 0x01d8, 0x0158 },
{ 0x01d9, 0x016e },
{ 0x01db, 0x0170 },
{ 0x01de, 0x0162 },
{ 0x01e0, 0x0155 },
{ 0x01e3, 0x0103 },
{ 0x01e5, 0x013a },
{ 0x01e6, 0x0107 },
{ 0x01e8, 0x010d },
{ 0x01ea, 0x0119 },
{ 0x01ec, 0x011b },
{ 0x01ef, 0x010f },
{ 0x01f0, 0x0111 },
{ 0x01f1, 0x0144 },
{ 0x01f2, 0x0148 },
{ 0x01f5, 0x0151 },
{ 0x01f8, 0x0159 },
{ 0x01f9, 0x016f },
{ 0x01fb, 0x0171 },
{ 0x01fe, 0x0163 },
{ 0x01ff, 0x02d9 },
{ 0x02a1, 0x0126 },
{ 0x02a6, 0x0124 },
{ 0x02a9, 0x0130 },
{ 0x02ab, 0x011e },
{ 0x02ac, 0x0134 },
{ 0x02b1, 0x0127 },
{ 0x02b6, 0x0125 },
{ 0x02b9, 0x0131 },
{ 0x02bb, 0x011f },
{ 0x02bc, 0x0135 },
{ 0x02c5, 0x010a },
{ 0x02c6, 0x0108 },
{ 0x02d5, 0x0120 },
{ 0x02d8, 0x011c },
{ 0x02dd, 0x016c },
{ 0x02de, 0x015c },
{ 0x02e5, 0x010b },
{ 0x02e6, 0x0109 },
{ 0x02f5, 0x0121 },
{ 0x02f8, 0x011d },
{ 0x02fd, 0x016d },
{ 0x02fe, 0x015d },
{ 0x03a2, 0x0138 },
{ 0x03a3, 0x0156 },
{ 0x03a5, 0x0128 },
{ 0x03a6, 0x013b },
{ 0x03aa, 0x0112 },
{ 0x03ab, 0x0122 },
{ 0x03ac, 0x0166 },
{ 0x03b3, 0x0157 },
{ 0x03b5, 0x0129 },
{ 0x03b6, 0x013c },
{ 0x03ba, 0x0113 },
{ 0x03bb, 0x0123 },
{ 0x03bc, 0x0167 },
{ 0x03bd, 0x014a },
{ 0x03bf, 0x014b },
{ 0x03c0, 0x0100 },
{ 0x03c7, 0x012e },
{ 0x03cc, 0x0116 },
{ 0x03cf, 0x012a },
{ 0x03d1, 0x0145 },
{ 0x03d2, 0x014c },
{ 0x03d3, 0x0136 },
{ 0x03d9, 0x0172 },
{ 0x03dd, 0x0168 },
{ 0x03de, 0x016a },
{ 0x03e0, 0x0101 },
{ 0x03e7, 0x012f },
{ 0x03ec, 0x0117 },
{ 0x03ef, 0x012b },
{ 0x03f1, 0x0146 },
{ 0x03f2, 0x014d },
{ 0x03f3, 0x0137 },
{ 0x03f9, 0x0173 },
{ 0x03fd, 0x0169 },
{ 0x03fe, 0x016b },
{ 0x047e, 0x203e },
{ 0x04a1, 0x3002 },
{ 0x04a2, 0x300c },
{ 0x04a3, 0x300d },
{ 0x04a4, 0x3001 },
{ 0x04a5, 0x30fb },
{ 0x04a6, 0x30f2 },
{ 0x04a7, 0x30a1 },
{ 0x04a8, 0x30a3 },
{ 0x04a9, 0x30a5 },
{ 0x04aa, 0x30a7 },
{ 0x04ab, 0x30a9 },
{ 0x04ac, 0x30e3 },
{ 0x04ad, 0x30e5 },
{ 0x04ae, 0x30e7 },
{ 0x04af, 0x30c3 },
{ 0x04b0, 0x30fc },
{ 0x04b1, 0x30a2 },
{ 0x04b2, 0x30a4 },
{ 0x04b3, 0x30a6 },
{ 0x04b4, 0x30a8 },
{ 0x04b5, 0x30aa },
{ 0x04b6, 0x30ab },
{ 0x04b7, 0x30ad },
{ 0x04b8, 0x30af },
{ 0x04b9, 0x30b1 },
{ 0x04ba, 0x30b3 },
{ 0x04bb, 0x30b5 },
{ 0x04bc, 0x30b7 },
{ 0x04bd, 0x30b9 },
{ 0x04be, 0x30bb },
{ 0x04bf, 0x30bd },
{ 0x04c0, 0x30bf },
{ 0x04c1, 0x30c1 },
{ 0x04c2, 0x30c4 },
{ 0x04c3, 0x30c6 },
{ 0x04c4, 0x30c8 },
{ 0x04c5, 0x30ca },
{ 0x04c6, 0x30cb },
{ 0x04c7, 0x30cc },
{ 0x04c8, 0x30cd },
{ 0x04c9, 0x30ce },
{ 0x04ca, 0x30cf },
{ 0x04cb, 0x30d2 },
{ 0x04cc, 0x30d5 },
{ 0x04cd, 0x30d8 },
{ 0x04ce, 0x30db },
{ 0x04cf, 0x30de },
{ 0x04d0, 0x30df },
{ 0x04d1, 0x30e0 },
{ 0x04d2, 0x30e1 },
{ 0x04d3, 0x30e2 },
{ 0x04d4, 0x30e4 },
{ 0x04d5, 0x30e6 },
{ 0x04d6, 0x30e8 },
{ 0x04d7, 0x30e9 },
{ 0x04d8, 0x30ea },
{ 0x04d9, 0x30eb },
{ 0x04da, 0x30ec },
{ 0x04db, 0x30ed },
{ 0x04dc, 0x30ef },
{ 0x04dd, 0x30f3 },
{ 0x04de, 0x309b },
{ 0x04df, 0x309c },
{ 0x05ac, 0x060c },
{ 0x05bb, 0x061b },
{ 0x05bf, 0x061f },
{ 0x05c1, 0x0621 },
{ 0x05c2, 0x0622 },
{ 0x05c3, 0x0623 },
{ 0x05c4, 0x0624 },
{ 0x05c5, 0x0625 },
{ 0x05c6, 0x0626 },
{ 0x05c7, 0x0627 },
{ 0x05c8, 0x0628 },
{ 0x05c9, 0x0629 },
{ 0x05ca, 0x062a },
{ 0x05cb, 0x062b },
{ 0x05cc, 0x062c },
{ 0x05cd, 0x062d },
{ 0x05ce, 0x062e },
{ 0x05cf, 0x062f },
{ 0x05d0, 0x0630 },
{ 0x05d1, 0x0631 },
{ 0x05d2, 0x0632 },
{ 0x05d3, 0x0633 },
{ 0x05d4, 0x0634 },
{ 0x05d5, 0x0635 },
{ 0x05d6, 0x0636 },
{ 0x05d7, 0x0637 },
{ 0x05d8, 0x0638 },
{ 0x05d9, 0x0639 },
{ 0x05da, 0x063a },
{ 0x05e0, 0x0640 },
{ 0x05e1, 0x0641 },
{ 0x05e2, 0x0642 },
{ 0x05e3, 0x0643 },
{ 0x05e4, 0x0644 },
{ 0x05e5, 0x0645 },
{ 0x05e6, 0x0646 },
{ 0x05e7, 0x0647 },
{ 0x05e8, 0x0648 },
{ 0x05e9, 0x0649 },
{ 0x05ea, 0x064a },
{ 0x05eb, 0x064b },
{ 0x05ec, 0x064c },
{ 0x05ed, 0x064d },
{ 0x05ee, 0x064e },
{ 0x05ef, 0x064f },
{ 0x05f0, 0x0650 },
{ 0x05f1, 0x0651 },
{ 0x05f2, 0x0652 },
{ 0x06a1, 0x0452 },
{ 0x06a2, 0x0453 },
{ 0x06a3, 0x0451 },
{ 0x06a4, 0x0454 },
{ 0x06a5, 0x0455 },
{ 0x06a6, 0x0456 },
{ 0x06a7, 0x0457 },
{ 0x06a8, 0x0458 },
{ 0x06a9, 0x0459 },
{ 0x06aa, 0x045a },
{ 0x06ab, 0x045b },
{ 0x06ac, 0x045c },
{ 0x06ae, 0x045e },
{ 0x06af, 0x045f },
{ 0x06b0, 0x2116 },
{ 0x06b1, 0x0402 },
{ 0x06b2, 0x0403 },
{ 0x06b3, 0x0401 },
{ 0x06b4, 0x0404 },
{ 0x06b5, 0x0405 },
{ 0x06b6, 0x0406 },
{ 0x06b7, 0x0407 },
{ 0x06b8, 0x0408 },
{ 0x06b9, 0x0409 },
{ 0x06ba, 0x040a },
{ 0x06bb, 0x040b },
{ 0x06bc, 0x040c },
{ 0x06be, 0x040e },
{ 0x06bf, 0x040f },
{ 0x06c0, 0x044e },
{ 0x06c1, 0x0430 },
{ 0x06c2, 0x0431 },
{ 0x06c3, 0x0446 },
{ 0x06c4, 0x0434 },
{ 0x06c5, 0x0435 },
{ 0x06c6, 0x0444 },
{ 0x06c7, 0x0433 },
{ 0x06c8, 0x0445 },
{ 0x06c9, 0x0438 },
{ 0x06ca, 0x0439 },
{ 0x06cb, 0x043a },
{ 0x06cc, 0x043b },
{ 0x06cd, 0x043c },
{ 0x06ce, 0x043d },
{ 0x06cf, 0x043e },
{ 0x06d0, 0x043f },
{ 0x06d1, 0x044f },
{ 0x06d2, 0x0440 },
{ 0x06d3, 0x0441 },
{ 0x06d4, 0x0442 },
{ 0x06d5, 0x0443 },
{ 0x06d6, 0x0436 },
{ 0x06d7, 0x0432 },
{ 0x06d8, 0x044c },
{ 0x06d9, 0x044b },
{ 0x06da, 0x0437 },
{ 0x06db, 0x0448 },
{ 0x06dc, 0x044d },
{ 0x06dd, 0x0449 },
{ 0x06de, 0x0447 },
{ 0x06df, 0x044a },
{ 0x06e0, 0x042e },
{ 0x06e1, 0x0410 },
{ 0x06e2, 0x0411 },
{ 0x06e3, 0x0426 },
{ 0x06e4, 0x0414 },
{ 0x06e5, 0x0415 },
{ 0x06e6, 0x0424 },
{ 0x06e7, 0x0413 },
{ 0x06e8, 0x0425 },
{ 0x06e9, 0x0418 },
{ 0x06ea, 0x0419 },
{ 0x06eb, 0x041a },
{ 0x06ec, 0x041b },
{ 0x06ed, 0x041c },
{ 0x06ee, 0x041d },
{ 0x06ef, 0x041e },
{ 0x06f0, 0x041f },
{ 0x06f1, 0x042f },
{ 0x06f2, 0x0420 },
{ 0x06f3, 0x0421 },
{ 0x06f4, 0x0422 },
{ 0x06f5, 0x0423 },
{ 0x06f6, 0x0416 },
{ 0x06f7, 0x0412 },
{ 0x06f8, 0x042c },
{ 0x06f9, 0x042b },
{ 0x06fa, 0x0417 },
{ 0x06fb, 0x0428 },
{ 0x06fc, 0x042d },
{ 0x06fd, 0x0429 },
{ 0x06fe, 0x0427 },
{ 0x06ff, 0x042a },
{ 0x07a1, 0x0386 },
{ 0x07a2, 0x0388 },
{ 0x07a3, 0x0389 },
{ 0x07a4, 0x038a },
{ 0x07a5, 0x03aa },
{ 0x07a7, 0x038c },
{ 0x07a8, 0x038e },
{ 0x07a9, 0x03ab },
{ 0x07ab, 0x038f },
{ 0x07ae, 0x0385 },
{ 0x07af, 0x2015 },
{ 0x07b1, 0x03ac },
{ 0x07b2, 0x03ad },
{ 0x07b3, 0x03ae },
{ 0x07b4, 0x03af },
{ 0x07b5, 0x03ca },
{ 0x07b6, 0x0390 },
{ 0x07b7, 0x03cc },
{ 0x07b8, 0x03cd },
{ 0x07b9, 0x03cb },
{ 0x07ba, 0x03b0 },
{ 0x07bb, 0x03ce },
{ 0x07c1, 0x0391 },
{ 0x07c2, 0x0392 },
{ 0x07c3, 0x0393 },
{ 0x07c4, 0x0394 },
{ 0x07c5, 0x0395 },
{ 0x07c6, 0x0396 },
{ 0x07c7, 0x0397 },
{ 0x07c8, 0x0398 },
{ 0x07c9, 0x0399 },
{ 0x07ca, 0x039a },
{ 0x07cb, 0x039b },
{ 0x07cc, 0x039c },
{ 0x07cd, 0x039d },
{ 0x07ce, 0x039e },
{ 0x07cf, 0x039f },
{ 0x07d0, 0x03a0 },
{ 0x07d1, 0x03a1 },
{ 0x07d2, 0x03a3 },
{ 0x07d4, 0x03a4 },
{ 0x07d5, 0x03a5 },
{ 0x07d6, 0x03a6 },
{ 0x07d7, 0x03a7 },
{ 0x07d8, 0x03a8 },
{ 0x07d9, 0x03a9 },
{ 0x07e1, 0x03b1 },
{ 0x07e2, 0x03b2 },
{ 0x07e3, 0x03b3 },
{ 0x07e4, 0x03b4 },
{ 0x07e5, 0x03b5 },
{ 0x07e6, 0x03b6 },
{ 0x07e7, 0x03b7 },
{ 0x07e8, 0x03b8 },
{ 0x07e9, 0x03b9 },
{ 0x07ea, 0x03ba },
{ 0x07eb, 0x03bb },
{ 0x07ec, 0x03bc },
{ 0x07ed, 0x03bd },
{ 0x07ee, 0x03be },
{ 0x07ef, 0x03bf },
{ 0x07f0, 0x03c0 },
{ 0x07f1, 0x03c1 },
{ 0x07f2, 0x03c3 },
{ 0x07f3, 0x03c2 },
{ 0x07f4, 0x03c4 },
{ 0x07f5, 0x03c5 },
{ 0x07f6, 0x03c6 },
{ 0x07f7, 0x03c7 },
{ 0x07f8, 0x03c8 },
{ 0x07f9, 0x03c9 },
{ 0x08a1, 0x23b7 },
{ 0x08a2, 0x250c },
{ 0x08a3, 0x2500 },
{ 0x08a4, 0x2320 },
{ 0x08a5, 0x2321 },
{ 0x08a6, 0x2502 },
{ 0x08a7, 0x23a1 },
{ 0x08a8, 0x23a3 },
{ 0x08a9, 0x23a4 },
{ 0x08aa, 0x23a6 },
{ 0x08ab, 0x239b },
{ 0x08ac, 0x239d },
{ 0x08ad, 0x239e },
{ 0x08ae, 0x23a0 },
{ 0x08af, 0x23a8 },
{ 0x08b0, 0x23ac },
{ 0x08bc, 0x2264 },
{ 0x08bd, 0x2260 },
{ 0x08be, 0x2265 },
{ 0x08bf, 0x222b },
{ 0x08c0, 0x2234 },
{ 0x08c1, 0x221d },
{ 0x08c2, 0x221e },
{ 0x08c5, 0x2207 },
{ 0x08c8, 0x223c },
{ 0x08c9, 0x2243 },
{ 0x08cd, 0x21d4 },
{ 0x08ce, 0x21d2 },
{ 0x08cf, 0x2261 },
{ 0x08d6, 0x221a },
{ 0x08da, 0x2282 },
{ 0x08db, 0x2283 },
{ 0x08dc, 0x2229 },
{ 0x08dd, 0x222a },
{ 0x08de, 0x2227 },
{ 0x08df, 0x2228 },
{ 0x08ef, 0x2202 },
{ 0x08f6, 0x0192 },
{ 0x08fb, 0x2190 },
{ 0x08fc, 0x2191 },
{ 0x08fd, 0x2192 },
{ 0x08fe, 0x2193 },
{ 0x09e0, 0x25c6 },
{ 0x09e1, 0x2592 },
{ 0x09e2, 0x2409 },
{ 0x09e3, 0x240c },
{ 0x09e4, 0x240d },
{ 0x09e5, 0x240a },
{ 0x09e8, 0x2424 },
{ 0x09e9, 0x240b },
{ 0x09ea, 0x2518 },
{ 0x09eb, 0x2510 },
{ 0x09ec, 0x250c },
{ 0x09ed, 0x2514 },
{ 0x09ee, 0x253c },
{ 0x09ef, 0x23ba },
{ 0x09f0, 0x23bb },
{ 0x09f1, 0x2500 },
{ 0x09f2, 0x23bc },
{ 0x09f3, 0x23bd },
{ 0x09f4, 0x251c },
{ 0x09f5, 0x2524 },
{ 0x09f6, 0x2534 },
{ 0x09f7, 0x252c },
{ 0x09f8, 0x2502 },
{ 0x0aa1, 0x2003 },
{ 0x0aa2, 0x2002 },
{ 0x0aa3, 0x2004 },
{ 0x0aa4, 0x2005 },
{ 0x0aa5, 0x2007 },
{ 0x0aa6, 0x2008 },
{ 0x0aa7, 0x2009 },
{ 0x0aa8, 0x200a },
{ 0x0aa9, 0x2014 },
{ 0x0aaa, 0x2013 },
{ 0x0aae, 0x2026 },
{ 0x0aaf, 0x2025 },
{ 0x0ab0, 0x2153 },
{ 0x0ab1, 0x2154 },
{ 0x0ab2, 0x2155 },
{ 0x0ab3, 0x2156 },
{ 0x0ab4, 0x2157 },
{ 0x0ab5, 0x2158 },
{ 0x0ab6, 0x2159 },
{ 0x0ab7, 0x215a },
{ 0x0ab8, 0x2105 },
{ 0x0abb, 0x2012 },
{ 0x0abc, 0x2329 },
{ 0x0abe, 0x232a },
{ 0x0ac3, 0x215b },
{ 0x0ac4, 0x215c },
{ 0x0ac5, 0x215d },
{ 0x0ac6, 0x215e },
{ 0x0ac9, 0x2122 },
{ 0x0aca, 0x2613 },
{ 0x0acc, 0x25c1 },
{ 0x0acd, 0x25b7 },
{ 0x0ace, 0x25cb },
{ 0x0acf, 0x25af },
{ 0x0ad0, 0x2018 },
{ 0x0ad1, 0x2019 },
{ 0x0ad2, 0x201c },
{ 0x0ad3, 0x201d },
{ 0x0ad4, 0x211e },
{ 0x0ad6, 0x2032 },
{ 0x0ad7, 0x2033 },
{ 0x0ad9, 0x271d },
{ 0x0adb, 0x25ac },
{ 0x0adc, 0x25c0 },
{ 0x0add, 0x25b6 },
{ 0x0ade, 0x25cf },
{ 0x0adf, 0x25ae },
{ 0x0ae0, 0x25e6 },
{ 0x0ae1, 0x25ab },
{ 0x0ae2, 0x25ad },
{ 0x0ae3, 0x25b3 },
{ 0x0ae4, 0x25bd },
{ 0x0ae5, 0x2606 },
{ 0x0ae6, 0x2022 },
{ 0x0ae7, 0x25aa },
{ 0x0ae8, 0x25b2 },
{ 0x0ae9, 0x25bc },
{ 0x0aea, 0x261c },
{ 0x0aeb, 0x261e },
{ 0x0aec, 0x2663 },
{ 0x0aed, 0x2666 },
{ 0x0aee, 0x2665 },
{ 0x0af0, 0x2720 },
{ 0x0af1, 0x2020 },
{ 0x0af2, 0x2021 },
{ 0x0af3, 0x2713 },
{ 0x0af4, 0x2717 },
{ 0x0af5, 0x266f },
{ 0x0af6, 0x266d },
{ 0x0af7, 0x2642 },
{ 0x0af8, 0x2640 },
{ 0x0af9, 0x260e },
{ 0x0afa, 0x2315 },
{ 0x0afb, 0x2117 },
{ 0x0afc, 0x2038 },
{ 0x0afd, 0x201a },
{ 0x0afe, 0x201e },
{ 0x0ba3, 0x003c },
{ 0x0ba6, 0x003e },
{ 0x0ba8, 0x2228 },
{ 0x0ba9, 0x2227 },
{ 0x0bc0, 0x00af },
{ 0x0bc2, 0x22a5 },
{ 0x0bc3, 0x2229 },
{ 0x0bc4, 0x230a },
{ 0x0bc6, 0x005f },
{ 0x0bca, 0x2218 },
{ 0x0bcc, 0x2395 },
{ 0x0bce, 0x22a4 },
{ 0x0bcf, 0x25cb },
{ 0x0bd3, 0x2308 },
{ 0x0bd6, 0x222a },
{ 0x0bd8, 0x2283 },
{ 0x0bda, 0x2282 },
{ 0x0bdc, 0x22a2 },
{ 0x0bfc, 0x22a3 },
{ 0x0cdf, 0x2017 },
{ 0x0ce0, 0x05d0 },
{ 0x0ce1, 0x05d1 },
{ 0x0ce2, 0x05d2 },
{ 0x0ce3, 0x05d3 },
{ 0x0ce4, 0x05d4 },
{ 0x0ce5, 0x05d5 },
{ 0x0ce6, 0x05d6 },
{ 0x0ce7, 0x05d7 },
{ 0x0ce8, 0x05d8 },
{ 0x0ce9, 0x05d9 },
{ 0x0cea, 0x05da },
{ 0x0ceb, 0x05db },
{ 0x0cec, 0x05dc },
{ 0x0ced, 0x05dd },
{ 0x0cee, 0x05de },
{ 0x0cef, 0x05df },
{ 0x0cf0, 0x05e0 },
{ 0x0cf1, 0x05e1 },
{ 0x0cf2, 0x05e2 },
{ 0x0cf3, 0x05e3 },
{ 0x0cf4, 0x05e4 },
{ 0x0cf5, 0x05e5 },
{ 0x0cf6, 0x05e6 },
{ 0x0cf7, 0x05e7 },
{ 0x0cf8, 0x05e8 },
{ 0x0cf9, 0x05e9 },
{ 0x0cfa, 0x05ea },
{ 0x0da1, 0x0e01 },
{ 0x0da2, 0x0e02 },
{ 0x0da3, 0x0e03 },
{ 0x0da4, 0x0e04 },
{ 0x0da5, 0x0e05 },
{ 0x0da6, 0x0e06 },
{ 0x0da7, 0x0e07 },
{ 0x0da8, 0x0e08 },
{ 0x0da9, 0x0e09 },
{ 0x0daa, 0x0e0a },
{ 0x0dab, 0x0e0b },
{ 0x0dac, 0x0e0c },
{ 0x0dad, 0x0e0d },
{ 0x0dae, 0x0e0e },
{ 0x0daf, 0x0e0f },
{ 0x0db0, 0x0e10 },
{ 0x0db1, 0x0e11 },
{ 0x0db2, 0x0e12 },
{ 0x0db3, 0x0e13 },
{ 0x0db4, 0x0e14 },
{ 0x0db5, 0x0e15 },
{ 0x0db6, 0x0e16 },
{ 0x0db7, 0x0e17 },
{ 0x0db8, 0x0e18 },
{ 0x0db9, 0x0e19 },
{ 0x0dba, 0x0e1a },
{ 0x0dbb, 0x0e1b },
{ 0x0dbc, 0x0e1c },
{ 0x0dbd, 0x0e1d },
{ 0x0dbe, 0x0e1e },
{ 0x0dbf, 0x0e1f },
{ 0x0dc0, 0x0e20 },
{ 0x0dc1, 0x0e21 },
{ 0x0dc2, 0x0e22 },
{ 0x0dc3, 0x0e23 },
{ 0x0dc4, 0x0e24 },
{ 0x0dc5, 0x0e25 },
{ 0x0dc6, 0x0e26 },
{ 0x0dc7, 0x0e27 },
{ 0x0dc8, 0x0e28 },
{ 0x0dc9, 0x0e29 },
{ 0x0dca, 0x0e2a },
{ 0x0dcb, 0x0e2b },
{ 0x0dcc, 0x0e2c },
{ 0x0dcd, 0x0e2d },
{ 0x0dce, 0x0e2e },
{ 0x0dcf, 0x0e2f },
{ 0x0dd0, 0x0e30 },
{ 0x0dd1, 0x0e31 },
{ 0x0dd2, 0x0e32 },
{ 0x0dd3, 0x0e33 },
{ 0x0dd4, 0x0e34 },
{ 0x0dd5, 0x0e35 },
{ 0x0dd6, 0x0e36 },
{ 0x0dd7, 0x0e37 },
{ 0x0dd8, 0x0e38 },
{ 0x0dd9, 0x0e39 },
{ 0x0dda, 0x0e3a },
{ 0x0ddf, 0x0e3f },
{ 0x0de0, 0x0e40 },
{ 0x0de1, 0x0e41 },
{ 0x0de2, 0x0e42 },
{ 0x0de3, 0x0e43 },
{ 0x0de4, 0x0e44 },
{ 0x0de5, 0x0e45 },
{ 0x0de6, 0x0e46 },
{ 0x0de7, 0x0e47 },
{ 0x0de8, 0x0e48 },
{ 0x0de9, 0x0e49 },
{ 0x0dea, 0x0e4a },
{ 0x0deb, 0x0e4b },
{ 0x0dec, 0x0e4c },
{ 0x0ded, 0x0e4d },
{ 0x0df0, 0x0e50 },
{ 0x0df1, 0x0e51 },
{ 0x0df2, 0x0e52 },
{ 0x0df3, 0x0e53 },
{ 0x0df4, 0x0e54 },
{ 0x0df5, 0x0e55 },
{ 0x0df6, 0x0e56 },
{ 0x0df7, 0x0e57 },
{ 0x0df8, 0x0e58 },
{ 0x0df9, 0x0e59 },
{ 0x0ea1, 0x3131 },
{ 0x0ea2, 0x3132 },
{ 0x0ea3, 0x3133 },
{ 0x0ea4, 0x3134 },
{ 0x0ea5, 0x3135 },
{ 0x0ea6, 0x3136 },
{ 0x0ea7, 0x3137 },
{ 0x0ea8, 0x3138 },
{ 0x0ea9, 0x3139 },
{ 0x0eaa, 0x313a },
{ 0x0eab, 0x313b },
{ 0x0eac, 0x313c },
{ 0x0ead, 0x313d },
{ 0x0eae, 0x313e },
{ 0x0eaf, 0x313f },
{ 0x0eb0, 0x3140 },
{ 0x0eb1, 0x3141 },
{ 0x0eb2, 0x3142 },
{ 0x0eb3, 0x3143 },
{ 0x0eb4, 0x3144 },
{ 0x0eb5, 0x3145 },
{ 0x0eb6, 0x3146 },
{ 0x0eb7, 0x3147 },
{ 0x0eb8, 0x3148 },
{ 0x0eb9, 0x3149 },
{ 0x0eba, 0x314a },
{ 0x0ebb, 0x314b },
{ 0x0ebc, 0x314c },
{ 0x0ebd, 0x314d },
{ 0x0ebe, 0x314e },
{ 0x0ebf, 0x314f },
{ 0x0ec0, 0x3150 },
{ 0x0ec1, 0x3151 },
{ 0x0ec2, 0x3152 },
{ 0x0ec3, 0x3153 },
{ 0x0ec4, 0x3154 },
{ 0x0ec5, 0x3155 },
{ 0x0ec6, 0x3156 },
{ 0x0ec7, 0x3157 },
{ 0x0ec8, 0x3158 },
{ 0x0ec9, 0x3159 },
{ 0x0eca, 0x315a },
{ 0x0ecb, 0x315b },
{ 0x0ecc, 0x315c },
{ 0x0ecd, 0x315d },
{ 0x0ece, 0x315e },
{ 0x0ecf, 0x315f },
{ 0x0ed0, 0x3160 },
{ 0x0ed1, 0x3161 },
{ 0x0ed2, 0x3162 },
{ 0x0ed3, 0x3163 },
{ 0x0ed4, 0x11a8 },
{ 0x0ed5, 0x11a9 },
{ 0x0ed6, 0x11aa },
{ 0x0ed7, 0x11ab },
{ 0x0ed8, 0x11ac },
{ 0x0ed9, 0x11ad },
{ 0x0eda, 0x11ae },
{ 0x0edb, 0x11af },
{ 0x0edc, 0x11b0 },
{ 0x0edd, 0x11b1 },
{ 0x0ede, 0x11b2 },
{ 0x0edf, 0x11b3 },
{ 0x0ee0, 0x11b4 },
{ 0x0ee1, 0x11b5 },
{ 0x0ee2, 0x11b6 },
{ 0x0ee3, 0x11b7 },
{ 0x0ee4, 0x11b8 },
{ 0x0ee5, 0x11b9 },
{ 0x0ee6, 0x11ba },
{ 0x0ee7, 0x11bb },
{ 0x0ee8, 0x11bc },
{ 0x0ee9, 0x11bd },
{ 0x0eea, 0x11be },
{ 0x0eeb, 0x11bf },
{ 0x0eec, 0x11c0 },
{ 0x0eed, 0x11c1 },
{ 0x0eee, 0x11c2 },
{ 0x0eef, 0x316d },
{ 0x0ef0, 0x3171 },
{ 0x0ef1, 0x3178 },
{ 0x0ef2, 0x317f },
{ 0x0ef3, 0x3181 },
{ 0x0ef4, 0x3184 },
{ 0x0ef5, 0x3186 },
{ 0x0ef6, 0x318d },
{ 0x0ef7, 0x318e },
{ 0x0ef8, 0x11eb },
{ 0x0ef9, 0x11f0 },
{ 0x0efa, 0x11f9 },
{ 0x0eff, 0x20a9 },
{ 0x13a4, 0x20ac },
{ 0x13bc, 0x0152 },
{ 0x13bd, 0x0153 },
{ 0x13be, 0x0178 },
{ 0x20ac, 0x20ac },
{ 0xfe50, '`' },
{ 0xfe51, 0x00b4 },
{ 0xfe52, '^' },
{ 0xfe53, '~' },
{ 0xfe54, 0x00af },
{ 0xfe55, 0x02d8 },
{ 0xfe56, 0x02d9 },
{ 0xfe57, 0x00a8 },
{ 0xfe58, 0x02da },
{ 0xfe59, 0x02dd },
{ 0xfe5a, 0x02c7 },
{ 0xfe5b, 0x00b8 },
{ 0xfe5c, 0x02db },
{ 0xfe5d, 0x037a },
{ 0xfe5e, 0x309b },
{ 0xfe5f, 0x309c },
{ 0xfe63, '/' },
{ 0xfe64, 0x02bc },
{ 0xfe65, 0x02bd },
{ 0xfe66, 0x02f5 },
{ 0xfe67, 0x02f3 },
{ 0xfe68, 0x02cd },
{ 0xfe69, 0xa788 },
{ 0xfe6a, 0x02f7 },
{ 0xfe6e, ',' },
{ 0xfe6f, 0x00a4 },
{ 0xfe80, 'a' }, // XK_dead_a
{ 0xfe81, 'A' }, // XK_dead_A
{ 0xfe82, 'e' }, // XK_dead_e
{ 0xfe83, 'E' }, // XK_dead_E
{ 0xfe84, 'i' }, // XK_dead_i
{ 0xfe85, 'I' }, // XK_dead_I
{ 0xfe86, 'o' }, // XK_dead_o
{ 0xfe87, 'O' }, // XK_dead_O
{ 0xfe88, 'u' }, // XK_dead_u
{ 0xfe89, 'U' }, // XK_dead_U
{ 0xfe8a, 0x0259 },
{ 0xfe8b, 0x018f },
{ 0xfe8c, 0x00b5 },
{ 0xfe90, '_' },
{ 0xfe91, 0x02c8 },
{ 0xfe92, 0x02cc },
{ 0xff80 /*XKB_KEY_KP_Space*/, ' ' },
{ 0xff95 /*XKB_KEY_KP_7*/, 0x0037 },
{ 0xff96 /*XKB_KEY_KP_4*/, 0x0034 },
{ 0xff97 /*XKB_KEY_KP_8*/, 0x0038 },
{ 0xff98 /*XKB_KEY_KP_6*/, 0x0036 },
{ 0xff99 /*XKB_KEY_KP_2*/, 0x0032 },
{ 0xff9a /*XKB_KEY_KP_9*/, 0x0039 },
{ 0xff9b /*XKB_KEY_KP_3*/, 0x0033 },
{ 0xff9c /*XKB_KEY_KP_1*/, 0x0031 },
{ 0xff9d /*XKB_KEY_KP_5*/, 0x0035 },
{ 0xff9e /*XKB_KEY_KP_0*/, 0x0030 },
{ 0xffaa /*XKB_KEY_KP_Multiply*/, '*' },
{ 0xffab /*XKB_KEY_KP_Add*/, '+' },
{ 0xffac /*XKB_KEY_KP_Separator*/, ',' },
{ 0xffad /*XKB_KEY_KP_Subtract*/, '-' },
{ 0xffae /*XKB_KEY_KP_Decimal*/, '.' },
{ 0xffaf /*XKB_KEY_KP_Divide*/, '/' },
{ 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 },
{ 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 },
{ 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 },
{ 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 },
{ 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 },
{ 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 },
{ 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 },
{ 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 },
{ 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 },
{ 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 },
{ 0xffbd /*XKB_KEY_KP_Equal*/, '=' }
};
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Convert XKB KeySym to Unicode
//
long _glfwKeySym2Unicode(unsigned int keysym)
{
int min = 0;
int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
int mid;
// First check for Latin-1 characters (1:1 mapping)
if ((keysym >= 0x0020 && keysym <= 0x007e) ||
(keysym >= 0x00a0 && keysym <= 0x00ff))
{
return keysym;
}
// Also check for directly encoded 24-bit UCS characters
if ((keysym & 0xff000000) == 0x01000000)
return keysym & 0x00ffffff;
// Binary search in table
while (max >= min)
{
mid = (min + max) / 2;
if (keysymtab[mid].keysym < keysym)
min = mid + 1;
else if (keysymtab[mid].keysym > keysym)
max = mid - 1;
else
return keysymtab[mid].ucs;
}
// No matching Unicode value found
return -1;
}

28
glfw/xkb_unicode.h vendored
View File

@@ -1,28 +0,0 @@
//========================================================================
// GLFW 3.3 Linux - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2014 Jonas Ådahl <jadahl@gmail.com>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
long _glfwKeySym2Unicode(unsigned int keysym);

124
kittens/ask/main.py Normal file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
from kitty.cli import parse_args
from kitty.constants import cache_dir
from ..tui.operations import alternate_screen, styled
readline = None
def get_history_items():
return list(map(readline.get_history_item, range(1, readline.get_current_history_length() + 1)))
def sort_key(item):
return len(item), item.lower()
class HistoryCompleter:
def __init__(self, name=None):
self.matches = []
self.history_path = None
if name:
ddir = os.path.join(cache_dir(), 'ask')
try:
os.makedirs(ddir)
except FileExistsError:
pass
self.history_path = os.path.join(ddir, name)
def complete(self, text, state):
response = None
if state == 0:
history_values = get_history_items()
if text:
self.matches = sorted(
(h for h in history_values if h and h.startswith(text)), key=sort_key)
else:
self.matches = []
try:
response = self.matches[state]
except IndexError:
response = None
return response
def __enter__(self):
if self.history_path:
if os.path.exists(self.history_path):
readline.read_history_file(self.history_path)
readline.set_completer(self.complete)
readline.parse_and_bind('tab: complete')
return self
def __exit__(self, *a):
if self.history_path:
readline.write_history_file(self.history_path)
def option_text():
return '''\
--type -t
choices=line
default=line
Type of input. Defaults to asking for a line of text.
--message -m
The message to display to the user. If not specified a default
message is shown.
--name -n
The name for this question. Used to store history of previous answers which can
be used for completions and via the browse history readline bindings.
'''
def main(args):
# For some reason importing readline in a key handler in the main kitty process
# causes a crash of the python interpreter, probably because of some global
# lock
global readline
import readline as rl
readline = rl
msg = 'Ask the user for input'
try:
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask')
except SystemExit as e:
if e.code != 0:
print(e.args[0])
input('Press enter to quit...')
raise SystemExit(e.code)
readline.read_init_file()
ans = {'items': items}
with alternate_screen(), HistoryCompleter(args.name):
if args.message:
print(styled(args.message, bold=True))
prompt = '> '
try:
ans['response'] = input(prompt)
except (KeyboardInterrupt, EOFError):
pass
return ans
def handle_result(args, data, target_window_id, boss):
if 'response' in data:
func, *args = data['items']
getattr(boss, func)(data['response'], *args)
if __name__ == '__main__':
import sys
ans = main(sys.argv)
if ans:
print(ans)

View File

View File

@@ -2,17 +2,16 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
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.fast_data_types import set_clipboard_string
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
@@ -20,11 +19,19 @@ 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
class Mark(object):
__slots__ = ('index', 'start', 'end', 'text')
def __init__(self, index, start, end, text):
self.index, self.start, self.end = index, start, end
self.text = text
@lru_cache(maxsize=2048)
def encode_hint(num):
res = ''
@@ -79,17 +86,19 @@ def render(lines, current_input):
return '\r\n'.join(ans)
class URLHints(Handler):
class Hints(Handler):
def __init__(self, lines, index_map):
def __init__(self, lines, index_map, args):
self.lines, self.index_map = tuple(lines), index_map
self.current_input = ''
self.current_text = None
self.args = args
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
self.chosen = None
def init_terminal_state(self):
self.write(set_cursor_visible(False))
self.write(set_window_title(_('Choose URL')))
self.write(set_window_title(self.window_title))
def initialize(self, *args):
Handler.initialize(self, *args)
@@ -104,7 +113,7 @@ class URLHints(Handler):
changed = True
if changed:
matches = [
t for idx, t in self.index_map.items()
m.text for idx, m in self.index_map.items()
if encode_hint(idx).startswith(self.current_input)
]
if len(matches) == 1:
@@ -121,7 +130,7 @@ class URLHints(Handler):
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.chosen = self.index_map[idx].text
self.quit_loop(0)
elif key_event.key is ESCAPE:
self.quit_loop(1)
@@ -143,10 +152,10 @@ class URLHints(Handler):
self.write(self.current_text)
def regex_finditer(pat, line):
def regex_finditer(pat, minimum_match_length, line):
for m in pat.finditer(line):
s, e = m.span()
if e - s > 2:
s, e = m.span(pat.groups)
if e - s >= minimum_match_length:
yield s, e
@@ -158,81 +167,151 @@ def find_urls(pat, line):
idx = url.rfind('[')
if idx > -1:
e -= len(url) - idx
while line[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
e -= 1
yield s, e
def mark(finditer, line, index_map):
def mark(finditer, line, all_marks):
marks = []
for s, e in finditer(line):
idx = len(index_map)
idx = len(all_marks)
text = line[s:e]
marks.append(Mark(idx, s, e, text))
index_map[idx] = text
all_marks.append(marks[-1])
return line, marks
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:
def run_loop(args, lines, index_map):
loop = Loop()
handler = Hints(lines, index_map, args)
loop.loop(handler)
if handler.chosen and loop.return_code == 0:
return {'match': handler.chosen, 'program': args.program}
raise SystemExit(loop.return_code)
def escape(chars):
return chars.replace('\\', '\\\\').replace('-', r'\-').replace(']', r'\]')
def run(args, text):
if args.type == 'url':
from .url_regex import url_delimiters
url_pat = '(?:{})://[^{}]{{3,}}'.format(
'|'.join(args.url_prefixes.split(',')), url_delimiters
)
finditer = partial(find_urls, re.compile(url_pat))
elif args.type == 'path':
finditer = partial(regex_finditer, re.compile(r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'), args.minimum_match_length)
elif args.type == 'line':
finditer = partial(regex_finditer, re.compile(r'(?m)^\s*(.+)\s*$'), args.minimum_match_length)
elif args.type == 'word':
chars = args.word_characters
if chars is None:
import json
chars = json.loads(os.environ['KITTY_COMMON_OPTS'])['select_by_word_characters']
pat = re.compile('(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length))
finditer = partial(regex_finditer, pat, args.minimum_match_length)
else:
finditer = partial(regex_finditer, re.compile(args.regex))
finditer = partial(regex_finditer, re.compile(args.regex), args.minimum_match_length)
lines = []
index_map = {}
all_marks = []
for line in text.splitlines():
marked = mark(finditer, line, index_map)
marked = mark(finditer, line, all_marks)
lines.append(marked)
if not index_map:
input(_('No URLs found, press Enter to abort.'))
if not all_marks:
input(_('No {} found, press Enter to abort.').format(
'URLs' if args.type == 'url' else 'matches'
))
return
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)
largest_index = all_marks[-1].index
for m in all_marks:
m.index = largest_index - m.index
index_map = {m.index: m for m in all_marks}
return run_loop(args, lines, index_map)
OPTIONS = partial('''\
OPTIONS = partial(r'''
--program
default=default
What program to use to open matched URLs. Defaults
to the default URL open program for the operating system.
What program to use to open matched text. Defaults to the default open program
for the operating system. Use a value of - to paste the match into the
terminal window instead. A value of @ will copy the match to the clipboard.
--type
default=url
choices=url,regex,path,line,word
The type of text to search for.
--regex
Instead of searching for URLs search for the specified regular
expression instead.
default=(?m)^\s*(.+)\s*$
The regular expression to use when --type=regex. If you specify a group in the
regular expression only the group will be matched. This allow you to match text
ignoring a prefix/suffix, as needed. The default expression matches lines.
--url-prefixes
default={0}
Comma separeted list of recognized URL prefixes. Defaults to:
{0}
Comma separated list of recognized URL prefixes.
--word-characters
Characters to consider as part of a word. In addition, all chacraters marked as
alpha-numeric in the unicode database will be considered as word characters.
Defaults to the select_by_word_characters setting from kitty.conf.
--minimum-match-length
default=3
type=int
The minimum number of characters to consider a match.
'''.format, ','.join(sorted(URL_PREFIXES)))
def main(args=sys.argv):
msg = 'Highlight URLs inside the specified text'
def main(args):
msg = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
text = ''
if sys.stdin.isatty():
if '--help' not in args and '-h' not in args:
print('You must pass the text to be hinted on STDIN', file=sys.stderr)
input(_('Press Enter to quit'))
return
else:
text = sys.stdin.buffer.read().decode('utf-8')
sys.stdin = open('/dev/tty')
try:
args, items = parse_args(args[1:], OPTIONS, '[path to file or omit to use stdin]', msg, 'url_hints')
args, items = parse_args(args[1:], OPTIONS, '', msg, '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])
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
if items:
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
input(_('Press Enter to quit'))
return
return run(args, text)
def handle_result(args, data, target_window_id, boss):
program = data['program']
if program == '-':
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(data['match'])
elif program == '@':
set_clipboard_string(data['match'])
else:
boss.open_url(data['match'], None if program == 'default' else program)
if __name__ == '__main__':
# Run with kitty +kitten hints
ans = main(sys.argv)
if ans:
print(ans)

76
kittens/runner.py Normal file
View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import importlib
import os
import sys
from functools import partial
aliases = {'url_hints': 'hints'}
def resolved_kitten(k):
return aliases.get(k, k)
def import_kitten_main_module(config_dir, kitten):
if kitten.endswith('.py'):
path = os.path.expanduser(kitten)
if not os.path.isabs(path):
path = os.path.join(config_dir, path)
path = os.path.abspath(path)
if os.path.dirname(path):
sys.path.insert(0, os.path.dirname(path))
with open(path) as f:
src = f.read()
code = compile(src, path, 'exec')
g = {'__name__': 'kitten'}
exec(code, g)
return {'start': g['main'], 'end': g['handle_result']}
else:
kitten = resolved_kitten(kitten)
m = importlib.import_module('kittens.{}.main'.format(kitten))
return {'start': m.main, 'end': m.handle_result}
def create_kitten_handler(kitten, orig_args):
from kitty.constants import config_dir
kitten = resolved_kitten(kitten)
m = import_kitten_main_module(config_dir, kitten)
return partial(m['end'], [kitten] + orig_args)
def launch(args):
config_dir, kitten = args[:2]
kitten = resolved_kitten(kitten)
del args[:2]
args = [kitten] + args
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
from kittens.tui.operations import clear_screen, reset_mode
m = import_kitten_main_module(config_dir, kitten)
result = m['start'](args)
print(reset_mode('ALTERNATE_SCREEN') + clear_screen(), end='')
if result is not None:
import json
print('OK:', json.dumps(result))
sys.stderr.flush()
sys.stdout.flush()
def run_kitten(kitten):
import runpy
kitten = resolved_kitten(kitten)
runpy.run_module('kittens.{}.main'.format(kitten), run_name='__main__')
def main():
try:
args = sys.argv[1:]
launch(args)
except Exception:
print('Unhandled exception running kitten:')
import traceback
traceback.print_exc()
input('Press Enter to quit...')

View File

@@ -16,7 +16,7 @@ from collections import namedtuple
from contextlib import closing, contextmanager
from functools import partial
from kitty.fast_data_types import parse_input_from_terminal
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,
@@ -142,10 +142,12 @@ 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 = input_fd or sys.stdin.fileno()
self.output_fd = output_fd or sys.stdout.fileno()
self.wakeup_read_fd, self.wakeup_write_fd = os.pipe()
self.sel = s = selectors.DefaultSelector()
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
@@ -169,7 +171,10 @@ class Loop:
def _read_ready(self, handler):
if not self.read_allowed:
return
data = os.read(self.input_fd, io.DEFAULT_BUFFER_SIZE)
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)

View File

@@ -2,6 +2,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
from contextlib import contextmanager
from kitty.terminfo import string_capabilities
@@ -151,3 +152,11 @@ def cursor(write):
write(SAVE_CURSOR)
yield
write(RESTORE_CURSOR)
@contextmanager
def alternate_screen(f=None):
f = f or sys.stdout
print(set_mode('ALTERNATE_SCREEN'), end='', file=f)
yield
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)

View File

@@ -3,15 +3,13 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import shlex
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
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,
@@ -44,7 +42,7 @@ def codepoint_ok(code):
@lru_cache(maxsize=256)
def points_for_word(w):
from .unicode_names import codepoints_for_word
return codepoints_for_word(w)
return codepoints_for_word(w.lower())
@lru_cache(maxsize=4096)
@@ -207,7 +205,7 @@ class Table:
col_width = longest + 2
col_width = min(col_width, 40)
space_for_desc = col_width - 2 - idx_size - 4
num_cols = self.num_cols = cols // col_width
num_cols = self.num_cols = max(cols // col_width, 1)
buf = []
a = buf.append
rows_left = rows
@@ -224,6 +222,8 @@ class Table:
return self.text
def move_current(self, rows=0, cols=0):
if len(self.codepoints) == 0:
return
if cols:
self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
self.layout_dirty = True
@@ -361,9 +361,9 @@ class UnicodeInput(Handler):
y += 1
if self.mode is NAME:
writeln(_('Enter the hex code for the character'))
elif self.mode is HEX:
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)
@@ -426,7 +426,6 @@ class UnicodeInput(Handler):
if not os.path.exists(favorites_path):
with open(favorites_path, 'wb') as f:
f.write(serialize_favorites(load_favorites()).encode('utf-8'))
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
with self.suspend():
p = subprocess.Popen(editor + [favorites_path])
if p.wait() == 0:
@@ -454,17 +453,34 @@ class UnicodeInput(Handler):
self.refresh()
def main(args=sys.argv):
def main(args):
loop = Loop()
with cached_values_for('unicode-input') as cached_values:
handler = UnicodeInput(cached_values)
loop.loop(handler)
if handler.current_char and loop.return_code == 0:
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)]
raise SystemExit(loop.return_code)
return handler.current_char
if loop.return_code != 0:
raise SystemExit(loop.return_code)
def handle_result(args, current_char, target_window_id, boss):
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(current_char)
if __name__ == '__main__':
import sys
if '-h' in sys.argv or '--help' in sys.argv:
print('Choose a unicode character to input into the terminal')
raise SystemExit(0)
ans = main(sys.argv)
if ans:
print(ans)

View File

@@ -8,7 +8,7 @@
#include "names.h"
static PyObject*
all_words(PyObject *self UNUSED) {
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++) {

View File

@@ -2,29 +2,37 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
import json
import os
import re
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, initial_window_size
from .constants import appname, set_boss, wakeup
from .config import (
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
)
from .constants import appname, editor, set_boss, config_dir
from .fast_data_types import (
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
set_dpi_from_os_window, show_window, toggle_fullscreen,
viewport_for_window
set_dpi_from_os_window, set_in_sequence_mode, show_window,
toggle_fullscreen, viewport_for_window
)
from .fonts.render import prerender, resize_fonts, set_font_family
from .keys import get_shortcut
from .keys import get_shortcut, shortcut_matches
from .remote_control import handle_cmd
from .rgb import Color, color_from_int
from .session import create_session
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
from .utils import (
end_startup_notification, get_primary_selection, init_startup_notification,
open_url, safe_print, set_primary_selection, single_instance
log_error, open_url, parse_address_spec, remove_socket_file, safe_print,
set_primary_selection, single_instance
)
@@ -33,6 +41,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):
@@ -64,16 +81,21 @@ class Boss:
def __init__(self, os_window_id, opts, args, cached_values):
self.window_id_map = WeakValueDictionary()
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.pending_sequences = None
self.cached_values = cached_values
self.os_window_map = {}
self.cursor_blinking = True
self.shutting_down = False
talk_fd = getattr(single_instance, 'socket', None)
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
listen_fd = -1
if opts.allow_remote_control and args.listen_on:
listen_fd = listen_on(args.listen_on)
self.child_monitor = ChildMonitor(
self.on_child_death,
DumpCommands(args) if args.dump_commands or args.dump_bytes else None,
talk_fd
talk_fd, listen_fd
)
set_boss(self)
self.current_font_size = opts.font_size
@@ -98,6 +120,7 @@ class Boss:
self.os_window_map[os_window_id] = tm
if dpi_changed:
self.on_dpi_change(os_window_id)
return os_window_id
def list_os_windows(self):
for os_window_id, tm in self.os_window_map.items():
@@ -106,33 +129,48 @@ class Boss:
'tabs': list(tm.list_tabs()),
}
@property
def all_tab_managers(self):
yield from self.os_window_map.values()
@property
def all_tabs(self):
for tm in self.all_tab_managers:
yield from tm
@property
def all_windows(self):
for tab in self.all_tabs:
yield from tab
def match_windows(self, match):
field, exp = match.split(':', 1)
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
for window in self.all_windows:
if window.matches(field, pat):
yield window
def tab_for_window(self, window):
for tm in self.os_window_map.values():
for tab in tm:
for w in tab:
if w.id == window.id:
return tab
for tab in self.all_tabs:
for w in tab:
if w.id == window.id:
return tab
def match_tabs(self, match):
field, exp = match.split(':', 1)
try:
field, exp = match.split(':', 1)
except ValueError:
return
pat = re.compile(exp)
tms = tuple(self.os_window_map.values())
found = False
if field in ('title', 'id'):
for tm in tms:
for tab in tm:
if tab.matches(field, pat):
yield tab
found = True
for tab in self.all_tabs:
if tab.matches(field, pat):
yield tab
found = True
if not found:
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
for tab in tabs:
@@ -152,7 +190,7 @@ class Boss:
def _new_os_window(self, args, cwd_from=None):
sw = self.args_to_special_window(args, cwd_from) if args else None
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
self.add_os_window(startup_session)
return self.add_os_window(startup_session)
def new_os_window(self, *args):
self._new_os_window(args)
@@ -166,30 +204,45 @@ class Boss:
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
self.window_id_map[window.id] = window
def peer_messages_received(self, messages):
import json
for msg in messages:
msg = json.loads(msg.decode('utf-8'))
def _handle_remote_command(self, cmd, window=None):
response = None
if self.opts.allow_remote_control or getattr(window, 'allow_remote_control', False):
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
import traceback
response = {'ok': False, 'error': str(err)}
if not getattr(err, 'hide_traceback', False):
response['tb'] = traceback.format_exc()
else:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
return response
def peer_message_received(self, msg):
msg = msg.decode('utf-8')
cmd_prefix = '\x1bP@kitty-cmd'
if msg.startswith(cmd_prefix):
cmd = msg[len(cmd_prefix):-2]
response = self._handle_remote_command(cmd)
if response is not None:
response = (cmd_prefix + json.dumps(response) + '\x1b\\').encode('utf-8')
return response
else:
msg = json.loads(msg)
if isinstance(msg, dict) and msg.get('cmd') == 'new_instance':
startup_id = msg.get('startup_id')
args, rest = parse_args(msg['args'][1:])
args.args = rest
opts = create_opts(args)
session = create_session(opts, args)
if not os.path.isabs(args.directory):
args.directory = os.path.join(msg['cwd'], args.directory)
session = create_session(opts, args, respect_cwd=True)
self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts, self.cached_values), startup_id=startup_id)
else:
safe_print('Unknown message received from peer, ignoring')
log_error('Unknown message received from peer, ignoring')
def handle_remote_cmd(self, cmd, window=None):
response = None
if self.opts.allow_remote_control:
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
import traceback
response = {'ok': False, 'error': str(err), 'tb': traceback.format_exc()}
else:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
response = self._handle_remote_command(cmd, window)
if response is not None:
if window is not None:
window.send_cmd_response(response)
@@ -259,19 +312,16 @@ class Boss:
tm.resize()
def increase_font_size(self):
self.change_font_size(
self.set_font_size(
min(
self.opts.font_size * 5, self.current_font_size +
self.opts.font_size_delta))
def decrease_font_size(self):
self.change_font_size(
max(
MINIMUM_FONT_SIZE, self.current_font_size -
self.opts.font_size_delta))
self.set_font_size(self.current_font_size - self.opts.font_size_delta)
def restore_font_size(self):
self.change_font_size(self.opts.font_size)
self.set_font_size(self.opts.font_size)
def _change_font_size(self, new_size=None, on_dpi_change=False):
if new_size is not None:
@@ -289,7 +339,8 @@ class Boss:
tm.refresh_sprite_positions()
glfw_post_empty_event()
def change_font_size(self, new_size):
def set_font_size(self, new_size):
new_size = max(MINIMUM_FONT_SIZE, new_size)
if new_size == self.current_font_size:
return
self._change_font_size(new_size)
@@ -317,8 +368,37 @@ class Boss:
def dispatch_special_key(self, key, scancode, action, mods):
# Handles shortcuts, return True if the key was consumed
key_action = get_shortcut(self.opts.keymap, mods, key, scancode)
self.current_key_press_info = key, scancode, action, mods
return self.dispatch_action(key_action)
if key_action is None:
sequences = get_shortcut(self.opts.sequence_map, mods, key, scancode)
if sequences:
self.pending_sequences = sequences
set_in_sequence_mode(True)
return True
else:
self.current_key_press_info = key, scancode, action, mods
return self.dispatch_action(key_action)
def process_sequence(self, key, scancode, action, mods):
if not self.pending_sequences:
set_in_sequence_mode(False)
remaining = {}
matched_action = None
for seq, key_action in self.pending_sequences.items():
if shortcut_matches(seq[0], mods, key, scancode):
seq = seq[1:]
if seq:
remaining[seq] = key_action
else:
matched_action = key_action
if remaining:
self.pending_sequences = remaining
else:
self.pending_sequences = None
set_in_sequence_mode(False)
if matched_action is not None:
self.dispatch_action(matched_action)
def default_bg_changed_for(self, window_id):
w = self.window_id_map.get(window_id)
@@ -383,49 +463,93 @@ class Boss:
SpecialWindow(
self.opts.scrollback_pager, data, _('History'), overlay_for=window.id))
def input_unicode_character(self):
def edit_config_file(self, *a):
confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay.
cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + editor + [confpath]
self.new_os_window(*cmd)
def get_output(self, source_window, num_lines=1):
output = ''
s = source_window.screen
if num_lines is None:
num_lines = s.lines
for i in range(min(num_lines, s.lines)):
output += str(s.linebuf.line(i))
return output
def _run_kitten(self, kitten, args=(), type_of_input='none'):
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
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))
orig_args, args = list(args), list(args)
args[0:0] = [config_dir, kitten]
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
elif type_of_input == 'none':
data = None
else:
raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
tab.new_special_window(
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
copts = {k: self.opts[k] for k in ('select_by_word_characters', 'open_url_with')}
overlay_window = tab.new_special_window(
SpecialWindow(
['kitty', '+runpy', 'from kittens.{}.main import main; main()'.format(kitten)] + args,
['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
stdin=data,
env={'KITTY_COMMON_OPTS': json.dumps(copts)},
overlay_for=w.id))
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
def run_kitten(self, type_of_input, kitten, *args):
import shlex
cmdline = args[0] if args else ''
args = shlex.split(cmdline) if cmdline else []
self._run_kitten(kitten, args, type_of_input)
def on_kitten_finish(self, target_window_id, end_kitten, source_window):
output = self.get_output(source_window, num_lines=None)
if output.startswith('OK: '):
data = json.loads(output.partition(' ')[2].strip())
end_kitten(data, target_window_id, self)
def input_unicode_character(self):
self._run_kitten('unicode_input')
def set_tab_title(self):
tab = self.active_tab
if tab:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
self._run_kitten('ask', args)
def do_set_tab_title(self, title, tab_id):
tm = self.active_tab_manager
if tm is not None and title:
tab_id = int(tab_id)
for tab in tm.tabs:
if tab.id == tab_id:
tab.set_title(title)
break
def kitty_shell(self, window_type):
cmd = ['kitty', '@']
if window_type == 'tab':
window = self._new_tab(cmd).active_window
elif window_type == 'os_window':
os_window_id = self._new_os_window(cmd)
window = self.os_window_map[os_window_id].active_window
elif window_type == 'overlay':
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
else:
window = None
else:
window = self._new_window(cmd)
if window is not None:
window.allow_remote_control = True
def switch_focus_to(self, window_idx):
tab = self.active_tab
@@ -435,18 +559,16 @@ class Boss:
old_focus.focus_changed(False)
tab.active_window.focus_changed(True)
def open_url(self, url):
def open_url(self, url, program=None):
if url:
open_url(url, self.opts.open_url_with)
open_url(url, program or self.opts.open_url_with)
def open_url_lines(self, lines):
self.open_url(''.join(lines))
def open_url_lines(self, lines, program=None):
self.open_url(''.join(lines), program)
def destroy(self):
self.shutting_down = True
self.child_monitor.shutdown_monitor()
wakeup()
self.child_monitor.join()
del self.child_monitor
for tm in self.os_window_map.values():
tm.destroy()
@@ -482,6 +604,11 @@ class Boss:
if tm is not None:
tm.goto_tab(tab_num - 1)
def set_active_tab(self, tab):
tm = self.active_tab_manager
if tm is not None:
tm.set_active_tab(tab)
def next_tab(self):
tm = self.active_tab_manager
if tm is not None:
@@ -533,7 +660,7 @@ class Boss:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager
if tm is not None:
tm.new_tab(special_window=special_window, cwd_from=cwd_from)
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
def new_tab(self, *args):
self._new_tab(args)
@@ -547,9 +674,9 @@ class Boss:
tab = self.active_tab
if tab is not None:
if args:
tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
return tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
else:
tab.new_window(cwd_from=cwd_from)
return tab.new_window(cwd_from=cwd_from)
def new_window(self, *args):
self._new_window(args)
@@ -570,3 +697,11 @@ class Boss:
tm = self.active_tab_manager
if tm is not None:
tm.move_tab(-1)
def patch_colors(self, spec, configured=False):
if configured:
for k, v in spec.items():
if hasattr(self.opts, k):
setattr(self.opts, k, color_from_int(v))
for tm in self.all_tab_managers:
tm.tab_bar.patch_colors(spec)

View File

@@ -6,9 +6,7 @@
layout(std140) uniform CellRenderData {
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style;
int color1, color2;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
uint xnum, ynum, cursor_x, cursor_y, cursor_w;
@@ -21,7 +19,7 @@ layout(location=1) in uvec4 sprite_coords;
layout(location=2) in uint is_selected;
const int fg_index_map[] = int[3](0, 1, 0);
const uvec2 cell_pos_map[] = uvec2[4](
uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom
@@ -135,10 +133,10 @@ void main() {
// set cell color indices {{{
uvec2 default_colors = uvec2(default_fg, default_bg);
ivec2 color_indices = ivec2(color1, color2);
uint text_attrs = sprite_coords[3];
int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK];
int bg_index = color_indices[1 - fg_index];
uint is_inverted = ((text_attrs >> 6) & REVERSE_MASK) + inverted;
int fg_index = fg_index_map[is_inverted];
int bg_index = 1 - fg_index;
float cursor = is_cursor(c, r);
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
// }}}

View File

@@ -5,16 +5,7 @@
* Distributed under terms of the GPL3 license.
*/
#ifdef __APPLE__
#include <pthread.h>
// I cant figure out how to get pthread.h to include this definition on macOS. MACOSX_DEPLOYMENT_TARGET does not work.
extern int pthread_setname_np(const char *name);
#else
// Need _GNU_SOURCE for pthread_setname_np on linux
#define _GNU_SOURCE
#include <pthread.h>
#undef _GNU_SOURCE
#endif
#include "threading.h"
#include "state.h"
#include "screen.h"
#include <termios.h>
@@ -28,12 +19,17 @@ extern int pthread_setname_np(const char *name);
extern PyTypeObject Screen_Type;
#define EXTRA_FDS 2
#ifndef MSG_NOSIGNAL
// Apple does not implement MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
static void (*parse_func)(Screen*, PyObject*);
typedef struct {
char *data;
size_t sz;
int fd;
} Message;
typedef struct {
@@ -44,7 +40,7 @@ typedef struct {
bool shutting_down;
pthread_t io_thread, talk_thread;
int talk_fd;
int talk_fd, listen_fd;
Message *messages;
size_t messages_capacity, messages_count;
} ChildMonitor;
@@ -63,6 +59,9 @@ static const Child EMPTY_CHILD = {0};
pthread_mutex_##op(&screen->which##_buf_lock);
#define children_mutex(op) \
pthread_mutex_##op(&children_lock);
#define peer_mutex(op) \
pthread_mutex_##op(&talk_data.peer_lock);
static Child children[MAX_CHILDREN] = {{0}};
@@ -72,23 +71,12 @@ static unsigned long remove_notify[MAX_CHILDREN] = {0};
static size_t add_queue_count = 0, remove_queue_count = 0;
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
static pthread_mutex_t children_lock;
static bool signal_received = false;
static bool kill_signal_received = false;
static ChildMonitor *the_monitor = NULL;
static uint8_t drain_buf[1024];
static int signal_fds[2], wakeup_fds[2];
static inline void
set_thread_name(const char *name) {
int ret = 0;
#ifdef __APPLE__
ret = pthread_setname_np(name);
#else
ret = pthread_setname_np(pthread_self(), name);
#endif
if (ret != 0) perror("Failed to set thread name");
}
// Main thread functions {{{
@@ -139,11 +127,11 @@ static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
ChildMonitor *self;
PyObject *dump_callback, *death_notify;
int talk_fd = -1;
int talk_fd = -1, listen_fd = -1;
int ret;
if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; }
if (!PyArg_ParseTuple(args, "OO|i", &death_notify, &dump_callback, &talk_fd)) return NULL;
if (!PyArg_ParseTuple(args, "OO|ii", &death_notify, &dump_callback, &talk_fd, &listen_fd)) return NULL;
if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) {
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
return NULL;
@@ -151,12 +139,12 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
if (!self_pipe(wakeup_fds)) return PyErr_SetFromErrno(PyExc_OSError);
if (!self_pipe(signal_fds)) return PyErr_SetFromErrno(PyExc_OSError);
struct sigaction act = {.sa_handler=handle_signal};
if (sigaction(SIGINT, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError);
if (sigaction(SIGTERM, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError);
if (siginterrupt(SIGINT, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);
if (siginterrupt(SIGTERM, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);
#define SA(which) { if (sigaction(which, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError); if (siginterrupt(which, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);}
SA(SIGINT); SA(SIGTERM); SA(SIGCHLD);
#undef SA
self = (ChildMonitor *)type->tp_alloc(type, 0);
self->talk_fd = talk_fd;
self->listen_fd = listen_fd;
if (self == NULL) return PyErr_NoMemory();
self->death_notify = death_notify; Py_INCREF(death_notify);
if (dump_callback != Py_None) {
@@ -205,12 +193,17 @@ wakeup_io_loop(bool in_signal_handler) {
static void* io_loop(void *data);
static void* talk_loop(void *data);
static void send_response(int fd, const char *msg, size_t msg_sz);
static void wakeup_talk_loop(bool);
static bool talk_thread_started = false;
static PyObject *
start(ChildMonitor *self) {
start(PyObject *s, PyObject *a UNUSED) {
#define start_doc "start() -> Start the I/O thread"
if (self->talk_fd > -1) {
ChildMonitor *self = (ChildMonitor*)s;
if (self->talk_fd > -1 || self->listen_fd > -1) {
if (pthread_create(&self->talk_thread, NULL, talk_loop, self) != 0) return PyErr_SetFromErrno(PyExc_OSError);
talk_thread_started = true;
}
int ret = pthread_create(&self->io_thread, NULL, io_loop, self);
if (ret != 0) return PyErr_SetFromErrno(PyExc_OSError);
@@ -218,17 +211,9 @@ start(ChildMonitor *self) {
Py_RETURN_NONE;
}
static PyObject *
join(ChildMonitor *self) {
#define join_doc "join() -> Wait for the I/O thread to finish"
int ret = pthread_join(self->io_thread, NULL);
if (ret != 0) return PyErr_SetFromErrno(PyExc_OSError);
Py_RETURN_NONE;
}
static PyObject *
wakeup(ChildMonitor UNUSED *self) {
wakeup(PYNOARG) {
#define wakeup_doc "wakeup() -> wakeup the ChildMonitor I/O thread, forcing it to exit from poll() if it is waiting there."
wakeup_io_loop(false);
Py_RETURN_NONE;
@@ -266,7 +251,7 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
size_t space_left = screen->write_buf_sz - screen->write_buf_used;
if (space_left < sz) {
if (screen->write_buf_used + sz > 100 * 1024 * 1024) {
fprintf(stderr, "Too much data being sent to child with id: %lu, ignoring it\n", id);
log_error("Too much data being sent to child with id: %lu, ignoring it", id);
screen_mutex(unlock, write);
break;
}
@@ -301,11 +286,21 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
}
static PyObject *
shutdown_monitor(ChildMonitor *self) {
shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
#define shutdown_monitor_doc "shutdown_monitor() -> Shutdown the monitor loop."
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGCHLD, SIG_DFL);
self->shutting_down = true;
wakeup_talk_loop(false);
wakeup_io_loop(false);
int ret = pthread_join(self->io_thread, NULL);
if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to join() I/O thread with error: %s", strerror(ret));
if (talk_thread_started) {
ret = pthread_join(self->talk_thread, NULL);
if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to join() talk thread with error: %s", strerror(ret));
}
talk_thread_started = false;
Py_RETURN_NONE;
}
@@ -343,14 +338,14 @@ parse_input(ChildMonitor *self) {
if (msg) {
for (size_t i = 0; i < self->messages_count; i++) {
Message *m = self->messages + i;
PyTuple_SET_ITEM(msg, i, PyBytes_FromStringAndSize(m->data, m->sz));
PyTuple_SET_ITEM(msg, i, Py_BuildValue("y#i", m->data, (int)m->sz, m->fd));
free(m->data); m->data = NULL; m->sz = 0;
}
self->messages_count = 0;
} else fatal("Out of memory");
}
if (UNLIKELY(signal_received)) {
if (UNLIKELY(kill_signal_received)) {
global_state.close_all_windows = true;
} else {
count = self->count;
@@ -361,7 +356,13 @@ parse_input(ChildMonitor *self) {
}
children_mutex(unlock);
if (msg) {
call_boss(peer_messages_received, "(O)", msg);
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(msg); i++) {
PyObject *resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "O", PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 0));
int peer_fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 1));
if (resp && PyBytes_Check(resp)) send_response(peer_fd, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
else { send_response(peer_fd, NULL, 0); if (!resp) PyErr_Print(); }
Py_CLEAR(resp);
}
Py_CLEAR(msg);
}
@@ -411,7 +412,7 @@ pty_resize(int fd, struct winsize *dim) {
if (ioctl(fd, TIOCSWINSZ, dim) == -1) {
if (errno == EINTR) continue;
if (errno != EBADF && errno != ENOTTY) {
fprintf(stderr, "Failed to resize tty associated with fd: %d with error: %s", fd, strerror(errno));
log_error("Failed to resize tty associated with fd: %d with error: %s", fd, strerror(errno));
return false;
}
}
@@ -439,7 +440,7 @@ resize_pty(ChildMonitor *self, PyObject *args) {
if (fd == -1) FIND(add_queue, add_queue_count);
if (fd != -1) {
if (!pty_resize(fd, &dim)) PyErr_SetFromErrno(PyExc_OSError);
} else fprintf(stderr, "Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %zu)\n", window_id, self->count, add_queue_count);
} else log_error("Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %zu)", window_id, self->count, add_queue_count);
children_mutex(unlock);
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
@@ -568,7 +569,8 @@ simple_render_screen(PyObject UNUSED *self, PyObject *args) {
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 = false;
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;
}
@@ -689,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);
@@ -733,8 +735,32 @@ process_pending_resizes(double now) {
}
}
static inline void
close_all_windows() {
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
global_state.close_all_windows = false;
}
static inline bool
process_pending_closes(ChildMonitor *self) {
bool has_open_windows = false;
for (size_t w = global_state.num_os_windows; w > 0; w--) {
OSWindow *os_window = global_state.os_windows + w - 1;
if (should_os_window_close(os_window)) {
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id);
} else has_open_windows = true;
}
return has_open_windows;
}
static PyObject*
main_loop(ChildMonitor *self) {
main_loop(ChildMonitor *self, PyObject *a UNUSED) {
#define main_loop_doc "The main thread loop"
bool has_open_windows = true;
@@ -744,23 +770,8 @@ main_loop(ChildMonitor *self) {
render(now);
wait_for_events();
parse_input(self);
if (global_state.close_all_windows) {
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
global_state.close_all_windows = false;
}
has_open_windows = false;
for (size_t w = global_state.num_os_windows; w > 0; w--) {
OSWindow *os_window = global_state.os_windows + w - 1;
if (should_os_window_close(os_window)) {
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->viewport_width, os_window->viewport_height);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id);
} else has_open_windows = true;
}
if (global_state.close_all_windows) close_all_windows();
has_open_windows = process_pending_closes(self);
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
@@ -873,6 +884,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;
@@ -924,10 +987,10 @@ io_loop(void *data) {
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
if (fds[1].revents && POLLIN) {
data_received = true;
drain_fd(fds[1].fd);
children_mutex(lock);
signal_received = true;
children_mutex(unlock);
bool kill_signal = false, child_died = false;
read_signals(fds[1].fd, &kill_signal, &child_died);
if (kill_signal) { children_mutex(lock); kill_signal_received = true; children_mutex(unlock); }
if (child_died) reap_children(self, OPT(close_on_child_death));
}
for (i = 0; i < self->count; i++) {
if (fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) {
@@ -948,7 +1011,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
@@ -975,58 +1038,258 @@ io_loop(void *data) {
// {{{ Talk thread functions
static inline void
handle_peer(ChildMonitor *self, int s) {
size_t bufsz = 0;
char *buf = NULL;
size_t buf_used = 0;
#define MAX_PEERS 256
#define MAX_LISTENERS 2
while(true) {
if (buf_used >= bufsz) {
bufsz = MAX(1024, bufsz) * 2;
if (bufsz > 1024 * 1024) return;
buf = realloc(buf, bufsz);
if (buf == NULL) return;
}
ssize_t n = recv(s, buf + buf_used, bufsz - buf_used, 0);
if (n == 0) break;
if (n < 0) {
if (errno == EINTR) continue;
perror("Error reading from talk peer");
typedef struct {
char *data;
size_t capacity, used;
int fd;
bool finished, close_socket;
} PeerReadData;
static PeerReadData empty_prd = {.fd = -1, 0};
typedef struct {
char *data;
size_t sz, pos;
int fd;
bool finished;
} PeerWriteData;
static PeerWriteData empty_pwd = {.fd = -1, 0};
typedef struct {
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
struct pollfd fds[MAX_PEERS + MAX_LISTENERS + 1];
PeerReadData reads[MAX_LISTENERS];
PeerWriteData writes[MAX_LISTENERS];
PeerWriteData queued_writes[MAX_LISTENERS];
int wakeup_fds[2];
pthread_mutex_t peer_lock;
} TalkData;
static TalkData talk_data = {0};
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
static inline bool
accept_peer(int listen_fd, bool shutting_down) {
int peer = accept(listen_fd, NULL, NULL);
if (UNLIKELY(peer == -1)) {
if (errno == EINTR) return true;
if (!shutting_down) perror("accept() on talk socket failed!");
return false;
}
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_reads < arraysz(talk_data.reads)) {
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
talk_data.num_talk_fds++;
} else nuke_socket(peer);
return true;
}
static inline bool
read_from_peer(ChildMonitor *self, int s) {
bool read_finished = false;
for (size_t i = 0; i < talk_data.num_reads; i++) {
PeerReadData *rd = talk_data.reads + i;
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
if (rd->fd == s) {
if (rd->used >= rd->capacity) {
if (rd->capacity >= 1024 * 1024) failed("Ignoring too large message from peer");
rd->capacity = MAX(8192, rd->capacity * 2);
rd->data = realloc(rd->data, rd->capacity);
if (!rd->data) failed("Out of memory");
}
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
if (n == 0) {
read_finished = true; rd->finished = true;
children_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
Message *m = self->messages + self->messages_count++;
m->data = rd->data; rd->data = NULL; m->sz = rd->used; m->fd = s;
children_mutex(unlock);
wakeup_main_loop();
} else if (n < 0) {
if (errno != EINTR) {
perror("Error reading from talk peer");
failed("");
}
} else rd->used += n;
break;
}
buf_used += n;
}
if (buf_used) {
children_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
Message *m = self->messages + self->messages_count++;
m->data = buf; m->sz = buf_used;
children_mutex(unlock);
wakeup_main_loop();
} else free(buf);
#undef failed
return read_finished;
}
static inline bool
write_to_peer(int fd) {
bool write_finished = false;
for (size_t i = 0; i < talk_data.num_writes; i++) {
PeerWriteData *wd = talk_data.writes + i;
#define failed(msg) { write_finished = true; log_error("%s", msg); wd->finished = true; break; }
if (wd->fd == fd) {
ssize_t n = send(fd, wd->data + wd->pos, wd->sz - wd->pos, MSG_NOSIGNAL);
if (n == 0) { failed("send() to peer failed to send any data"); }
else if (n < 0) {
if (errno != EINTR) { perror("write() to peer socket failed with error"); failed(""); }
} else {
wd->pos += n;
if (wd->pos >= wd->sz) { write_finished = true; wd->finished = true; }
}
break;
}
}
#undef failed
return write_finished;
}
static inline void
remove_poll_fd(int fd) {
size_t count = talk_data.num_talk_fds + talk_data.num_listen_fds;
for (size_t i = talk_data.num_listen_fds; i < count; i++) {
struct pollfd *pfd = talk_data.fds + i;
if (pfd->fd == fd) {
size_t num_to_right = count - 1 - i;
if (num_to_right) memmove(talk_data.fds + i, talk_data.fds + i + 1, num_to_right * sizeof(struct pollfd));
talk_data.num_talk_fds--;
break;
}
}
}
static inline void
prune_finished_reads() {
if (!talk_data.num_reads) return;
for (ssize_t i = talk_data.num_reads - 1; i >= 0; i--) {
PeerReadData *rd = talk_data.reads + i;
if (rd->finished) {
remove_poll_fd(rd->fd);
if (rd->close_socket) { nuke_socket(rd->fd); }
else shutdown(rd->fd, SHUT_RD);
free(rd->data);
ssize_t num_to_right = talk_data.num_reads - 1 - i;
if (num_to_right > 0) memmove(talk_data.reads + i, talk_data.reads + i + 1, num_to_right * sizeof(PeerReadData));
else talk_data.reads[i] = empty_prd;
talk_data.num_reads--;
}
}
}
static inline void
prune_finished_writes() {
if (!talk_data.num_writes) return;
for (ssize_t i = talk_data.num_writes - 1; i >= 0; i--) {
PeerWriteData *wd = talk_data.writes + i;
if (wd->finished) {
remove_poll_fd(wd->fd);
shutdown(wd->fd, SHUT_WR); close(wd->fd);
free(wd->data);
ssize_t num_to_right = talk_data.num_writes - 1 - i;
if (num_to_right > 0) memmove(talk_data.writes + i, talk_data.writes + i + 1, num_to_right * sizeof(PeerWriteData));
else talk_data.writes[i] = empty_pwd;
talk_data.num_writes--;
}
}
}
static void
wakeup_talk_loop(bool in_signal_handler) {
if (talk_data.wakeup_fds[1] <= 0) return;
while(true) {
ssize_t ret = write(talk_data.wakeup_fds[1], "w", 1);
if (ret < 0) {
if (errno == EINTR) continue;
if (!in_signal_handler) perror("Failed to write to talk wakeup fd with error");
}
break;
}
}
static inline void
move_queued_writes() {
while (talk_data.num_queued_writes) {
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_writes < arraysz(talk_data.writes)) {
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
talk_data.writes[talk_data.num_writes++] = *src;
talk_data.num_talk_fds++;
} else {
log_error("Cannot send response to peer, too many peers");
free(src->data); nuke_socket(src->fd);
}
*src = empty_pwd;
}
}
static void*
talk_loop(void *data) {
// The I/O thread loop
// The talk thread loop
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyTalkMon");
set_thread_name("KittyPeerMon");
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
if (!self_pipe(talk_data.wakeup_fds)) { perror("Failed to create wakeup fds for talk thread"); return 0; }
#define add_listener(which) \
if (self->which > -1) { \
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
}
add_listener(talk_fd); add_listener(listen_fd);
#undef add_listener
talk_data.fds[talk_data.num_listen_fds].fd = talk_data.wakeup_fds[0]; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN;
while (LIKELY(!self->shutting_down)) {
int peer = accept(self->talk_fd, NULL, NULL);
if (peer == -1) {
if (errno == EINTR) continue;
if (!self->shutting_down) perror("accept() on talk socket failed!");
break;
}
handle_peer(self, peer);
shutdown(peer, SHUT_RDWR);
close(peer);
for (size_t i = 0; i < talk_data.num_listen_fds + talk_data.num_talk_fds; i++) { talk_data.fds[i].revents = 0; }
int ret = poll(talk_data.fds, talk_data.num_listen_fds + talk_data.num_talk_fds, -1);
if (ret > 0) {
bool has_finished_reads = false, has_finished_writes = false;
for (size_t i = 0; i < talk_data.num_listen_fds - 1; i++) {
if (talk_data.fds[i].revents & POLLIN) {if (!accept_peer(talk_data.fds[i].fd, self->shutting_down)) goto end; }
}
if (talk_data.fds[talk_data.num_listen_fds - 1].revents & POLLIN) drain_fd(talk_data.fds[talk_data.num_listen_fds - 1].fd); // wakeup
for (size_t i = talk_data.num_listen_fds; i < talk_data.num_talk_fds + talk_data.num_listen_fds; i++) {
if (talk_data.fds[i].revents & (POLLIN | POLLHUP)) { if (read_from_peer(self, talk_data.fds[i].fd)) has_finished_reads = true; }
if (talk_data.fds[i].revents & POLLOUT) { if (write_to_peer(talk_data.fds[i].fd)) has_finished_writes = true; }
}
if (has_finished_reads) prune_finished_reads();
if (has_finished_writes) prune_finished_writes();
peer_mutex(lock);
if (talk_data.num_queued_writes) move_queued_writes();
peer_mutex(unlock);
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
}
end:
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
return 0;
}
static inline bool
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
bool ok = false;
peer_mutex(lock);
if (talk_data.num_queued_writes < arraysz(talk_data.queued_writes)) {
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
memcpy(talk_data.queued_writes[talk_data.num_queued_writes].data, msg, msg_sz);
talk_data.queued_writes[talk_data.num_queued_writes].sz = msg_sz;
talk_data.queued_writes[talk_data.num_queued_writes++].fd = fd;
ok = true;
}
} else log_error("Cannot send response to peer, too many peers");
peer_mutex(unlock);
return ok;
}
static void
send_response(int fd, const char *msg, size_t msg_sz) {
if (msg == NULL) { shutdown(fd, SHUT_WR); close(fd); return; }
if (!add_peer_writer(fd, msg, msg_sz)) { shutdown(fd, SHUT_WR); close(fd); }
else wakeup_talk_loop(false);
}
// }}}
// Boilerplate {{{
@@ -1034,7 +1297,6 @@ static PyMethodDef methods[] = {
METHOD(add_child, METH_VARARGS)
METHOD(needs_write, METH_VARARGS)
METHOD(start, METH_NOARGS)
METHOD(join, METH_NOARGS)
METHOD(wakeup, METH_NOARGS)
METHOD(shutdown_monitor, METH_NOARGS)
METHOD(main_loop, METH_NOARGS)
@@ -1056,8 +1318,16 @@ PyTypeObject ChildMonitor_Type = {
.tp_new = new,
};
static PyObject*
safe_pipe(PYNOARG) {
int fds[2] = {0};
if (!self_pipe(fds)) return PyErr_SetFromErrno(PyExc_OSError);
return Py_BuildValue("ii", fds[0], fds[1]);
}
static PyMethodDef module_methods[] = {
METHOD(simple_render_screen, METH_VARARGS)
METHODB(safe_pipe, METH_NOARGS),
{NULL} /* Sentinel */
};

View File

@@ -42,8 +42,8 @@ static PyObject*
spawn(PyObject *self UNUSED, PyObject *args) {
PyObject *argv_p, *env_p;
int master, slave, stdin_read_fd, stdin_write_fd;
char* cwd;
if (!PyArg_ParseTuple(args, "sO!O!iiii", &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
char *cwd, *exe;
if (!PyArg_ParseTuple(args, "ssO!O!iiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
char name[2048] = {0};
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
char **argv = serialize_string_tuple(argv_p);
@@ -76,7 +76,7 @@ spawn(PyObject *self UNUSED, PyObject *args) {
close(tfd);
environ = env;
execvp(argv[0], argv);
execvp(exe, argv);
// Report the failure and exec a shell instead, so that we are not left
// with a forked but not exec'ed process
write_to_stderr("Failed to launch child: ");

View File

@@ -7,7 +7,7 @@ import os
import kitty.fast_data_types as fast_data_types
from .constants import terminfo_dir, is_macos
from .constants import is_macos, shell_path, terminfo_dir
def cwd_of_process(pid):
@@ -70,7 +70,13 @@ class Child:
if os.path.isdir(terminfo_dir):
env['TERMINFO'] = terminfo_dir
env = tuple('{}={}'.format(k, v) for k, v in env.items())
pid = fast_data_types.spawn(self.cwd, tuple(self.argv), env, master, slave, stdin_read_fd, stdin_write_fd)
argv = list(self.argv)
exe = argv[0]
if is_macos and exe == shell_path:
# Some macOS machines need the shell to have argv[0] prefixed by
# hyphen, see https://github.com/kovidgoyal/kitty/issues/247
argv[0] = ('-' + exe.split('/')[-1])
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd)
os.close(slave)
self.pid = pid
self.child_fd = master

View File

@@ -2,16 +2,16 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import subprocess
import sys
from collections import deque
from .config import load_config
from .constants import appname, defconf, is_macos, str_version
from .config import defaults, load_config
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .layout import all_layouts
is_macos
OPTIONS = '''
--class
dest=cls
@@ -32,9 +32,23 @@ only use this if you are running a program that does not set titles.
--config
type=list
default={config_path}
Specify a path to the configuration file(s) to use.
Can be specified multiple times to read multiple configuration files in sequence, which are merged.
Specify a path to the configuration file(s) to use. All configuration files are
merged onto the builtin kitty.conf, overriding the builtin values. This option
can be specified multiple times to read multiple configuration files in
sequence, which are merged. Use the special value NONE to not load a config
file.
If this option is not specified, config files are searched for in the order:
"$XDG_CONFIG_HOME/kitty/kitty.conf", "~/.config/kitty/kitty.conf", {macos_confpath}
"$XDG_CONFIG_DIRS/kitty/kitty.conf". The first one that exists is used as the
config file.
If the environment variable "KITTY_CONFIG_DIRECTORY" is specified, that
directory is always used and the above searching does not happen.
If "/etc/xdg/kitty/kitty.conf" exists it is merged before (i.e. with lower
priority) than any user config files. It can be used to specify system-wide
defaults for all users.
--override -o
@@ -84,6 +98,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 +131,16 @@ Path to file in which to store the raw bytes received from the child process
--debug-gl
type=bool-set
Debug OpenGL commands. This will cause all OpenGL calls to check for errors instead of ignoring them. Useful when debugging rendering problems
--debug-font-fallback
type=bool-set
Print out information about the selection of fallback fonts for characters not present in the main font.
--debug-config
type=bool-set
Print out information about the system and kitty configuration.
'''
@@ -186,10 +220,10 @@ def parse_option_spec(spec=OPTIONS):
current_cmd['choices'] = {x.strip() for x in current_cmd['choices'].split(',')}
elif state is HELP:
if line:
current_cmd['help'] += ' ' + line
current_cmd['help'] += ' ' + line.lstrip()
else:
if prev_line:
current_cmd['help'] += '\n'
current_cmd['help'] += '\n\n\t'
else:
state = NORMAL
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
@@ -213,8 +247,12 @@ def prettify(text):
return text
def version():
return '{} {} created by {}'.format(italic(appname), green(str_version), title('Kovid Goyal'))
def version(add_rev=False):
rev = ''
from . import fast_data_types
if add_rev and hasattr(fast_data_types, 'KITTY_VCS_REV'):
rev = ' ({})'.format(fast_data_types.KITTY_VCS_REV[:10])
return '{} {}{} created by {}'.format(italic(appname), green(str_version), rev, title('Kovid Goyal'))
def wrap(text, limit=80):
@@ -252,6 +290,7 @@ def wrap(text, limit=80):
def print_help_for_seq(seq, usage, message, appname):
from kitty.icat import screen_size
screen_size.changed = True
try:
linesz = min(screen_size().cols, 76)
except EnvironmentError:
@@ -271,8 +310,9 @@ def print_help_for_seq(seq, usage, message, appname):
lines.append('')
a((' ' * leading_indent) + j.join(lines))
usage = usage or '[program-to-run ...]'
a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage))
usage = '[program-to-run ...]' if usage is None else usage
optstring = '[options] ' if seq else ''
a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage))
a('')
message = message or (
'Run the |G {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
@@ -300,7 +340,7 @@ def print_help_for_seq(seq, usage, message, appname):
a('')
text = '\n'.join(blocks) + '\n\n' + version()
if sys.stdout.isatty():
if print_help_for_seq.allow_pager and sys.stdout.isatty():
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
p.communicate(text.encode('utf-8'))
raise SystemExit(p.wait())
@@ -308,6 +348,9 @@ def print_help_for_seq(seq, usage, message, appname):
print(text)
print_help_for_seq.allow_pager = True
def defval_for_opt(opt):
dv = opt.get('default')
typ = opt.get('type', '')
@@ -437,7 +480,7 @@ def parse_cmdline(oc, disabled, args=None):
def options_spec():
if not hasattr(options_spec, 'ans'):
options_spec.ans = OPTIONS.format(
appname=appname, config_path=defconf,
appname=appname, macos_confpath='~/Library/Preferences/kitty/kitty.conf, ' if is_macos else '',
window_layout_choices=', '.join(all_layouts)
)
return options_spec.ans
@@ -450,8 +493,85 @@ def parse_args(args=None, ospec=options_spec, usage=None, message=None, appname=
return parse_cmdline(oc, disabled, args=args)
def create_opts(args):
config = args.config or (defconf, )
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
def resolve_config(config_files_on_cmd_line):
if config_files_on_cmd_line:
if 'NONE' not in config_files_on_cmd_line:
yield SYSTEM_CONF
for cf in config_files_on_cmd_line:
yield cf
else:
yield SYSTEM_CONF
yield defconf
def print_shortcut(key, action):
if not getattr(print_shortcut, 'maps', None):
from kitty.keys import defines
v = vars(defines)
mmap = {m.split('_')[-1].lower(): x for m, x in v.items() if m.startswith('GLFW_MOD_')}
kmap = {k.split('_')[-1].lower(): x for k, x in v.items() if k.startswith('GLFW_KEY_')}
krmap = {v: k for k, v in kmap.items()}
print_shortcut.maps = mmap, krmap
mmap, krmap = print_shortcut.maps
names = []
mods, key = key
for name, val in mmap.items():
if mods & val:
names.append(name)
if key:
names.append(krmap[key])
print('\t', '+'.join(names), action)
def compare_keymaps(final, initial):
added = set(final) - set(initial)
removed = set(initial) - set(final)
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
if added:
print(title('Added shortcuts:'))
for k in added:
print_shortcut(k, final[k])
if removed:
print(title('Removed shortcuts:'))
for k in removed:
print_shortcut(k, initial[k])
if changed:
print(title('Changed shortcuts:'))
for k in changed:
print_shortcut(k, final[k])
def compare_opts(opts):
print('\nConfig options different from defaults:')
for f in sorted(defaults._fields):
if getattr(opts, f) != getattr(defaults, f):
if f == 'keymap':
compare_keymaps(opts.keymap, defaults.keymap)
else:
print(title('{:20s}'.format(f)), getattr(opts, f))
def create_opts(args, debug_config=False):
config = tuple(resolve_config(args.config))
if debug_config:
print(version(add_rev=True))
print(' '.join(os.uname()))
if is_macos:
print(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
else:
print('Running under:', green('Wayland' if is_wayland else 'X11'))
if os.path.exists('/etc/issue'):
print(open('/etc/issue', encoding='utf-8', errors='replace').read().strip())
if os.path.exists('/etc/lsb-release'):
print(open('/etc/lsb-release', encoding='utf-8', errors='replace').read().strip())
config = tuple(x for x in config if os.path.exists(x))
if config:
print(green('Loaded config files:'), ', '.join(config))
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
opts = load_config(*config, overrides=overrides)
if debug_config:
compare_opts(opts)
return opts

547
kitty/cmds.py Normal file
View File

@@ -0,0 +1,547 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import json
import os
import sys
from .cli import parse_args
from .config import parse_config, parse_send_text_bytes
from .constants import appname
from .tabs import SpecialWindow
from .utils import non_blocking_read
class MatchError(ValueError):
hide_traceback = True
def __init__(self, expression, target='windows'):
ValueError.__init__(self, 'No matching {} for expression: {}'.format(target, expression))
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...'):
def w(func):
func.short_desc = short_desc
func.argspec = argspec
func.desc = desc or short_desc
func.name = func.__name__[4:].replace('_', '-')
func.options_spec = options_spec
func.is_cmd = True
func.impl = lambda: globals()[func.__name__[4:]]
func.no_response = no_response
return func
return w
MATCH_WINDOW_OPTION = '''\
--match -m
The window to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of windows. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression.
'''
MATCH_TAB_OPTION = '''\
--match -m
The tab to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of tabs. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression. When using title or id, first a matching tab is
looked for and if not found a matching window is looked for, and the tab
for that window is used.
'''
# ls {{{
@cmd(
'List all tabs/windows',
'List all windows. The list is returned as JSON tree. The top-level is a list of'
' operating system {appname} windows. Each OS window has an |_ id| and a list'
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
' |_ command-line| of the process running in the window.\n\n'
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
argspec=''
)
def cmd_ls(global_opts, opts, args):
pass
def ls(boss, window):
data = list(boss.list_os_windows())
data = json.dumps(data, indent=2, sort_keys=True)
return data
# }}}
# set_font_size {{{
@cmd(
'Set the font size in all windows',
'Sets the font size to the specified size, in pts.',
argspec='FONT_SIZE'
)
def cmd_set_font_size(global_opts, opts, args):
try:
return {'size': float(args[0])}
except IndexError:
raise SystemExit('No font size specified')
def set_font_size(boss, window, payload):
boss.set_font_size(payload['size'])
# }}}
# send_text {{{
@cmd(
'Send arbitrary text to specified windows',
'Send arbitrary text to specified windows. The text follows Python'
' escaping rules. So you can use escapes like |_ \\x1b| to send control codes'
' and |_ \\u21fa| to send unicode characters. If you use the |_ --match| option'
' the text will be sent to all matched windows. By default, text is sent to'
' only the currently active window.',
options_spec=MATCH_WINDOW_OPTION + '''\n
--stdin
type=bool-set
Read the text to be sent from |_ stdin|. Note that in this case the text is sent as is,
not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end reading.
--from-file
Path to a file whose contents you wish to send. Note that in this case the file contents
are sent as is, not interpreted for escapes.
''',
no_response=True,
argspec='[TEXT TO SEND]'
)
def cmd_send_text(global_opts, opts, args):
limit = 1024
ret = {'match': opts.match, 'is_binary': False}
def pipe(src=sys.stdin):
ret['is_binary'] = True
import select
with non_blocking_read() as fd:
keep_going = True
while keep_going:
rd = select.select([fd], [], [])
if rd:
data = sys.stdin.buffer.read()
if not data:
break
data = data.decode('utf-8')
if '\x04' in data:
data = data[:data.index('\x04')]
keep_going = False
while data:
ret['text'] = data[:limit]
yield ret
data = data[limit:]
else:
break
def chunks(text):
ret['is_binary'] = False
while text:
ret['text'] = text[:limit]
yield ret
text = text[limit:]
def file_pipe(path):
ret['is_binary'] = True
with open(path, encoding='utf-8') as f:
while True:
data = f.read(limit)
if not data:
break
ret['text'] = data
yield ret
sources = []
if opts.stdin:
sources.append(pipe())
if opts.from_file:
sources.append(file_pipe(opts.from_file))
text = ' '.join(args)
sources.append(chunks(text))
def chain():
for src in sources:
yield from src
return chain()
def send_text(boss, window, payload):
windows = [boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
for window in windows:
if window is not None:
window.write_to_child(data)
# }}}
# set_window_title {{{
@cmd(
'Set the window title',
'Set the title for the specified window(s). If you use the |_ --match| option'
' the title will be set for all matched windows. By default, only the window'
' in which the command is run is affected. If you do not specify a title, the'
' last title set by the child process running in the window will be used.',
options_spec=MATCH_WINDOW_OPTION,
argspec='TITLE ...'
)
def cmd_set_window_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_window_title(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window:
window.set_title(payload['title'])
# }}}
# set_tab_title {{{
@cmd(
'Set the tab title',
'Set the title for the specified tab(s). If you use the |_ --match| option'
' the title will be set for all matched tabs. By default, only the tab'
' in which the command is run is affected. If you do not specify a title, the'
' title of the currently active window in the tab is used.',
options_spec=MATCH_TAB_OPTION,
argspec='TITLE ...'
)
def cmd_set_tab_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_tab_title(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
for tab in tabs:
if tab:
tab.set_title(payload['title'])
# }}}
# close_window {{{
@cmd(
'Close the specified window(s)',
options_spec=MATCH_WINDOW_OPTION + '''\n
--self
type=bool-set
If specified close the window this command is run in, rather than the active window.
''',
argspec=''
)
def cmd_close_window(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_window(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload['self'] else boss.active_window]
for window in windows:
if window:
boss.close_window(window)
# }}}
# close_tab {{{
@cmd(
'Close the specified tab(s)',
options_spec=MATCH_TAB_OPTION + '''\n
--self
type=bool-set
If specified close the tab this command is run in, rather than the active tab.
''',
argspec=''
)
def cmd_close_tab(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_tab(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window and payload['self'] else boss.active_tab]
for tab in tabs:
if window:
if tab:
boss.close_tab(tab)
# }}}
# new_window {{{
@cmd(
'Open new window',
'Open a new window in the specified tab. If you use the |_ --match| option'
' the first matching tab is used. Otherwise the currently active tab is used.'
' Prints out the id of the newly opened window. Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:\n'
'|_ kitty @ new-window --title Email mutt|',
options_spec=MATCH_TAB_OPTION + '''\n
--title
The title for the new window. By default it will use the title set by the
program running in it.
--cwd
The initial working directory for the new window.
--keep-focus
type=bool-set
Keep the current window focused instead of switching to the newly opened window
--new-tab
type=bool-set
Open a new tab
--tab-title
When using --new-tab set the title of the tab.
''',
argspec='[CMD ...]'
)
def cmd_new_window(global_opts, opts, args):
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
'keep_focus': opts.keep_focus, 'args': args or []}
def new_window(boss, window, payload):
w = SpecialWindow(cmd=payload['args'] or None, override_title=payload['title'], cwd=payload['cwd'])
old_window = boss.active_window
if payload['new_tab']:
boss._new_tab(w)
tab = boss.active_tab
if payload['tab_title']:
tab.set_title(payload['tab_title'])
wid = boss.active_window.id
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(wid)
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.active_tab]
tab = tabs[0]
w = tab.new_special_window(w)
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(w.id)
# }}}
# focus_window {{{
@cmd(
'Focus the specified window',
options_spec=MATCH_WINDOW_OPTION,
argspec='',
)
def cmd_focus_window(global_opts, opts, args):
return {'match': opts.match}
def focus_window(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window:
boss.set_active_window(window)
break
# }}}
# focus_tab {{{
@cmd(
'Focus the specified tab',
'The active window in the specified tab will be focused.',
options_spec=MATCH_TAB_OPTION,
argspec='',
)
def cmd_focus_tab(global_opts, opts, args):
return {'match': opts.match}
def focus_tab(boss, window, payload):
match = payload['match']
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
tab = tabs[0]
boss.set_active_tab(tab)
# }}}
# get_text {{{
@cmd(
'Get text from the specified window',
options_spec=MATCH_WINDOW_OPTION + '''\n
--extent
default=screen
choices=screen, all, selection
What text to get. The default of screen means all text currently on the screen. all means
all the screen+scrollback and selection means currently selected text.
--ansi
type=bool-set
By default, only plain text is returned. If you specify this flag, the text will
include the formatting escape codes for colors/bold/italic/etc. Note that when
getting the current selection, the result is always plain text.
--self
type=bool-set
If specified get text from the window this command is run in, rather than the active window.
''',
argspec=''
)
def cmd_get_text(global_opts, opts, args):
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
def get_text(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload['self'] else boss.active_window]
window = windows[0]
if payload['extent'] == 'selection':
ans = window.text_for_selection()
else:
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True)
return ans
# }}}
# set_colors {{{
@cmd(
'Set terminal colors',
'Set the terminal colors for the specified windows/tabs (defaults to active window). You can either specify the path to a conf file'
' (in the same format as kitty.conf) to read the colors from or you can specify individual colors,'
' for example: kitty @ set-colors foreground=red background=white',
options_spec='''\
--all -a
type=bool-set
By default, colors are only changed for the currently active window. This option will
cause colors to be changed in all windows.
--configured -c
type=bool-set
Also change the configured colors (i.e. the colors kitty will use for new
windows or after a reset).
--reset
type=bool-set
Restore all colors to the values they had at kitty startup. Note that if you specify
this option, any color arguments are ignored and --configured and --all are implied.
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t'),
argspec='COLOR_OR_FILE ...'
)
def cmd_set_colors(global_opts, opts, args):
from .rgb import color_as_int, Color
colors = {}
if not opts.reset:
for spec in args:
if '=' in spec:
colors.update(parse_config((spec.replace('=', ' '),)))
else:
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
colors.update(parse_config(f))
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
return {
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': colors, 'reset': opts.reset
}
def set_colors(boss, window, payload):
from .rgb import color_as_int
if payload['all']:
windows = tuple(boss.all_windows)
else:
windows = (window or boss.active_window,)
if payload['match_window']:
windows = tuple(boss.match_windows(payload['match_window']))
if not windows:
raise MatchError(payload['match_window'])
if payload['match_tab']:
tabs = tuple(boss.match_tabs(payload['match_tab']))
if not tabs:
raise MatchError(payload['match_tab'], 'tabs')
for tab in tabs:
windows += tuple(tab)
if payload['reset']:
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
profiles = tuple(w.screen.color_profile for w in windows)
from .fast_data_types import patch_color_profiles
patch_color_profiles(payload['colors'], profiles, payload['configured'])
boss.patch_colors(payload['colors'], payload['configured'])
default_bg_changed = 'background' in payload['colors']
for w in windows:
if default_bg_changed:
boss.default_bg_changed_for(w.id)
w.refresh()
# }}}
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
def parse_subcommand_cli(func, args):
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name))
return opts, items
def display_subcommand_help(func):
try:
parse_args(['--help'], (func.options_spec or '\n').format, func.argspec, func.desc, func.name)
except SystemExit:
pass

View File

@@ -59,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 =
@@ -75,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"];
@@ -124,7 +151,7 @@ cocoa_create_global_menu(void) {
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
[NSApp setWindowsMenu:windowMenu];
[windowMenu release];
[preferences_menu_item release];
[bar release];
}

View File

@@ -5,6 +5,7 @@
* Distributed under terms of the GPL3 license.
*/
#define EXTRA_INIT if (PyModule_AddFunctions(module, module_methods) != 0) return false;
#include "data-types.h"
#include <structmember.h>
@@ -29,23 +30,29 @@ static uint32_t FG_BG_256[256] = {
0xffffff, // 15
};
PyObject* create_256_color_table() {
// colors 16..232: the 6x6x6 color cube
const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
uint8_t i, j=16;
for(i = 0; i < 217; i++, j++) {
uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6];
FG_BG_256[j] = (r << 16) | (g << 8) | b;
}
// colors 233..255: grayscale
for(i = 1; i < 24; i++, j++) {
uint8_t v = 8 + i * 10;
FG_BG_256[j] = (v << 16) | (v << 8) | v;
static inline void
init_FG_BG_table() {
if (UNLIKELY(FG_BG_256[255] == 0)) {
// colors 16..232: the 6x6x6 color cube
const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
uint8_t i, j=16;
for(i = 0; i < 217; i++, j++) {
uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6];
FG_BG_256[j] = (r << 16) | (g << 8) | b;
}
// colors 233..255: grayscale
for(i = 1; i < 24; i++, j++) {
uint8_t v = 8 + i * 10;
FG_BG_256[j] = (v << 16) | (v << 8) | v;
}
}
}
PyObject *ans = PyTuple_New(255);
PyObject* create_256_color_table() {
init_FG_BG_table();
PyObject *ans = PyTuple_New(arraysz(FG_BG_256));
if (ans == NULL) return PyErr_NoMemory();
for (i=0; i < 255; i++) {
for (size_t i=0; i < arraysz(FG_BG_256); i++) {
PyObject *temp = PyLong_FromUnsignedLong(FG_BG_256[i]);
if (temp == NULL) { Py_CLEAR(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, temp);
@@ -59,7 +66,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
self = (ColorProfile *)type->tp_alloc(type, 0);
if (self != NULL) {
if (FG_BG_256[255] == 0) create_256_color_table();
init_FG_BG_table();
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256));
self->dirty = true;
@@ -80,12 +87,10 @@ alloc_color_profile() {
static PyObject*
update_ansi_color_table(ColorProfile *self, PyObject *val) {
#define update_ansi_color_table_doc "Update the 16 basic colors"
index_type i;
#define update_ansi_color_table_doc "Update the 256 basic colors"
if (!PyList_Check(val)) { PyErr_SetString(PyExc_TypeError, "color table must be a list"); return NULL; }
if (PyList_GET_SIZE(val) != 16) { PyErr_SetString(PyExc_TypeError, "color table must have 16 items"); return NULL; }
for (i = 0; i < 16; i++) {
if (PyList_GET_SIZE(val) != arraysz(FG_BG_256)) { PyErr_SetString(PyExc_TypeError, "color table must have 256 items"); return NULL; }
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
self->color_table[i] = PyLong_AsUnsignedLong(PyList_GET_ITEM(val, i));
self->orig_color_table[i] = self->color_table[i];
}
@@ -93,6 +98,43 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
Py_RETURN_NONE;
}
static PyObject*
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
char key[32] = {0};
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
snprintf(key, sizeof(key) - 1, "color%zu", i);
v = PyDict_GetItemString(spec, key);
if (v) {
color_type color = PyLong_AsUnsignedLong(v);
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
self->color_table[i] = color;
if (change_configured) self->orig_color_table[i] = color;
self->dirty = true;
}
}
}
#define S(config_name, profile_name) { \
v = PyDict_GetItemString(spec, #config_name); \
if (v) { \
color_type color = PyLong_AsUnsignedLong(v); \
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { \
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); \
self->overridden.profile_name = (color << 8) | 2; \
if (change_configured) self->configured.profile_name = color; \
self->dirty = true; \
} \
} \
}
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
#undef S
Py_RETURN_NONE;
}
color_type
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
color_type t = entry & 0xFF, r;
@@ -133,7 +175,7 @@ as_color(ColorProfile *self, PyObject *val) {
}
static PyObject*
reset_color_table(ColorProfile *self) {
reset_color_table(ColorProfile *self, PyObject *a UNUSED) {
#define reset_color_table_doc "Reset all customized colors back to defaults"
memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256));
self->dirty = true;
@@ -179,11 +221,15 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
}
static PyObject*
color_table_address(ColorProfile *self) {
color_table_address(ColorProfile *self, PyObject *a UNUSED) {
#define color_table_address_doc "Pointer address to start of color table"
return PyLong_FromVoidPtr((void*)self->color_table);
}
static PyObject*
default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
return create_256_color_table();
}
// Boilerplate {{{
@@ -236,5 +282,12 @@ PyTypeObject ColorProfile_Type = {
.tp_new = new,
};
static PyMethodDef module_methods[] = {
METHODB(default_color_table, METH_NOARGS),
METHODB(patch_color_profiles, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
INIT_TYPE(ColorProfile)
// }}}

View File

@@ -17,10 +17,11 @@ from .config_utils import (
init_config, parse_config_base, positive_float, positive_int, to_bool,
to_color, unit_float
)
from .constants import cache_dir
from .constants import cache_dir, defconf
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts
from .utils import safe_print
from .rgb import color_from_int
from .utils import log_error
MINIMUM_FONT_SIZE = 4
@@ -50,19 +51,15 @@ def to_cursor_shape(x):
def parse_mods(parts):
def map_mod(m):
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.get(m, m)
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER', '': 'ALT', '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
@@ -111,37 +108,64 @@ 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':
elif func in ('run_kitten', 'run_simple_kitten'):
if func == 'run_simple_kitten':
func = 'run_kitten'
args = rest.split(' ', 2)
elif func == 'goto_tab':
args = (max(0, int(rest)), )
elif func == 'goto_layout' or func == 'kitty_shell':
args = [rest]
elif func == 'set_font_size':
args = (float(rest),)
elif func in shlex_actions:
args = shlex.split(rest)
return KeyAction(func, args)
def parse_key(val, keymap):
all_key_actions = set()
sequence_sep = '>'
def parse_key(val, keymap, sequence_map):
sc, action = val.partition(' ')[::2]
sc, action = sc.strip(), action.strip()
sc, action = sc.strip().strip(sequence_sep), action.strip()
if not sc or not action:
return
mods, key = parse_shortcut(sc)
if key is None:
safe_print(
'Shortcut: {} has an unknown key, ignoring'.format(val),
file=sys.stderr
)
return
is_sequence = sequence_sep in sc
if is_sequence:
trigger = None
rest = []
for part in sc.split(sequence_sep):
mods, key = parse_shortcut(part)
if key is None:
log_error('Shortcut: {} has unknown key, ignoring'.format(
sc))
return
if trigger is None:
trigger = mods, key
else:
rest.append((mods, key))
rest = tuple(rest)
else:
mods, key = parse_shortcut(sc)
if key is None:
log_error('Shortcut: {} has unknown key, ignoring'.format(
sc))
return
try:
paction = parse_key_action(action)
except Exception:
safe_print(
'Invalid shortcut action: {}. Ignoring.'.format(action),
file=sys.stderr
)
log_error('Invalid shortcut action: {}. Ignoring.'.format(
action))
else:
if paction is not None:
keymap[(mods, key)] = paction
all_key_actions.add(paction.func)
if is_sequence:
s = sequence_map.setdefault(trigger, {})
s[rest] = paction
else:
keymap[(mods, key)] = paction
def parse_symbol_map(val):
@@ -149,9 +173,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:
@@ -182,14 +205,12 @@ def parse_send_text_bytes(text):
).encode('utf-8')
def parse_send_text(val, keymap):
def parse_send_text(val, keymap, sequence_map):
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:
@@ -197,7 +218,7 @@ def parse_send_text(val, keymap):
mode, sc = parts[:2]
text = ' '.join(parts[2:])
key_str = '{} send_text {} {}'.format(sc, mode, text)
return parse_key(key_str, keymap)
return parse_key(key_str, keymap, sequence_map)
def to_modifiers(val):
@@ -281,6 +302,7 @@ type_map = {
'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,
@@ -299,7 +321,6 @@ type_map = {
'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,
@@ -315,8 +336,8 @@ for name in (
' selection_foreground selection_background url_color'
).split():
type_map[name] = to_color
for i in range(16):
type_map['color%d' % i] = to_color
for i in range(256):
type_map['color{}'.format(i)] = to_color
for a in ('active', 'inactive'):
for b in ('foreground', 'background'):
type_map['%s_tab_%s' % (a, b)] = to_color
@@ -324,14 +345,14 @@ for a in ('active', 'inactive'):
def special_handling(key, val, ans):
if key == 'map':
parse_key(val, ans['keymap'])
parse_key(val, ans['keymap'], ans['sequence_map'])
return True
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
return True
if key == 'send_text':
# For legacy compatibility
parse_send_text(val, ans['keymap'])
parse_send_text(val, ans['keymap'], ans['sequence_map'])
return True
@@ -344,6 +365,7 @@ default_config_path = os.path.join(
def parse_config(lines, check_keys=True):
ans = {
'keymap': {},
'sequence_map': {},
'symbol_map': {},
}
parse_config_base(
@@ -357,24 +379,48 @@ def parse_config(lines, check_keys=True):
return ans
Options, defaults = init_config(default_config_path, parse_config)
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset(
'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
def parse_defaults(lines, check_keys=False):
ans = parse_config(lines, check_keys)
dfctl = defines.default_color_table()
for i in range(16, 256):
k = 'color{}'.format(i)
ans.setdefault(k, color_from_int(dfctl[i]))
return ans
Options, defaults = init_config(default_config_path, parse_defaults)
actions = frozenset(all_key_actions) | frozenset(
'run_simple_kitten combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
split()
)
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
def merge_keymaps(defaults, newvals):
ans = defaults.copy()
for k, v in newvals.items():
def merge_keys(ans, defaults, newvals):
ans['keymap'] = defaults['keymap'].copy()
ans['sequence_map'] = {t: r.copy() for t, r in defaults['sequence_map'].items()}
# Merge the keymap
for k, v in newvals['keymap'].items():
ans['sequence_map'].pop(k, None)
f = v.func
if f in no_op_actions:
ans.pop(k, None)
continue
if f in actions:
ans[k] = v
return ans
ans['keymap'].pop(k, None)
elif f in actions:
ans['keymap'][k] = v
# Merge the sequence map
for trigger, rest_map in newvals['sequence_map'].items():
ans['keymap'].pop(trigger, None)
if trigger in newvals['keymap']:
log_error('The shortcut for {} has conflicting definitions'.format(newvals['keymap'][trigger].func))
s = ans['sequence_map'].setdefault(trigger, {})
for k, v in rest_map.items():
f = v.func
if f in no_op_actions:
s.pop(k, None)
elif f in actions:
s[k] = v
ans['sequence_map'] = {k: v for k, v in ans['sequence_map'].items() if v}
def merge_dicts(defaults, newvals):
@@ -387,13 +433,16 @@ def merge_configs(defaults, vals):
ans = {}
for k, v in defaults.items():
if isinstance(v, dict):
newvals = vals.get(k, {})
if k == 'keymap':
ans['keymap'] = merge_keymaps(v, newvals)
else:
if k not in ('keymap', 'sequence_map'):
newvals = vals.get(k, {})
ans[k] = merge_dicts(v, newvals)
else:
ans[k] = vals.get(k, v)
merge_keys(
ans,
{'keymap': defaults.get('keymap', {}), 'sequence_map': defaults.get('sequence_map', {})},
{'keymap': vals.get('keymap', {}), 'sequence_map': vals.get('sequence_map', {})}
)
return ans
@@ -403,7 +452,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:
@@ -412,7 +461,7 @@ def load_config(*paths, overrides=None) -> Options:
if overrides is not None:
vals = parse_config(overrides)
ans = merge_configs(ans, vals)
return Options(**ans)
return Options(ans)
def build_ansi_color_table(opts: Options = defaults):
@@ -423,7 +472,7 @@ def build_ansi_color_table(opts: Options = defaults):
def col(i):
return as_int(getattr(opts, 'color{}'.format(i)))
return list(map(col, range(16)))
return list(map(col, range(256)))
def atomic_save(data, path):
@@ -438,11 +487,8 @@ def atomic_save(data, path):
except FileNotFoundError:
pass
except Exception as err:
safe_print(
'Failed to delete temp file {} for atomic save with error: {}'.
format(p, err),
file=sys.stderr
)
log_error('Failed to delete temp file {} for atomic save with error: {}'.format(
p, err))
@contextmanager
@@ -455,10 +501,8 @@ def cached_values_for(name):
except FileNotFoundError:
pass
except Exception as err:
safe_print(
'Failed to load cached in {} values with error: {}'.format(name, err),
file=sys.stderr
)
log_error('Failed to load cached in {} values with error: {}'.format(
name, err))
yield cached_values
@@ -466,10 +510,8 @@ def cached_values_for(name):
data = json.dumps(cached_values).encode('utf-8')
atomic_save(data, cached_path)
except Exception as err:
safe_print(
'Failed to save cached values with error: {}'.format(err),
file=sys.stderr
)
log_error('Failed to save cached values with error: {}'.format(
err))
def initial_window_size(opts, cached_values):
@@ -479,5 +521,29 @@ def initial_window_size(opts, cached_values):
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

View File

@@ -2,12 +2,11 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import sys
from collections import namedtuple
from .utils import safe_print
from .rgb import to_color as as_color
from .utils import log_error
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
@@ -32,36 +31,95 @@ def to_bool(x):
return x.lower() in 'y yes true'.split()
def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes):
line = line.strip()
if not line or line.startswith('#'):
return
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if special_handling(key, val, ans):
return
if key == 'include':
val = val.strip()
if not os.path.isabs(val):
val = os.path.join(base_path_for_includes, val)
try:
with open(val, encoding='utf-8', errors='replace') as include:
_parse(include, type_map, special_handling, ans, all_keys)
except FileNotFoundError:
log_error('Could not find included config file: {}, ignoring'.format(val))
except EnvironmentError:
log_error('Could not read from included config file: {}, ignoring'.format(val))
return
if all_keys is not None and key not in all_keys:
log_error('Ignoring unknown config key: {}'.format(key))
return
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
def _parse(lines, type_map, special_handling, ans, all_keys):
name = getattr(lines, 'name', None)
if name:
base_path_for_includes = os.path.dirname(os.path.abspath(name))
else:
from .constants import config_dir
base_path_for_includes = config_dir
for line in lines:
parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes)
def parse_config_base(
lines, defaults, type_map, special_handling, ans, check_keys=True
):
if check_keys:
all_keys = defaults._asdict()
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if special_handling(key, val, ans):
continue
if check_keys:
if key not in all_keys:
safe_print(
'Ignoring unknown config key: {}'.format(key),
file=sys.stderr
)
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
all_keys = defaults._asdict() if check_keys else None
_parse(lines, type_map, special_handling, ans, all_keys)
def create_options_class(keys):
keys = tuple(sorted(keys))
slots = keys + ('_fields',)
def __init__(self, kw):
for k, v in kw.items():
setattr(self, k, v)
def __iter__(self):
return iter(keys)
def __len__(self):
return len(keys)
def __getitem__(self, i):
if isinstance(i, int):
i = keys[i]
try:
return getattr(self, i)
except AttributeError:
raise KeyError('No option named: {}'.format(i))
def _asdict(self):
return {k: getattr(self, k) for k in self._fields}
def _replace(self, **kw):
ans = self._asdict()
ans.update(kw)
return self.__class__(ans)
ans = type('Options', (), {
'__slots__': slots, '__init__': __init__, '_asdict': _asdict, '_replace': _replace, '__iter__': __iter__,
'__len__': __len__, '__getitem__': __getitem__
})
ans._fields = keys
return ans
def init_config(defaults_path, parse_config):
with open(defaults_path, encoding='utf-8') as f:
defaults = parse_config(f.read().splitlines(), check_keys=False)
Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults)
with open(defaults_path, encoding='utf-8', errors='replace') as f:
defaults = parse_config(f, check_keys=False)
Options = create_options_class(defaults.keys())
defaults = Options(defaults)
return Options, defaults

View File

@@ -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, 8, 0)
version = (0, 9, 0)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat
base = os.path.dirname(os.path.abspath(__file__))
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
ScreenGeometry = namedtuple('ScreenGeometry', 'xstart ystart xnum ynum dx dy')
@@ -25,7 +27,22 @@ def _get_config_dir():
if 'KITTY_CONFIG_DIRECTORY' in os.environ:
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config')))
locations = []
if 'XDG_CONFIG_HOME' in os.environ:
locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME'])))
locations.append(os.path.expanduser('~/.config'))
if is_macos:
locations.append(os.path.expanduser('~/Library/Preferences'))
if 'XDG_CONFIG_DIRS' in os.environ:
for loc in os.environ['XDG_CONFIG_DIRS'].split(os.pathsep):
locations.append(os.path.abspath(os.path.expanduser(loc)))
for loc in locations:
if loc:
q = os.path.join(loc, appname)
if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')):
return q
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config'))
ans = os.path.join(candidate, appname)
os.makedirs(ans, exist_ok=True)
return ans
@@ -44,10 +61,7 @@ def _get_cache_dir():
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
os.makedirs(candidate, exist_ok=True)
return candidate
@@ -77,7 +91,10 @@ logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
try:
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
except KeyError:
print('Failed to read login shell from /etc/passwd for current user, falling back to /bin/sh', file=sys.stderr)
try:
print('Failed to read login shell via getpwuid() for current user, falling back to /bin/sh', file=sys.stderr)
except Exception:
pass
shell_path = '/bin/sh'

View File

@@ -253,7 +253,7 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
}
static PyObject *
reset_display_attrs(Cursor *self) {
reset_display_attrs(Cursor *self, PyObject *a UNUSED) {
#define reset_display_attrs_doc "Reset all display attributes to unset"
cursor_reset_display_attrs(self);
Py_RETURN_NONE;
@@ -272,7 +272,7 @@ void cursor_copy_to(Cursor *src, Cursor *dest) {
}
static PyObject*
copy(Cursor *self);
copy(Cursor *self, PyObject*);
#define copy_doc "Create a clone of this cursor"
// Boilerplate {{{
@@ -342,7 +342,7 @@ cursor_copy(Cursor *self) {
}
static PyObject*
copy(Cursor *self) {
copy(Cursor *self, PyObject *a UNUSED) {
return (PyObject*)cursor_copy(self);
}

View File

@@ -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
@@ -88,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) {
@@ -135,7 +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, ""},
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
#ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS),
#endif
@@ -174,6 +147,7 @@ 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);
@@ -193,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;
@@ -228,6 +203,9 @@ PyInit_fast_data_types(void) {
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
PyModule_AddStringMacro(m, ERROR_PREFIX);
#ifdef KITTY_VCS_REV
PyModule_AddStringMacro(m, KITTY_VCS_REV);
#endif
PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);

View File

@@ -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)
@@ -26,7 +27,8 @@
#define xstr(s) str(s)
#define str(s) #s
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); }
void log_error(const char *fmt, ...);
#define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); }
typedef unsigned long long id_type;
typedef uint32_t char_type;
@@ -207,7 +209,7 @@ typedef struct {
#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__) \
} \
@@ -215,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) { \
@@ -262,6 +265,5 @@ void wakeup_io_loop(bool);
void scroll_event(double, double);
void fake_scroll(int, bool);
void set_special_key_combo(int glfw_key, int mods);
void on_text_input(unsigned int codepoint, int mods);
void on_key_input(int key, int scancode, int action, int mods);
void on_key_input(int key, int scancode, int action, int mods, const char*, int);
void request_window_attention(id_type, bool);

View File

@@ -330,19 +330,39 @@ face_has_codepoint(PyObject* face, char_type cp) {
return glyph_id_for_codepoint(face, cp) > 0;
}
static inline bool
has_emoji_presentation(Cell *cell) {
return (cell->attrs & WIDTH_MASK) == 2 && is_emoji(cell->ch) && cell->cc_idx[0] != VS15;
}
static inline bool
has_cell_text(Font *self, Cell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
if (!face_has_codepoint(self->face, codepoint_for_mark(cell->cc_idx[i]))) return false;
combining_type cc_idx = cell->cc_idx[i];
if (cc_idx == VS15 || cc_idx == VS16) continue;
if (!face_has_codepoint(self->face, codepoint_for_mark(cc_idx))) return false;
}
return true;
}
static inline void
output_cell_fallback_data(Cell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) {
printf("U+%x ", cell->ch);
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
printf("U+%x ", codepoint_for_mark(cell->cc_idx[i]));
}
if (bold) printf("bold ");
if (italic) printf("italic ");
if (emoji_presentation) printf("emoji_presentation ");
PyObject_Print(face, stdout, 0);
if (new_face) printf(" (new face)");
printf("\n");
}
static inline ssize_t
load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation) {
if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; }
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;
@@ -352,6 +372,7 @@ load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation)
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);
@@ -364,17 +385,17 @@ load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation)
return ans;
}
static inline ssize_t
fallback_font(Cell *cell) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
bool emoji_presentation = (cell->attrs & WIDTH_MASK) == 2 && is_emoji(cell->ch) && cell->cc_idx[0] != VS15;
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 && 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;
}
}
@@ -418,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
@@ -744,7 +765,7 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
if (num_cells_consumed) {
if (num_cells_consumed > MAX_GLYPHS_IN_GROUP) {
// Nasty, a single glyph used more than MAX_GLYPHS_IN_GROUP cells, we cannot render this case correctly
fprintf(stderr, "The glyph: %u needs more than %u cells, cannot render it\n", glyph_id, MAX_GLYPHS_IN_GROUP);
log_error("The glyph: %u needs more than %u cells, cannot render it", glyph_id, MAX_GLYPHS_IN_GROUP);
current_group->num_glyphs--;
while (num_cells_consumed) {
G(group_idx)++; current_group = G(groups) + G(group_idx);
@@ -773,6 +794,17 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
#undef MAX_GLYPHS_IN_GROUP
}
static inline void
merge_groups_for_pua_space_ligature() {
if (G(group_idx) == 1) {
Group *g = G(groups), *g1 = G(groups) + 1;
g->num_cells += g1->num_cells;
g->num_glyphs += g1->num_glyphs;
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
G(group_idx) = 0;
}
}
static inline void
render_groups(Font *font) {
unsigned idx = 0;
@@ -783,7 +815,10 @@ render_groups(Font *font) {
/* printf("Group: idx: %u num_cells: %u num_glyphs: %u first_glyph_idx: %u first_cell_idx: %u total_num_glyphs: %zu\n", */
/* idx, group->num_cells, group->num_glyphs, group->first_glyph_idx, group->first_cell_idx, group_state.num_glyphs); */
glyph_index primary = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
for (unsigned i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) ed.data[i-1] = G(info)[group->first_glyph_idx + i].codepoint;
unsigned int i;
int last = -1;
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
render_group(group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
idx++;
}
@@ -826,10 +861,11 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
#undef G
static inline void
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) {
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
switch(font_idx) {
default:
shape_run(first_cell, num_cells, &fonts.fonts[font_idx]);
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
render_groups(&fonts.fonts[font_idx]);
break;
case BLANK_FONT:
@@ -844,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;
@@ -854,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;
@@ -1015,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; }}
@@ -1046,7 +1097,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
static Py_UCS4 char_buf[2 + arraysz(cell.cc_idx)];
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
cell.ch = char_buf[0];
for (unsigned i = 0; i + 1 < PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
if (bold) cell.attrs |= 1 << BOLD_SHIFT;
if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
ssize_t ans = fallback_font(&cell);

View File

@@ -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,

View File

@@ -279,7 +279,7 @@ calc_cell_width(Face *self) {
for (char_type i = 32; i < 128; i++) {
int glyph_index = FT_Get_Char_Index(self->face, i);
if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) {
ans = MAX(ans, (unsigned long)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
ans = MAX(ans, (unsigned int)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
}
}
return ans;
@@ -324,7 +324,6 @@ typedef struct {
unsigned int factor, right_edge;
} ProcessedBitmap;
static inline void
trim_borders(ProcessedBitmap *ans, size_t extra) {
bool column_has_text = false;
@@ -354,9 +353,13 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
ans->rows = bitmap->rows;
ans->pixel_mode = bitmap->pixel_mode;
if (ans->width > max_width) {
size_t extra = bitmap->width - max_width;
size_t extra = ans->width - max_width;
if (italic && extra < cell_width / 2) {
trim_borders(ans, extra);
} else if (extra == 2 && num_cells == 1) {
// there exist fonts that have bitmaps just a couple of pixels
// wider than their advances, rather than rescale, which looks
// bad, we just crop the bitmap on the right. See https://github.com/kovidgoyal/kitty/issues/352
} else if (rescale && self->is_scalable && extra > 1) {
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
float ar = (float)max_width / (float)bitmap->width;
@@ -532,7 +535,8 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
}
static PyObject*
display_name(Face *self) {
display_name(PyObject *s, PyObject *a UNUSED) {
Face *self = (Face*)s;
const char *psname = FT_Get_Postscript_Name(self->face);
if (psname) return Py_BuildValue("s", psname);
Py_INCREF(self->path);
@@ -540,8 +544,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 {{{

View File

@@ -117,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;

10
kitty/glfw-wrapper.c generated
View File

@@ -254,14 +254,8 @@ load_glfw(const char* path) {
*(void **) (&glfwSetCursor_impl) = dlsym(handle, "glfwSetCursor");
if (glfwSetCursor_impl == NULL) fail("Failed to load glfw function glfwSetCursor with error: %s", dlerror());
*(void **) (&glfwSetKeyCallback_impl) = dlsym(handle, "glfwSetKeyCallback");
if (glfwSetKeyCallback_impl == NULL) fail("Failed to load glfw function glfwSetKeyCallback with error: %s", dlerror());
*(void **) (&glfwSetCharCallback_impl) = dlsym(handle, "glfwSetCharCallback");
if (glfwSetCharCallback_impl == NULL) fail("Failed to load glfw function glfwSetCharCallback with error: %s", dlerror());
*(void **) (&glfwSetCharModsCallback_impl) = dlsym(handle, "glfwSetCharModsCallback");
if (glfwSetCharModsCallback_impl == NULL) fail("Failed to load glfw function glfwSetCharModsCallback with error: %s", dlerror());
*(void **) (&glfwSetKeyboardCallback_impl) = dlsym(handle, "glfwSetKeyboardCallback");
if (glfwSetKeyboardCallback_impl == NULL) fail("Failed to load glfw function glfwSetKeyboardCallback with error: %s", dlerror());
*(void **) (&glfwSetMouseButtonCallback_impl) = dlsym(handle, "glfwSetMouseButtonCallback");
if (glfwSetMouseButtonCallback_impl == NULL) fail("Failed to load glfw function glfwSetMouseButtonCallback with error: %s", dlerror());

80
kitty/glfw-wrapper.h generated
View File

@@ -1133,9 +1133,15 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
*/
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
/*! @brief The function signature for keyboard key callbacks.
/*! @brief The function signature for key callbacks.
*
* This is the function signature for keyboard key callback functions.
* This is the function signature for key callback functions.
* The semantics of this function are that the key that is interacted with on the
* keyboard is reported, and the text, if any generated by the key is reported.
* So, for example, if on a US-ASCII keyboard the user presses Shift+= GLFW
* will report the text "+" and the key as GLFW_KEY_EQUAL. The reported key takes into
* account any current keyboard maps defined in the OS. So with a dvorak mapping, pressing
* the "s" key will generate text "o" and GLFW_KEY_O.
*
* @param[in] window The window that received the event.
* @param[in] key The [keyboard key](@ref keys) that was pressed or released.
@@ -1143,56 +1149,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
* @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
* @param[in] reserved Reserved for future use.
*
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
* than the unshifted key. So for example, if ISO_Shift_Level_5 is used to
* convert the key A into UP GLFW will report the key as UP with no modifiers.
*
* @sa @ref input_key
* @sa @ref glfwSetKeyCallback
* @sa @ref glfwSetKeyboardCallback
*
* @since Added in version 1.0.
* @glfw3 Added window handle, scancode and modifier mask parameters.
* @since Added in version 4.0.
*
* @ingroup input
*/
typedef void (* GLFWkeyfun)(GLFWwindow*,int,int,int,int);
/*! @brief The function signature for Unicode character callbacks.
*
* This is the function signature for Unicode character callback functions.
*
* @param[in] window The window that received the event.
* @param[in] codepoint The Unicode code point of the character.
*
* @sa @ref input_char
* @sa @ref glfwSetCharCallback
*
* @since Added in version 2.4.
* @glfw3 Added window handle parameter.
*
* @ingroup input
*/
typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int);
/*! @brief The function signature for Unicode character with modifiers
* callbacks.
*
* This is the function signature for Unicode character with modifiers callback
* functions. It is called for each input character, regardless of what
* modifier keys are held down.
*
* @param[in] window The window that received the event.
* @param[in] codepoint The Unicode code point of the character.
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
*
* @sa @ref input_char
* @sa @ref glfwSetCharModsCallback
*
* @deprecated Scheduled for removal in version 4.0.
*
* @since Added in version 3.1.
*
* @ingroup input
*/
typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int);
typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, int);
/*! @brief The function signature for file drop callbacks.
*
@@ -1585,7 +1557,7 @@ typedef void (*glfwRequestWindowAttention_func)(GLFWwindow*);
glfwRequestWindowAttention_func glfwRequestWindowAttention_impl;
#define glfwRequestWindowAttention glfwRequestWindowAttention_impl
typedef int (*glfwWindowBell_func)(GLFWwindow*, int64_t);
typedef int (*glfwWindowBell_func)(GLFWwindow*);
glfwWindowBell_func glfwWindowBell_impl;
#define glfwWindowBell glfwWindowBell_impl
@@ -1713,17 +1685,9 @@ typedef void (*glfwSetCursor_func)(GLFWwindow*, GLFWcursor*);
glfwSetCursor_func glfwSetCursor_impl;
#define glfwSetCursor glfwSetCursor_impl
typedef GLFWkeyfun (*glfwSetKeyCallback_func)(GLFWwindow*, GLFWkeyfun);
glfwSetKeyCallback_func glfwSetKeyCallback_impl;
#define glfwSetKeyCallback glfwSetKeyCallback_impl
typedef GLFWcharfun (*glfwSetCharCallback_func)(GLFWwindow*, GLFWcharfun);
glfwSetCharCallback_func glfwSetCharCallback_impl;
#define glfwSetCharCallback glfwSetCharCallback_impl
typedef GLFWcharmodsfun (*glfwSetCharModsCallback_func)(GLFWwindow*, GLFWcharmodsfun);
glfwSetCharModsCallback_func glfwSetCharModsCallback_impl;
#define glfwSetCharModsCallback glfwSetCharModsCallback_impl
typedef GLFWkeyboardfun (*glfwSetKeyboardCallback_func)(GLFWwindow*, GLFWkeyboardfun);
glfwSetKeyboardCallback_func glfwSetKeyboardCallback_impl;
#define glfwSetKeyboardCallback glfwSetKeyboardCallback_impl
typedef GLFWmousebuttonfun (*glfwSetMouseButtonCallback_func)(GLFWwindow*, GLFWmousebuttonfun);
glfwSetMouseButtonCallback_func glfwSetMouseButtonCallback_impl;

View File

@@ -16,6 +16,7 @@ extern void cocoa_set_titlebar_color(void *w);
#endif
static GLFWcursor *standard_cursor = NULL, *click_cursor = NULL, *arrow_cursor = NULL;
static bool event_loop_blocking_with_no_timeout = false;
void
update_os_window_viewport(OSWindow *window, bool notify_boss) {
@@ -30,6 +31,8 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
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, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
}
@@ -72,9 +75,20 @@ 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); }
glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
// On Cocoa, glfwWaitEvents() can block indefinitely because of the way Cocoa
// works. See https://github.com/glfw/glfw/issues/1251. I have noticed this
// happening in particular with window resize events, when waiting with no
// timeout. See https://github.com/kovidgoyal/kitty/issues/458
// So we use an unlovely hack to workaround that case
#ifdef __APPLE__
#define unjam_event_loop() { if (event_loop_blocking_with_no_timeout) wakeup_main_loop(); }
#else
#define unjam_event_loop()
#endif
static void
framebuffer_size_callback(GLFWwindow *w, int width, int height) {
if (!set_callback_window(w)) return;
@@ -82,7 +96,8 @@ framebuffer_size_callback(GLFWwindow *w, int width, int height) {
OSWindow *window = global_state.callback_os_window;
window->has_pending_resizes = true; global_state.has_pending_resizes = true;
window->last_resize_event_at = monotonic();
} else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height);
unjam_event_loop();
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
global_state.callback_os_window = NULL;
}
@@ -94,21 +109,13 @@ refresh_callback(GLFWwindow *w) {
}
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();
if (is_window_ready_for_callbacks()) on_text_input(codepoint, mods);
global_state.callback_os_window = NULL;
}
static void
key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) {
key_callback(GLFWwindow *w, int key, int scancode, int action, int mods, const char* text, int state) {
if (!set_callback_window(w)) return;
global_state.callback_os_window->cursor_blink_zero_time = monotonic();
if (key >= 0 && key <= GLFW_KEY_LAST) {
global_state.callback_os_window->is_key_pressed[key] = action == GLFW_RELEASE ? false : true;
if (is_window_ready_for_callbacks()) on_key_input(key, scancode, action, mods);
}
if (is_window_ready_for_callbacks()) on_key_input(key, scancode, action, mods, text, state);
global_state.callback_os_window = NULL;
}
@@ -229,7 +236,11 @@ static GLFWimage logo = {0};
static PyObject*
set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
Py_ssize_t sz;
if(!PyArg_ParseTuple(args, "s#ii", &(logo.pixels), &sz, &(logo.width), &(logo.height))) return NULL;
const char *logo_data;
if(!PyArg_ParseTuple(args, "s#ii", &(logo_data), &sz, &(logo.width), &(logo.height))) return NULL;
sz = (MAX(logo.width * logo.height, sz));
logo.pixels = malloc(sz);
if (logo.pixels) memcpy(logo.pixels, logo_data, sz);
Py_RETURN_NONE;
}
@@ -296,6 +307,7 @@ set_dpi_from_os_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) {
@@ -303,7 +315,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
char *title, *wm_class_class, *wm_class_name;
PyObject *load_programs = NULL;
if (!PyArg_ParseTuple(args, "iisss|Oiii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
bool is_first_window = standard_cursor == NULL;
if (is_first_window) {
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
@@ -336,11 +347,11 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
PyErr_SetString(PyExc_ValueError, "Too many windows");
return NULL;
}
bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false;
bool want_semi_transparent = (1.0 - OPT(background_opacity) >= 0.01) ? 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; }
@@ -357,6 +368,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();
@@ -372,17 +384,16 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
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);
glfwSetCursorPosCallback(glfw_window, cursor_pos_callback);
glfwSetKeyCallback(glfw_window, key_callback);
glfwSetKeyboardCallback(glfw_window, key_callback);
glfwSetWindowFocusCallback(glfw_window, window_focus_callback);
glfwSetDropCallback(glfw_window, drop_callback);
#ifdef __APPLE__
if (OPT(macos_hide_titlebar)) {
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
else fprintf(stderr, "Failed to load glfwGetCocoaWindow\n");
else log_error("Failed to load glfwGetCocoaWindow");
}
cocoa_set_titlebar_color(glfwGetCocoaWindow(glfw_window));
#endif
@@ -394,7 +405,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;
}
}
@@ -459,7 +470,7 @@ destroy_os_window(OSWindow *w) {
// Global functions {{{
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);
}
@@ -480,31 +491,19 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
}
PyObject*
glfw_terminate(PyObject UNUSED *self) {
glfw_terminate(PYNOARG) {
glfwTerminate();
Py_RETURN_NONE;
}
PyObject*
glfw_wait_events(PyObject UNUSED *self, PyObject *args) {
double time = -1;
if (PyTuple_GET_SIZE(args) > 0) {
time = PyFloat_AsDouble(PyTuple_GET_ITEM(args, 0));
if (PyErr_Occurred()) PyErr_Clear();
}
if (time < 0) glfwWaitEvents();
else glfwWaitEventsTimeout(time);
Py_RETURN_NONE;
}
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;
}
@@ -522,7 +521,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);
@@ -547,14 +546,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);
@@ -571,7 +570,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;
@@ -601,7 +600,7 @@ ring_audio_bell(OSWindow *w) {
if (now - last_bell_at <= 0.1) return;
last_bell_at = now;
if (w->handle) {
glfwWindowBell(w->handle, OPT(x11_bell_volume));
glfwWindowBell(w->handle);
}
}
@@ -622,9 +621,7 @@ set_os_window_title(OSWindow *w, const char *title) {
void
hide_mouse(OSWindow *w) {
if (glfwGetInputMode(w->handle, GLFW_CURSOR) != GLFW_CURSOR_HIDDEN) {
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
void
@@ -634,8 +631,8 @@ swap_window_buffers(OSWindow *w) {
void
event_loop_wait(double timeout) {
if (timeout < 0) glfwWaitEvents();
else if (timeout > 0) glfwWaitEventsTimeout(timeout);
if (timeout < 0) { event_loop_blocking_with_no_timeout = true; glfwWaitEvents(); event_loop_blocking_with_no_timeout = false; }
else glfwWaitEventsTimeout(timeout);
}
void
@@ -661,14 +658,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);
@@ -676,10 +673,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;
}
@@ -698,10 +695,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;
}
@@ -710,7 +707,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;
}
@@ -768,7 +765,6 @@ static PyMethodDef module_methods[] = {
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, ""},
@@ -777,10 +773,19 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */
};
void cleanup_glfw(void) {
if (logo.pixels) free(logo.pixels);
logo.pixels = NULL;
}
// constants {{{
bool
init_glfw(PyObject *m) {
if (PyModule_AddFunctions(m, module_methods) != 0) return false;
if (Py_AtExit(cleanup_glfw) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the glfw exit handler");
return false;
}
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
ADDC(GLFW_RELEASE);
ADDC(GLFW_PRESS);

View File

@@ -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;
@@ -509,7 +509,7 @@ 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;

View File

@@ -161,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);
}
@@ -215,7 +215,7 @@ as_text(HistoryBuf *self, PyObject *args) {
static PyObject*
dirty_lines(HistoryBuf *self) {
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++) {

View File

@@ -76,66 +76,6 @@ is_modifier_key(int key) {
}
}
static inline int
get_localized_key(int key, int scancode) {
const char *name = glfwGetKeyName(key, scancode);
if (name == NULL || name[1] != 0) return key;
switch(name[0]) {
#define K(ch, name) case ch: return GLFW_KEY_##name
// key names {{{
K('A', A); K('a', A);
K('B', B); K('b', B);
K('C', C); K('c', C);
K('D', D); K('d', D);
K('E', E); K('e', E);
K('F', F); K('f', F);
K('G', G); K('g', G);
K('H', H); K('h', H);
K('I', I); K('i', I);
K('J', J); K('j', J);
K('K', K); K('k', K);
K('L', L); K('l', L);
K('M', M); K('m', M);
K('N', N); K('n', N);
K('O', O); K('o', O);
K('P', P); K('p', P);
K('Q', Q); K('q', Q);
K('R', R); K('r', R);
K('S', S); K('s', S);
K('T', T); K('t', T);
K('U', U); K('u', U);
K('V', V); K('v', V);
K('W', W); K('w', W);
K('X', X); K('x', X);
K('Y', Y); K('y', Y);
K('Z', Z); K('z', Z);
K('0', 0);
K('1', 1);
K('2', 2);
K('3', 3);
K('5', 5);
K('6', 6);
K('7', 7);
K('8', 8);
K('9', 9);
K('\'', APOSTROPHE);
K(',', COMMA);
K('.', PERIOD);
K('/', SLASH);
K('-', MINUS);
K(';', SEMICOLON);
K('=', EQUAL);
K('[', LEFT_BRACKET);
K(']', RIGHT_BRACKET);
K('`', GRAVE_ACCENT);
K('\\', BACKSLASH);
// }}}
#undef K
default:
return key;
}
}
static inline void
send_key_to_child(Window *w, int key, int mods, int action) {
Screen *screen = w->render_data.screen;
@@ -152,14 +92,26 @@ send_key_to_child(Window *w, int key, int mods, int action) {
}
}
static inline bool
is_ascii_control_char(char c) {
return (0 <= c && c <= 31) || c == 127;
}
void
on_key_input(int key, int scancode, int action, int mods) {
on_key_input(int key, int scancode, int action, int mods, const char* text, int state UNUSED) {
Window *w = active_window();
if (!w) return;
if (global_state.in_sequence_mode) {
if (action != GLFW_RELEASE) call_boss(process_sequence, "iiii", key, scancode, action, mods);
return;
}
Screen *screen = w->render_data.screen;
int lkey = get_localized_key(key, scancode);
bool has_text = text && !is_ascii_control_char(text[0]);
#ifdef __APPLE__
if (has_text && IS_ALT_MODS(mods) && OPT(macos_option_as_alt)) has_text = false;
#endif
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
uint16_t qkey = key_map[lkey];
uint16_t qkey = (0 <= key && key < (ssize_t)arraysz(key_map)) ? key_map[key] : UINT8_MAX;
bool special = false;
if (qkey != UINT8_MAX) {
qkey = SPECIAL_INDEX(qkey);
@@ -167,7 +119,7 @@ on_key_input(int key, int scancode, int action, int mods) {
}
/* printf("key: %s mods: %d special: %d\n", key_name(lkey), mods, special); */
if (special) {
PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", lkey, scancode, action, mods);
PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", key, scancode, action, mods);
if (ret == NULL) { PyErr_Print(); }
else {
bool consumed = ret == Py_True;
@@ -176,20 +128,17 @@ on_key_input(int key, int scancode, int action, int mods) {
}
}
}
if (action == GLFW_REPEAT && !screen->modes.mDECARM) return;
if (screen->scrolled_by && action == GLFW_PRESS && !is_modifier_key(key)) {
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
}
#ifdef __APPLE__
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 ||
(action == GLFW_REPEAT && screen->modes.mDECARM) ||
screen->modes.mEXTENDED_KEYBOARD
) {
send_key_to_child(w, lkey, mods, action);
bool ok_to_send = action == GLFW_PRESS || action == GLFW_REPEAT || screen->modes.mEXTENDED_KEYBOARD;
if (ok_to_send) {
if (has_text) {
schedule_write_to_child(w->id, text, strlen(text));
} else {
send_key_to_child(w, key, mods, action);
}
}
}

218
kitty/keys.h generated
View File

@@ -1126,6 +1126,58 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key)
case 0x6:
switch(key & 0x7f) { default: return NULL;
case 18: // A
return "\x02\x1b\x01";
case 19: // B
return "\x02\x1b\x02";
case 20: // C
return "\x02\x1b\x03";
case 21: // D
return "\x02\x1b\x04";
case 22: // E
return "\x02\x1b\x05";
case 23: // F
return "\x02\x1b\x06";
case 24: // G
return "\x02\x1b\x07";
case 25: // H
return "\x02\x1b\x08";
case 26: // I
return "\x02\x1b\x09";
case 27: // J
return "\x02\x1b\x0a";
case 28: // K
return "\x02\x1b\x0b";
case 29: // L
return "\x02\x1b\x0c";
case 30: // M
return "\x02\x1b\x0d";
case 31: // N
return "\x02\x1b\x0e";
case 32: // O
return "\x02\x1b\x0f";
case 33: // P
return "\x02\x1b\x10";
case 34: // Q
return "\x02\x1b\x11";
case 35: // R
return "\x02\x1b\x12";
case 36: // S
return "\x02\x1b\x13";
case 37: // T
return "\x02\x1b\x14";
case 38: // U
return "\x02\x1b\x15";
case 39: // V
return "\x02\x1b\x16";
case 40: // W
return "\x02\x1b\x17";
case 41: // X
return "\x02\x1b\x18";
case 42: // Y
return "\x02\x1b\x19";
case 43: // Z
return "\x02\x1b\x1a";
case 50: // ESCAPE
return "\x01\x1b";
case 51: // ENTER
@@ -1695,6 +1747,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x01\x0d";
} // end switch(key)
} // end switch(mods)
break;
case 2: // REPEAT
switch (mods & 0xf) {
@@ -2332,6 +2385,58 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key)
case 0x6:
switch(key & 0x7f) { default: return NULL;
case 18: // A
return "\x02\x1b\x01";
case 19: // B
return "\x02\x1b\x02";
case 20: // C
return "\x02\x1b\x03";
case 21: // D
return "\x02\x1b\x04";
case 22: // E
return "\x02\x1b\x05";
case 23: // F
return "\x02\x1b\x06";
case 24: // G
return "\x02\x1b\x07";
case 25: // H
return "\x02\x1b\x08";
case 26: // I
return "\x02\x1b\x09";
case 27: // J
return "\x02\x1b\x0a";
case 28: // K
return "\x02\x1b\x0b";
case 29: // L
return "\x02\x1b\x0c";
case 30: // M
return "\x02\x1b\x0d";
case 31: // N
return "\x02\x1b\x0e";
case 32: // O
return "\x02\x1b\x0f";
case 33: // P
return "\x02\x1b\x10";
case 34: // Q
return "\x02\x1b\x11";
case 35: // R
return "\x02\x1b\x12";
case 36: // S
return "\x02\x1b\x13";
case 37: // T
return "\x02\x1b\x14";
case 38: // U
return "\x02\x1b\x15";
case 39: // V
return "\x02\x1b\x16";
case 40: // W
return "\x02\x1b\x17";
case 41: // X
return "\x02\x1b\x18";
case 42: // Y
return "\x02\x1b\x19";
case 43: // Z
return "\x02\x1b\x1a";
case 50: // ESCAPE
return "\x01\x1b";
case 51: // ENTER
@@ -2901,8 +3006,10 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x01\x0d";
} // end switch(key)
} // end switch(mods)
break;
} // end switch(action) in mode NORMAL
break;
case APPLICATION:
@@ -3546,6 +3653,58 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key)
case 0x6:
switch(key & 0x7f) { default: return NULL;
case 18: // A
return "\x02\x1b\x01";
case 19: // B
return "\x02\x1b\x02";
case 20: // C
return "\x02\x1b\x03";
case 21: // D
return "\x02\x1b\x04";
case 22: // E
return "\x02\x1b\x05";
case 23: // F
return "\x02\x1b\x06";
case 24: // G
return "\x02\x1b\x07";
case 25: // H
return "\x02\x1b\x08";
case 26: // I
return "\x02\x1b\x09";
case 27: // J
return "\x02\x1b\x0a";
case 28: // K
return "\x02\x1b\x0b";
case 29: // L
return "\x02\x1b\x0c";
case 30: // M
return "\x02\x1b\x0d";
case 31: // N
return "\x02\x1b\x0e";
case 32: // O
return "\x02\x1b\x0f";
case 33: // P
return "\x02\x1b\x10";
case 34: // Q
return "\x02\x1b\x11";
case 35: // R
return "\x02\x1b\x12";
case 36: // S
return "\x02\x1b\x13";
case 37: // T
return "\x02\x1b\x14";
case 38: // U
return "\x02\x1b\x15";
case 39: // V
return "\x02\x1b\x16";
case 40: // W
return "\x02\x1b\x17";
case 41: // X
return "\x02\x1b\x18";
case 42: // Y
return "\x02\x1b\x19";
case 43: // Z
return "\x02\x1b\x1a";
case 50: // ESCAPE
return "\x01\x1b";
case 51: // ENTER
@@ -4115,6 +4274,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x01\x0d";
} // end switch(key)
} // end switch(mods)
break;
case 2: // REPEAT
switch (mods & 0xf) {
@@ -4752,6 +4912,58 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key)
case 0x6:
switch(key & 0x7f) { default: return NULL;
case 18: // A
return "\x02\x1b\x01";
case 19: // B
return "\x02\x1b\x02";
case 20: // C
return "\x02\x1b\x03";
case 21: // D
return "\x02\x1b\x04";
case 22: // E
return "\x02\x1b\x05";
case 23: // F
return "\x02\x1b\x06";
case 24: // G
return "\x02\x1b\x07";
case 25: // H
return "\x02\x1b\x08";
case 26: // I
return "\x02\x1b\x09";
case 27: // J
return "\x02\x1b\x0a";
case 28: // K
return "\x02\x1b\x0b";
case 29: // L
return "\x02\x1b\x0c";
case 30: // M
return "\x02\x1b\x0d";
case 31: // N
return "\x02\x1b\x0e";
case 32: // O
return "\x02\x1b\x0f";
case 33: // P
return "\x02\x1b\x10";
case 34: // Q
return "\x02\x1b\x11";
case 35: // R
return "\x02\x1b\x12";
case 36: // S
return "\x02\x1b\x13";
case 37: // T
return "\x02\x1b\x14";
case 38: // U
return "\x02\x1b\x15";
case 39: // V
return "\x02\x1b\x16";
case 40: // W
return "\x02\x1b\x17";
case 41: // X
return "\x02\x1b\x18";
case 42: // Y
return "\x02\x1b\x19";
case 43: // Z
return "\x02\x1b\x1a";
case 50: // ESCAPE
return "\x01\x1b";
case 51: // ENTER
@@ -5321,8 +5533,10 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x01\x0d";
} // end switch(key)
} // end switch(mods)
break;
} // end switch(action) in mode APPLICATION
break;
case EXTENDED:
@@ -8926,6 +9140,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x05\x4b\x72\x50\x42\x68";
} // end switch(key)
} // end switch(mods)
break;
case 1: // PRESS
switch (mods & 0xf) {
@@ -12530,6 +12745,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x05\x4b\x70\x50\x42\x68";
} // end switch(key)
} // end switch(mods)
break;
case 2: // REPEAT
switch (mods & 0xf) {
@@ -16134,8 +16350,10 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
return "\x05\x4b\x74\x50\x42\x68";
} // end switch(key)
} // end switch(mods)
break;
} // end switch(action) in mode EXTENDED
break;
}

View File

@@ -36,6 +36,7 @@ shift_alt_codes = alt_codes.copy()
shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt')
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL
for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1',
@@ -204,6 +205,7 @@ ASCII_C0_SHIFTED = {
'SLASH': b'\x7f',
}
CTRL_SHIFT_KEYS = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase}
def key_to_bytes(key, smkx, extended, mods, action):
@@ -222,6 +224,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
m = UN_SHIFTED_PRINTABLE if mods == defines.GLFW_MOD_ALT else SHIFTED_PRINTABLE
data.append(0o33)
data.extend(m[key])
elif mods == ctrl_alt_mod and key in CTRL_ALT_KEYS:
data.append(0x1b), data.extend(control_codes[key])
else:
key_map = cursor_key_mode_map[smkx]
x = key_map.get(key)
@@ -247,6 +251,10 @@ def get_shortcut(keymap, mods, key, scancode):
return keymap.get((mods & 0b1111, key))
def shortcut_matches(s, mods, key, scancode):
return s[0] & 0b1111 == mods & 0b1111 and s[1] == key
def generate_key_table():
# To run this, use: python3 . -c "from kitty.keys import *; generate_key_table()"
import os
@@ -332,13 +340,15 @@ def generate_key_table():
ind('return NULL;')
i -= 2
i -= 1
ind('} // end switch(mods)\n')
ind('} // end switch(mods)')
ind('break;\n')
i -= 1
else:
ind('return NULL;\n')
i -= 1
i -= 1
ind('}} // end switch(action) in mode {}\n\n'.format(mode))
ind('}} // end switch(action) in mode {}'.format(mode))
ind('break;\n\n')
i -= 1
i -= 1
ind('}')

View File

@@ -1,5 +1,11 @@
# vim:fileencoding=utf-8:ft=conf
# You can include secondary config files via the "include" directive.
# If you use a relative path for include, it is resolved with respect to the
# location od the current config file. For example:
# include other.conf
# Fonts {{{
# Font family. You can also specify different fonts for the
# bold/italic/bold-italic variants. By default they are derived automatically,
# by the OSes font system. Setting them manually is useful for font families
@@ -11,6 +17,7 @@
#
# You can get a list of full family names available on your computer by running
# kitty list-fonts
# The default values shown below rely on your OS to choose an appropriate monospace font family.
font_family monospace
italic_font auto
bold_font auto
@@ -32,12 +39,189 @@ font_size_delta 2
adjust_line_height 0
adjust_column_width 0
# Symbol mapping (special font for specified unicode code points). Map the
# specified unicode codepoints to a particular font. Useful if you need special
# rendering for some symbols, such as for Powerline. Avoids the need for
# patched fonts. Each unicode code point is specified in the form U+<code point
# in hexadecimal>. You can specify multiple code points, separated by commas
# and ranges separated by hyphens. symbol_map itself can be specified multiple times.
# Syntax is:
#
# symbol_map codepoints Font Family Name
#
# For example:
#
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
# Change the sizes of the lines used for the box drawing unicode characters
# These values are in pts. They will be scaled by the monitor DPI to arrive at
# a pixel value. There must be four values corresponding to thin, normal, thick,
# and very thick lines;
box_drawing_scale 0.001, 1, 1.5, 2
# }}}
# Cursor customization {{{
# The cursor color
cursor #cccccc
# The cursor shape can be one of (block, beam, underline)
cursor_shape block
# The interval (in seconds) at which to blink the cursor. Set to zero to
# disable blinking. Note that numbers smaller than repaint_delay will be
# limited to repaint_delay.
cursor_blink_interval 0.5
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
# zero to never stop blinking.
cursor_stop_blinking_after 15.0
# }}}
# Scrollback {{{
# Number of lines of history to keep in memory for scrolling back
scrollback_lines 2000
# Program with which to view scrollback in a new window. The scrollback buffer is passed as
# STDIN to this program. If you change it, make sure the program you use can
# handle ANSI escape sequences for colors and text formatting.
scrollback_pager less +G -R
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
# numbers to change scroll direction.
wheel_scroll_multiplier 5.0
# }}}
# Mouse {{{
# The color and style for highlighting URLs on mouse-over. url_style can be one of:
# none, single, double, curly
url_color #0087BD
url_style curly
# The modifier keys to press when clicking with the mouse on URLs to open the URL
open_url_modifiers ctrl+shift
# The program with which to open URLs that are clicked on. The special value "default" means to
# use the operating system's default URL handler.
open_url_with default
# Copy to clipboard on select. With this enabled, simply selecting text with
# the mouse will cause the text to be copied to clipboard. Useful on platforms
# such as macOS/Wayland that do not have the concept of primary selections. Note
# that this is a security risk, as all programs, including websites open in your
# browser can read the contents of the clipboard.
copy_on_select no
# The modifiers to use rectangular selection (i.e. to select text in a
# rectangular block with the mouse)
rectangle_select_modifiers ctrl+alt
# Characters considered part of a word when double clicking. In addition to these characters
# any character that is marked as an alpha-numeric character in the unicode
# database will be matched.
select_by_word_characters :@-./_~?&=%+#
# The interval between successive clicks to detect double/triple clicks (in seconds)
click_interval 0.5
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
# zero to disable mouse cursor hiding.
mouse_hide_wait 3.0
# Set the active window to the window under the mouse when moving the mouse around
focus_follows_mouse no
# }}}
# Performance tuning {{{
# Delay (in milliseconds) between screen updates. Decreasing it, increases
# frames-per-second (FPS) at the cost of more CPU usage. The default value
# yields ~100 FPS which is more than sufficient for most uses. Note that to
# actually achieve 100FPS you have to either set sync_to_monitor to no or use a
# monitor with a high refresh rate.
repaint_delay 10
# Delay (in milliseconds) before input from the program running in the terminal
# is processed. Note that decreasing it will increase responsiveness, but also
# increase CPU usage and might cause flicker in full screen programs that
# redraw the entire screen on each loop, because kitty is so fast that partial
# screen updates will be drawn.
input_delay 3
# Sync screen updates to the refresh rate of the monitor. This prevents
# tearing (https://en.wikipedia.org/wiki/Screen_tearing) when scrolling. However,
# it limits the rendering speed to the refresh rate of your monitor. With a
# very high speed mouse/high keyboard repeat rate, you may notice some slight input latency.
# If so, set this to no.
sync_to_monitor yes
# }}}
# Audio/visual bell {{{
# Visual bell duration. Flash the screen when a bell occurs for the specified number of
# seconds. Set to zero to disable.
visual_bell_duration 0.0
# Enable/disable the audio bell. Useful in environments that require silence.
enable_audio_bell yes
# }}}
# Window layout {{{
# If enabled, the window size will be remembered so that new instances of kitty will have the same
# size as the previous instance. If disabled, the window will initially have size configured
# by initial_window_width/height, in pixels.
remember_window_size yes
initial_window_width 640
initial_window_height 400
# The enabled window layouts. A comma separated list of layout names. The special value * means
# all layouts. The first listed layout will be used as the startup layout.
# For a list of available layouts, see the README.
enabled_layouts *
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
# Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.
window_border_width 1
# The window margin (in pts) (blank area outside the border)
window_margin_width 0
# The window padding (in pts) (blank area between the text and the window border)
window_padding_width 0
# The color for the border of the active window
active_border_color #00ff00
# The color for the border of inactive windows
inactive_border_color #cccccc
# Fade the text in inactive windows by the specified amount (a number between
# zero and one, with 0 being fully faded).
inactive_text_alpha 1.0
# }}}
# Tab bar {{{
# Which edge to show the tab bar on, top or bottom
tab_bar_edge bottom
# The separator between tabs in the tab bar
tab_separator " ┇"
# Tab bar colors and styles
active_tab_foreground #000
active_tab_background #eee
active_tab_font_style bold-italic
inactive_tab_foreground #444
inactive_tab_background #999
inactive_tab_font_style normal
# }}}
# Color scheme {{{
# The foreground color
foreground #dddddd
@@ -62,155 +246,9 @@ selection_foreground #000000
# The background for selections
selection_background #FFFACD
# The color and style for highlighting URLs on mouse-over. url_style can be one of:
# none, single, double, curly
url_color #0087BD
url_style curly
# The cursor color
cursor #cccccc
# The cursor shape can be one of (block, beam, underline)
cursor_shape block
# The interval (in seconds) at which to blink the cursor. Set to zero to
# disable blinking. Note that numbers smaller than repaint_delay will be
# limited to repaint_delay.
cursor_blink_interval 0.5
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
# zero to never stop blinking.
cursor_stop_blinking_after 15.0
# Number of lines of history to keep in memory for scrolling back
scrollback_lines 2000
# Program with which to view scrollback in a new window. The scrollback buffer is passed as
# STDIN to this program. If you change it, make sure the program you use can
# handle ANSI escape sequences for colors and text formatting.
scrollback_pager less +G -R
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
# numbers to change scroll direction.
wheel_scroll_multiplier 5.0
# The interval between successive clicks to detect double/triple clicks (in seconds)
click_interval 0.5
# Characters considered part of a word when double clicking. In addition to these characters
# any character that is marked as an alpha-numeric character in the unicode
# database will be matched.
select_by_word_characters :@-./_~?&=%+#
# The shell program to execute. The default value of . means
# to use whatever shell is set as the default shell for the current user.
shell .
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
# zero to disable mouse cursor hiding.
mouse_hide_wait 3.0
# Set the active window to the window under the mouse when moving the mouse around
focus_follows_mouse no
# The enabled window layouts. A comma separated list of layout names. The special value * means
# all layouts. The first listed layout will be used as the startup layout.
# For a list of available layouts, see the file layouts.py
enabled_layouts *
# If enabled, the window size will be remembered so that new instances of kitty will have the same
# size as the previous instance. If disabled, the window will initially have size configured
# by initial_window_width/height, in pixels.
remember_window_size yes
initial_window_width 640
initial_window_height 400
# Delay (in milliseconds) between screen updates. Decreasing it, increases
# frames-per-second (FPS) at the cost of more CPU usage. The default value
# yields ~100 FPS which is more than sufficient for most uses. Note that to
# actually achieve 100FPS you have to either set sync_to_monitor to no or use a
# monitor with a high refresh rate.
repaint_delay 10
# Delay (in milliseconds) before input from the program running in the terminal
# is processed. Note that decreasing it will increase responsiveness, but also
# increase CPU usage and might cause flicker in full screen programs that
# redraw the entire screen on each loop, because kitty is so fast that partial
# screen updates will be drawn.
input_delay 3
# Sync screen updates to the refresh rate of the monitor. This prevents
# tearing (https://en.wikipedia.org/wiki/Screen_tearing) when scrolling. However,
# it limits the rendering speed to the refresh rate of your monitor. With a
# very high speed mouse/high keyboard repeat rate, you may notice some slight input latency.
# If so, set this to no.
sync_to_monitor yes
# Visual bell duration. Flash the screen when a bell occurs for the specified number of
# seconds. Set to zero to disable.
visual_bell_duration 0.0
# Enable/disable the audio bell. Useful in environments that require silence.
enable_audio_bell yes
# The modifier keys to press when clicking with the mouse on URLs to open the URL
open_url_modifiers ctrl+shift
# The program with which to open URLs that are clicked on. The special value "default" means to
# use the operating system's default URL handler.
open_url_with default
# The modifiers to use rectangular selection (i.e. to select text in a
# rectangular block with the mouse)
rectangle_select_modifiers ctrl+alt
# Allow other programs to control kitty. If you turn this on other programs can
# control all aspects of kitty, including sending text to kitty windows,
# opening new windows, closing windows, reading the content of windows, etc.
# Note that this even works over ssh connections.
allow_remote_control no
# The value of the TERM environment variable to set
term xterm-kitty
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
# Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.
window_border_width 1
# The window margin (in pts) (blank area outside the border)
window_margin_width 0
# The window padding (in pts) (blank area between the text and the window border)
window_padding_width 0
# The color for the border of the active window
active_border_color #00ff00
# The color for the border of inactive windows
inactive_border_color #cccccc
# Fade the text in inactive windows by the specified amount (a number between
# zero and one, with 0 being fully faded).
inactive_text_alpha 1.0
# Which edge to show the tab bar on, top or bottom
tab_bar_edge bottom
# The separator between tabs in the tab bar
tab_separator " ┇"
# Tab bar colors and styles
active_tab_foreground #000
active_tab_background #eee
active_tab_font_style bold-italic
inactive_tab_foreground #444
inactive_tab_background #999
inactive_tab_font_style normal
# The 16 terminal colors. There are 8 basic colors, each color has a dull and
# bright version.
# bright version. You can also set the remaining colors from the 256 color table
# as color16 to color256.
# black
color0 #000000
@@ -243,9 +281,42 @@ color14 #14ffff
# white
color7 #dddddd
color15 #ffffff
# }}}
# Key mapping
# Advanced {{{
# The shell program to execute. The default value of . means
# to use whatever shell is set as the default shell for the current user.
# Note that on macOS if you change this, you might need to add --login to
# ensure that the shell starts in interactive mode and reads its startup rc files.
shell .
# Close the window when the child process (shell) exits. If no (the default),
# the terminal will remain open when the child exits as long as there are still
# processes outputting to the terminal (for example disowned or backgrounded
# processes). If yes, the window will close as soon as the child process exits.
# Note that setting it to yes means that any background processes still using
# the terminal can fail silently because their stdout/stderr/stdin no longer
# work.
close_on_child_death no
# Allow other programs to control kitty. If you turn this on other programs can
# control all aspects of kitty, including sending text to kitty windows,
# opening new windows, closing windows, reading the content of windows, etc.
# Note that this even works over ssh connections.
allow_remote_control no
# The value of the TERM environment variable to set. Changing this can break
# many terminal programs, only change it if you know what you are doing, not
# because you read some advice on Stack Overflow to change it.
term xterm-kitty
# }}}
# Keyboard shortcuts {{{
# For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html
# For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html
#
@@ -258,8 +329,14 @@ color15 #ffffff
# For example:
# map ctrl+shift+e combine : new_window : next_layout
# this will create a new window and switch to the next available layout
#
# You can use multi-key shortcuts using the syntax shown below:
# map key1>key2>key3 action
# For example:
# map ctrl+f>2 set_font_size 20
# this will change the font size to 20 points when you press ctrl+f and then 2
# Clipboard
# Clipboard {{{
map ctrl+shift+v paste_from_clipboard
map ctrl+shift+s paste_from_selection
map ctrl+shift+c copy_to_clipboard
@@ -269,8 +346,9 @@ map shift+insert paste_from_selection
# you can specify your own, for example:
# map ctrl+shift+o pass_selection_to_program firefox
map ctrl+shift+o pass_selection_to_program
# }}}
# Scrolling
# Scrolling {{{
map ctrl+shift+up scroll_line_up
map ctrl+shift+down scroll_line_down
map ctrl+shift+k scroll_line_up
@@ -280,8 +358,9 @@ map ctrl+shift+page_down scroll_page_down
map ctrl+shift+home scroll_home
map ctrl+shift+end scroll_end
map ctrl+shift+h show_scrollback
# }}}
# Window management
# Window management {{{
map ctrl+shift+enter new_window
map ctrl+shift+n new_os_window
map ctrl+shift+w close_window
@@ -315,9 +394,9 @@ map ctrl+shift+0 tenth_window
# You can open a new window with the current working directory set to the
# working directory of the current window using
# map ctrl+alt+enter new_window_with_cwd
# }}}
# Tab management
# Tab management {{{
map ctrl+shift+right next_tab
map ctrl+shift+left previous_tab
map ctrl+shift+t new_tab
@@ -325,25 +404,63 @@ map ctrl+shift+q close_tab
map ctrl+shift+l next_layout
map ctrl+shift+. move_tab_forward
map ctrl+shift+, move_tab_backward
map ctrl+shift+alt+t set_tab_title
# You can also create shortcuts to go to specific tabs, with 1 being the first tab
# map ctrl+alt+1 goto_tab 1
# map ctrl+alt+2 goto_tab 2
# Just as with new_window above, you can also pass the name of arbitrary
# commands to run when using new_tab and use new_tab_with_cwd.
# }}}
# Layout management {{{
# You can create shortcuts to switch to specific layouts
# map ctrl+alt+1 goto_layout tall
# map ctrl+alt+2 goto_layout stack
# }}}
# Miscellaneous
# Font sizes {{{
map ctrl+shift+equal increase_font_size
map ctrl+shift+minus decrease_font_size
map ctrl+shift+backspace restore_font_size
# To setup shortcuts for specific font sizes, follow the example below:
# map ctrl+shift+f6 set_font_size 10.0
# map ctrl+shift+f7 set_font_size 20.5
# }}}
# Select and act on visible text {{{
# Use the hints kitten to select text and either pass it to an external program or
# insert it into the terminal or copy it to the clipboard.
#
# Open a currently visible URL using the keyboard. The program used to open the
# URL is specified in open_url_with.
map ctrl+shift+e run_kitten text hints
# Select a path/filename and insert it into the terminal. Useful, for instance to
# run git commands on a filename output from a previous git command.
map ctrl+shift+p>f run_kitten text hints --type path --program -
# Select a path/filename and open it with the default open program.
map ctrl+shift+p>shift+f run_kitten text hints --type path
# Select a line of text and insert it into the terminal. Use for the
# output of things like: ls -1
map ctrl+shift+p>l run_kitten text hints --type line --program -
# Select words and insert into terminal.
map ctrl+shift+p>w run_kitten text hints --type word --program -
# The hints kitten has many more modes of operation that you can map to different
# shortcuts. For a full description run: kitty +kitten hints --help
# }}}
# Miscellaneous {{{
map ctrl+shift+f11 toggle_fullscreen
map ctrl+shift+u input_unicode_character
# Open a currently visible URL using the keyboard. The program used ot open the URL is specified in open_url_with.
# You can customize how the URLs are detected and opened by specifying command line options to
# url_hints. For example:
# map ctrl+shift+e run_simple_kitten text url_hints --program firefox --regex "http://[^ ]+"
map ctrl+shift+e run_simple_kitten text url_hints
map ctrl+shift+f2 edit_config_file
You can customize how the URLs are
# Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.
map ctrl+shift+escape kitty_shell window
# Sending arbitrary text on shortcut key presses
# You can tell kitty to send arbitrary (UTF-8) encoded text to
@@ -361,30 +478,12 @@ map ctrl+shift+e run_simple_kitten text url_hints
# to the start of the line (same as pressing the Home key):
# map ctrl+alt+a send_text normal Word\x1b[H
# map ctrl+alt+a send_text application Word\x1bOH
# }}}
# Symbol mapping (special font for specified unicode code points). Map the
# specified unicode codepoints to a particular font. Useful if you need special
# rendering for some symbols, such as for Powerline. Avoids the need for
# patched fonts. Each unicode code point is specified in the form U+<code point
# in hexadecimal>. You can specify multiple code points, separated by commas
# and ranges separated by hyphens. symbol_map itself can be specified multiple times.
# Syntax is:
#
# symbol_map codepoints Font Family Name
#
# For example:
#
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
# }}}
# OS specific tweaks
# Copy to clipboard on select. With this enabled, simply selecting text with
# the mouse will cause the text to be copied to clipboard. Useful on platforms
# such as macOS/Wayland that do not have the concept of primary selections. Note
# that this is a security risk, as all programs, including websites open in your
# browser can read the contents of the clipboard.
copy_on_select no
# OS specific tweaks {{{
# Change the color of the kitty window's titlebar on macOS. A value of "system"
# means to use the default system color, a value of "background" means to use
@@ -400,7 +499,4 @@ macos_hide_titlebar no
# break any Alt+key keyboard shortcuts in your terminal programs, but you
# can use the macOS unicode input technique.
macos_option_as_alt yes
# The number is a percentage of maximum volume.
# See man XBell for details.
x11_bell_volume 80
# }}}

View File

@@ -46,7 +46,7 @@ linebuf_mark_line_clean(LineBuf *self, index_type y) {
}
static PyObject*
clear(LineBuf *self) {
clear(LineBuf *self, PyObject *a UNUSED) {
#define clear_doc "Clear all lines in this LineBuf"
linebuf_clear(self, BLANK_CHAR);
Py_RETURN_NONE;
@@ -167,7 +167,7 @@ set_continued(LineBuf *self, PyObject *args) {
}
static PyObject*
dirty_lines(LineBuf *self) {
dirty_lines(LineBuf *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++) {
@@ -424,7 +424,7 @@ __str__(LineBuf *self) {
if (lines == NULL) return PyErr_NoMemory();
for (index_type i = 0; i < self->ynum; i++) {
init_line(self, self->line, self->line_map[i]);
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);
}

View File

@@ -199,8 +199,8 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
}
static PyObject *
as_unicode(Line* self) {
PyObject *
line_as_unicode(Line* self) {
return unicode_in_range(self, 0, xlimit_for_line(self), true, 0);
}
@@ -263,7 +263,7 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
}
static PyObject*
as_ansi(Line* self) {
as_ansi(Line* self, PyObject *a UNUSED) {
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
static Py_UCS4 t[5120] = {0};
index_type num = line_as_ansi(self, t, 5120);
@@ -272,7 +272,7 @@ as_ansi(Line* self) {
}
static PyObject*
is_continued(Line* self) {
is_continued(Line* self, PyObject *a UNUSED) {
#define is_continued_doc "Return the line's continued flag"
PyObject *ans = self->continued ? Py_True : Py_False;
Py_INCREF(ans);
@@ -281,7 +281,7 @@ is_continued(Line* self) {
static PyObject*
__repr__(Line* self) {
PyObject *s = as_unicode(self);
PyObject *s = line_as_unicode(self);
if (s == NULL) return NULL;
PyObject *ans = PyObject_Repr(s);
Py_CLEAR(s);
@@ -570,7 +570,7 @@ PyTypeObject Line_Type = {
.tp_basicsize = sizeof(Line),
.tp_dealloc = (destructor)dealloc,
.tp_repr = (reprfunc)__repr__,
.tp_str = (reprfunc)as_unicode,
.tp_str = (reprfunc)line_as_unicode,
.tp_as_sequence = &sequence_methods,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_richcompare = richcmp,

View File

@@ -57,10 +57,10 @@ unsigned int line_length(Line *self);
size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type);
PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char);
PyObject* line_as_unicode(Line *);
void linebuf_init_line(LineBuf *, index_type);
void linebuf_clear(LineBuf *, char_type ch);
void linebuf_init_line(LineBuf *, index_type);
void linebuf_index(LineBuf* self, index_type top, index_type bottom);
void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom);
void linebuf_clear_line(LineBuf *self, index_type y);
@@ -105,7 +105,7 @@ void historybuf_clear(HistoryBuf *self);
index_type num = line_as_ansi(line, buf, columns * 100 - 2); \
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num); \
} else { \
t = PyObject_Str((PyObject*)line); \
t = line_as_unicode(line); \
} \
if (t == NULL) goto end; \
ret = PyObject_CallFunctionObjArgs(callback, t, NULL); \

73
kitty/logging.c Normal file
View File

@@ -0,0 +1,73 @@
/*
* logging.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#ifdef __APPLE__
#include <os/log.h>
#endif
static bool use_os_log = false;
void
log_error(const char *fmt, ...) {
va_list ar;
struct timeval tv;
#ifdef __APPLE__
// Apple does not provide a varargs style os_logv
char logbuf[16 * 1024] = {0};
#else
char logbuf[4];
#endif
char *p = logbuf;
#define bufprint(func, ...) { if ((size_t)(p - logbuf) < sizeof(logbuf) - 2) { p += func(p, sizeof(logbuf) - (p - logbuf), __VA_ARGS__); } }
if (!use_os_log) { // Apple's os_log already records timestamps
gettimeofday(&tv, NULL);
struct tm *tmp = localtime(&tv.tv_sec);
if (tmp) {
char tbuf[256] = {0}, buf[256] = {0};
if (strftime(buf, sizeof(buf), "%j %H:%M:%S.%%06u", tmp) != 0) {
snprintf(tbuf, sizeof(tbuf), buf, tv.tv_usec);
fprintf(stderr, "[%s] ", tbuf);
}
}
}
va_start(ar, fmt);
if (use_os_log) { bufprint(vsnprintf, fmt, ar); }
else vfprintf(stderr, fmt, ar);
va_end(ar);
#ifdef __APPLE__
if (use_os_log) os_log(OS_LOG_DEFAULT, "%{public}s", logbuf);
#endif
if (!use_os_log) fprintf(stderr, "\n");
}
static PyObject*
log_error_string(PyObject *self UNUSED, PyObject *args) {
const char *msg;
if (!PyArg_ParseTuple(args, "s", &msg)) return NULL;
log_error("%s", msg);
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
METHODB(log_error_string, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_logging(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
#ifdef __APPLE__
if (getenv("KITTY_LAUNCHED_BY_LAUNCH_SERVICES") != NULL) use_os_log = true;
#endif
return true;
}

View File

@@ -4,7 +4,6 @@
import locale
import os
import signal
import sys
from contextlib import contextmanager
@@ -12,14 +11,14 @@ from .borders import load_borders_program
from .boss import Boss
from .cli import create_opts, parse_args
from .config import cached_values_for, initial_window_size
from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file
from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file, config_dir
from .fast_data_types import (
create_os_window, glfw_init, glfw_terminate, install_sigchld_handler,
set_default_window_icon, set_options, show_window
create_os_window, glfw_init, glfw_terminate, set_default_window_icon,
set_options, show_window
)
from .fonts.box_drawing import set_scale
from .utils import (
detach, end_startup_notification, init_startup_notification,
detach, end_startup_notification, init_startup_notification, log_error,
single_instance
)
from .window import load_shader_programs
@@ -39,7 +38,7 @@ def init_graphics():
def run_app(opts, args):
set_scale(opts.box_drawing_scale)
set_options(opts, is_wayland, args.debug_gl)
set_options(opts, is_wayland, args.debug_gl, args.debug_font_fallback)
if is_macos:
from .fast_data_types import macos_change_titlebar_color
macos_change_titlebar_color(opts.macos_titlebar_color)
@@ -93,7 +92,21 @@ def setup_profiling(args):
print('To view the graphical call data, use: kcachegrind', cg)
def main():
def macos_cmdline():
try:
with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f:
raw = f.read()
except FileNotFoundError:
return []
import shlex
raw = raw.strip()
ans = shlex.split(raw)
if ans and ans[0] == 'kitty':
del ans[0]
return ans
def _main():
try:
sys.setswitchinterval(1000.0) # we have only a single python thread
except AttributeError:
@@ -124,12 +137,21 @@ def main():
if rpath and rpath not in items:
os.environ['PATH'] += os.pathsep + rpath
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
args = sys.argv[1:]
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
os.chdir(os.path.expanduser('~'))
if not os.path.isdir(os.getcwd()):
args = macos_cmdline()
try:
cwd_ok = os.path.isdir(os.getcwd())
except Exception:
cwd_ok = False
if not cwd_ok:
os.chdir(os.path.expanduser('~'))
args, rest = parse_args()
args, rest = parse_args(args=args)
args.args = rest
if args.debug_config:
create_opts(args, debug_config=True)
return
if getattr(args, 'detach', False):
detach()
if args.cmd:
@@ -143,7 +165,9 @@ def main():
is_first = single_instance(args.instance_group)
if not is_first:
import json
data = {'cmd': 'new_instance', 'args': tuple(sys.argv), 'startup_id': os.environ.get('DESKTOP_STARTUP_ID')}
data = {'cmd': 'new_instance', 'args': tuple(sys.argv),
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'),
'cwd': os.getcwd()}
data = json.dumps(data, ensure_ascii=False).encode('utf-8')
single_instance.socket.sendall(data)
return
@@ -152,8 +176,16 @@ def main():
try:
with setup_profiling(args):
# Avoid needing to launch threads to reap zombies
install_sigchld_handler()
run_app(opts, args)
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
finally:
glfw_terminate()
def main():
try:
_main()
except Exception:
import traceback
tb = traceback.format_exc()
log_error(tb)
raise SystemExit(1)

View File

@@ -16,12 +16,14 @@
static MouseShape mouse_cursor_shape = BEAM;
typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
// Encoding of mouse events {{{
#define SHIFT_INDICATOR (1 << 2)
#define ALT_INDICATOR (1 << 3)
#define CONTROL_INDICATOR (1 << 4)
#define MOTION_INDICATOR (1 << 5)
#define EXTRA_BUTTON_INDICATOR (1 << 6)
static inline unsigned int
button_map(int button) {
switch(button) {
@@ -89,24 +91,53 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
}
// }}}
static inline double
window_left(Window *w) {
return w->geometry.left - OPT(window_padding_width) * (global_state.logical_dpi_x / 72.0);
}
static inline double
window_right(Window *w) {
return w->geometry.right + OPT(window_padding_width) * (global_state.logical_dpi_x / 72.0);
}
static inline double
window_top(Window *w) {
return w->geometry.top - OPT(window_padding_width) * (global_state.logical_dpi_y / 72.0);
}
static inline double
window_bottom(Window *w) {
return w->geometry.bottom + OPT(window_padding_width) * (global_state.logical_dpi_y / 72.0);
}
static inline bool
contains_mouse(Window *w) {
WindowGeometry *g = &w->geometry;
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
return (w->visible && g->left <= x && x <= g->right && g->top <= y && y <= g->bottom);
return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w));
}
static inline bool
cell_for_pos(Window *w, unsigned int *x, unsigned int *y) {
WindowGeometry *g = &w->geometry;
unsigned int qx = (unsigned int)((double)(global_state.callback_os_window->mouse_x - g->left) / global_state.cell_width);
unsigned int qy = (unsigned int)((double)(global_state.callback_os_window->mouse_y - g->top) / global_state.cell_height);
bool ret = false;
Screen *screen = w->render_data.screen;
if (screen && qx <= screen->columns && qy <= screen->lines) {
*x = qx; *y = qy; ret = true;
if (!screen) return false;
unsigned int qx = 0, qy = 0;
double mouse_x = global_state.callback_os_window->mouse_x;
double mouse_y = global_state.callback_os_window->mouse_y;
double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w);
if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false;
if (mouse_x >= g->right) qx = screen->columns - 1;
else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / global_state.cell_width);
if (mouse_y >= g->bottom) qy = screen->lines - 1;
else if (mouse_y >= g->top) qy = (unsigned int)((double)(mouse_y - g->top) / global_state.cell_height);
if (qx < screen->columns && qy < screen->lines) {
*x = qx; *y = qy;
return true;
}
return ret;
return false;
}
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
@@ -116,10 +147,9 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
Screen *screen = w->render_data.screen;
if (from_button) {
if (is_release) screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true);
else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers));
else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
} else if (screen->selection.in_progress) {
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
call_boss(set_primary_selection, NULL);
}
}
@@ -127,9 +157,10 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
bool
drag_scroll(Window *w, OSWindow *frame) {
unsigned int margin = global_state.cell_height / 2;
double left = window_left(w), top = window_top(w), right = window_right(w), bottom = window_bottom(w);
double x = frame->mouse_x, y = frame->mouse_y;
if (y < w->geometry.top || y > w->geometry.bottom) return false;
if (x < w->geometry.left || x > w->geometry.right) return false;
if (y < top || y > bottom) return false;
if (x < left || x > right) return false;
bool upwards = y <= (w->geometry.top + margin);
if (upwards || y >= w->geometry.bottom - margin) {
Screen *screen = w->render_data.screen;
@@ -147,6 +178,14 @@ drag_scroll(Window *w, OSWindow *frame) {
return false;
}
static inline void
extend_selection(Window *w) {
Screen *screen = w->render_data.screen;
if (screen_has_selection(screen)) {
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
}
}
static inline void
extend_url(Screen *screen, Line *line, index_type *x, index_type *y) {
unsigned int count = 0;
@@ -220,20 +259,23 @@ multi_click(Window *w, unsigned int count) {
Screen *screen = w->render_data.screen;
index_type start, end;
bool found_selection = false;
SelectionExtendMode mode = EXTEND_CELL;
unsigned int y1 = w->mouse_cell_y, y2 = w->mouse_cell_y;
switch(count) {
case 2:
found_selection = screen_selection_range_for_word(screen, w->mouse_cell_x, w->mouse_cell_y, &start, &end);
found_selection = screen_selection_range_for_word(screen, w->mouse_cell_x, &y1, &y2, &start, &end);
mode = EXTEND_WORD;
break;
case 3:
found_selection = screen_selection_range_for_line(screen, w->mouse_cell_y, &start, &end);
mode = EXTEND_LINE;
break;
default:
break;
}
if (found_selection) {
screen_start_selection(screen, start, w->mouse_cell_y, false);
screen_update_selection(screen, end, w->mouse_cell_y, true);
call_boss(set_primary_selection, NULL);
screen_start_selection(screen, start, y1, false, mode);
screen_update_selection(screen, end, y2, false);
}
}
@@ -269,7 +311,7 @@ HANDLER(handle_button_event) {
Screen *screen = w->render_data.screen;
if (!screen) return;
bool handle_in_kitty = (
modifiers == GLFW_MOD_SHIFT ||
modifiers == GLFW_MOD_SHIFT || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT) ||
screen->modes.mouse_tracking_mode == 0 ||
button == GLFW_MOUSE_BUTTON_MIDDLE ||
(modifiers == (int)OPT(open_url_modifiers) && button == GLFW_MOUSE_BUTTON_LEFT)
@@ -279,16 +321,15 @@ HANDLER(handle_button_event) {
case GLFW_MOUSE_BUTTON_LEFT:
update_drag(true, w, is_release, modifiers);
if (is_release) {
if (modifiers == (int)OPT(open_url_modifiers)) {
open_url(w);
} else {
if (is_release) add_click(w, button, modifiers, window_idx);
}
}
if (modifiers == (int)OPT(open_url_modifiers)) open_url(w);
} else add_click(w, button, modifiers, window_idx);
break;
case GLFW_MOUSE_BUTTON_MIDDLE:
if (is_release && !modifiers) { call_boss(paste_from_selection, NULL); return; }
break;
case GLFW_MOUSE_BUTTON_RIGHT:
if (is_release) { extend_selection(w); }
break;
}
} else {
int sz = encode_mouse_event(w, button, is_release ? RELEASE : PRESS, modifiers);

View File

@@ -123,7 +123,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define DUMP_UNUSED UNUSED
#define REPORT_ERROR(...) fprintf(stderr, "%s ", ERROR_PREFIX); fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n");
#define REPORT_ERROR(...) log_error(ERROR_PREFIX " " __VA_ARGS__);
#define REPORT_COMMAND(...)
#define REPORT_VA_COMMAND(...)
@@ -310,7 +310,7 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
}
if (i > 0) {
code = utoi(screen->parser_buf, i);
if (i < limit - 1 && screen->parser_buf[i] == ';') i++;
if (i < limit && screen->parser_buf[i] == ';') i++;
}
PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + i, limit - i);
if (string != NULL) {

View File

@@ -4,394 +4,15 @@
import json
import re
import socket
import sys
import types
from functools import partial
from .cli import emph, parse_args
from .config import parse_send_text_bytes
from .constants import appname, version
from .tabs import SpecialWindow
from .utils import non_blocking_read, read_with_timeout
def cmd(short_desc, desc=None, options_spec=None, no_response=False):
def w(func):
func.short_desc = short_desc
func.desc = desc or short_desc
func.name = func.__name__[4:].replace('_', '-')
func.options_spec = options_spec
func.is_cmd = True
func.impl = lambda: globals()[func.__name__[4:]]
func.no_response = no_response
return func
return w
def parse_subcommand_cli(func, args):
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, '...', func.desc, '{} @ {}'.format(appname, func.name))
return opts, items
@cmd(
'List all tabs/windows',
'List all windows. The list is returned as JSON tree. The top-level is a list of'
' operating system {appname} windows. Each OS window has an |_ id| and a list'
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
' |_ command-line| of the process running in the window.\n\n'
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname)
)
def cmd_ls(global_opts, opts, args):
pass
def ls(boss, window):
data = list(boss.list_os_windows())
data = json.dumps(data, indent=2, sort_keys=True)
return data
MATCH_WINDOW_OPTION = '''\
--match -m
The window to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of windows. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression.
'''
MATCH_TAB_OPTION = '''\
--match -m
The tab to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of tabs. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression. When using title or id, first a matching tab is
looked for and if not found a matching window is looked for, and the tab
for that window is used.
'''
@cmd(
'Send arbitrary text to specified windows',
'Send arbitrary text to specified windows. The text follows Python'
' escaping rules. So you can use escapes like |_ \\x1b| to send control codes'
' and |_ \\u21fa| to send unicode characters. If you use the |_ --match| option'
' the text will be sent to all matched windows. By default, text is sent to'
' only the currently active window.',
options_spec=MATCH_WINDOW_OPTION + '''\n
--stdin
type=bool-set
Read the text to be sent from |_ stdin|. Note that in this case the text is sent as is,
not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end reading.
--from-file
Path to a file whose contents you wish to send. Note that in this case the file contents
are sent as is, not interpreted for escapes.
''',
no_response=True
)
def cmd_send_text(global_opts, opts, args):
limit = 1024
ret = {'match': opts.match, 'is_binary': False}
def pipe(src=sys.stdin):
ret['is_binary'] = True
import select
with non_blocking_read() as fd:
keep_going = True
while keep_going:
rd = select.select([fd], [], [])
if rd:
data = sys.stdin.buffer.read()
if not data:
break
data = data.decode('utf-8')
if '\x04' in data:
data = data[:data.index('\x04')]
keep_going = False
while data:
ret['text'] = data[:limit]
yield ret
data = data[limit:]
else:
break
def chunks(text):
ret['is_binary'] = False
while text:
ret['text'] = text[:limit]
yield ret
text = text[limit:]
def file_pipe(path):
ret['is_binary'] = True
with open(path, encoding='utf-8') as f:
while True:
data = f.read(limit)
if not data:
break
ret['text'] = data
yield ret
sources = []
if opts.stdin:
sources.append(pipe())
if opts.from_file:
sources.append(file_pipe(opts.from_file))
text = ' '.join(args)
sources.append(chunks(text))
def chain():
for src in sources:
yield from src
return chain()
def send_text(boss, window, payload):
windows = [boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
for window in windows:
if window is not None:
window.write_to_child(data)
@cmd(
'Set the window title',
'Set the title for the specified window(s). If you use the |_ --match| option'
' the title will be set for all matched windows. By default, only the window'
' in which the command is run is affected. If you do not specify a title, the'
' last title set by the child process running in the window will be used.',
options_spec=MATCH_WINDOW_OPTION
)
def cmd_set_window_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_window_title(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
for window in windows:
if window:
window.set_title(payload['title'])
@cmd(
'Set the tab title',
'Set the title for the specified tab(s). If you use the |_ --match| option'
' the title will be set for all matched tabs. By default, only the tab'
' in which the command is run is affected. If you do not specify a title, the'
' title of the currently active window in the tab is used.',
options_spec=MATCH_TAB_OPTION
)
def cmd_set_tab_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_tab_title(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
for tab in tabs:
if tab:
tab.set_title(payload['title'])
@cmd(
'Close the specified window(s)',
options_spec=MATCH_WINDOW_OPTION + '''\n
--self
type=bool-set
If specified close the window this command is run in, rather than the active window.
'''
)
def cmd_close_window(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_window(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
windows = [window if window and payload['self'] else boss.active_window]
for window in windows:
if window:
boss.close_window(window)
@cmd(
'Close the specified tab(s)',
options_spec=MATCH_TAB_OPTION + '''\n
--self
type=bool-set
If specified close the tab this command is run in, rather than the active tab.
'''
)
def cmd_close_tab(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_tab(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
tabs = [boss.tab_for_window(window) if window and payload['self'] else boss.active_tab]
for tab in tabs:
if window:
if tab:
boss.close_tab(tab)
@cmd(
'Open new window',
'Open a new window in the specified tab. If you use the |_ --match| option'
' the first matching tab is used. Otherwise the currently active tab is used.'
' Prints out the id of the newly opened window. Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:\n'
'|_ kitty @ new-window --title Email mutt|',
options_spec=MATCH_TAB_OPTION + '''\n
--title
The title for the new window. By default it will use the title set by the
program running in it.
--cwd
The initial working directory for the new window.
--keep-focus
type=bool-set
Keep the current window focused instead of switching to the newly opened window
--new-tab
type=bool-set
Open a new tab
--tab-title
When using --new-tab set the title of the tab.
'''
)
def cmd_new_window(global_opts, opts, args):
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
'keep_focus': opts.keep_focus, 'args': args or []}
def new_window(boss, window, payload):
w = SpecialWindow(cmd=payload['args'] or None, override_title=payload['title'], cwd=payload['cwd'])
old_window = boss.active_window
if payload['new_tab']:
boss._new_tab(w)
tab = boss.active_tab
if payload['tab_title']:
tab.set_title(payload['tab_title'])
wid = boss.active_window.id
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(wid)
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
tabs = [boss.active_tab]
tab = tabs[0]
w = tab.new_special_window(w)
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(w.id)
@cmd(
'Focus the specified window',
options_spec=MATCH_WINDOW_OPTION
)
def cmd_focus_window(global_opts, opts, args):
return {'match': opts.match}
def focus_window(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
for window in windows:
if window:
boss.set_active_window(window)
break
@cmd(
'Get text from the specified window',
options_spec=MATCH_WINDOW_OPTION + '''\n
--extent
default=screen
choices=screen, all, selection
What text to get. The default of screen means all text currently on the screen. all means
all the screen+scrollback and selection means currently selected text.
--ansi
type=bool-set
By default, only plain text is returned. If you specify this flag, the text will
include the formatting escape codes for colors/bold/italic/etc. Note that when
getting the current selection, the result is always plain text.
--self
type=bool-set
If specified get text from the window this command is run in, rather than the active window.
'''
)
def cmd_get_text(global_opts, opts, args):
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
def get_text(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
windows = [window if window and payload['self'] else boss.active_window]
window = windows[0]
if payload['extent'] == 'selection':
ans = window.text_for_selection()
else:
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True)
return ans
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
from .utils import parse_address_spec, read_with_timeout
from .cmds import cmap, parse_subcommand_cli
def handle_cmd(boss, window, cmd):
@@ -411,21 +32,30 @@ def handle_cmd(boss, window, cmd):
global_options_spec = partial('''\
--to
An address for the kitty instance to control. Corresponds to the address
given to the kitty instance via the --listen-on option. If not specified,
messages are sent to the controlling terminal for this process, i.e. they
will only work if this process is run within an existing kitty window.
'''.format, appname=appname)
def read_from_stdin(send, no_response):
def do_io(to, send, no_response):
send = ('@kitty-cmd' + json.dumps(send)).encode('ascii')
out = sys.stdout if sys.stdout.isatty() else sys.stderr
if not out.isatty():
raise SystemExit('Neither stdout nor stderr is a terminal')
out.buffer.write(b'\x1bP' + send + b'\x1b\\')
out.flush()
send = b'\x1bP' + send + b'\x1b\\'
if to:
family, address = parse_address_spec(to)[:2]
s = socket.socket(family)
s.connect(address)
out = s.makefile('wb')
else:
out = open('/dev/tty', 'wb')
with out:
out.write(send)
if to:
s.shutdown(socket.SHUT_WR)
if no_response:
return {'ok': True}
if not sys.stdin.isatty():
raise SystemExit('stdin is not a terminal')
received = b''
dcs = re.compile(br'\x1bP@kitty-cmd([^\x1b]+)\x1b\\')
@@ -437,7 +67,12 @@ def read_from_stdin(send, no_response):
match = dcs.search(received)
return match is None
read_with_timeout(more_needed)
if to:
src = s.makefile('rb')
else:
src = open('/dev/tty', 'rb')
with src:
read_with_timeout(more_needed, src=src)
if match is None:
raise SystemExit('Failed to receive response from ' + appname)
response = json.loads(match.group(1).decode('ascii'))
@@ -458,7 +93,9 @@ def main(args):
global_opts, items = parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname))
if not items:
raise SystemExit('You must specify a command')
from kitty.shell import main
main(global_opts)
return
cmd = items[0]
try:
func = cmap[cmd]
@@ -474,11 +111,11 @@ def main(args):
if func.no_response and isinstance(payload, types.GeneratorType):
for item in payload:
send['payload'] = item
read_from_stdin(send, func.no_response)
do_io(global_opts.to, send, func.no_response)
return
if payload is not None:
send['payload'] = payload
response = read_from_stdin(send, func.no_response)
response = do_io(global_opts.to, send, func.no_response)
if not response.get('ok'):
if response.get('tb'):
print(response['tb'], file=sys.stderr)

View File

@@ -70,7 +70,7 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
copy_range(src->line, src_x, dest->line, dest_x, num);
if (is_tracked_line && src_x <= *track_x && *track_x < src_x + num) {
*track_y = dest_y;
*track_x = dest_x + (*track_x - src_x);
*track_x = dest_x + (*track_x - src_x + 1);
}
src_x += num; dest_x += num;
}

8
kitty/rgb.py generated
View File

@@ -27,6 +27,14 @@ def parse_rgb(spec):
return Color(*map(parse_single_color, colors))
def color_from_int(x):
return Color((x >> 16) & 255, (x >> 8) & 255, x & 255)
def color_as_int(x):
return x.red << 16 | x.green << 8 | x.blue
def to_color(raw, validate=False):
# See man XParseColor
x = raw.strip().lower()

View File

@@ -219,7 +219,7 @@ screen_change_scrollback_size(Screen *self, unsigned int size) {
}
static PyObject*
reset_callbacks(Screen *self) {
reset_callbacks(Screen *self, PyObject *a UNUSED) {
Py_CLEAR(self->callbacks);
self->callbacks = Py_None;
Py_INCREF(self->callbacks);
@@ -561,7 +561,7 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
default:
private = mode >= 1 << 5;
if (private) mode >>= 5;
fprintf(stderr, "%s %s %u %s\n", ERROR_PREFIX, "Unsupported screen mode: ", mode, private ? "(private)" : "");
log_error("%s %s %u %s", ERROR_PREFIX, "Unsupported screen mode: ", mode, private ? "(private)" : "");
}
#undef SIMPLE_MODE
#undef MOUSE_MODE
@@ -648,7 +648,7 @@ screen_clear_tab_stop(Screen *self, unsigned int how) {
for (unsigned int i = 0; i < self->columns; i++) self->tabstops[i] = false;
break;
default:
fprintf(stderr, "%s %s %u\n", ERROR_PREFIX, "Unsupported clear tab stop mode: ", how);
log_error("%s %s %u", ERROR_PREFIX, "Unsupported clear tab stop mode: ", how);
break;
}
}
@@ -1066,7 +1066,7 @@ bool
screen_invert_colors(Screen *self) {
bool inverted = false;
if (self->start_visual_bell_at > 0) {
if (monotonic() - self->start_visual_bell_at <= global_state.opts.visual_bell_duration) inverted = true;
if (monotonic() - self->start_visual_bell_at <= OPT(visual_bell_duration)) inverted = true;
else self->start_visual_bell_at = 0;
}
if (self->modes.mDECSCNM) inverted = inverted ? false : true;
@@ -1076,6 +1076,7 @@ screen_invert_colors(Screen *self) {
void
screen_bell(Screen *self) {
request_window_attention(self->window_id, OPT(enable_audio_bell));
if (OPT(visual_bell_duration) > 0.0f) self->start_visual_bell_at = monotonic();
}
void
@@ -1397,13 +1398,20 @@ apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, Selection
}
bool
screen_has_selection(Screen *self) {
SelectionBoundary start, end;
selection_limits_(selection, &start, &end);
return !is_selection_empty(self, start.x, start.y, end.x, end.y);
}
void
screen_apply_selection(Screen *self, void *address, size_t size) {
memset(address, 0, size);
self->last_selection_scrolled_by = self->scrolled_by;
self->selection_updated_once = true;
selection_limits_(selection, &self->last_rendered_selection_start, &self->last_rendered_selection_end);
apply_selection(self, address, &self->last_rendered_selection_start, &self->last_rendered_selection_end, 1, self->rectangle_select);
apply_selection(self, address, &self->last_rendered_selection_start, &self->last_rendered_selection_end, 1, self->selection.rectangle_select);
selection_limits_(url_range, &self->last_rendered_url_start, &self->last_rendered_url_end);
apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2, false);
}
@@ -1444,8 +1452,8 @@ screen_open_url(Screen *self) {
// }}}
// Python interface {{{
#define WRAP0(name) static PyObject* name(Screen *self) { screen_##name(self); Py_RETURN_NONE; }
#define WRAP0x(name) static PyObject* xxx_##name(Screen *self) { screen_##name(self); Py_RETURN_NONE; }
#define WRAP0(name) static PyObject* name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; }
#define WRAP0x(name) static PyObject* xxx_##name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; }
#define WRAP1(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v); Py_RETURN_NONE; }
#define WRAP1B(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; int b=false; if(!PyArg_ParseTuple(args, "|Ip", &v, &b)) return NULL; screen_##name(self, v, b); Py_RETURN_NONE; }
#define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; }
@@ -1458,7 +1466,7 @@ as_text(Screen *self, PyObject *args) {
}
static PyObject*
refresh_sprite_positions(Screen *self) {
refresh_sprite_positions(Screen *self, PyObject *a UNUSED) {
self->is_dirty = true;
for (index_type i = 0; i < self->lines; i++) {
linebuf_mark_line_dirty(self->main_linebuf, i);
@@ -1572,13 +1580,13 @@ set_mode(Screen *self, PyObject *args) {
}
static PyObject*
reset_dirty(Screen *self) {
reset_dirty(Screen *self, PyObject *a UNUSED) {
screen_reset_dirty(self);
Py_RETURN_NONE;
}
static PyObject*
is_using_alternate_linebuf(Screen *self) {
is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) {
if (self->linebuf == self->alt_linebuf) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
@@ -1619,9 +1627,17 @@ WRAP0(carriage_return)
WRAP2(resize, 1, 1)
WRAP2(set_margins, 1, 1)
WRAP2(rescale_images, 1, 1)
WRAP2B(start_selection)
WRAP2B(update_selection)
static PyObject*
start_selection(Screen *self, PyObject *args) {
unsigned int x, y;
int rectangle_select = 0, extend_mode = EXTEND_CELL;
if (!PyArg_ParseTuple(args, "II|pp", &x, &y, &rectangle_select, &extend_mode)) return NULL;
screen_start_selection(self, x, y, rectangle_select, extend_mode);
Py_RETURN_NONE;
}
static PyObject*
change_scrollback_size(Screen *self, PyObject *args) {
unsigned int count = 1;
@@ -1631,12 +1647,12 @@ change_scrollback_size(Screen *self, PyObject *args) {
}
static PyObject*
text_for_selection(Screen *self) {
text_for_selection(Screen *self, PyObject *a UNUSED) {
FullSelectionBoundary start, end;
full_selection_limits_(selection, &start, &end);
PyObject *ans = NULL;
if (start.y == end.y && start.x == end.x) ans = PyTuple_New(0);
else text_for_range(ans, start, end, self->rectangle_select, true, range_line_, int);
else text_for_range(ans, start, end, self->selection.rectangle_select, true, range_line_, int);
return ans;
}
@@ -1660,17 +1676,31 @@ is_opt_word_char(char_type ch) {
}
bool
screen_selection_range_for_word(Screen *self, index_type x, index_type y, index_type *s, index_type *e) {
if (y >= self->lines || x >= self->columns) return false;
screen_selection_range_for_word(Screen *self, index_type x, index_type *y1, index_type *y2, index_type *s, index_type *e) {
if (*y1 >= self->lines || x >= self->columns) return false;
index_type start, end;
Line *line = visual_line_(self, y);
Line *line = visual_line_(self, *y1);
*y2 = *y1;
#define is_ok(x) (is_word_char((line->cells[x].ch)) || is_opt_word_char(line->cells[x].ch))
if (!is_ok(x)) {
start = x; end = x + 1;
} else {
start = x, end = x;
while(start > 0 && is_ok(start - 1)) start--;
while(end < self->columns - 1 && is_ok(end + 1)) end++;
while(true) {
while(start > 0 && is_ok(start - 1)) start--;
if (start > 0 || !line->continued || *y1 == 0) break;
line = visual_line_(self, *y1 - 1);
if (!is_ok(self->columns - 1)) break;
(*y1)--; start = self->columns - 1;
}
line = visual_line_(self, *y2);
while(true) {
while(end < self->columns - 1 && is_ok(end + 1)) end++;
if (end < self->columns - 1 || *y2 >= self->lines - 1) break;
line = visual_line_(self, *y2 + 1);
if (!line->continued || !is_ok(0)) break;
(*y2)++; end = 0;
}
}
*s = start; *e = end;
return true;
@@ -1726,10 +1756,10 @@ screen_is_selection_dirty(Screen *self) {
}
void
screen_start_selection(Screen *self, index_type x, index_type y, bool rectangle_select) {
self->rectangle_select = rectangle_select;
screen_start_selection(Screen *self, index_type x, index_type y, bool rectangle_select, SelectionExtendMode extend_mode) {
#define A(attr, val) self->selection.attr = val;
A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); A(in_progress, true);
A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by);
A(in_progress, true); A(rectangle_select, rectangle_select); A(extend_mode, extend_mode);
#undef A
}
@@ -1744,29 +1774,73 @@ void
screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by;
if (ended) self->selection.in_progress = false;
index_type start, end;
bool found = false;
bool extending_leftwards = self->selection.end_y < self->selection.start_y || (self->selection.end_y == self->selection.start_y && self->selection.end_x < self->selection.start_x);
switch(self->selection.extend_mode) {
case EXTEND_WORD: {
index_type y1 = y, y2;
found = screen_selection_range_for_word(self, x, &y1, &y2, &start, &end);
if (found) {
if (extending_leftwards) {
self->selection.end_x = start; self->selection.end_y = y1;
y1 = self->selection.start_y;
found = screen_selection_range_for_word(self, self->selection.start_x, &y1, &y2, &start, &end);
if (found) {
self->selection.start_x = end; self->selection.start_y = y2;
}
} else {
self->selection.end_x = end; self->selection.end_y = y2;
y1 = self->selection.start_y;
found = screen_selection_range_for_word(self, self->selection.start_x, &y1, &y2, &start, &end);
if (found) {
self->selection.start_x = start; self->selection.start_y = y1;
}
}
}
break;
}
case EXTEND_LINE:
if (extending_leftwards) {
found = screen_selection_range_for_line(self, self->selection.end_y, &start, &end);
if (found) { self->selection.end_x = start; }
found = screen_selection_range_for_line(self, self->selection.start_y, &start, &end);
if (found) { self->selection.start_x = end; }
} else {
found = screen_selection_range_for_line(self, self->selection.start_y, &start, &end);
if (found) { self->selection.start_x = start; }
found = screen_selection_range_for_line(self, self->selection.end_y, &start, &end);
if (found) { self->selection.end_x = end; }
}
break;
case EXTEND_CELL:
break;
}
call_boss(set_primary_selection, NULL);
}
static PyObject*
mark_as_dirty(Screen *self) {
mark_as_dirty(Screen *self, PyObject *a UNUSED) {
self->is_dirty = true;
Py_RETURN_NONE;
}
static PyObject*
current_char_width(Screen *self) {
current_char_width(Screen *self, PyObject *a UNUSED) {
#define current_char_width_doc "The width of the character under the cursor"
return PyLong_FromUnsignedLong(screen_current_char_width(self));
}
static PyObject*
is_main_linebuf(Screen *self) {
is_main_linebuf(Screen *self, PyObject *a UNUSED) {
PyObject *ans = (self->linebuf == self->main_linebuf) ? Py_True : Py_False;
Py_INCREF(ans);
return ans;
}
static PyObject*
toggle_alt_screen(Screen *self) {
toggle_alt_screen(Screen *self, PyObject *a UNUSED) {
screen_toggle_screen_buffer(self);
Py_RETURN_NONE;
}

View File

@@ -22,9 +22,12 @@ typedef struct {
unsigned int x, y;
} SelectionBoundary;
typedef enum SelectionExtendModes { EXTEND_CELL, EXTEND_WORD, EXTEND_LINE } SelectionExtendMode;
typedef struct {
unsigned int start_x, start_y, start_scrolled_by, end_x, end_y, end_scrolled_by;
bool in_progress;
bool in_progress, rectangle_select;
SelectionExtendMode extend_mode;
} Selection;
#define SAVEPOINTS_SZ 256
@@ -61,7 +64,7 @@ typedef struct {
Selection selection;
SelectionBoundary last_rendered_selection_start, last_rendered_selection_end, last_rendered_url_start, last_rendered_url_end;
Selection url_range;
bool use_latin1, selection_updated_once, is_dirty, scroll_changed, rectangle_select;
bool use_latin1, selection_updated_once, is_dirty, scroll_changed;
Cursor *cursor;
SavepointBuffer main_savepoints, alt_savepoints;
SavemodesBuffer modes_savepoints;
@@ -150,12 +153,13 @@ void report_device_status(Screen *self, unsigned int which, bool UNUSED);
void report_mode_status(Screen *self, unsigned int which, bool);
void screen_apply_selection(Screen *self, void *address, size_t size);
bool screen_is_selection_dirty(Screen *self);
bool screen_has_selection(Screen*);
bool screen_invert_colors(Screen *self);
void screen_update_cell_data(Screen *self, void *address, size_t sz);
bool screen_is_cursor_visible(Screen *self);
bool screen_selection_range_for_line(Screen *self, index_type y, index_type *start, index_type *end);
bool screen_selection_range_for_word(Screen *self, index_type x, index_type y, index_type *start, index_type *end);
void screen_start_selection(Screen *self, index_type x, index_type y, bool);
bool screen_selection_range_for_word(Screen *self, index_type x, index_type *, index_type *, index_type *start, index_type *end);
void screen_start_selection(Screen *self, index_type x, index_type y, bool, SelectionExtendMode);
void screen_update_selection(Screen *self, index_type x, index_type y, bool ended);
bool screen_history_scroll(Screen *self, int amt, bool upwards);
Line* screen_visual_line(Screen *self, index_type y);

View File

@@ -105,7 +105,7 @@ def parse_session(raw, opts):
return ans
def create_session(opts, args=None, special_window=None, cwd_from=None):
def create_session(opts, args=None, special_window=None, cwd_from=None, respect_cwd=False):
if args and args.session:
with open(args.session) as f:
return parse_session(f.read(), opts)
@@ -122,6 +122,8 @@ def create_session(opts, args=None, special_window=None, cwd_from=None):
cmd = args.args if args and args.args else resolved_shell(opts)
from kitty.tabs import SpecialWindow
k = {'cwd_from': cwd_from}
if respect_cwd:
k['cwd'] = args.directory
if getattr(args, 'title', None):
k['override_title'] = args.title
special_window = SpecialWindow(cmd, **k)

View File

@@ -30,7 +30,7 @@ copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int
// ARB_copy_image not available, do a slow roundtrip copy
if (!copy_image_warned) {
copy_image_warned = true;
fprintf(stderr, "WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation.\n");
log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation");
}
size_t sz = width * height * num_levels;
pixel *src = malloc(sz * sizeof(pixel));
@@ -214,9 +214,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style;
GLint color1, color2;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
GLuint xnum, ynum, cursor_x, cursor_y, cursor_w;
};
@@ -241,7 +239,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
unsigned int x, y, z;
sprite_tracker_current_layout(&x, &y, &z);
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
rd->color1 = inverted & 1; rd->color2 = 1 - (inverted & 1);
rd->inverted = inverted ? 1 : 0;
rd->background_opacity = OPT(background_opacity);
#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name)
@@ -472,7 +470,7 @@ init_cursor_program() {
#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location
SET_LOC(color);
else SET_LOC(pos);
else { fatal("Unknown uniform in cursor program"); }
else { fatal("Unknown uniform in cursor program: %s", p->uniforms[i].name); }
}
if (left) { fatal("Left over uniforms in cursor program"); }
#undef SET_LOC
@@ -573,7 +571,7 @@ compile_program(PyObject UNUSED *self, PyObject *args) {
if (ret != GL_TRUE) {
GLsizei len;
glGetProgramInfoLog(programs[which].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);
PyErr_SetString(PyExc_ValueError, "Failed to compile shader");
goto end;
}
@@ -587,9 +585,8 @@ end:
Py_RETURN_NONE;
}
#define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self)
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
#define PYWRAP2(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args, PyObject *kw)
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
#define ONE_INT(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); Py_RETURN_NONE; }
#define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); Py_RETURN_NONE; }

206
kitty/shell.py Normal file
View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import readline
import shlex
import sys
import traceback
import types
from functools import lru_cache
from .cli import (
emph, green, italic, parse_option_spec, print_help_for_seq, title
)
from .cmds import cmap, display_subcommand_help, parse_subcommand_cli
from .constants import cache_dir, version
all_commands = tuple(sorted(cmap))
match_commands = tuple(sorted(all_commands + ('exit', 'help', 'quit')))
def cmd_names_matching(prefix):
for cmd in match_commands:
if not prefix or cmd.startswith(prefix):
yield cmd + ' '
@lru_cache()
def options_for_cmd(cmd):
alias_map = {}
try:
func = cmap[cmd]
except KeyError:
return (), alias_map
seq, disabled = parse_option_spec(func.options_spec)
ans = []
for opt in seq:
if isinstance(opt, str):
continue
for alias in opt['aliases']:
ans.append(alias)
alias_map[alias] = opt
return tuple(sorted(ans)), alias_map
def options_matching(prefix, aliases, alias_map):
for alias in aliases:
if (not prefix or alias.startswith(prefix)) and alias.startswith('--'):
yield alias + ' '
class Completer:
def __init__(self):
self.matches = []
ddir = cache_dir()
try:
os.makedirs(ddir)
except FileExistsError:
pass
self.history_path = os.path.join(ddir, 'shell.history')
def complete(self, text, state):
if state == 0:
line = readline.get_line_buffer()
cmdline = shlex.split(line)
if len(cmdline) < 2 and not line.endswith(' '):
self.matches = list(cmd_names_matching(text))
else:
self.matches = list(options_matching(text, *options_for_cmd(cmdline[0])))
if state < len(self.matches):
return self.matches[state]
def __enter__(self):
if os.path.exists(self.history_path):
readline.read_history_file(self.history_path)
readline.set_completer(self.complete)
readline.parse_and_bind('tab: complete')
delims = readline.get_completer_delims()
readline.set_completer_delims(delims.replace('-', ''))
return self
def __exit__(self, *a):
readline.write_history_file(self.history_path)
def print_err(*a, **kw):
kw['file'] = sys.stderr
print(*a, **kw)
def print_help(which=None):
if which is None:
print('Control kitty by sending it commands.')
print()
print(title('Commands') + ':')
for cmd in all_commands:
c = cmap[cmd]
print(' ', green(c.name))
print(' ', c.short_desc)
print(' ', green('exit'))
print(' ', 'Exit this shell')
print('\nUse help {} for help on individual commands'.format(italic('command')))
else:
try:
func = cmap[which]
except KeyError:
if which == 'exit':
print('Exit this shell')
elif which == 'help':
print('Show help')
else:
print('Unknown command: {}'.format(emph(which)))
return
display_subcommand_help(func)
def run_cmd(global_opts, cmd, func, opts, items):
from .remote_control import do_io
payload = func(global_opts, opts, items)
send = {
'cmd': cmd,
'version': version,
}
if func.no_response and isinstance(payload, types.GeneratorType):
for item in payload:
send['payload'] = item
do_io(global_opts.to, send, func.no_response)
return
if payload is not None:
send['payload'] = payload
response = do_io(global_opts.to, send, func.no_response)
if not response.get('ok'):
if response.get('tb'):
print_err(response['tb'])
print_err(response['error'])
return
if 'data' in response:
print(response['data'])
def real_main(global_opts):
readline.read_init_file()
print_help_for_seq.allow_pager = False
print('Welcome to the kitty shell!')
print('Use {} for assistance or {} to quit'.format(green('help'), green('exit')))
while True:
try:
cmdline = input('🐱 ')
except EOFError:
break
except KeyboardInterrupt:
print()
continue
if not cmdline:
continue
cmdline = shlex.split(cmdline)
cmd = cmdline[0].lower()
try:
func = cmap[cmd]
except KeyError:
if cmd in ('exit', 'quit'):
break
if cmd == 'help':
print_help(cmdline[1] if len(cmdline) > 1 else None)
continue
print_err('"{}" is an unknown command. Use "help" to see a list of commands.'.format(emph(cmd)))
continue
try:
opts, items = parse_subcommand_cli(func, cmdline)
except SystemExit as e:
if e.code != 0:
print_err(e)
print_err('Use "{}" to see how to use this command.'.format(emph('help ' + cmd)))
continue
except Exception:
print_err('Unhandled error:')
traceback.print_exc()
continue
else:
try:
run_cmd(global_opts, cmd, func, opts, items)
except SystemExit as e:
print_err(e)
continue
except KeyboardInterrupt:
print()
continue
except Exception:
print_err('Unhandled error:')
traceback.print_exc()
continue
def main(global_opts):
try:
with Completer():
real_main(global_opts)
except Exception:
traceback.print_exc()
input('Press enter to quit...')
raise SystemExit(1)

View File

@@ -201,6 +201,7 @@ static inline void
set_active_tab(id_type os_window_id, unsigned int idx) {
WITH_OS_WINDOW(os_window_id)
os_window->active_tab = idx;
os_window->needs_render = true;
END_WITH_OS_WINDOW
}
@@ -208,6 +209,7 @@ static inline void
set_active_window(id_type os_window_id, id_type tab_id, unsigned int idx) {
WITH_TAB(os_window_id, tab_id)
tab->active_window = idx;
osw->needs_render = true;
END_WITH_TAB;
}
@@ -268,9 +270,8 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
// Python API {{{
#define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self)
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
#define PYWRAP2(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args, PyObject *kw)
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
#define ONE_UINT(name) PYWRAP1(name) { name((unsigned int)PyLong_AsUnsignedLong(args)); Py_RETURN_NONE; }
#define TWO_UINT(name) PYWRAP1(name) { unsigned int a, b; PA("II", &a, &b); name(a, b); Py_RETURN_NONE; }
@@ -283,6 +284,7 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
#define KKI(name) PYWRAP1(name) { id_type a, b; unsigned int c; PA("KKI", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#define KKII(name) PYWRAP1(name) { id_type a, b; unsigned int c, d; PA("KKII", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; }
#define KK5I(name) PYWRAP1(name) { id_type a, b; unsigned int c, d, e, f, g; PA("KKIIIII", &a, &b, &c, &d, &e, &f, &g); name(a, b, c, d, e, f, g); Py_RETURN_NONE; }
#define BOOL_SET(name) PYWRAP1(set_##name) { global_state.name = PyObject_IsTrue(args); Py_RETURN_NONE; }
static inline color_type
color_as_int(PyObject *color) {
@@ -323,10 +325,11 @@ PYWRAP1(handle_for_window_id) {
PYWRAP1(set_options) {
PyObject *ret, *opts;
int is_wayland = 0, debug_gl = 0;
PA("O|pp", &opts, &is_wayland, &debug_gl);
int is_wayland = 0, debug_gl = 0, debug_font_fallback = 0;
PA("O|ppp", &opts, &is_wayland, &debug_gl, &debug_font_fallback);
global_state.is_wayland = is_wayland ? true : false;
global_state.debug_gl = debug_gl ? true : false;
global_state.debug_font_fallback = debug_font_fallback ? true : false;
#define GA(name) ret = PyObject_GetAttrString(opts, #name); if (ret == NULL) return NULL;
#define S(name, convert) { GA(name); global_state.opts.name = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
S(visual_bell_duration, PyFloat_AsDouble);
@@ -336,9 +339,9 @@ PYWRAP1(set_options) {
S(cursor_stop_blinking_after, PyFloat_AsDouble);
S(background_opacity, PyFloat_AsDouble);
S(inactive_text_alpha, PyFloat_AsDouble);
S(window_padding_width, PyFloat_AsDouble);
S(cursor_shape, PyLong_AsLong);
S(url_style, PyLong_AsUnsignedLong);
S(x11_bell_volume, PyLong_AsLong);
S(tab_bar_edge, PyLong_AsLong);
S(mouse_hide_wait, PyFloat_AsDouble);
S(wheel_scroll_multiplier, PyFloat_AsDouble);
@@ -352,6 +355,7 @@ PYWRAP1(set_options) {
S(repaint_delay, repaint_delay);
S(input_delay, repaint_delay);
S(sync_to_monitor, PyObject_IsTrue);
S(close_on_child_death, PyObject_IsTrue);
S(macos_option_as_alt, PyObject_IsTrue);
S(macos_hide_titlebar, PyObject_IsTrue);
@@ -365,6 +369,8 @@ PYWRAP1(set_options) {
GA(keymap); set_special_keys(ret);
Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
GA(sequence_map); set_special_keys(ret);
Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
#define read_adjust(name) { \
PyObject *al = PyObject_GetAttrString(opts, #name); \
@@ -384,6 +390,8 @@ PYWRAP1(set_options) {
Py_RETURN_NONE;
}
BOOL_SET(in_sequence_mode)
PYWRAP1(set_tab_bar_render_data) {
ScreenRenderData d = {0};
id_type os_window_id;
@@ -553,6 +561,7 @@ KK5I(add_borders_rect)
static PyMethodDef module_methods[] = {
MW(current_os_window, METH_NOARGS),
MW(set_options, METH_VARARGS),
MW(set_in_sequence_mode, METH_O),
MW(handle_for_window_id, METH_VARARGS),
MW(set_logical_dpi, METH_VARARGS),
MW(pt_to_px, METH_O),

View File

@@ -26,11 +26,12 @@ typedef struct {
bool macos_option_as_alt, macos_hide_titlebar;
int adjust_line_height_px, adjust_column_width_px;
float adjust_line_height_frac, adjust_column_width_frac;
int x11_bell_volume;
float background_opacity;
float inactive_text_alpha;
float window_padding_width;
Edge tab_bar_edge;
bool sync_to_monitor;
bool close_on_child_death;
} Options;
typedef struct {
@@ -97,11 +98,11 @@ typedef struct {
void *handle;
id_type id;
OSWindowGeometry before_fullscreen;
int viewport_width, viewport_height;
int viewport_width, viewport_height, window_width, window_height;
double viewport_x_ratio, viewport_y_ratio;
Tab *tabs;
unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id;
bool focused_at_last_render;
bool focused_at_last_render, needs_render;
ScreenRenderData tab_bar_render_data;
bool is_focused;
double cursor_blink_zero_time, last_mouse_activity_at;
@@ -130,13 +131,14 @@ typedef struct {
OSWindow *callback_os_window;
bool close_all_windows;
bool is_wayland;
bool debug_gl;
bool debug_gl, debug_font_fallback;
bool has_pending_resizes;
bool in_sequence_mode;
} GlobalState;
extern GlobalState global_state;
#define call_boss(name, ...) { \
#define call_boss(name, ...) if (global_state.boss) { \
PyObject *cret_ = PyObject_CallMethod(global_state.boss, #name, __VA_ARGS__); \
if (cret_ == NULL) { PyErr_Print(); } \
else Py_DECREF(cret_); \

View File

@@ -17,15 +17,15 @@ from .fast_data_types import (
)
from .layout import Rect, all_layouts
from .session import resolved_shell
from .utils import color_as_int
from .utils import color_as_int, log_error
from .window import Window, calculate_gl_geometry
TabbarData = namedtuple('TabbarData', 'title is_active is_last')
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd overlay_for')
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd overlay_for env')
def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None):
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for)
def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None):
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for, env)
class Tab: # {{{
@@ -38,7 +38,7 @@ class Tab: # {{{
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
self.opts, self.args = tab_manager.opts, tab_manager.args
self.name = getattr(session_tab, 'name', '')
self.enabled_layouts = list(getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts)
self.enabled_layouts = [x.lower() for x in getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts]
self.borders = Borders(self.os_window_id, self.id, self.opts)
self.windows = deque()
self.active_window_idx = 0
@@ -116,25 +116,35 @@ class Tab: # {{{
self.current_layout = self.create_layout_object(nl)
self.relayout()
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None):
def goto_layout(self, layout_name):
layout_name = layout_name.lower()
if layout_name not in self.enabled_layouts:
log_error('Unknown or disabled layout: {}'.format(layout_name))
return
self.current_layout = self.create_layout_object(layout_name)
self.relayout()
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None):
if cmd is None:
if use_shell:
cmd = resolved_shell(self.opts)
else:
cmd = self.args.args or resolved_shell(self.opts)
env = {}
fenv = {}
if env:
fenv.update(env)
if not is_macos and not is_wayland:
try:
env['WINDOWID'] = str(x11_window_id(self.os_window_id))
fenv['WINDOWID'] = str(x11_window_id(self.os_window_id))
except Exception:
import traceback
traceback.print_exc()
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, env, cwd_from)
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from)
ans.fork()
return ans
def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None):
child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd)
def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None):
child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env)
window = Window(self, child, self.opts, self.args, override_title=override_title)
if overlay_for is not None:
overlaid = next(w for w in self.windows if w.id == overlay_for)
@@ -239,6 +249,7 @@ class TabBar: # {{{
def __init__(self, os_window_id, opts):
self.os_window_id = os_window_id
self.opts = opts
self.num_tabs = 1
self.cell_width = 1
self.data_buffer_size = 0
@@ -269,6 +280,16 @@ class TabBar: # {{{
self.active_bg = as_rgb(color_as_int(opts.active_tab_background))
self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
def patch_colors(self, spec):
if 'active_tab_foreground' in spec:
self.active_fg = (spec['active_tab_foreground'] << 8) | 2
if 'active_tab_background' in spec:
self.active_bg = (spec['active_tab_background'] << 8) | 2
self.screen.color_profile.set_configured_colors(
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))
)
def layout(self):
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
if tab_bar.width < 2:
@@ -448,6 +469,7 @@ class TabManager: # {{{
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
self._set_active_tab(idx)
self.update_tab_bar()
return self.tabs[idx]
def remove(self, tab):
self._remove_tab(tab)

View File

@@ -3,14 +3,7 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import re
from binascii import unhexlify, hexlify
def safe_print(*a, **k):
try:
print(*a, **k)
except Exception:
pass
from binascii import hexlify, unhexlify
names = 'xterm-kitty', 'KovIdTTY'
@@ -459,7 +452,8 @@ def get_capabilities(query_string):
qname = termcap_aliases[name]
val = queryable_capabilities[qname]
except Exception as e:
safe_print(ERROR_PREFIX, 'Unknown terminfo property:', name)
from .utils import log_error
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
raise
if qname in string_capabilities and '%' not in val:
val = key_as_bytes(qname).decode('ascii')

28
kitty/threading.h Normal file
View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <stdio.h>
#include <pthread.h>
#ifdef __APPLE__
// 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 and that causes other issues on systems with old glibc
extern int pthread_setname_np(pthread_t, const char *name);
#endif
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");
}

View File

@@ -19,7 +19,7 @@ from time import monotonic
from .constants import appname, is_macos, is_wayland
from .fast_data_types import (
GLSL_VERSION, redirect_std_streams, x11_display,
GLSL_VERSION, log_error_string, redirect_std_streams, x11_display,
x11_window_id
)
from .rgb import Color, to_color
@@ -40,19 +40,18 @@ def safe_print(*a, **k):
pass
def log_error(*a, **k):
try:
msg = k.get('sep', ' ').join(map(str, a)) + k.get('end', '\n')
log_error_string(msg.replace('\0', ''))
except Exception:
pass
def ceil_int(x):
return int(math.ceil(x))
@contextmanager
def timeit(name, do_timing=False):
if do_timing:
st = monotonic()
yield
if do_timing:
safe_print('Time for {}: {}'.format(name, monotonic() - st))
def sanitize_title(x):
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19\x80-\x9f]', '', x))
@@ -262,6 +261,31 @@ def single_instance(group_id=None):
return True
def parse_address_spec(spec):
protocol, rest = spec.split(':', 1)
socket_path = None
if protocol == 'unix':
family = socket.AF_UNIX
address = rest
if address.startswith('@') and len(address) > 1:
address = '\0' + address[1:]
else:
socket_path = address
elif protocol in ('tcp', 'tcp6'):
family = socket.AF_INET if protocol == 'tcp' else socket.AF_INET6
host, port = rest.rsplit(':', 1)
address = host, int(port)
else:
raise ValueError('Unknown protocol in --listen-on value: {}'.format(spec))
return family, address, socket_path
def make_fd_non_blocking(fd):
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
return oldfl
@contextmanager
def non_blocking_read(src=sys.stdin):
import termios
@@ -271,22 +295,21 @@ def non_blocking_read(src=sys.stdin):
if src.isatty():
old = termios.tcgetattr(fd)
tty.setraw(fd)
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
oldfl = make_fd_non_blocking(fd)
yield fd
if src.isatty():
termios.tcsetattr(fd, termios.TCSADRAIN, old)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl)
def read_with_timeout(more_needed, timeout=10, src=sys.stdin):
def read_with_timeout(more_needed, timeout=10, src=sys.stdin.buffer):
import select
start_time = monotonic()
with non_blocking_read(src) as fd:
while timeout > monotonic() - start_time:
rd = select.select([fd], [], [], max(0, timeout - (monotonic() - start_time)))[0]
if rd:
data = sys.stdin.buffer.read()
data = src.read()
if not data:
break # eof
if not more_needed(data):

View File

@@ -182,6 +182,9 @@ class TestParser(BaseTest):
c.clear()
pb('\033]2;;;;\x07', ('set_title', ';;;'))
self.ae(c.titlebuf, ';;;')
c.clear()
pb('\033]2;\x07', ('set_title', ''))
self.ae(c.titlebuf, '')
pb('\033]110\x07', ('set_dynamic_color', 110, ''))
self.ae(c.colorbuf, '')

View File

@@ -245,6 +245,12 @@ class TestScreen(BaseTest):
y = s.cursor.y
self.assertIn('|', str(s.line(y)))
s = self.create_screen()
draw('a')
x_before = s.cursor.x
s.resize(s.lines - 1, s.columns)
self.ae(x_before, s.cursor.x)
def test_tab_stops(self):
# Taken from vttest/main.c
s = self.create_screen(cols=80, lines=2)

View File

@@ -94,7 +94,7 @@ int main(int argc, char *argv[]) {
#ifdef FOR_LAUNCHER
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../Frameworks/kitty");
#else
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../lib/kitty");
num = snprintf(lib, PATH_MAX, "%s%s%s%s", exe_dir, "/../", LIB_DIR_NAME, "/kitty");
#endif
#endif

Some files were not shown because too many files have changed in this diff Show More