Compare commits

...

233 Commits

Author SHA1 Message Date
Kovid Goyal
8b6240a067 version 0.9.1 2018-05-05 09:46:28 +05:30
Kovid Goyal
f5c713b118 More work on the diff kitten 2018-05-05 09:45:35 +05:30
Kovid Goyal
ac7596b7c5 ... 2018-05-05 07:30:59 +05:30
Kovid Goyal
f69ed984a4 Output GL version when running with --debug-gl
See #521
2018-05-05 07:26:30 +05:30
Kovid Goyal
83850b28ae Update GL wrapper generated by new version of glad 2018-05-04 18:12:31 +05:30
Kovid Goyal
5efa26a98d Micro-optimization 2018-05-04 15:48:58 +05:30
Kovid Goyal
51d2c01bc1 Highlight changed in individual lines in chunks that have the same number of adds/removes 2018-05-04 15:35:11 +05:30
Kovid Goyal
be9d876997 find common prefix and suffix of two strings 2018-05-03 20:15:28 +05:30
Kovid Goyal
866b53f384 More work on the diff kitten 2018-05-03 18:05:55 +05:30
Kovid Goyal
de1a2f7682 Delay load some particularly heavy stdlib modules that are not often needed 2018-05-03 17:53:22 +05:30
Kovid Goyal
86686bfac7 Avoid rendering tab bar more than once per tick 2018-05-03 17:00:18 +05:30
Kovid Goyal
3f316c39d1 Dynamically allocate the scrollback history buffer
Reduces startup memory consumption when using very large scrollback
buffer sizes.
2018-05-03 15:17:02 +05:30
Kovid Goyal
14459a7d18 Fix #506 2018-05-03 11:19:11 +05:30
Kovid Goyal
c062b84bc6 Forgot to remove args.cmd 2018-05-03 11:14:10 +05:30
Kovid Goyal
890a68fa53 Use -c as an alias for --config instead 2018-05-03 11:12:16 +05:30
Kovid Goyal
59017a6e6f Remove the -c command line flag as it is better to use +runpy instead 2018-05-03 11:11:25 +05:30
Kovid Goyal
06cb2cafe6 Add ssh script with terminfo copy to README 2018-05-03 09:40:18 +05:30
Kovid Goyal
e4ed170748 ... 2018-05-03 09:15:05 +05:30
Kovid Goyal
f011a6c86c Disallow use of both macos_titlebar_color and background_opacity
Fixes #519
2018-05-03 09:11:41 +05:30
Kovid Goyal
45fc1ace0c Ignore -e if it is specified on the command line.
This is for compatibility with broken software that assumes terminals
should run with an -e option to execute commands instead of just passing
the commands as arguments. Fixes #502
2018-05-03 08:41:39 +05:30
Kovid Goyal
8d51edec17 Option to disable bell symbol on tab 2018-05-02 22:05:38 +05:30
Kovid Goyal
f2afba2ef4 Change the window border color if a bell occurs in an unfocused window. Can be disabled by setting the bell_border_color to be the same as the inactive_border_color. 2018-05-02 22:02:45 +05:30
Kovid Goyal
efcd3a5df7 Show a bell on the tab if a bell occurs in one of the windows in the tab and the window is not the currently focused window. Fixes #514 2018-05-02 21:44:14 +05:30
Kovid Goyal
2a52acdef4 Fix tab title not changing to reflect the window title when switching between different windows in a tab 2018-05-02 21:07:45 +05:30
Kovid Goyal
eadfeeec9b Fix window focus gained/lost events not being reported to child programs when switching windows/tabs using the various keyboard shortcuts. 2018-05-02 20:50:42 +05:30
Kovid Goyal
f8a581129f Fix #513 2018-05-02 15:52:56 +05:30
Kovid Goyal
93ff651d9e ... 2018-05-02 15:52:10 +05:30
Kovid Goyal
bec0f02b11 More work on the diff kitten 2018-05-02 15:26:47 +05:30
Kovid Goyal
0721951c79 Add hunk title lines 2018-05-02 09:47:24 +05:30
Kovid Goyal
b37d07d31a Update CHANGELOG 2018-05-01 23:41:19 +05:30
Kovid Goyal
9cacfc0c26 macOS: When the macos_titlebar_color is set to background change the titlebar background to match the current background color of the active kitty window 2018-05-01 23:37:48 +05:30
Kovid Goyal
0b93b85cf2 Dont use case range in names.h as it prevents compilation with Visual Studio 2018-05-01 11:27:10 +05:30
Kovid Goyal
f7001ea068 Fix character names for control characters not being read from unicode database
Also allow unicode_names.c to be compiled with python 2 so I can re-use
it in calibre.
2018-05-01 10:13:58 +05:30
Kovid Goyal
aa93c3fb66 Fix passing @text to other programs such as when viewing the scrollback buffer not working correctly if kitty is itself scrolled up. Fixes #509 2018-05-01 07:38:05 +05:30
Kovid Goyal
166c7bf0e6 Update CHANGELOG 2018-04-29 20:16:11 +05:30
Kovid Goyal
bb1374f64e Only define VCS_REV where it is needed 2018-04-29 18:47:14 +05:30
Kovid Goyal
053835afbe Add an option to not request window attention on bell. Fixes #507 2018-04-29 18:16:41 +05:30
Kovid Goyal
4760580d1c More work on the diff kitten 2018-04-29 08:07:03 +05:30
Kovid Goyal
8fa492e0ee More work on the diff kitten 2018-04-28 14:07:24 +05:30
Kovid Goyal
040ae75c77 Remote control: Allow matching windows by number (visible position).
Fixes #501
2018-04-28 11:00:42 +05:30
Kovid Goyal
759aaf294a Remove extra level of indirection in creating iterator 2018-04-28 10:39:59 +05:30
Kovid Goyal
9ea0de868c Parse send_text payload only once at startup 2018-04-27 18:13:37 +05:30
Kovid Goyal
78002cf3ba Update glfw from upstream
Fixes #488
2018-04-27 15:32:54 +05:30
Kovid Goyal
f68f8bf9c9 macOS: Fix changing tab title and kitty shell not working
I forgot that Apple has its own special snowflake readline variant.
Fixes #494
2018-04-27 15:16:26 +05:30
Kovid Goyal
54438cae3f DRYer 2018-04-27 11:19:46 +05:30
Kovid Goyal
2e693c31df Refactor shortcut config parsing
Resolution of kitty_mod and creation of the maps now happens in one
central place after all config is parsed. Fixes #496

Also have --debug-config output multi-key shortcuts nicely as well.
2018-04-27 11:16:58 +05:30
Kovid Goyal
0f06bf1bdb Merge branch 'macos_hide_from_tasks' of https://github.com/cfal/kitty 2018-04-27 08:19:32 +05:30
Alex Lau
b7f4f2e898 Move call to cocoa_window.m 2018-04-26 12:42:56 -07:00
Kovid Goyal
7148ee8a0f When triple-clicking select all wrapped lines belonging to a single logical line. Fixes #492 2018-04-26 10:16:00 +05:30
Kovid Goyal
238508fc0a Update glfw from upstream
Proper cross-platform fix for issue of mouse cursor not being restored
over un-focused windows.
2018-04-26 08:31:52 +05:30
Kovid Goyal
84ab0f5062 More work on the diff kitten 2018-04-26 08:01:12 +05:30
Kovid Goyal
cf04bdb36a Merge branch 'master' of https://github.com/FabioLolix/kitty 2018-04-26 06:58:16 +05:30
FabioLolix
d09a30d16c Updated Readme - Arch package
Kitty has been moved to the Arch community repository
2018-04-25 21:13:17 +02:00
Kovid Goyal
d54b58d2f3 ... 2018-04-25 19:50:40 +05:30
Kovid Goyal
0be0963dc7 Utility method to truncate formatted lines to specified width 2018-04-25 19:34:04 +05:30
Kovid Goyal
61a2360df5 hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL. 2018-04-25 14:33:37 +05:30
Kovid Goyal
b3ed4e3bc2 ... 2018-04-25 09:16:10 +05:30
Kovid Goyal
316e7cb9f5 Also use cwd of child when opening programs via the hints kitten 2018-04-25 09:07:35 +05:30
Kovid Goyal
23851e31bf When calling pass_selection_to_program use the current directory of the child process as the cwd of the program. Fixes #491 2018-04-25 08:30:22 +05:30
Alex Lau
b7b64ec513 Default to off 2018-04-24 17:13:37 -07:00
Kovid Goyal
2b372373ae Update glfw from upstream 2018-04-24 18:15:28 +05:30
Kovid Goyal
7a543fac86 Wire up color handling in the diff kitten 2018-04-24 10:58:15 +05:30
Kovid Goyal
5c4b14468c Apply default colors in diff kitten 2018-04-24 10:33:18 +05:30
Alex Lau
7d4d978d97 Add macos_hide_from_tasks option 2018-04-23 22:02:54 -07:00
Kovid Goyal
fe5b8f3aec Fix using modifiers with subsequent keys in a muti-key shortcut not working 2018-04-24 09:42:37 +05:30
Kovid Goyal
f5abe7cbf7 Add a setting to clear all shortcuts defined up to that point 2018-04-24 09:08:22 +05:30
Kovid Goyal
bd0382830a Add a setting (kitty_mod) to change the modifier used by all the default kitty shortcuts, globally
See #487
2018-04-24 08:53:07 +05:30
Kovid Goyal
0b99bb534f Unicode input: When searching by name search for prefix matches as well as whole word matches
So now hori matches both "hori" and "horizontal". Switched to a
prefix-trie internally.
2018-04-24 07:45:20 +05:30
Kovid Goyal
9d67198ff9 Update path in gen-wcwidth.py 2018-04-23 21:25:33 +05:30
Kovid Goyal
fda57a81e1 Fix Shift+function key not working
Also support pressing F13-f25 function keys. Fixes #485
2018-04-23 11:28:02 +05:30
Kovid Goyal
9962ac2703 ... 2018-04-23 10:54:04 +05:30
Kovid Goyal
edc4b91d15 Expand ~ and env vars in program names. Fixes #483 2018-04-23 10:53:20 +05:30
Kovid Goyal
92ca8ab9cc Fix #482 2018-04-23 07:19:40 +05:30
Kovid Goyal
07e2d9175c Merge branch 'fixconfig' of https://github.com/Abominas/kitty 2018-04-23 06:59:44 +05:30
Abominas
c90dbb25b9 Fix config format crash 2018-04-22 17:35:58 -07:00
Kovid Goyal
b5da109e7f Wire up config infrastructure for diff kitten 2018-04-22 21:44:47 +05:30
Kovid Goyal
aa18429a8c DRYer 2018-04-22 21:22:08 +05:30
Kovid Goyal
e578d41adb Make merge_dicts() re-useable 2018-04-22 21:00:17 +05:30
Kovid Goyal
f3579e78af Make load_config() re-useable 2018-04-22 20:58:43 +05:30
Kovid Goyal
9c43707f40 Make the hinting defaults zero when missing 2018-04-22 20:34:00 +05:30
Kovid Goyal
99ff92a5a7 Don't fail to start if the user deletes the hintstyle key from their fontconfig configuration. Fixes #480 2018-04-22 20:29:46 +05:30
Kovid Goyal
d866c0d202 Fix NULL return without error set 2018-04-22 20:19:21 +05:30
Kovid Goyal
20c42ab519 tui: Allow handlers to finalize() as well as initialize() 2018-04-22 13:14:25 +05:30
Kovid Goyal
8bbc2b82d4 More work on the diff kitten 2018-04-22 13:05:58 +05:30
Kovid Goyal
6929358976 More work on the diff kitten 2018-04-21 15:45:16 +05:30
Kovid Goyal
82acd6b511 Add a note on how to create a script to launch kitty
Useful when running from source
2018-04-21 15:33:21 +05:30
Kovid Goyal
bb939ed4f8 Remove unused code 2018-04-20 22:19:31 +05:30
Kovid Goyal
cd25abc49f ... 2018-04-20 22:06:42 +05:30
Kovid Goyal
2e90e443ed Fix #476 2018-04-20 21:33:24 +05:30
Kovid Goyal
92bb6e293a Add threaded job infrastructure to tui 2018-04-20 21:12:10 +05:30
Kovid Goyal
ea9b431386 Allow changing the source used by test_compile 2018-04-20 21:02:05 +05:30
Kovid Goyal
23077fd6a6 DRYer 2018-04-20 20:56:29 +05:30
Kovid Goyal
5eee7ce338 Fix #475 2018-04-20 20:32:16 +05:30
Kovid Goyal
784894cac3 xkbcommon.org's HTTPS certificate has expired 2018-04-20 18:39:27 +05:30
Kovid Goyal
757983b729 Another ParseTuple mismatch 2018-04-20 18:29:55 +05:30
Kovid Goyal
b6660353cd Merge branch 'bigendian-fixes' of https://github.com/jamessan/kitty 2018-04-20 18:26:26 +05:30
James McCoy
215a7689d9 Correct types for various PyArg_ParseTuple/Py_BuildValue calls
Debian's [mips] build failed one of the font tests:

    FAIL: test_sprite_map (kitty_tests.fonts.Rendering)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/<<PKGBUILDDIR>>/kitty_tests/fonts.py", line 48, in test_sprite_map
        self.ae(test_sprite_position_for(0, 1), (0, 1, 1))
        self = <kitty_tests.fonts.Rendering testMethod=test_sprite_map>
    AssertionError: Tuples differ: (0, 0, 0) != (0, 1, 1)

    First differing element 1:
    0
    1

    - (0, 0, 0)
    ?     ^  ^

    + (0, 1, 1)
    ?     ^  ^

This was due to test_sprite_position_for() using the "I" code to parse
into an unsigned short.  Since mips is big endian, the "wrong" byte was
stored into extra_glyphs.data.

This commit changes all similar data type mismatches that were found
from auditing calls to PyArg_ParseTuple/Py_BuildValue.

[mips]: https://buildd.debian.org/status/fetch.php?pkg=kitty&arch=mips&ver=0.9.0-1&stamp=1524126606&raw=0
2018-04-20 08:28:26 -04:00
Kovid Goyal
dd18a1dfd8 Simplify is_ascii_control_char
No need to use macros for unsigned char
2018-04-20 14:30:07 +05:30
Kovid Goyal
32f16ee5f5 macOS: Add support for dead keys
I have modified GLFW to support dead keys on macOS. That was painful.
Fixes #465
2018-04-20 14:25:53 +05:30
Kovid Goyal
d4f426d2ce Merge branch 'unsigned-char-build' of https://github.com/jamessan/kitty 2018-04-20 10:15:13 +05:30
Kovid Goyal
a6d96c9a14 Fix #471 2018-04-20 09:32:01 +05:30
Kovid Goyal
bf3bfeaf69 Merge branch '32bit-long-fix' of https://github.com/jamessan/kitty 2018-04-20 09:24:18 +05:30
Kovid Goyal
6ce82177ea Merge branch 'remove-sysctl-include' of https://github.com/jamessan/kitty 2018-04-20 09:21:47 +05:30
James McCoy
dc6efb5b37 Use uint64_t to detect out of range graphics command id
parse_graphics_code() uses an unsigned long to detect if the id is
"> UINT32_MAX", however sizeof(long) == sizeof(void *) on Linux systems.
So this condition will be impossible on any 32-bit system.
2018-04-19 20:32:48 -04:00
James McCoy
b26d1f306d travis: Force unsigned char on one build 2018-04-19 20:23:11 -04:00
James McCoy
cf678d8179 Remove unnecessary <sys/sysctl.h> include
The code that required this include was removed in
66803e6873, and the include is causing
build failures for some of Debian's architectures.
2018-04-19 19:14:40 -04:00
Kovid Goyal
8cecb22481 Add a link to the debian package
See #263
2018-04-19 19:42:46 +05:30
Kovid Goyal
2504266806 macOS: Fix option as alt not working for keys that act as dead keys in the current keyboard layout 2018-04-19 17:44:31 +05:30
Kovid Goyal
e3795eb07e Dont add CPPFLAGS from the python build
They contain include paths specific to the python build such as -I. that
are not appropriate for building kitty. Also move cppflags in front of
cflags when invoking the compiler.
2018-04-19 10:33:10 +05:30
Kovid Goyal
796ead5d75 Merge branch 'support-cppflags' of https://github.com/jamessan/kitty 2018-04-19 10:27:22 +05:30
Kovid Goyal
d5907a1bf9 More work on the diff kitten 2018-04-19 10:26:14 +05:30
James McCoy
ddd4774169 setup.py: Support specifying CPPFLAGS distinctly from CFLAGS 2018-04-18 23:08:45 -04:00
Kovid Goyal
1dc83a47cf When rendering a private use unicode codepoint and a space as a two cell
ligature, set the foreground colors of the space cell to match the
colors of the first cell. Works around broken applications like
powerline that use different colors for the two cells. Fixes #467
2018-04-19 07:30:36 +05:30
Kovid Goyal
0a8dc4be0a Start work on the diff kitten 2018-04-17 17:43:56 +05:30
Kovid Goyal
3d99361790 Add a note about dotted and dashed underline styles 2018-04-17 08:21:28 +05:30
Kovid Goyal
1e278938cc Fix #463 2018-04-16 21:51:55 +05:30
Kovid Goyal
ba177c61bc ... 2018-04-16 16:46:42 +05:30
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
113 changed files with 43664 additions and 39337 deletions

View File

@@ -6,7 +6,7 @@ matrix:
group: beta
sudo: false
env:
- CC=gcc SANITIZE_ARG=--sanitize
- CC=gcc CFLAGS=-funsigned-char SANITIZE_ARG=--sanitize
language: python
python: "3.5"
addons:
@@ -20,8 +20,6 @@ matrix:
- libxcursor-dev
- libunistring-dev
- libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev
- python3-pil
@@ -44,8 +42,6 @@ matrix:
- libxcursor-dev
- libunistring-dev
- libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev
- python3-pil
@@ -68,8 +64,6 @@ matrix:
- libxcursor-dev
- libunistring-dev
- libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev
- python3-pil
- clang
@@ -102,6 +96,12 @@ install: |
fi
else
pushd /tmp
wget --no-check-certificate http://xkbcommon.org/download/libxkbcommon-0.6.1.tar.xz
tar xf libxkbcommon-0.6.1.tar.xz
cd libxkbcommon-0.6.1
./configure --prefix=$HOME/harfbuzz --disable-dependency-tracking --disable-static
make -j2
make install
wget https://github.com/behdad/harfbuzz/releases/download/1.6.3/harfbuzz-1.6.3.tar.bz2
tar xf harfbuzz-1.6.3.tar.bz2
cd harfbuzz-1.6.3

View File

@@ -3,6 +3,153 @@ Changelog
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
version 0.9.1 [2018-05-05]
------------------------------
- Show a bell symbol on the tab if a bell occurs in one of the windows in the tab and
the window is not the currently focused window
- Change the window border color if a bell occurs in an unfocused window. Can
be disabled by setting the bell_border_color to be the same as the
inactive_border_color.
- macOS: Add support for dead keys
- Unicode input: When searching by name search for prefix matches as well as
whole word matches
- Dynamically allocate the memory used for the scrollback history buffer.
Reduces startup memory consumption when using very large scrollback
buffer sizes.
- Add an option to not request window attention on bell.
- Remote control: Allow matching windows by number (visible position).
- macOS: Fix changing tab title and kitty shell not working
- When triple-clicking select all wrapped lines belonging to a single logical line.
- hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL.
- When calling pass_selection_to_program use the current directory of the child
process as the cwd of the program.
- Add macos_hide_from_tasks option to hide kitty from the macOS task switcher
- macOS: When the macos_titlebar_color is set to background change the titlebar
colors to match the current background color of the active kitty window
- Add a setting to clear all shortcuts defined up to that point in the config
file(s)
- Add a setting (kitty_mod) to change the modifier used by all the default
kitty shortcuts, globally
- Fix Shift+function key not working
- Support the F13 to F25 function keys
- Don't fail to start if the user deletes the hintstyle key from their
fontconfig configuration.
- When rendering a private use unicode codepoint and a space as a two cell
ligature, set the foreground colors of the space cell to match the colors of
the first cell. Works around applications like powerline that use different
colors for the two cells.
- Fix passing @text to other programs such as when viewing the scrollback
buffer not working correctly if kitty is itself scrolled up.
- Fix window focus gained/lost events not being reported to child programs when
switching windows/tabs using the various keyboard shortcuts.
- Fix tab title not changing to reflect the window title when switching between different windows in a tab
- Ignore -e if it is specified on the command line. This is for compatibility
with broken software that assumes terminals should run with an -e option to
execute commands instead of just passing the commands as arguments.
version 0.9.0 [2018-04-15]
------------------------------
- A new kitty command shell to allow controlling kitty via commands. Press
`ctrl+shift+escape` to run the shell.
- The hints kitten has become much more powerful. Now in addition to URLs you
can use it to select word, paths, filenames, lines, etc. from the screen.
These can be inserted into the terminal, copied to clipboard or sent to
external programs.
- Linux: Switch to libxkbcommon for keyboard handling. It allows kitty to
support XCompose and dead keys and also react to keyboard remapping/layout
change without needing a restart.
- Add support for multiple-key-sequence shortcuts
- A new remote control command `set-colors` to change the current and/or
configured colors.
- When double-clicking to select a word, select words that continue onto the
next/prev line as well.
- Add an `include` directive for the config files to read multiple config files
- Improve mouse selection for windows with padding. Moving the mouse into the
padding area now acts as if the mouse is over the nearest cell.
- Allow setting all 256 terminal colors in the config file
- Fix using `kitty --single-instance` to open a new window in a running kitty
instance, not respecting the `--directory` flag
- URL hints: Exclude trailing punctuation from URLs
- URL hints: Launch the browser from the kitty parent process rather than the
hints kitten. Fixes launching on some systems where xdg-open doesn't like
being run from a kitten.
- Allow using rectangle select mode by pressing shift in addition to the
rectangle select modifiers even when the terminal program has grabbed the
mouse.
version 0.8.4 [2018-03-31]
-----------------------------
- Fix presence of XDG_CONFIG_DIRS and absence of XDG_CONFIG_HOME preventing
kitty from starting
- Revert change in last release to cell width calculation. Instead just clip
the right edges of characters that overflow the cell by at most two pixels
version 0.8.3 [2018-03-29]
-----------------------------
- Fix a regression that broke the visual bell and invert screen colors escape
code
- Allow double-click and triple-click + drag to extend selections word at a
time or line at a time
- Add a keyboard shortcut to set the tab title
- Fix setting window title to empty via OSC escape code not working correctly
- Linux: Fix cell width calculation incorrect for some fonts (cell widths are
now calculated by actually rendering bitmaps, which is slower but more
accurate)
- Allow specifying a system wide kitty config file, for all users
- Add a --debug-config command line flag to output data about the system and
kitty configuration.
- Wayland: Fix auto-repeat of keys not working
version 0.8.2 [2018-03-17]
-----------------------------

View File

@@ -13,6 +13,7 @@
: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`]
@@ -31,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`]
@@ -39,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`]
@@ -67,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.
@@ -102,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`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `libxi`, `xinerama`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
=== Install and run from source
@@ -110,7 +116,7 @@ the following dependencies are installed first.
git clone https://github.com/kovidgoyal/kitty && cd kitty
....
Now build the C parts of kitty with the following command:
Now build the native code parts of kitty with the following command:
....
make
@@ -122,16 +128,32 @@ You can run kitty, as:
python3 .
....
If that works, you can create a script to launch kitty:
....
#!/usr/bin/env python3
import runpy
runpy.run_path('/path/to/kitty/dir', run_name='__main__')
....
And place it in `~/bin` or `/usr/bin` so that you can run kitty using
just `kitty`.
=== Linux packages
* Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
* Arch Linux: https://www.archlinux.org/packages/community/x86_64/kitty/
* Arch Linux: AUR stable package https://aur.archlinux.org/packages/kitty/
* Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
* NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix
* Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty
* openSUSE: https://build.opensuse.org/package/show/X11:terminals/kitty
* Debian: https://packages.qa.debian.org/k/kitty/news/20180419T070013Z.html
=== macOS packages
kitty is available as a macOS `dmg` file for easy installation from the
@@ -200,6 +222,7 @@ windows are:
|Next layout | {sc_next_layout}
|Move tab forward | {sc_move_tab_forward}
|Move tab backward | {sc_move_tab_backward}
|Set tab title | {sc_set_tab_title}
|===
@@ -235,7 +258,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}
|Edit kitty config file | {sc_edit_config_file}
|Open a kitty shell | {sc_kitty_shell}
|===
@@ -280,13 +304,23 @@ keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you dont like to use arrow
keys.
== URL hints
== Hints
kitty has a _hints mode_ to click URLs visible on the screen by using only the keyboard.
Press {sc_run_simple_kitten_text_url_hints} to activate hints mode, as shown below.
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 also hold down `ctrl+shift` and click on a URL to open it in a browser.
@@ -317,11 +351,16 @@ a pull request!
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
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 or use the `KITTY_CONFIG_DIRECTORY` environment
variable.
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
@@ -438,6 +477,8 @@ installs the terminfo file and `kitty` that installs the main program.
This allows users to install the terminfo file on servers into which they ssh,
without needing to install all of kitty.
You also need `tic` to compile the terminfo files, it is usually found in the development package of `ncurses`
This applies to creating packages for kitty for macOS package managers such as
brew or MacPorts as well.
@@ -464,7 +505,7 @@ 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:
```
@@ -502,21 +543,38 @@ 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, so that all
terminals work transparently.
In the meantime, you can automate it by using a simple script called, for example,
`~/bin/myssh`
....
#!/bin/sh
infocmp xterm-kitty | ssh $1 tic -x -o \~/.terminfo /dev/stdin && ssh $1
....
Then you can use `myssh server` to log in to `server` with the terminfo file
being automatically available.
=== How do I change the colors in a running kitty instance?
You can either use the
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands[OSC
terminal escape codes] to set colors or you can enable link:remote-control.asciidoc[remote control]
for kitty and use `kitty @ set-colors --help`.
=== How do I specify command line options for kitty on macOS?
Apple does not want you to use command line options with GUI applications. To
workaround that limitation, kitty will read command line options from the file
`~/Library/Preferences/kitty/macos-launch-services-cmdline` when it is launched
`<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.

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

@@ -44,7 +44,8 @@ def parse_ucd():
def add_word(w, c):
if c <= 32 or c == 127 or 128 <= c <= 159:
return
word_search_map[w.lower()].add(c)
if len(w) > 1:
word_search_map[w.lower()].add(c)
first = None
for word, c in html5.items():
@@ -54,7 +55,9 @@ def parse_ucd():
for line in get_data('ucd/UnicodeData.txt'):
parts = [x.strip() for x in line.split(';')]
codepoint = int(parts[0], 16)
name = parts[1]
name = parts[1] or parts[10]
if name == '<control>':
name = parts[10]
if name:
name_map[codepoint] = name
for word in name.lower().split():
@@ -207,7 +210,8 @@ def codepoint_to_mark_map(p, mark_map):
for spec in get_ranges(mark_map):
if isinstance(spec, tuple):
s = rmap[spec[0]]
p(f'\t\tcase {spec[0]} ... {spec[1]}: return {s} + c - {spec[0]};')
cases = ' '.join(f'case {i}:' for i in range(spec[0], spec[1]+1))
p(f'\t\t{cases} return {s} + c - {spec[0]};')
else:
p(f'\t\tcase {spec}: return {rmap[spec]};')
p('default: return 0;')
@@ -256,53 +260,111 @@ def gen_ucd():
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
rmap[0xfe0e], rmap[0xfe0f]
))
with open('kittens/url_hints/url_regex.py', 'w') as f:
with open('kittens/hints/url_regex.py', 'w') as f:
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz))))
def gen_names():
words = tuple(sorted(word_search_map))
with create_header('kittens/unicode_input/names.h') as p:
cp_map = list(sorted(name_map))
p(f'static const char* name_map[{len(cp_map)}] = {{' ' // {{{')
for cp in cp_map:
mark_to_cp = list(sorted(name_map))
cp_to_mark = {cp: m for m, cp in enumerate(mark_to_cp)}
# Mapping of mark to codepoint name
p(f'static const char* name_map[{len(mark_to_cp)}] = {{' ' // {{{')
for cp in mark_to_cp:
w = name_map[cp].replace('"', '\\"')
p(f'\t"{w}",')
p("}; // }}}\n")
p(f'static const char* idx_to_word[{len(words)}] = ' '{ // {{{')
for s in words:
s = s.replace('"', '\\"')
p(f'\t"{s}",')
p("}; // }}}\n")
first_letters = {ord(w[0]) for w in words if ord(w[0]) < 256}
wmap = {w: i for i, w in enumerate(words)}
p(f'static const unsigned short* words_for_first_letter[256] = ' '{ // {{{')
for fl in range(0, 256):
if fl in first_letters:
winds = [str(wmap[w]) for w in words if w.startswith(chr(fl))]
p(f'\t(const unsigned short[{len(winds) + 1}]){{{len(winds)}, ', ', '.join(winds), '},')
else:
p('NULL,')
p("}; // }}}\n")
p(f'static const char_type* codepoints_for_word_idx[{len(words)}] = ' '{ // {{{')
for s in words:
cps = word_search_map[s]
a = ', '.join(map(str, cps))
p(f'\t(const char_type[{len(cps) + 1}]){{{len(cps)}, ', a, '},')
p("}; // }}}\n")
# Mapping of mark to codepoint
p(f'static const char_type mark_to_cp[{len(mark_to_cp)}] = {{' ' // {{{')
p(', '.join(map(str, mark_to_cp)))
p('}; // }}}\n')
# Function to get mark number for codepoint
p('static char_type mark_for_codepoint(char_type c) {')
codepoint_to_mark_map(p, cp_map)
codepoint_to_mark_map(p, mark_to_cp)
p('}\n')
p('static inline const char* name_for_codepoint(char_type cp) {')
p('\tchar_type m = mark_for_codepoint(cp); if (m == 0) return NULL;')
p('\treturn name_map[m];')
p('}\n')
# Array of all words
word_map = tuple(sorted(word_search_map))
word_rmap = {w: i for i, w in enumerate(word_map)}
p(f'static const char* all_words_map[{len(word_map)}] = {{' ' // {{{')
cwords = (w.replace('"', '\\"') for w in word_map)
p(', '.join(f'"{w}"' for w in cwords))
p('}; // }}}\n')
# Array of sets of marks for each word
word_to_marks = {word_rmap[w]: frozenset(map(cp_to_mark.__getitem__, cps)) for w, cps in word_search_map.items()}
all_mark_groups = frozenset(word_to_marks.values())
array = [0]
mg_to_offset = {}
for mg in all_mark_groups:
mg_to_offset[mg] = len(array)
array.append(len(mg))
array.extend(sorted(mg))
p(f'static const char_type mark_groups[{len(array)}] = {{' ' // {{{')
p(', '.join(map(str, array)))
p('}; // }}}\n')
offsets_array = []
for wi, w in enumerate(word_map):
mg = word_to_marks[wi]
offsets_array.append(mg_to_offset[mg])
p(f'static const char_type mark_to_offset[{len(offsets_array)}] = {{' ' // {{{')
p(', '.join(map(str, offsets_array)))
p('}; // }}}\n')
# The trie
p(f'typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
all_trie_nodes = []
class TrieNode:
def __init__(self):
self.match_offset = 0
self.children_offset = 0
self.children = {}
def add_letter(self, letter):
if letter not in self.children:
self.children[letter] = len(all_trie_nodes)
all_trie_nodes.append(TrieNode())
return self.children[letter]
def __str__(self):
return f'{{ .children_offset={self.children_offset}, .match_offset={self.match_offset} }}'
root = TrieNode()
all_trie_nodes.append(root)
def add_word(word_idx):
word = word_map[word_idx]
parent = root
for letter in map(ord, word):
idx = parent.add_letter(letter)
parent = all_trie_nodes[idx]
parent.match_offset = offsets_array[word_idx]
for i in range(len(word_map)):
add_word(i)
children_array = [0]
for node in all_trie_nodes:
if node.children:
node.children_offset = len(children_array)
children_array.append(len(node.children))
for letter, child_offset in node.children.items():
children_array.append((child_offset << 8) | (letter & 0xff))
p(f'static const word_trie all_trie_nodes[{len(all_trie_nodes)}] = {{' ' // {{{')
p(',\n'.join(map(str, all_trie_nodes)))
p('\n}; // }}}\n')
p(f'static const uint32_t children_array[{len(children_array)}] = {{' ' // {{{')
p(', '.join(map(str, children_array)))
p('}; // }}}\n')
def gen_wcwidth():
seen = set()

View File

@@ -27,6 +27,10 @@ def regenerate():
)
def strip_trailing_whitespace(c):
return re.sub(r'\s+$', '', c, flags=re.MULTILINE) + '\n'
def export():
c = open('out/src/glad.c', 'rb').read().decode('utf-8')
functions = []
@@ -48,12 +52,13 @@ init_glad(GLADloadproc load, int debug) {
}
return ret;
}'''.replace('SUB', ';\n '.join(switch), 1)
open('../kitty/gl-wrapper.c', 'w').write(c)
open('../kitty/gl-wrapper.c', 'w').write(strip_trailing_whitespace(c))
raw = open('out/include/glad/glad.h').read()
raw = raw.replace('<KHR/khrplatform.h>', '"khrplatform.h"')
raw += '\nint init_glad(GLADloadproc, int);\n'
open('../kitty/gl-wrapper.h', 'w').write(raw)
shutil.copy2('out/include/KHR/khrplatform.h', '../kitty')
open('../kitty/gl-wrapper.h', 'w').write(strip_trailing_whitespace(raw))
raw = open('out/include/KHR/khrplatform.h', 'rb').read().decode('utf-8')
open('../kitty/khrplatform.h', 'w').write(strip_trailing_whitespace(raw))
if __name__ == '__main__':

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));
@@ -207,6 +208,9 @@ static GLFWbool updateUnicodeDataNS(void)
_glfw.ns.unicodeData = nil;
}
for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next)
window->ns.deadKeyState = 0;
_glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
if (!_glfw.ns.inputSource)
{
@@ -371,4 +375,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif
;
}

View File

@@ -37,6 +37,7 @@ typedef void* id;
#endif
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
typedef struct VkMacOSSurfaceCreateInfoMVK
{
@@ -98,6 +99,10 @@ typedef struct _GLFWwindowNS
// This is kept to counteract Cocoa doing the same internally
double cursorWarpDeltaX, cursorWarpDeltaY;
// The text input filter callback
GLFWcocoatextinputfilterfun textInputFilterCallback;
// Dead key state
UInt32 deadKeyState;
} _GLFWwindowNS;
// Cocoa-specific global data
@@ -114,6 +119,8 @@ typedef struct _GLFWlibraryNS
id listener;
char keyName[64];
char text[256];
GLFWbool debug_keyboard;
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
char* clipboardString;
@@ -166,4 +173,3 @@ void _glfwInitTimerNS(void);
void _glfwPollMonitorsNS(void);
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);

View File

@@ -128,6 +128,32 @@ static void updateCursorImage(_GLFWwindow* window)
hideCursor(window);
}
// Apply chosen cursor mode to a focused window
//
static void updateCursorMode(_GLFWwindow* window)
{
if (window->cursorMode == GLFW_CURSOR_DISABLED)
{
_glfw.ns.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
&_glfw.ns.restoreCursorPosX,
&_glfw.ns.restoreCursorPosY);
centerCursor(window);
CGAssociateMouseAndMouseCursorPosition(false);
}
else if (_glfw.ns.disabledCursorWindow == window)
{
_glfw.ns.disabledCursorWindow = NULL;
CGAssociateMouseAndMouseCursorPosition(true);
_glfwPlatformSetCursorPos(window,
_glfw.ns.restoreCursorPosX,
_glfw.ns.restoreCursorPosY);
}
if (cursorInClientArea(window))
updateCursorImage(window);
}
// Transforms the specified y-coordinate between the CG display and NS screen
// coordinate systems
//
@@ -183,10 +209,112 @@ static int translateFlags(NSUInteger flags)
return mods;
}
#define debug_key(...) if (_glfw.ns.debug_keyboard) NSLog(__VA_ARGS__)
static inline const char*
format_mods(int mods) {
static char buf[128];
char *p = buf, *s;
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x)
pr("mods: ");
s = p;
if (mods & GLFW_MOD_CONTROL) pr("ctrl+");
if (mods & GLFW_MOD_ALT) pr("alt+");
if (mods & GLFW_MOD_SHIFT) pr("shift+");
if (mods & GLFW_MOD_SUPER) pr("super+");
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
if (p == s) pr("none");
else p--;
pr(" ");
#undef pr
return buf;
}
static inline const char*
format_text(const char *src) {
static char buf[256];
char *p = buf;
if (!src[0]) return "<none>";
while (*src) {
p += snprintf(p, sizeof(buf) - (p - buf), "%x ", (unsigned char)*(src++));
}
if (p != buf) *(--p) = 0;
return buf;
}
static const char*
safe_name_for_scancode(unsigned int scancode) {
const char *ans = _glfwPlatformGetScancodeName(scancode);
if (!ans) return "<noname>";
if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>";
return ans;
}
// Translates a macOS keycode to a GLFW keycode
//
static int translateKey(unsigned int key)
static int translateKey(unsigned int key, GLFWbool apply_keymap)
{
if (apply_keymap) {
// Look for the effective key name after applying any keyboard layouts/mappings
const char *name = _glfwPlatformGetScancodeName(key);
if (name && name[1] == 0) {
// Single letter key name
switch(name[0]) {
#define K(ch, name) case ch: return GLFW_KEY_##name
K('A', A); K('a', A);
K('B', B); K('b', B);
K('C', C); K('c', C);
K('D', D); K('d', D);
K('E', E); K('e', E);
K('F', F); K('f', F);
K('G', G); K('g', G);
K('H', H); K('h', H);
K('I', I); K('i', I);
K('J', J); K('j', J);
K('K', K); K('k', K);
K('L', L); K('l', L);
K('M', M); K('m', M);
K('N', N); K('n', N);
K('O', O); K('o', O);
K('P', P); K('p', P);
K('Q', Q); K('q', Q);
K('R', R); K('r', R);
K('S', S); K('s', S);
K('T', T); K('t', T);
K('U', U); K('u', U);
K('V', V); K('v', V);
K('W', W); K('w', W);
K('X', X); K('x', X);
K('Y', Y); K('y', Y);
K('Z', Z); K('z', Z);
K('0', 0);
K('1', 1);
K('2', 2);
K('3', 3);
K('5', 5);
K('6', 6);
K('7', 7);
K('8', 8);
K('9', 9);
K('\'', APOSTROPHE);
K(',', COMMA);
K('.', PERIOD);
K('/', SLASH);
K('-', MINUS);
K('=', EQUAL);
K(';', SEMICOLON);
K('[', LEFT_BRACKET);
K(']', RIGHT_BRACKET);
K('`', GRAVE_ACCENT);
K('\\', BACKSLASH);
#undef K
default:
break;
}
}
}
if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0]))
return GLFW_KEY_UNKNOWN;
@@ -321,7 +449,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
centerCursor(window);
_glfwInputWindowFocus(window, GLFW_TRUE);
_glfwPlatformSetCursorMode(window, window->cursorMode);
updateCursorMode(window);
}
- (void)windowDidResignKey:(NSNotification *)notification
@@ -582,6 +710,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
window->ns.xscale = xscale;
window->ns.yscale = yscale;
_glfwInputWindowContentScale(window, xscale, yscale);
if (window->ns.layer)
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
}
}
@@ -614,14 +745,99 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[super updateTrackingAreas];
}
static inline UInt32
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
UInt32 mods = 0;
if (flags & NSEventModifierFlagShift)
mods |= shiftKey;
if (flags & NSEventModifierFlagControl)
mods |= controlKey;
if (flags & NSEventModifierFlagOption)
mods |= optionKey;
if (flags & NSEventModifierFlagCommand)
mods |= cmdKey;
if (flags & NSEventModifierFlagCapsLock)
mods |= alphaLock;
return (mods >> 8) & 0xFF;
}
static inline void
convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) {
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
src,
src_length,
kCFAllocatorNull);
CFStringGetCString(string,
dest,
dest_sz,
kCFStringEncodingUTF8);
CFRelease(string);
}
static inline GLFWbool
is_ascii_control_char(char x) {
return x == 0 || (1 <= x && x <= 31) || x == 127;
}
- (void)keyDown:(NSEvent *)event
{
const int key = translateKey([event keyCode]);
const int mods = translateFlags([event modifierFlags]);
_glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
const unsigned int scancode = [event keyCode];
const NSUInteger flags = [event modifierFlags];
const int mods = translateFlags(flags);
const int key = translateKey(scancode, GLFW_TRUE);
const GLFWbool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode) != 1;
_glfw.ns.text[0] = 0;
if (!_glfw.ns.unicodeData) {
// Using the cocoa API for key handling is disabled, as there is no
// reliable way to handle dead keys using it. Only use it if the
// keyboard unicode data is not available.
if (process_text) {
// this will call insertText with the text for this event, if any
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
} else {
static UniChar text[256];
UniCharCount char_count = 0;
if (UCKeyTranslate(
[(NSData*) _glfw.ns.unicodeData bytes],
scancode,
kUCKeyActionDown,
convert_cocoa_to_carbon_modifiers(flags),
LMGetKbdType(),
(process_text ? 0 : kUCKeyTranslateNoDeadKeysMask),
&(window->ns.deadKeyState),
sizeof(text)/sizeof(text[0]),
&char_count,
text
) != noErr) {
debug_key(@"UCKeyTranslate failed for scancode: 0x%x (%s) %s\n", scancode, safe_name_for_scancode(scancode), format_mods(mods));
window->ns.deadKeyState = 0;
return;
}
if (process_text) {
// We check if cocoa wants to insert text, as UCKeyTranslate
// inserts text even when the cmd key is pressed. For instance,
// cmd+a will result in the text a.
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
_glfw.ns.text[0] = 0;
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
} else {
window->ns.deadKeyState = 0;
}
if (window->ns.deadKeyState && char_count == 0) {
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
return;
}
}
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
scancode, safe_name_for_scancode(scancode), format_mods(mods),
format_text(_glfw.ns.text), _glfwGetKeyName(key));
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
}
- (void)flagsChanged:(NSEvent *)event
@@ -629,7 +845,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
int action;
const unsigned int modifierFlags =
[event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
const int key = translateKey([event keyCode]);
const int key = translateKey([event keyCode], GLFW_FALSE);
const int mods = translateFlags(modifierFlags);
const NSUInteger keyFlag = translateKeyToModifierFlag(key);
@@ -643,14 +859,14 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
else
action = GLFW_RELEASE;
_glfwInputKey(window, key, [event keyCode], action, mods);
_glfwInputKeyboard(window, key, [event keyCode], action, mods, "", 0);
}
- (void)keyUp:(NSEvent *)event
{
const int key = translateKey([event keyCode]);
const int key = translateKey([event keyCode], GLFW_TRUE);
const int mods = translateFlags([event modifierFlags]);
_glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods);
_glfwInputKeyboard(window, key, [event keyCode], GLFW_RELEASE, mods, "", 0);
}
- (void)scrollWheel:(NSEvent *)event
@@ -785,25 +1001,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
{
NSString* characters;
NSEvent* event = [NSApp currentEvent];
const int mods = translateFlags([event modifierFlags]);
const int plain = !(mods & GLFW_MOD_SUPER);
if ([string isKindOfClass:[NSAttributedString class]])
characters = [string string];
else
characters = (NSString*) string;
NSUInteger i, length = [characters length];
for (i = 0; i < length; i++)
{
const unichar codepoint = [characters characterAtIndex:i];
if ((codepoint & 0xff00) == 0xf700)
continue;
_glfwInputChar(window, codepoint, mods, plain);
}
snprintf(_glfw.ns.text, sizeof(_glfw.ns.text), "%s", [characters UTF8String]);
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
}
- (void)doCommandBySelector:(SEL)selector
@@ -1136,6 +1339,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
window->ns.deadKeyState = 0;
if (!initializeAppKit())
return GLFW_FALSE;
@@ -1358,7 +1562,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
[NSApp requestUserAttention:NSInformationalRequest];
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
NSBeep();
return GLFW_TRUE;
@@ -1635,26 +1839,8 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
{
if (mode == GLFW_CURSOR_DISABLED)
{
_glfw.ns.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
&_glfw.ns.restoreCursorPosX,
&_glfw.ns.restoreCursorPosY);
centerCursor(window);
CGAssociateMouseAndMouseCursorPosition(false);
}
else if (_glfw.ns.disabledCursorWindow == window)
{
_glfw.ns.disabledCursorWindow = NULL;
CGAssociateMouseAndMouseCursorPosition(true);
_glfwPlatformSetCursorPos(window,
_glfw.ns.restoreCursorPosX,
_glfw.ns.restoreCursorPosY);
}
if (cursorInClientArea(window))
updateCursorImage(window);
if (_glfwPlatformWindowFocused(window))
updateCursorMode(window);
}
const char* _glfwPlatformGetScancodeName(int scancode)
@@ -1680,16 +1866,7 @@ const char* _glfwPlatformGetScancodeName(int scancode)
if (!characterCount)
return NULL;
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
characters,
characterCount,
kCFAllocatorNull);
CFStringGetCString(string,
_glfw.ns.keyName,
sizeof(_glfw.ns.keyName),
kCFStringEncodingUTF8);
CFRelease(string);
convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName));
return _glfw.ns.keyName;
}
@@ -1871,6 +2048,7 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
[window->ns.view setWantsLayer:YES];
memset(&sci, 0, sizeof(sci));
@@ -1903,3 +2081,10 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
return window->ns.object;
}
GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
GLFWcocoatextinputfilterfun previous = window->ns.textInputFilterCallback;
window->ns.textInputFilterCallback = callback;
return previous;
}

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,8 +46,11 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
ans.sources = sinfo['common']['sources'] + sinfo[module]['sources']
ans.all_headers = [x for x in os.listdir(base) if x.endswith('.h')]
if module in ('x11', 'wayland'):
at_least_version('xkbcommon', 0, 5)
if module == 'x11':
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11'.split():
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
ans.ldpaths.extend(pkg_config(dep, '--libs'))
@@ -94,19 +101,20 @@ def collect_source_information():
).group(1).strip().split()
)
wayland_protocols = re.search('WaylandProtocols\s+(\S+)\s+', mraw).group(1)
wayland_protocols = re.search(r'WaylandProtocols\s+(\S+)\s+', mraw).group(1)
wayland_protocols = list(map(int, wayland_protocols.split('.')))
ans = {
'common': dict(extract_sources('common')),
'wayland_protocols': wayland_protocols,
}
joystick = 'null' if is_bsd else 'linux'
for group in 'cocoa win32 x11 wayland osmesa'.split():
m = re.search('_GLFW_' + group.upper(), raw)
ans[group] = dict(extract_sources('glfw', m.start()))
if group == 'x11':
ans[group]['headers'].append('linux_joystick.h')
ans[group]['sources'].append('linux_joystick.c')
elif group == 'wayland':
if group in ('x11', 'wayland'):
ans[group]['headers'].append('{}_joystick.h'.format(joystick))
ans[group]['sources'].append('{}_joystick.c'.format(joystick))
if group == 'wayland':
ans[group]['protocols'] = p = []
for m in re.finditer(r'WAYLAND_PROTOCOLS_PKGDATADIR\}/(.+?)"?$', raw, flags=re.M):
p.append(m.group(1))
@@ -188,6 +196,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
for line in '''\
void* glfwGetCocoaWindow(GLFWwindow* window)
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)
void glfwSetX11SelectionString(const char* string)
@@ -204,6 +213,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
{}
{}

193
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.
*
@@ -3188,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
@@ -3209,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.
@@ -4236,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.
*
@@ -4724,7 +4582,7 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid);
* This function may be called from the joystick callback, even for a joystick
* that is being disconnected.
*
* @param[in] joystick The joystick whose pointer to set.
* @param[in] jid The joystick whose pointer to set.
* @param[in] pointer The new value.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
@@ -4749,7 +4607,7 @@ GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer);
* This function may be called from the joystick callback, even for a joystick
* that is being disconnected.
*
* @param[in] joystick The joystick whose pointer to return.
* @param[in] jid The joystick whose pointer to return.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
@@ -4901,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.
@@ -5546,4 +5404,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window
#endif
#endif /* _glfw3_h_ */

56
glfw/init.c vendored
View File

@@ -57,37 +57,6 @@ static _GLFWinitconfig _glfwInitHints =
}
};
// Returns a generic string representation of the specified error
//
static const char* getErrorString(int code)
{
switch (code)
{
case GLFW_NOT_INITIALIZED:
return "The GLFW library is not initialized";
case GLFW_NO_CURRENT_CONTEXT:
return "There is no current context";
case GLFW_INVALID_ENUM:
return "Invalid argument for enum parameter";
case GLFW_INVALID_VALUE:
return "Invalid value for parameter";
case GLFW_OUT_OF_MEMORY:
return "Out of memory";
case GLFW_API_UNAVAILABLE:
return "The requested API is unavailable";
case GLFW_VERSION_UNAVAILABLE:
return "The requested API version is unavailable";
case GLFW_PLATFORM_ERROR:
return "An undocumented platform-specific error occurred";
case GLFW_FORMAT_UNAVAILABLE:
return "The requested format is unavailable";
case GLFW_NO_WINDOW_CONTEXT:
return "The specified window has no context";
default:
return "ERROR: UNKNOWN GLFW ERROR";
}
}
// Terminate the library
//
static void terminate(void)
@@ -173,7 +142,30 @@ void _glfwInputError(int code, const char* format, ...)
description[sizeof(description) - 1] = '\0';
}
else
strcpy(description, getErrorString(code));
{
if (code == GLFW_NOT_INITIALIZED)
strcpy(description, "The GLFW library is not initialized");
else if (code == GLFW_NO_CURRENT_CONTEXT)
strcpy(description, "There is no current context");
else if (code == GLFW_INVALID_ENUM)
strcpy(description, "Invalid argument for enum parameter");
else if (code == GLFW_INVALID_VALUE)
strcpy(description, "Invalid value for parameter");
else if (code == GLFW_OUT_OF_MEMORY)
strcpy(description, "Out of memory");
else if (code == GLFW_API_UNAVAILABLE)
strcpy(description, "The requested API is unavailable");
else if (code == GLFW_VERSION_UNAVAILABLE)
strcpy(description, "The requested API version is unavailable");
else if (code == GLFW_PLATFORM_ERROR)
strcpy(description, "A platform-specific error occurred");
else if (code == GLFW_FORMAT_UNAVAILABLE)
strcpy(description, "The requested format is unavailable");
else if (code == GLFW_NO_WINDOW_CONTEXT)
strcpy(description, "The specified window has no context");
else
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
}
if (_glfw.initialized)
{

191
glfw/input.c vendored
View File

@@ -255,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)
{
@@ -278,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);
}
}
@@ -454,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 //////
@@ -509,9 +619,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
_glfwPlatformGetCursorPos(window,
&window->virtualCursorPosX,
&window->virtualCursorPosY);
if (_glfwPlatformWindowFocused(window))
_glfwPlatformSetCursorMode(window, value);
_glfwPlatformSetCursorMode(window, value);
}
else if (mode == GLFW_STICKY_KEYS)
{
@@ -791,33 +899,13 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
_glfwPlatformSetCursor(window, cursor);
}
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboardfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.key, cbfun);
return cbfun;
}
GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* handle, GLFWcharfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.character, cbfun);
return cbfun;
}
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmodsfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.charmods, cbfun);
_GLFW_SWAP_POINTERS(window->callbacks.keyboard, cbfun);
return cbfun;
}
@@ -1305,4 +1393,3 @@ GLFWAPI uint64_t glfwGetTimerFrequency(void)
_GLFW_REQUIRE_INIT_OR_RETURN(0);
return _glfwPlatformGetTimerFrequency();
}

15
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;
@@ -653,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,
@@ -711,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);
@@ -763,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);

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*/
}

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,12 +55,12 @@
"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",
@@ -73,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": [
@@ -108,7 +108,7 @@
"x11": {
"headers": [
"x11_platform.h",
"xkb_unicode.h",
"xkb_glfw.h",
"posix_time.h",
"posix_thread.h",
"glx_context.h",
@@ -120,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",

View File

@@ -120,6 +120,8 @@ typedef struct
HRGN hRgnBlur;
BOOL fTransitionOnMaximized;
} DWM_BLURBEHIND;
#else
#include <dwmapi.h>
#endif /*Windows Vista*/
#ifndef DPI_ENUMS_DECLARED

135
glfw/win32_window.c vendored
View File

@@ -235,26 +235,6 @@ static void centerCursor(_GLFWwindow* window)
_glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0);
}
// Returns whether the cursor is in the client area of the specified window
//
static GLFWbool cursorInClientArea(_GLFWwindow* window)
{
RECT area;
POINT pos;
if (!GetCursorPos(&pos))
return GLFW_FALSE;
if (WindowFromPoint(pos) != window->win32.handle)
return GLFW_FALSE;
GetClientRect(window->win32.handle, &area);
ClientToScreen(window->win32.handle, (POINT*) &area.left);
ClientToScreen(window->win32.handle, (POINT*) &area.right);
return PtInRect(&area, pos);
}
// Updates the cursor image according to its cursor mode
//
static void updateCursorImage(_GLFWwindow* window)
@@ -286,6 +266,67 @@ static void updateClipRect(_GLFWwindow* window)
ClipCursor(NULL);
}
// Apply disabled cursor mode to a focused window
//
static void disableCursor(_GLFWwindow* window)
{
const RAWINPUTDEVICE rid = { 0x01, 0x02, 0, window->win32.handle };
_glfw.win32.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
&_glfw.win32.restoreCursorPosX,
&_glfw.win32.restoreCursorPosY);
updateCursorImage(window);
centerCursor(window);
updateClipRect(window);
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"Win32: Failed to register raw input device");
}
}
// Exit disabled cursor mode for the specified window
//
static void enableCursor(_GLFWwindow* window)
{
const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
_glfw.win32.disabledCursorWindow = NULL;
updateClipRect(NULL);
_glfwPlatformSetCursorPos(window,
_glfw.win32.restoreCursorPosX,
_glfw.win32.restoreCursorPosY);
updateCursorImage(window);
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"Win32: Failed to remove raw input device");
}
}
// Returns whether the cursor is in the client area of the specified window
//
static GLFWbool cursorInClientArea(_GLFWwindow* window)
{
RECT area;
POINT pos;
if (!GetCursorPos(&pos))
return GLFW_FALSE;
if (WindowFromPoint(pos) != window->win32.handle)
return GLFW_FALSE;
GetClientRect(window->win32.handle, &area);
ClientToScreen(window->win32.handle, (POINT*) &area.left);
ClientToScreen(window->win32.handle, (POINT*) &area.right);
return PtInRect(&area, pos);
}
// Update native window styles to match attributes
//
static void updateWindowStyles(const _GLFWwindow* window)
@@ -575,7 +616,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
if (lParam == 0 && window->win32.frameAction)
{
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
disableCursor(window);
window->win32.frameAction = GLFW_FALSE;
}
@@ -593,7 +634,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
break;
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
disableCursor(window);
return 0;
}
@@ -601,7 +642,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
case WM_KILLFOCUS:
{
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
enableCursor(window);
if (window->monitor && window->autoIconify)
_glfwPlatformIconifyWindow(window);
@@ -857,10 +898,10 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
case WM_ENTERSIZEMOVE:
case WM_ENTERMENULOOP:
{
// HACK: Postpone cursor disabling while the user is moving or
// resizing the window or using the menu
// HACK: Enable the cursor while the user is moving or
// resizing the window or using the window menu
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
enableCursor(window);
break;
}
@@ -871,7 +912,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
// HACK: Disable the cursor once the user is done moving or
// resizing the window or using the menu
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
disableCursor(window);
break;
}
@@ -1474,9 +1515,9 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
FlashWindow(window->win32.handle, TRUE);
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
return MessageBeep(0xFFFFFFFF & param) ? GLFW_TRUE : GLFW_FALSE;
return MessageBeep(0xFFFFFFFF) ? GLFW_TRUE : GLFW_FALSE;
}
void _glfwPlatformFocusWindow(_GLFWwindow* window)
@@ -1777,39 +1818,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
{
if (mode == GLFW_CURSOR_DISABLED)
{
const RAWINPUTDEVICE rid = { 0x01, 0x02, 0, window->win32.handle };
_glfw.win32.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
&_glfw.win32.restoreCursorPosX,
&_glfw.win32.restoreCursorPosY);
centerCursor(window);
updateClipRect(window);
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"Win32: Failed to register raw input device");
}
if (_glfwPlatformWindowFocused(window))
disableCursor(window);
}
else if (_glfw.win32.disabledCursorWindow == window)
{
const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
_glfw.win32.disabledCursorWindow = NULL;
updateClipRect(NULL);
_glfwPlatformSetCursorPos(window,
_glfw.win32.restoreCursorPosX,
_glfw.win32.restoreCursorPosY);
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"Win32: Failed to remove raw input device");
}
}
if (cursorInClientArea(window))
enableCursor(window);
else if (cursorInClientArea(window))
updateCursorImage(window);
}
@@ -2014,4 +2028,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();
}

425
glfw/wl_init.c vendored
View File

@@ -32,7 +32,6 @@
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <unistd.h>
#include <wayland-client.h>
@@ -350,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)
{
@@ -372,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,
@@ -482,65 +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 GLFWbool 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);
}
}
return xkb_keymap_key_repeats(_glfw.wl.xkb.keymap, syms[0]);
}
static void keyboardHandleKey(void* data,
struct wl_keyboard* keyboard,
uint32_t serial,
@@ -548,37 +411,19 @@ static void keyboardHandleKey(void* data,
uint32_t key,
uint32_t state)
{
int keyCode;
int action;
_GLFWwindow* window = _glfw.wl.keyboardFocus;
GLFWbool shouldRepeat;
struct itimerspec timer = {};
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)
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
{
shouldRepeat = inputChar(window, key);
if (shouldRepeat && _glfw.wl.keyboardRepeatRate > 0)
{
_glfw.wl.keyboardLastKey = keyCode;
_glfw.wl.keyboardLastScancode = key;
timer.it_interval.tv_sec = _glfw.wl.keyboardRepeatRate / 1000;
timer.it_interval.tv_nsec = (_glfw.wl.keyboardRepeatRate % 1000) * 1000000;
timer.it_value.tv_sec = _glfw.wl.keyboardRepeatDelay / 1000;
timer.it_value.tv_nsec = (_glfw.wl.keyboardRepeatDelay % 1000) * 1000000;
}
_glfw.wl.keyRepeatInfo.key = key;
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
}
timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL);
}
static void keyboardHandleModifiers(void* data,
@@ -589,41 +434,9 @@ static void keyboardHandleModifiers(void* data,
uint32_t modsLocked,
uint32_t group)
{
xkb_mod_mask_t mask;
unsigned int modifiers = 0;
if (!_glfw.wl.xkb.keymap)
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_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
}
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION
static void keyboardHandleRepeatInfo(void* data,
struct wl_keyboard* keyboard,
int32_t rate,
@@ -635,7 +448,6 @@ static void keyboardHandleRepeatInfo(void* data,
_glfw.wl.keyboardRepeatRate = rate;
_glfw.wl.keyboardRepeatDelay = delay;
}
#endif
static const struct wl_keyboard_listener keyboardListener = {
keyboardHandleKeymap,
@@ -643,9 +455,7 @@ static const struct wl_keyboard_listener keyboardListener = {
keyboardHandleLeave,
keyboardHandleKey,
keyboardHandleModifiers,
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION
keyboardHandleRepeatInfo,
#endif
};
static void seatHandleCapabilities(void* data,
@@ -798,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 //////
@@ -971,54 +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.keymap_key_repeats = (PFN_xkb_keymap_key_repeats)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_key_repeats");
_glfw.wl.xkb.state_new = (PFN_xkb_state_new)
_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)
{
@@ -1030,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);
@@ -1046,15 +666,13 @@ 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();
_glfw.wl.timerfd = -1;
if (_glfw.wl.seatVersion >= 4)
_glfw.wl.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (_glfw.wl.pointer && _glfw.wl.shm)
{
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
@@ -1073,7 +691,9 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void)
{
#ifdef __linux__
_glfwTerminateJoysticksLinux();
#endif
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
@@ -1081,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);
@@ -1154,4 +760,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif
;
}

101
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,8 +43,12 @@ 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"
@@ -101,46 +101,6 @@ 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 int (* PFN_xkb_keymap_key_repeats)(struct xkb_keymap*, xkb_keycode_t);
typedef struct xkb_state* (* PFN_xkb_state_new)(struct xkb_keymap*);
typedef void (* PFN_xkb_state_unref)(struct xkb_state*);
typedef int (* PFN_xkb_state_key_get_syms)(struct xkb_state*, xkb_keycode_t, const xkb_keysym_t**);
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_keymap_key_repeats _glfw.wl.xkb.keymap_key_repeats
#define xkb_state_new _glfw.wl.xkb.state_new
#define xkb_state_unref _glfw.wl.xkb.state_unref
#define xkb_state_key_get_syms _glfw.wl.xkb.state_key_get_syms
#define xkb_state_update_mask _glfw.wl.xkb.state_update_mask
#define xkb_state_serialize_mods _glfw.wl.xkb.state_serialize_mods
#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
#define _GLFW_DECORATION_WIDTH 4
#define _GLFW_DECORATION_TOP 24
#define _GLFW_DECORATION_VERTICAL (_GLFW_DECORATION_TOP + _GLFW_DECORATION_WIDTH)
@@ -241,52 +201,12 @@ typedef struct _GLFWlibraryWayland
int32_t keyboardRepeatRate;
int32_t keyboardRepeatDelay;
int keyboardLastKey;
int keyboardLastScancode;
int timerfd;
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
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_keymap_key_repeats keymap_key_repeats;
PFN_xkb_state_new state_new;
PFN_xkb_state_unref state_unref;
PFN_xkb_state_key_get_syms state_key_get_syms;
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;
@@ -336,4 +256,3 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version);

66
glfw/wl_window.c vendored
View File

@@ -35,7 +35,6 @@
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/timerfd.h>
#include <poll.h>
@@ -690,16 +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
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 },
{ _glfw.wl.timerfd, POLLIN },
};
ssize_t read_ret;
uint64_t repeats, i;
struct pollfd pfd = { wl_display_get_fd(display), POLLIN };
while (wl_display_prepare_read(display) != 0)
wl_display_dispatch_pending(display);
@@ -719,9 +734,12 @@ handleEvents(int timeout)
return;
}
if (poll(fds, 2, timeout) > 0)
dispatchPendingKeyRepeats();
timeout = adjustTimeoutForKeyRepeat(timeout);
if (poll(&pfd, 1, timeout) > 0)
{
if (fds[0].revents & POLLIN)
if (pfd.revents & POLLIN)
{
wl_display_read_events(display);
wl_display_dispatch_pending(display);
@@ -731,22 +749,12 @@ handleEvents(int timeout)
wl_display_cancel_read(display);
}
if (fds[1].revents & POLLIN)
{
read_ret = read(_glfw.wl.timerfd, &repeats, sizeof(repeats));
if (read_ret != 8)
return;
for (i = 0; i < repeats; ++i)
_glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey,
_glfw.wl.keyboardLastScancode, GLFW_REPEAT,
_glfw.wl.xkb.modifiers);
}
}
else
{
wl_display_cancel_read(display);
}
dispatchPendingKeyRepeats();
}
// Translates a GLFW standard cursor to a theme cursor name
@@ -1079,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;
}
@@ -1230,13 +1242,12 @@ 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,
@@ -1547,4 +1558,3 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return window->wl.surface;
}

447
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,
@@ -479,7 +166,11 @@ static GLFWbool initExtensions(void)
&_glfw.x11.vidmode.errorBase);
}
#if defined(__CYGWIN__)
_glfw.x11.xi.handle = _glfw_dlopen("libXi-6.so");
#else
_glfw.x11.xi.handle = _glfw_dlopen("libXi.so.6");
#endif
if (_glfw.x11.xi.handle)
{
_glfw.x11.xi.QueryVersion = (PFN_XIQueryVersion)
@@ -505,7 +196,11 @@ static GLFWbool initExtensions(void)
}
}
#if defined(__CYGWIN__)
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr-2.so");
#else
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr.so.2");
#endif
if (_glfw.x11.randr.handle)
{
_glfw.x11.randr.AllocGamma = (PFN_XRRAllocGamma)
@@ -593,7 +288,11 @@ static GLFWbool initExtensions(void)
RROutputChangeNotifyMask);
}
#if defined(__CYGWIN__)
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor-1.so");
#else
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor.so.1");
#endif
if (_glfw.x11.xcursor.handle)
{
_glfw.x11.xcursor.ImageCreate = (PFN_XcursorImageCreate)
@@ -604,7 +303,11 @@ static GLFWbool initExtensions(void)
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
}
#if defined(__CYGWIN__)
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama-1.so");
#else
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama.so.1");
#endif
if (_glfw.x11.xinerama.handle)
{
_glfw.x11.xinerama.IsActive = (PFN_XineramaIsActive)
@@ -623,35 +326,11 @@ static GLFWbool initExtensions(void)
}
}
_glfw.x11.xkb.major = 1;
_glfw.x11.xkb.minor = 0;
_glfw.x11.xkb.available =
XkbQueryExtension(_glfw.x11.display,
&_glfw.x11.xkb.majorOpcode,
&_glfw.x11.xkb.eventBase,
&_glfw.x11.xkb.errorBase,
&_glfw.x11.xkb.major,
&_glfw.x11.xkb.minor);
if (_glfw.x11.xkb.available)
{
Bool supported;
if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported))
{
if (supported)
_glfw.x11.xkb.detectable = GLFW_TRUE;
}
}
_glfw.x11.x11xcb.handle = _glfw_dlopen("libX11-xcb.so.1");
if (_glfw.x11.x11xcb.handle)
{
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
_glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
}
#if defined(__CYGWIN__)
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender-1.so");
#else
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1");
#endif
if (_glfw.x11.xrender.handle)
{
_glfw.x11.xrender.QueryExtension = (PFN_XRenderQueryExtension)
@@ -674,10 +353,31 @@ static GLFWbool initExtensions(void)
}
}
// Update the key code LUT
// FIXME: We should listen to XkbMapNotify events to track changes to
// the keyboard mapping.
createKeyTables();
_glfw.x11.xkb.major = 1;
_glfw.x11.xkb.minor = 0;
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
&_glfw.x11.xkb.majorOpcode,
&_glfw.x11.xkb.eventBase,
&_glfw.x11.xkb.errorBase,
&_glfw.x11.xkb.major,
&_glfw.x11.xkb.minor);
if (!_glfw.x11.xkb.available)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load Xkb extension");
return GLFW_FALSE;
}
Bool supported;
if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported))
{
if (supported)
_glfw.x11.xkb.detectable = GLFW_TRUE;
}
if (!glfw_xkb_set_x11_events_mask()) return GLFW_FALSE;
if (!glfw_xkb_create_context(&_glfw.x11.xkb)) return GLFW_FALSE;
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return GLFW_FALSE;
if (!glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL)) return GLFW_FALSE;
// Detect whether an EWMH-conformant window manager is running
detectEWMH();
@@ -896,16 +596,6 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot)
int _glfwPlatformInit(void)
{
#if !defined(X_HAVE_UTF8_STRING)
// HACK: If the current locale is "C" and the Xlib UTF-8 functions are
// unavailable, apply the environment's locale in the hope that it's
// both available and not "C"
// This is done because the "C" locale breaks wide character input,
// which is what we fall back on when UTF-8 support is missing
if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0)
setlocale(LC_CTYPE, "");
#endif
XInitThreads();
XrmInitialize();
@@ -939,21 +629,6 @@ int _glfwPlatformInit(void)
_glfw.x11.helperWindowHandle = createHelperWindow();
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
if (XSupportsLocale())
{
XSetLocaleModifiers("");
_glfw.x11.im = XOpenIM(_glfw.x11.display, 0, NULL, NULL);
if (_glfw.x11.im)
{
if (!hasUsableInputMethodStyle())
{
XCloseIM(_glfw.x11.im);
_glfw.x11.im = NULL;
}
}
}
#if defined(__linux__)
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
@@ -985,27 +660,16 @@ void _glfwPlatformTerminate(void)
_glfw.x11.hiddenCursorHandle = (Cursor) 0;
}
glfw_xkb_release(&_glfw.x11.xkb);
free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString);
if (_glfw.x11.im)
{
XCloseIM(_glfw.x11.im);
_glfw.x11.im = NULL;
}
if (_glfw.x11.display)
{
XCloseDisplay(_glfw.x11.display);
_glfw.x11.display = NULL;
}
if (_glfw.x11.x11xcb.handle)
{
_glfw_dlclose(_glfw.x11.x11xcb.handle);
_glfw.x11.x11xcb.handle = NULL;
}
if (_glfw.x11.xcursor.handle)
{
_glfw_dlclose(_glfw.x11.xcursor.handle);
@@ -1024,6 +688,24 @@ void _glfwPlatformTerminate(void)
_glfw.x11.xinerama.handle = NULL;
}
if (_glfw.x11.xrender.handle)
{
_glfw_dlclose(_glfw.x11.xrender.handle);
_glfw.x11.xrender.handle = NULL;
}
if (_glfw.x11.vidmode.handle)
{
_glfw_dlclose(_glfw.x11.vidmode.handle);
_glfw.x11.vidmode.handle = NULL;
}
if (_glfw.x11.xi.handle)
{
_glfw_dlclose(_glfw.x11.xi.handle);
_glfw.x11.xi.handle = NULL;
}
// NOTE: These need to be unloaded after XCloseDisplay, as they register
// cleanup callbacks that get called by that function
_glfwTerminateEGL();
@@ -1050,4 +732,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif
;
}

36
glfw/x11_platform.h vendored
View File

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

334
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)
@@ -571,6 +535,61 @@ static void updateCursorImage(_GLFWwindow* window)
}
}
// Apply disabled cursor mode to a focused window
//
static void disableCursor(_GLFWwindow* window)
{
if (_glfw.x11.xi.available)
{
XIEventMask em;
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISetMask(mask, XI_RawMotion);
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
_glfw.x11.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
&_glfw.x11.restoreCursorPosX,
&_glfw.x11.restoreCursorPosY);
updateCursorImage(window);
centerCursor(window);
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
window->x11.handle,
_glfw.x11.hiddenCursorHandle,
CurrentTime);
}
// Exit disabled cursor mode for the specified window
//
static void enableCursor(_GLFWwindow* window)
{
if (_glfw.x11.xi.available)
{
XIEventMask em;
unsigned char mask[] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
_glfw.x11.disabledCursorWindow = NULL;
XUngrabPointer(_glfw.x11.display, CurrentTime);
_glfwPlatformSetCursorPos(window,
_glfw.x11.restoreCursorPosX,
_glfw.x11.restoreCursorPosY);
updateCursorImage(window);
}
// Create the X11 window (and its colormap)
//
static GLFWbool createNativeWindow(_GLFWwindow* window,
@@ -746,19 +765,6 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
}
_glfwPlatformSetWindowTitle(window, wndconfig->title);
if (_glfw.x11.im)
{
window->x11.ic = XCreateIC(_glfw.x11.im,
XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
XNClientWindow,
window->x11.handle,
XNFocusWindow,
window->x11.handle,
NULL);
}
_glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos);
_glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height);
@@ -1141,16 +1147,8 @@ static void releaseMonitor(_GLFWwindow* window)
static void processEvent(XEvent *event)
{
_GLFWwindow* window = NULL;
int keycode = 0;
Bool filtered = False;
// HACK: Save scancode as some IMs clear the field in XFilterEvent
if (event->type == KeyPress || event->type == KeyRelease)
keycode = event->xkey.keycode;
if (_glfw.x11.im)
filtered = XFilterEvent(event, None);
if (_glfw.x11.randr.available)
{
if (event->type == _glfw.x11.randr.eventBase + RRNotify)
@@ -1208,6 +1206,30 @@ static void processEvent(XEvent *event)
handleSelectionRequest(event);
return;
}
else if (event->type == _glfw.x11.xkb.eventBase)
{
XkbEvent *kb_event = (XkbEvent*)event;
switch(kb_event->any.xkb_type) {
case XkbNewKeyboardNotify:
if(!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
/* fallthrough */
case XkbMapNotify:
{
glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL);
return;
}
case XkbStateNotify:
{
XkbStateNotifyEvent *state_event = (XkbStateNotifyEvent*)kb_event;
glfw_xkb_update_modifiers(
&_glfw.x11.xkb, state_event->base_mods, state_event->latched_mods,
state_event->locked_mods, state_event->base_group, state_event->latched_group, state_event->locked_group
);
return;
}
}
return;
}
window = findWindowByHandle(event->xany.window);
if (window == NULL)
@@ -1220,105 +1242,12 @@ static void processEvent(XEvent *event)
{
case KeyPress:
{
const int key = translateKey(keycode);
const int mods = translateState(event->xkey.state);
const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT));
if (window->x11.ic)
{
// HACK: Ignore duplicate key press events generated by ibus
// These have the same timestamp as the original event
// Corresponding release events are filtered out
// implicitly by the GLFW key repeat logic
if (window->x11.lastKeyTime < event->xkey.time)
{
if (keycode)
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
window->x11.lastKeyTime = event->xkey.time;
}
if (!filtered)
{
int count;
Status status;
#if defined(X_HAVE_UTF8_STRING)
char buffer[100];
char* chars = buffer;
count = Xutf8LookupString(window->x11.ic,
&event->xkey,
buffer, sizeof(buffer) - 1,
NULL, &status);
if (status == XBufferOverflow)
{
chars = calloc(count + 1, 1);
count = Xutf8LookupString(window->x11.ic,
&event->xkey,
chars, count,
NULL, &status);
}
if (status == XLookupChars || status == XLookupBoth)
{
const char* c = chars;
chars[count] = '\0';
while (c - chars < count)
_glfwInputChar(window, decodeUTF8(&c), mods, plain);
}
#else /*X_HAVE_UTF8_STRING*/
wchar_t buffer[16];
wchar_t* chars = buffer;
count = XwcLookupString(window->x11.ic,
&event->xkey,
buffer,
sizeof(buffer) / sizeof(wchar_t),
NULL,
&status);
if (status == XBufferOverflow)
{
chars = calloc(count, sizeof(wchar_t));
count = XwcLookupString(window->x11.ic,
&event->xkey,
chars, count,
NULL, &status);
}
if (status == XLookupChars || status == XLookupBoth)
{
int i;
for (i = 0; i < count; i++)
_glfwInputChar(window, chars[i], mods, plain);
}
#endif /*X_HAVE_UTF8_STRING*/
if (chars != buffer)
free(chars);
}
}
else
{
KeySym keysym;
XLookupString(&event->xkey, NULL, 0, &keysym, NULL);
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
const long character = _glfwKeySym2Unicode(keysym);
if (character != -1)
_glfwInputChar(window, character, mods, plain);
}
glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_PRESS);
return;
}
case KeyRelease:
{
const int key = translateKey(keycode);
const int mods = translateState(event->xkey.state);
if (!_glfw.x11.xkb.detectable)
{
// HACK: Key repeat events will arrive as KeyRelease/KeyPress
@@ -1332,7 +1261,7 @@ static void processEvent(XEvent *event)
if (next.type == KeyPress &&
next.xkey.window == event->xkey.window &&
next.xkey.keycode == keycode)
next.xkey.keycode == event->xkey.keycode)
{
// HACK: The time of repeat events sometimes doesn't
// match that of the press event, so add an
@@ -1351,7 +1280,7 @@ static void processEvent(XEvent *event)
}
}
_glfwInputKey(window, key, keycode, GLFW_RELEASE, mods);
glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_RELEASE);
return;
}
@@ -1432,7 +1361,7 @@ static void processEvent(XEvent *event)
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
// ignore the defined cursor for hidden cursor mode
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_HIDDEN);
updateCursorImage(window);
_glfwInputCursorEnter(window, GLFW_TRUE);
return;
@@ -1725,7 +1654,7 @@ static void processEvent(XEvent *event)
case FocusIn:
{
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
disableCursor(window);
if (event->xfocus.mode == NotifyGrab ||
event->xfocus.mode == NotifyUngrab)
@@ -1735,9 +1664,6 @@ static void processEvent(XEvent *event)
return;
}
if (window->x11.ic)
XSetICFocus(window->x11.ic);
_glfwInputWindowFocus(window, GLFW_TRUE);
return;
}
@@ -1745,7 +1671,7 @@ static void processEvent(XEvent *event)
case FocusOut:
{
if (window->cursorMode == GLFW_CURSOR_DISABLED)
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
enableCursor(window);
if (event->xfocus.mode == NotifyGrab ||
event->xfocus.mode == NotifyUngrab)
@@ -1755,9 +1681,6 @@ static void processEvent(XEvent *event)
return;
}
if (window->x11.ic)
XUnsetICFocus(window->x11.ic);
if (window->monitor && window->autoIconify)
_glfwPlatformIconifyWindow(window);
@@ -1989,12 +1912,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->monitor)
releaseMonitor(window);
if (window->x11.ic)
{
XDestroyIC(window->x11.ic);
window->x11.ic = NULL;
}
if (window->context.destroy)
window->context.destroy(window);
@@ -2340,9 +2257,9 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
0, 1, 0);
}
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
int _glfwPlatformWindowBell(_GLFWwindow* window)
{
return XkbBell(_glfw.x11.display, window->x11.handle, (int)param, (Atom)0) ? GLFW_TRUE : GLFW_FALSE;
return XkbBell(_glfw.x11.display, window->x11.handle, 100, (Atom)0) ? GLFW_TRUE : GLFW_FALSE;
}
void _glfwPlatformFocusWindow(_GLFWwindow* window)
@@ -2713,80 +2630,26 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
{
if (mode == GLFW_CURSOR_DISABLED)
{
if (_glfw.x11.xi.available)
{
XIEventMask em;
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISetMask(mask, XI_RawMotion);
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
_glfw.x11.disabledCursorWindow = window;
_glfwPlatformGetCursorPos(window,
&_glfw.x11.restoreCursorPosX,
&_glfw.x11.restoreCursorPosY);
centerCursor(window);
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
GrabModeAsync, GrabModeAsync,
window->x11.handle,
_glfw.x11.hiddenCursorHandle,
CurrentTime);
if (_glfwPlatformWindowFocused(window))
disableCursor(window);
}
else if (_glfw.x11.disabledCursorWindow == window)
{
if (_glfw.x11.xi.available)
{
XIEventMask em;
unsigned char mask[] = { 0 };
enableCursor(window);
else
updateCursorImage(window);
em.deviceid = XIAllMasterDevices;
em.mask_len = sizeof(mask);
em.mask = mask;
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
}
_glfw.x11.disabledCursorWindow = NULL;
XUngrabPointer(_glfw.x11.display, CurrentTime);
_glfwPlatformSetCursorPos(window,
_glfw.x11.restoreCursorPosX,
_glfw.x11.restoreCursorPosY);
}
updateCursorImage(window);
XFlush(_glfw.x11.display);
}
const char* _glfwPlatformGetScancodeName(int scancode)
{
if (!_glfw.x11.xkb.available)
return NULL;
const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0);
if (keysym == NoSymbol)
return NULL;
const long ch = _glfwKeySym2Unicode(keysym);
if (ch == -1)
return NULL;
const size_t count = encodeUTF8(_glfw.x11.keyName, (unsigned int) ch);
if (count == 0)
return NULL;
_glfw.x11.keyName[count] = '\0';
return _glfw.x11.keyName;
return glfw_xkb_keysym_name(scancode);
}
int _glfwPlatformGetKeyScancode(int key)
{
return _glfw.x11.scancodes[key];
return glfw_xkb_sym_for_key(key);
}
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
@@ -2857,7 +2720,7 @@ void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
if (!_glfw.vk.KHR_surface)
return;
if (!_glfw.vk.KHR_xcb_surface || !_glfw.x11.x11xcb.handle)
if (!_glfw.vk.KHR_xcb_surface)
{
if (!_glfw.vk.KHR_xlib_surface)
return;
@@ -2867,7 +2730,7 @@ void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
// NOTE: VK_KHR_xcb_surface is preferred due to some early ICDs exposing but
// not correctly implementing VK_KHR_xlib_surface
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
if (_glfw.vk.KHR_xcb_surface)
extensions[1] = "VK_KHR_xcb_surface";
else
extensions[1] = "VK_KHR_xlib_surface";
@@ -2880,7 +2743,7 @@ int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display,
_glfw.x11.screen));
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
if (_glfw.vk.KHR_xcb_surface)
{
PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR
vkGetPhysicalDeviceXcbPresentationSupportKHR =
@@ -2931,7 +2794,7 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
const VkAllocationCallbacks* allocator,
VkSurfaceKHR* surface)
{
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
if (_glfw.vk.KHR_xcb_surface)
{
VkResult err;
VkXcbSurfaceCreateInfoKHR sci;
@@ -3044,4 +2907,3 @@ GLFWAPI const char* glfwGetX11SelectionString(void)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return getSelectionString(_glfw.x11.PRIMARY);
}

427
glfw/xkb_glfw.c vendored Normal file
View File

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

83
glfw/xkb_glfw.h vendored Normal file
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)
return self
def __exit__(self, *a):
if self.history_path:
readline.write_history_file(self.history_path)
def option_text():
return '''\
--type -t
choices=line
default=line
Type of input. Defaults to asking for a line of text.
--message -m
The message to display to the user. If not specified a default
message is shown.
--name -n
The name for this question. Used to store history of previous answers which can
be used for completions and via the browse history readline bindings.
'''
def main(args):
# For some reason importing readline in a key handler in the main kitty process
# causes a crash of the python interpreter, probably because of some global
# lock
global readline
import readline as rl
readline = rl
from kitty.shell import init_readline
msg = 'Ask the user for input'
try:
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask')
except SystemExit as e:
if e.code != 0:
print(e.args[0])
input('Press enter to quit...')
raise SystemExit(e.code)
init_readline(readline)
ans = {'items': items}
with alternate_screen(), HistoryCompleter(args.name):
if args.message:
print(styled(args.message, bold=True))
prompt = '> '
try:
ans['response'] = input(prompt)
except (KeyboardInterrupt, EOFError):
pass
return ans
def handle_result(args, data, target_window_id, boss):
if 'response' in data:
func, *args = data['items']
getattr(boss, func)(data['response'], *args)
if __name__ == '__main__':
import sys
ans = main(sys.argv)
if ans:
print(ans)

0
kittens/diff/__init__.py Normal file
View File

138
kittens/diff/collect.py Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from functools import lru_cache
from hashlib import md5
from mimetypes import guess_type
path_name_map = {}
class Collection:
def __init__(self):
self.changes = {}
self.renames = {}
self.adds = set()
self.removes = set()
self.all_paths = []
self.type_map = {}
def add_change(self, left_path, right_path):
self.changes[left_path] = right_path
self.all_paths.append(left_path)
self.type_map[left_path] = 'diff'
def add_rename(self, left_path, right_path):
self.renames[left_path] = right_path
self.all_paths.append(left_path)
self.type_map[left_path] = 'rename'
def add_add(self, right_path):
self.adds.add(right_path)
self.all_paths.append(right_path)
self.type_map[right_path] = 'add'
def add_removal(self, left_path):
self.removes.add(left_path)
self.all_paths.append(left_path)
self.type_map[left_path] = 'removal'
def finalize(self):
self.all_paths.sort(key=path_name_map.get)
def __iter__(self):
for path in self.all_paths:
typ = self.type_map[path]
data = self.changes[path] if typ == 'diff' else None
yield path, self.type_map[path], data
def collect_files(collection, left, right):
left_names, right_names = set(), set()
left_path_map, right_path_map = {}, {}
def walk(base, names, pmap):
for dirpath, dirnames, filenames in os.walk(base):
for filename in filenames:
path = os.path.abspath(os.path.join(dirpath, filename))
path_name_map[path] = name = os.path.relpath(path, base)
names.add(name)
pmap[name] = path
walk(left, left_names, left_path_map), walk(right, right_names, right_path_map)
common_names = left_names & right_names
changed_names = {n for n in common_names if data_for_path(left_path_map[n]) != data_for_path(right_path_map[n])}
for n in changed_names:
collection.add_change(left_path_map[n], right_path_map[n])
removed = left_names - common_names
added = right_names - common_names
ahash = {a: hash_for_path(right_path_map[a]) for a in added}
rhash = {r: hash_for_path(left_path_map[r]) for r in removed}
for name, rh in rhash.items():
for n, ah in ahash.items():
if ah == rh and data_for_path(left_path_map[name]) == data_for_path(right_path_map[n]):
collection.add_rename(left_path_map[name], right_path_map[name])
added.discard(n)
break
else:
collection.add_removal(left_path_map[name])
for name in added:
collection.add_add(right_path_map[name])
sanitize_pat = re.compile('[\x00-\x1f\x7f\x80-\x9f]')
def sanitize_sub(m):
return '<{:x}>'.format(ord(m.group()[0]))
def sanitize(text):
return sanitize_pat.sub(sanitize_sub, text)
@lru_cache(maxsize=1024)
def mime_type_for_path(path):
return guess_type(path)[0] or 'application/octet-stream'
@lru_cache(maxsize=1024)
def data_for_path(path):
with open(path, 'rb') as f:
ans = f.read()
if not mime_type_for_path(path).startswith('image/'):
try:
ans = ans.decode('utf-8')
except UnicodeDecodeError:
pass
return ans
@lru_cache(maxsize=1024)
def lines_for_path(path):
data = data_for_path(path)
return tuple(map(sanitize, data.splitlines()))
@lru_cache(maxsize=1024)
def hash_for_path(path):
md5(data_for_path(path)).digest()
def create_collection(left, right):
collection = Collection()
if os.path.isdir(left):
collect_files(collection, left, right)
else:
pl, pr = os.path.abspath(left), os.path.abspath(right)
path_name_map[left] = pl
path_name_map[right] = pr
collection.add_change(pl, pr)
collection.finalize()
return collection

98
kittens/diff/config.py Normal file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
from kitty.config_utils import (
init_config, load_config as _load_config, merge_dicts, parse_config_base,
resolve_config, to_color
)
from kitty.constants import config_dir
from kitty.rgb import color_as_sgr
defaults = None
default_config_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'diff.conf'
)
formats = {
'title': '',
'margin': '',
'text': '',
}
def set_formats(opts):
formats['text'] = '38' + color_as_sgr(opts.foreground) + ';48' + color_as_sgr(opts.background)
formats['title'] = '38' + color_as_sgr(opts.title_fg) + ';48' + color_as_sgr(opts.title_bg) + ';1'
formats['margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.margin_bg)
formats['added_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.added_margin_bg)
formats['removed_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.removed_margin_bg)
formats['added'] = '38' + color_as_sgr(opts.foreground) + ';48' + color_as_sgr(opts.added_bg)
formats['removed'] = '38' + color_as_sgr(opts.foreground) + ';48' + color_as_sgr(opts.removed_bg)
formats['filler'] = '48' + color_as_sgr(opts.filler_bg)
formats['hunk_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_margin_bg)
formats['hunk'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_bg)
formats['removed_highlight'] = '48' + color_as_sgr(opts.highlight_removed_bg)
formats['added_highlight'] = '48' + color_as_sgr(opts.highlight_added_bg)
type_map = {}
for name in (
'foreground background title_fg title_bg margin_bg margin_fg removed_bg removed_margin_bg added_bg added_margin_bg filler_bg hunk_bg hunk_margin_bg'
' highlight_removed_bg highlight_added_bg'
).split():
type_map[name] = to_color
def special_handling(*a):
pass
def parse_config(lines, check_keys=True):
ans = {}
parse_config_base(
lines,
defaults,
type_map,
special_handling,
ans,
check_keys=check_keys
)
return ans
def merge_configs(defaults, vals):
ans = {}
for k, v in defaults.items():
if isinstance(v, dict):
newvals = vals.get(k, {})
ans[k] = merge_dicts(v, newvals)
else:
ans[k] = vals.get(k, v)
return ans
def parse_defaults(lines, check_keys=False):
return parse_config(lines, check_keys)
Options, defaults = init_config(default_config_path, parse_defaults)
def load_config(*paths, overrides=None):
return _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
SYSTEM_CONF = '/etc/xdg/kitty/diff.conf'
defconf = os.path.join(config_dir, 'diff.conf')
def init_config(args):
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
opts = load_config(*config, overrides=overrides)
set_formats(opts)
return opts

15
kittens/diff/diff.conf Normal file
View File

@@ -0,0 +1,15 @@
foreground black
background white
title_fg black
title_bg white
margin_bg #fafbfc
margin_fg #aaaaaa
removed_bg #ffeef0
highlight_removed_bg #fdb8c0
removed_margin_bg #ffdce0
added_bg #e6ffed
highlight_added_bg #acf2bd
added_margin_bg #cdffd8
filler_bg #fafbfc
hunk_margin_bg #dbedff
hunk_bg #f1f8ff

205
kittens/diff/git.py Normal file
View File

@@ -0,0 +1,205 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import concurrent.futures
import os
import subprocess
from .collect import lines_for_path
from .diff_speedup import changed_center
left_lines = right_lines = None
def run_diff(file1, file2, context=3):
# returns: ok, is_different, patch
p = subprocess.Popen([
'git', 'diff', '--no-color', '--no-ext-diff', '--exit-code', '-U' + str(context), '--no-index', '--'
] + [file1, file2],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
stdout, stderr = p.communicate()
returncode = p.wait()
if returncode in (0, 1):
return True, returncode == 1, stdout.decode('utf-8')
return False, returncode, stderr.decode('utf-8')
class Chunk:
__slots__ = ('is_context', 'left_start', 'right_start', 'left_count', 'right_count', 'centers')
def __init__(self, left_start, right_start, is_context=False):
self.is_context = is_context
self.left_start = left_start
self.right_start = right_start
self.left_count = self.right_count = 0
self.centers = None
def add_line(self):
self.right_count += 1
def remove_line(self):
self.left_count += 1
def context_line(self):
self.left_count += 1
self.right_count += 1
def finalize(self):
if not self.is_context and self.left_count == self.right_count:
self.centers = tuple(changed_center(left_lines[self.left_start + i], right_lines[self.right_start + i]) for i in range(self.left_count))
def __repr__(self):
return 'Chunk(is_context={}, left_start={}, left_count={}, right_start={}, right_count={})'.format(
self.is_context, self.left_start, self.left_count, self.right_start, self.right_count)
class Hunk:
def __init__(self, title, left, right):
self.left_start, self.left_count = left
self.right_start, self.right_count = right
self.left_start -= 1 # 0-index
self.right_start -= 1 # 0-index
self.title = title
self.chunks = []
self.current_chunk = None
self.largest_line_number = max(self.left_start + self.left_count, self.right_start + self.right_count)
def new_chunk(self, is_context=False):
if self.chunks:
c = self.chunks[-1]
left_start = c.left_start + c.left_count
right_start = c.right_start + c.right_count
else:
left_start = self.left_start
right_start = self.right_start
return Chunk(left_start, right_start, is_context)
def ensure_diff_chunk(self):
if self.current_chunk is None:
self.current_chunk = self.new_chunk(is_context=False)
elif self.current_chunk.is_context:
self.chunks.append(self.current_chunk)
self.current_chunk = self.new_chunk(is_context=False)
def ensure_context_chunk(self):
if self.current_chunk is None:
self.current_chunk = self.new_chunk(is_context=True)
elif not self.current_chunk.is_context:
self.chunks.append(self.current_chunk)
self.current_chunk = self.new_chunk(is_context=True)
def add_line(self):
self.ensure_diff_chunk()
self.current_chunk.add_line()
def remove_line(self):
self.ensure_diff_chunk()
self.current_chunk.remove_line()
def context_line(self):
self.ensure_context_chunk()
self.current_chunk.context_line()
def finalize(self):
self.chunks.append(self.current_chunk)
del self.current_chunk
# Sanity check
c = self.chunks[-1]
if c.left_start + c.left_count != self.left_start + self.left_count:
raise ValueError('Left side line mismatch {} != {}'.format(c.left_start + c.left_count, self.left_start + self.left_count))
if c.right_start + c.right_count != self.right_start + self.right_count:
raise ValueError('Left side line mismatch {} != {}'.format(c.right_start + c.right_count, self.right_start + self.right_count))
for c in self.chunks:
c.finalize()
def parse_range(x):
parts = x[1:].split(',', 1)
start = abs(int(parts[0]))
count = 1 if len(parts) < 2 else int(parts[1])
return start, count
def parse_hunk_header(line):
parts = tuple(filter(None, line.split('@@', 2)))
linespec = parts[0].strip()
title = ''
if len(parts) == 2:
title = parts[1].strip()
left, right = map(parse_range, linespec.split())
return Hunk(title, left, right)
class Patch:
def __init__(self, all_hunks):
self.all_hunks = all_hunks
self.largest_line_number = self.all_hunks[-1].largest_line_number if self.all_hunks else 0
def __iter__(self):
return iter(self.all_hunks)
def __len__(self):
return len(self.all_hunks)
def parse_patch(raw):
all_hunks = []
for line in raw.splitlines():
if line.startswith('@@ '):
current_hunk = parse_hunk_header(line)
all_hunks.append(current_hunk)
else:
if not all_hunks:
continue
q = line[0]
if q == '+':
all_hunks[-1].add_line()
elif q == '-':
all_hunks[-1].remove_line()
else:
all_hunks[-1].context_line()
for h in all_hunks:
h.finalize()
return Patch(all_hunks)
class Differ:
def __init__(self):
self.jmap = {}
self.jobs = []
def add_diff(self, file1, file2):
self.jmap[file1] = file2
self.jobs.append(file1)
def __call__(self, context=3):
global left_lines, right_lines
ans = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
for future in concurrent.futures.as_completed(jobs):
key = jobs[future]
left_path, right_path = key, self.jmap[key]
try:
ok, returncode, output = future.result()
except FileNotFoundError as err:
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
except Exception as e:
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
if not ok:
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
left_lines = lines_for_path(left_path)
right_lines = lines_for_path(right_path)
try:
patch = parse_patch(output)
except Exception:
import traceback
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(key[0], key[1])
else:
ans[key] = patch
return ans

167
kittens/diff/main.py Normal file
View File

@@ -0,0 +1,167 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from functools import partial
from gettext import gettext as _
from kitty.cli import CONFIG_HELP, appname, parse_args
from kitty.key_encoding import ESCAPE
from ..tui.handler import Handler
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, set_default_colors, set_line_wrapping, set_window_title
)
from .collect import create_collection, data_for_path
from .config import init_config
from .git import Differ
from .render import render_diff
INITIALIZING, COLLECTED, DIFFED = range(3)
def generate_diff(collection, context):
d = Differ()
for path, item_type, changed_path in collection:
if item_type == 'diff':
is_binary = isinstance(data_for_path(path), bytes)
if not is_binary:
d.add_diff(path, changed_path)
return d(context)
class DiffHandler(Handler):
def __init__(self, args, opts, left, right):
self.state = INITIALIZING
self.opts = opts
self.left, self.right = left, right
self.report_traceback_on_exit = None
self.args = args
self.scroll_pos = 0
def create_collection(self):
self.start_job('collect', create_collection, self.left, self.right)
def generate_diff(self):
self.start_job('diff', generate_diff, self.collection, self.args.context)
def render_diff(self):
self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols))
def init_terminal_state(self):
self.write(set_line_wrapping(False))
self.write(set_window_title('kitty +diff'))
self.write(set_default_colors(self.opts.foreground, self.opts.background))
def initialize(self):
self.init_terminal_state()
self.draw_screen()
self.create_collection()
def finalize(self):
self.write(set_default_colors())
def draw_screen(self):
if self.state < DIFFED:
self.write(clear_screen())
self.write(_('Calculating diff, please wait...'))
return
self.write(clear_screen())
for i in range(self.screen_size.rows - 1):
lpos = self.scroll_pos + i
if lpos >= len(self.diff_lines):
text = ''
else:
text = self.diff_lines[lpos].text
self.write(text)
self.write('\x1b[0m\n\r')
def on_key(self, key_event):
if self.state is INITIALIZING:
if key_event.key is ESCAPE:
self.quit_loop(0)
return
def on_resize(self, screen_size):
self.screen_size = screen_size
if self.state > COLLECTED:
self.render_diff()
self.draw_screen()
def on_job_done(self, job_id, job_result):
if 'tb' in job_result:
self.report_traceback_on_exit = job_result['tb']
self.quit_loop(1)
return
if job_id == 'collect':
self.collection = job_result['result']
self.generate_diff()
elif job_id == 'diff':
diff_map = job_result['result']
if isinstance(diff_map, str):
self.report_traceback_on_exit = diff_map
self.quit_loop(1)
return
self.state = DIFFED
self.diff_map = diff_map
self.render_diff()
self.draw_screen()
def on_interrupt(self):
self.quit_loop(1)
def on_eot(self):
self.quit_loop(1)
OPTIONS = partial('''\
--context
type=int
default=3
Number of lines of context to show between changes.
--config
type=list
{config_help}
--override -o
type=list
Override individual configuration options, can be specified multiple times.
Syntax: |_ name=value|. For example: |_ -o background=gray|
'''.format, config_help=CONFIG_HELP.format(conf_name='diff', appname=appname))
def main(args):
msg = 'Show a side-by-side diff of the specified files/directories'
args, items = parse_args(args[1:], OPTIONS, 'file_or_directory file_or_directory', msg, 'kitty +kitten diff')
if len(items) != 2:
raise SystemExit('You must specify exactly two files/directories to compare')
left, right = items
if os.path.isdir(left) != os.path.isdir(right):
raise SystemExit('The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.')
opts = init_config(args)
loop = Loop()
handler = DiffHandler(args, opts, left, right)
loop.loop(handler)
if loop.return_code != 0:
if handler.report_traceback_on_exit:
print(handler.report_traceback_on_exit, file=sys.stderr)
raise SystemExit(loop.return_code)
def handle_result(args, current_char, target_window_id, boss):
pass
if __name__ == '__main__':
main(sys.argv)

334
kittens/diff/render.py Normal file
View File

@@ -0,0 +1,334 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from gettext import gettext as _
from itertools import repeat
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from .collect import data_for_path, lines_for_path, path_name_map, sanitize
from .config import formats
class HunkRef:
__slots__ = ('hunk_num', 'line_num', 'chunk_num')
def __init__(self, hunk_num, chunk_num=None, line_num=None):
self.hunk_num = hunk_num
self.chunk_num = chunk_num
self.line_num = line_num
class LineRef:
__slots__ = ('src_line_number', 'wrapped_line_idx')
def __init__(self, sln, wli):
self.src_line_number = sln
self.wrapped_line_idx = wli
class Reference:
__slots__ = ('path', 'extra')
def __init__(self, path, extra=None):
self.path = path
self.extra = extra
class Line:
__slots__ = ('text', 'ref')
def __init__(self, text, ref):
self.text = text
self.ref = ref
def yield_lines_from(iterator, reference):
for text in iterator:
yield Line(text, reference)
def human_readable(size, sep=' '):
""" Convert a size in bytes into a human readable form """
divisor, suffix = 1, "B"
for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
if size < (1 << ((i + 1) * 10)):
divisor, suffix = (1 << (i * 10)), candidate
break
size = str(float(size)/divisor)
if size.find(".") > -1:
size = size[:size.find(".")+2]
if size.endswith('.0'):
size = size[:-2]
return size + sep + suffix
def fit_in(text, count):
p = truncate_point_for_length(text, count)
if p >= len(text):
return text
if count > 1:
p = truncate_point_for_length(text, count - 1)
return text[:p] + ''
def fill_in(text, sz):
w = wcswidth(text)
if w < sz:
text += ' ' * (sz - w)
return text
def place_in(text, sz):
return fill_in(fit_in(text, sz), sz)
def format_func(which):
def formatted(text):
fmt = formats[which]
return '\x1b[' + fmt + 'm' + text + '\x1b[0m'
formatted.__name__ = which + '_format'
return formatted
text_format = format_func('text')
title_format = format_func('title')
margin_format = format_func('margin')
added_format = format_func('added')
removed_format = format_func('removed')
removed_margin_format = format_func('removed_margin')
added_margin_format = format_func('added_margin')
filler_format = format_func('filler')
hunk_margin_format = format_func('hunk_margin')
hunk_format = format_func('hunk')
highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_highlight', 'added')}
def highlight_boundaries(ltype):
s, e = highlight_map[ltype]
start = '\x1b[' + formats[s] + 'm'
stop = '\x1b[' + formats[e] + 'm'
return start, stop
def title_lines(left_path, args, columns, margin_size):
name = fit_in(sanitize(path_name_map[left_path]), columns - 2 * margin_size)
yield title_format(place_in(' ' + name, columns))
yield title_format('' * columns)
def binary_lines(path, other_path, columns, margin_size):
template = _('Binary file: {}')
def fl(path):
text = template.format(human_readable(len(data_for_path(path))))
text = place_in(text, columns // 2 - margin_size)
return margin_format(' ' * margin_size) + text_format(text)
return fl(path) + fl(other_path)
def split_to_size(line, width):
if not line:
yield line
while line:
p = truncate_point_for_length(line, width)
yield line[:p]
line = line[p:]
def split_to_size_with_center(line, width, prefix_count, suffix_count, start, stop):
sz = len(line)
if prefix_count + suffix_count == sz:
yield from split_to_size(line, width)
return
suffix_pos = sz - suffix_count
pos = state = 0
while line:
p = truncate_point_for_length(line, width)
if state is 0:
if pos + p > prefix_count:
state = 1
a, line = line[:p], line[p:]
if pos + p > suffix_pos:
a = a[:suffix_pos - pos] + stop + a[suffix_pos - pos:]
state = 2
yield a[:prefix_count - pos] + start + a[prefix_count - pos:]
else:
yield line[:p]
line = line[p:]
elif state is 1:
if pos + p > suffix_pos:
state = 2
a, line = line[:p], line[p:]
yield start + a[:suffix_pos - pos] + stop + a[suffix_pos - pos:]
else:
yield start + line[:p]
line = line[p:]
elif state is 2:
yield line[:p]
line = line[p:]
pos += p
margin_bg_map = {'filler': filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
text_bg_map = {'filler': filler_format, 'remove': removed_format, 'add': added_format, 'context': text_format}
class DiffData:
def __init__(self, left_path, right_path, available_cols, margin_size):
self.left_path, self.right_path = left_path, right_path
self.available_cols = available_cols
self.margin_size = margin_size
self.left_lines, self.right_lines = map(lines_for_path, (left_path, right_path))
self.filler_line = render_diff_line('', '', 'filler', margin_size, available_cols)
self.left_filler_line = render_diff_line('', '', 'remove', margin_size, available_cols)
self.right_filler_line = render_diff_line('', '', 'add', margin_size, available_cols)
def render_diff_line(number, text, ltype, margin_size, available_cols):
margin = margin_bg_map[ltype](place_in(number, margin_size))
content = text_bg_map[ltype](fill_in(text or '', available_cols))
return margin + content
def render_diff_pair(left_line_number, left, left_is_change, right_line_number, right, right_is_change, is_first, margin_size, available_cols):
ltype = 'filler' if left_line_number is None else ('remove' if left_is_change else 'context')
rtype = 'filler' if right_line_number is None else ('add' if right_is_change else 'context')
return (
render_diff_line(left_line_number if is_first else None, left, ltype, margin_size, available_cols) +
render_diff_line(right_line_number if is_first else None, right, rtype, margin_size, available_cols)
)
def hunk_title(hunk_num, hunk, margin_size, available_cols):
m = hunk_margin_format(' ' * margin_size)
t = '@@ -{},{} +{},{} @@ {}'.format(hunk.left_start + 1, hunk.left_count, hunk.right_start + 1, hunk.right_count, hunk.title)
return m + hunk_format(place_in(t, available_cols))
def render_half_line(line_number, src, ltype, margin_size, available_cols, changed_center=None):
if changed_center is not None and changed_center[0]:
start, stop = highlight_boundaries(ltype)
lines = split_to_size_with_center(src[line_number], available_cols, changed_center[0], changed_center[1], start, stop)
else:
lines = split_to_size(src[line_number], available_cols)
line_number = str(line_number + 1)
for line in lines:
yield render_diff_line(line_number, line, ltype, margin_size, available_cols)
line_number = ''
def lines_for_chunk(data, hunk_num, chunk, chunk_num):
if chunk.is_context:
for i in range(chunk.left_count):
left_line_number = chunk.left_start + i
right_line_number = chunk.right_start + i
lines = split_to_size(data.left_lines[left_line_number], data.available_cols)
ref = Reference(data.left_path, HunkRef(hunk_num, chunk_num, i))
left_line_number = str(left_line_number + 1)
right_line_number = str(right_line_number + 1)
for text in lines:
line = render_diff_line(left_line_number, text, 'context', data.margin_size, data.available_cols)
if right_line_number == left_line_number:
r = line
else:
r = render_diff_line(right_line_number, text, 'context', data.margin_size, data.available_cols)
yield Line(line + r, ref)
left_line_number = right_line_number = ''
else:
common = min(chunk.left_count, chunk.right_count)
for i in range(max(chunk.left_count, chunk.right_count)):
ref = Reference(data.left_path, HunkRef(hunk_num, chunk_num, i))
ll, rl = [], []
if i < chunk.left_count:
ll.extend(render_half_line(
chunk.left_start + i, data.left_lines, 'remove', data.margin_size,
data.available_cols, None if chunk.centers is None else chunk.centers[i]))
if i < chunk.right_count:
rl.extend(render_half_line(
chunk.right_start + i, data.right_lines, 'add', data.margin_size,
data.available_cols, None if chunk.centers is None else chunk.centers[i]))
if i < common:
extra = len(ll) - len(rl)
if extra != 0:
if extra < 0:
x, fl = ll, data.left_filler_line
extra = -extra
else:
x, fl = rl, data.right_filler_line
x.extend(repeat(fl, extra))
else:
if ll:
x, count = rl, len(ll)
else:
x, count = ll, len(rl)
x.extend(repeat(data.filler_line, count))
for left_line, right_line in zip(ll, rl):
yield Line(left_line + right_line, ref)
def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size):
available_cols = columns // 2 - margin_size
data = DiffData(left_path, right_path, available_cols, margin_size)
for hunk_num, hunk in enumerate(hunks):
yield Line(hunk_title(hunk_num, hunk, margin_size, columns - margin_size), Reference(left_path, HunkRef(hunk_num)))
for cnum, chunk in enumerate(hunk.chunks):
yield from lines_for_chunk(data, hunk_num, chunk, cnum)
def all_lines(path, args, columns, margin_size, is_add=True):
available_cols = columns // 2 - margin_size
ltype = 'add' if is_add else 'remove'
lines = lines_for_path(path)
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
for line_number in range(len(lines)):
h = render_half_line(line_number, lines, ltype, margin_size, available_cols)
for i, hl in enumerate(h):
ref = Reference(path, LineRef(line_number, i))
text = (filler + h) if is_add else (h + filler)
yield Line(text, ref)
def rename_lines(path, other_path, args, columns, margin_size):
m = ' ' * margin_size
for line in split_to_size(margin_size + _('The file {0} was renamed to {1}').format(
sanitize(path_name_map[path]), sanitize(path_name_map[other_path])), columns - margin_size):
yield m + line
def render_diff(collection, diff_map, args, columns):
largest_line_number = 0
for path, item_type, other_path in collection:
if item_type == 'diff':
patch = diff_map.get(path)
if patch is not None:
largest_line_number = max(largest_line_number, patch.largest_line_number)
margin_size = max(3, len(str(largest_line_number)) + 1)
for path, item_type, other_path in collection:
item_ref = Reference(path)
if item_type == 'diff':
yield from yield_lines_from(title_lines(path, args, columns, margin_size), item_ref)
is_binary = isinstance(data_for_path(path), bytes)
if is_binary:
yield from yield_lines_from(binary_lines(path, other_path, columns, margin_size), item_ref)
else:
yield from lines_for_diff(path, other_path, diff_map[path], args, columns, margin_size)
elif item_type == 'add':
yield from yield_lines_from(title_lines(other_path, args, columns, margin_size), item_ref)
yield from all_lines(other_path, args, columns, margin_size, is_add=True)
elif item_type == 'removal':
yield from yield_lines_from(title_lines(path, args, columns, margin_size), item_ref)
yield from all_lines(path, args, columns, margin_size, is_add=False)
elif item_type == 'rename':
yield from yield_lines_from(title_lines(path, args, columns, margin_size), item_ref)
yield from yield_lines_from(rename_lines(path, other_path, args, columns, margin_size))

52
kittens/diff/speedup.c Normal file
View File

@@ -0,0 +1,52 @@
/*
* speedup.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
static PyObject*
changed_center(PyObject *self UNUSED, PyObject *args) {
unsigned int prefix_count = 0, suffix_count = 0;
PyObject *lp, *rp;
if (!PyArg_ParseTuple(args, "UU", &lp, &rp)) return NULL;
const size_t left_len = PyUnicode_GET_LENGTH(lp), right_len = PyUnicode_GET_LENGTH(rp);
#define R(which, index) PyUnicode_READ(PyUnicode_KIND(which), PyUnicode_DATA(which), index)
while(prefix_count < MIN(left_len, right_len)) {
if (R(lp, prefix_count) != R(rp, prefix_count)) break;
prefix_count++;
}
if (left_len && right_len && prefix_count < MIN(left_len, right_len)) {
while(suffix_count < MIN(left_len - prefix_count, right_len - prefix_count)) {
if(R(lp, left_len - 1 - suffix_count) != R(rp, right_len - 1 - suffix_count)) break;
suffix_count++;
}
}
#undef R
return Py_BuildValue("II", prefix_count, suffix_count);
}
static PyMethodDef module_methods[] = {
{"changed_center", (PyCFunction)changed_center, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "diff_speedup", /* name of module */
.m_doc = NULL,
.m_size = -1,
.m_methods = module_methods
};
EXPORTED PyMODINIT_FUNC
PyInit_diff_speedup(void) {
PyObject *m;
m = PyModule_Create(&module);
if (m == NULL) return NULL;
return m;
}

View File

326
kittens/hints/main.py Normal file
View File

@@ -0,0 +1,326 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import string
import sys
from functools import lru_cache, partial
from gettext import gettext as _
from kitty.cli import parse_args
from kitty.fast_data_types import set_clipboard_string
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
from ..tui.handler import Handler
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, faint, set_cursor_visible, set_window_title, styled
)
URL_PREFIXES = 'http https file ftp'.split()
HINT_ALPHABET = string.digits + string.ascii_lowercase
class Mark(object):
__slots__ = ('index', 'start', 'end', 'text')
def __init__(self, index, start, end, text):
self.index, self.start, self.end = index, start, end
self.text = text
@lru_cache(maxsize=2048)
def encode_hint(num):
res = ''
d = len(HINT_ALPHABET)
while not res or num > 0:
num, i = divmod(num, d)
res = HINT_ALPHABET[i] + res
return res
def decode_hint(x):
return int(x, 36)
def render(lines, current_input):
ans = []
def mark(m):
hint = encode_hint(m.index)
text = m.text
if current_input and not hint.startswith(current_input):
return faint(text)
hint = hint[len(current_input):] or ' '
text = text[len(hint):]
return styled(
hint,
fg='black',
bg='green',
bold=True
) + styled(
text, fg='gray', fg_intense=True, bold=True
)
for line, marks in lines:
if not marks:
ans.append(faint(line))
continue
buf = []
for i, m in enumerate(marks):
if i == 0 and m.start:
buf.append(faint(line[:m.start]))
buf.append(mark(m))
if m is not marks[-1]:
buf.append(faint(line[m.end:marks[i + 1].start]))
rest = line[marks[-1].end:]
if rest:
buf.append(faint(rest))
ans.append(''.join(buf))
return '\r\n'.join(ans)
class Hints(Handler):
def __init__(self, lines, index_map, args):
self.lines, self.index_map = tuple(lines), index_map
self.current_input = ''
self.current_text = None
self.args = args
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
self.chosen = None
def init_terminal_state(self):
self.write(set_cursor_visible(False))
self.write(set_window_title(self.window_title))
def initialize(self):
self.init_terminal_state()
self.draw_screen()
def on_text(self, text, in_bracketed_paste):
changed = False
for c in text:
if c in HINT_ALPHABET:
self.current_input += c
changed = True
if changed:
matches = [
m.text for idx, m in self.index_map.items()
if encode_hint(idx).startswith(self.current_input)
]
if len(matches) == 1:
self.chosen = matches[0]
self.quit_loop(0)
return
self.current_text = None
self.draw_screen()
def on_key(self, key_event):
if key_event is backspace_key:
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event is enter_key and self.current_input:
idx = decode_hint(self.current_input)
self.chosen = self.index_map[idx].text
self.quit_loop(0)
elif key_event.key is ESCAPE:
self.quit_loop(1)
def on_interrupt(self):
self.quit_loop(1)
def on_eot(self):
self.quit_loop(1)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.draw_screen()
def draw_screen(self):
if self.current_text is None:
self.current_text = render(self.lines, self.current_input)
self.write(clear_screen())
self.write(self.current_text)
def regex_finditer(pat, minimum_match_length, line):
for m in pat.finditer(line):
s, e = m.span(pat.groups)
if e - s >= minimum_match_length:
yield s, e
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
def find_urls(pat, line):
for m in pat.finditer(line):
s, e = m.span()
if s > 4 and line[s - 5:s] == 'link:': # asciidoc URLs
url = line[s:e]
idx = url.rfind('[')
if idx > -1:
e -= len(url) - idx
while line[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
e -= 1
# Detect a bracketed URL
if s > 0 and e > s + 4 and line[s-1] in '({[<' and line[e-1] == closing_bracket_map[line[s-1]]:
e -= 1
yield s, e
def mark(finditer, line, all_marks):
marks = []
for s, e in finditer(line):
idx = len(all_marks)
text = line[s:e]
marks.append(Mark(idx, s, e, text))
all_marks.append(marks[-1])
return line, marks
def run_loop(args, lines, index_map):
loop = Loop()
handler = Hints(lines, index_map, args)
loop.loop(handler)
if handler.chosen and loop.return_code == 0:
return {'match': handler.chosen, 'program': args.program}
raise SystemExit(loop.return_code)
def escape(chars):
return chars.replace('\\', '\\\\').replace('-', r'\-').replace(']', r'\]')
def run(args, text):
if args.type == 'url':
from .url_regex import url_delimiters
url_pat = '(?:{})://[^{}]{{3,}}'.format(
'|'.join(args.url_prefixes.split(',')), url_delimiters
)
finditer = partial(find_urls, re.compile(url_pat))
elif args.type == 'path':
finditer = partial(regex_finditer, re.compile(r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'), args.minimum_match_length)
elif args.type == 'line':
finditer = partial(regex_finditer, re.compile(r'(?m)^\s*(.+)\s*$'), args.minimum_match_length)
elif args.type == 'word':
chars = args.word_characters
if chars is None:
import json
chars = json.loads(os.environ['KITTY_COMMON_OPTS'])['select_by_word_characters']
pat = re.compile(r'(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length))
finditer = partial(regex_finditer, pat, args.minimum_match_length)
else:
finditer = partial(regex_finditer, re.compile(args.regex), args.minimum_match_length)
lines = []
all_marks = []
for line in text.splitlines():
marked = mark(finditer, line, all_marks)
lines.append(marked)
if not all_marks:
input(_('No {} found, press Enter to abort.').format(
'URLs' if args.type == 'url' else 'matches'
))
return
largest_index = all_marks[-1].index
for m in all_marks:
m.index = largest_index - m.index
index_map = {m.index: m for m in all_marks}
return run_loop(args, lines, index_map)
OPTIONS = partial(r'''
--program
default=default
What program to use to open matched text. Defaults to the default open program
for the operating system. Use a value of - to paste the match into the
terminal window instead. A value of @ will copy the match to the clipboard.
--type
default=url
choices=url,regex,path,line,word
The type of text to search for.
--regex
default=(?m)^\s*(.+)\s*$
The regular expression to use when --type=regex. If you specify a group in the
regular expression only the group will be matched. This allow you to match text
ignoring a prefix/suffix, as needed. The default expression matches lines.
--url-prefixes
default={0}
Comma separated list of recognized URL prefixes.
--word-characters
Characters to consider as part of a word. In addition, all characters marked as
alpha-numeric in the unicode database will be considered as word characters.
Defaults to the select_by_word_characters setting from kitty.conf.
--minimum-match-length
default=3
type=int
The minimum number of characters to consider a match.
'''.format, ','.join(sorted(URL_PREFIXES)))
def main(args):
msg = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
text = ''
if sys.stdin.isatty():
if '--help' not in args and '-h' not in args:
print('You must pass the text to be hinted on STDIN', file=sys.stderr)
input(_('Press Enter to quit'))
return
else:
text = sys.stdin.buffer.read().decode('utf-8')
sys.stdin = open('/dev/tty')
try:
args, items = parse_args(args[1:], OPTIONS, '', msg, 'hints')
except SystemExit as e:
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
if items:
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
input(_('Press Enter to quit'))
return
return run(args, text)
def handle_result(args, data, target_window_id, boss):
program = data['program']
if program == '-':
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(data['match'])
elif program == '@':
set_clipboard_string(data['match'])
else:
cwd = None
w = boss.window_id_map.get(target_window_id)
if w is not None:
cwd = w.cwd_of_child
boss.open_url(data['match'], None if program == 'default' else program, cwd=cwd)
if __name__ == '__main__':
# Run with kitty +kitten hints
ans = main(sys.argv)
if ans:
print(ans)

View File

@@ -1 +1 @@
url_delimiters = '\x00-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
url_delimiters = '\x00-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa

87
kittens/runner.py Normal file
View File

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

View File

@@ -5,9 +5,23 @@
class Handler:
def initialize(self, screen_size, quit_loop, wakeup):
def _initialize(self, screen_size, quit_loop, wakeup, start_job):
self.screen_size, self.quit_loop = screen_size, quit_loop
self.wakeup = wakeup
self.start_job = start_job
def __enter__(self):
self.initialize()
def __exit__(self, *a):
del self.write_buf[:]
self.finalize()
def initialize(self):
pass
def finalize(self):
pass
def on_resize(self, screen_size):
self.screen_size = screen_size
@@ -30,6 +44,12 @@ class Handler:
def on_eot(self):
pass
def on_wakeup(self):
pass
def on_job_done(self, job_id, job_result):
pass
def write(self, data):
if isinstance(data, str):
data = data.encode('utf-8')

View File

@@ -15,6 +15,7 @@ import tty
from collections import namedtuple
from contextlib import closing, contextmanager
from functools import partial
from queue import Empty, Queue
from kitty.fast_data_types import parse_input_from_terminal, safe_pipe
from kitty.icat import screen_size
@@ -24,7 +25,7 @@ from kitty.key_encoding import (
)
from .handler import Handler
from .operations import init_state, reset_state, clear_screen
from .operations import clear_screen, init_state, reset_state
def log(*a, **kw):
@@ -120,8 +121,7 @@ class UnhandledException(Handler):
def __init__(self, tb):
self.tb = tb
def initialize(self, screen_size, quit_loop, wakeup):
Handler.initialize(self, screen_size, quit_loop, wakeup)
def initialize(self):
self.write(clear_screen())
self.write(self.tb.replace('\n', '\r\n'))
self.write('\r\n')
@@ -167,6 +167,24 @@ class Loop:
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
if self.sanitize_bracketed_paste:
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
self.jobs_queue = Queue()
def start_job(self, job_id, func, *args, **kw):
from threading import Thread
t = Thread(target=partial(self._run_job, job_id, func), args=args, kwargs=kw, name='LoopJob')
t.daemon = True
t.start()
def _run_job(self, job_id, func, *args, **kw):
try:
result = func(*args, **kw)
except Exception as err:
import traceback
entry = {'id': job_id, 'exception': err, 'tb': traceback.format_exc()}
else:
entry = {'id': job_id, 'result': result}
self.jobs_queue.put(entry)
self._wakeup_write(b'j')
def _read_ready(self, handler):
if not self.read_allowed:
@@ -283,6 +301,17 @@ class Loop:
handler.on_term()
if b'i' in data:
handler.on_interrupt()
if b'j' in data:
while True:
try:
entry = self.jobs_queue.get_nowait()
except Empty:
break
else:
job_id = entry.pop('id')
handler.on_job_done(job_id, entry)
if b'1' in data:
handler.on_wakeup()
def _wakeup_write(self, val):
while not os.write(self.wakeup_write_fd, val):
@@ -329,13 +358,40 @@ class Loop:
handler._term_manager = term_manager
keep_going = True
try:
handler.initialize(screen_size(), self.quit, self.wakeup)
handler._initialize(screen_size(), self.quit, self.wakeup, self.start_job)
with handler:
while keep_going:
has_data_to_write = bool(handler.write_buf)
if not has_data_to_write and not self.read_allowed:
break
if has_data_to_write != waiting_for_write:
waiting_for_write = has_data_to_write
self._modify_output_selector(waiting_for_write)
events = select()
for key, mask in events:
key.data(handler)
except Exception:
import traceback
tb = traceback.format_exc()
self.return_code = 1
keep_going = False
while keep_going:
finalize_output = b''.join(handler.write_buf).decode('utf-8')
if tb is not None:
self._report_error_loop(finalize_output + tb, term_manager)
if tb is None:
os.write(self.output_fd, finalize_output.encode('utf-8'))
def _report_error_loop(self, tb, term_manager):
select = self.sel.select
waiting_for_write = False
handler = UnhandledException(tb)
handler.write_buf = []
handler._term_manager = term_manager
handler._initialize(screen_size(), self.quit, self.wakeup, self.start_job)
with handler:
while True:
has_data_to_write = bool(handler.write_buf)
if not has_data_to_write and not self.read_allowed:
break
@@ -344,31 +400,4 @@ class Loop:
self._modify_output_selector(waiting_for_write)
events = select()
for key, mask in events:
try:
key.data(handler)
except Exception:
import traceback
tb = traceback.format_exc()
self.return_code = 1
keep_going = False
break
if tb is not None:
self._report_error_loop(tb, term_manager)
def _report_error_loop(self, tb, term_manager):
select = self.sel.select
waiting_for_write = False
handler = UnhandledException(tb)
handler.write_buf = []
handler._term_manager = term_manager
handler.initialize(screen_size(), self.quit, self.wakeup)
while True:
has_data_to_write = bool(handler.write_buf)
if not has_data_to_write and not self.read_allowed:
break
if has_data_to_write != waiting_for_write:
waiting_for_write = has_data_to_write
self._modify_output_selector(waiting_for_write)
events = select()
for key, mask in events:
key.data(handler)
key.data(handler)

View File

@@ -2,8 +2,10 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
from contextlib import contextmanager
from kitty.rgb import Color, color_as_sharp, to_color
from kitty.terminfo import string_capabilities
S7C1T = '\033 F'
@@ -151,3 +153,24 @@ def cursor(write):
write(SAVE_CURSOR)
yield
write(RESTORE_CURSOR)
@contextmanager
def alternate_screen(f=None):
f = f or sys.stdout
print(set_mode('ALTERNATE_SCREEN'), end='', file=f)
yield
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
def set_default_colors(fg=None, bg=None):
ans = ''
if fg is None:
ans += '\x1b]110\x1b\\'
else:
ans += '\x1b]10;{}\x1b\\'.format(color_as_sharp(fg if isinstance(fg, Color) else to_color(fg)))
if bg is None:
ans += '\x1b]111\x1b\\'
else:
ans += '\x1b]11;{}\x1b\\'.format(color_as_sharp(bg if isinstance(bg, Color) else to_color(bg)))
return ans

View File

@@ -5,7 +5,6 @@
import os
import string
import subprocess
import sys
from functools import lru_cache
from gettext import gettext as _
@@ -43,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)
@@ -57,7 +56,7 @@ def name(cp):
@lru_cache(maxsize=256)
def codepoints_matching_search(parts):
ans = []
if parts and parts[0]:
if parts and parts[0] and len(parts[0]) > 1:
codepoints = points_for_word(parts[0])
for word in parts[1:]:
pts = points_for_word(word)
@@ -223,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
@@ -326,8 +327,7 @@ class UnicodeInput(Handler):
self.write(set_line_wrapping(False))
self.write(set_window_title(_('Unicode input')))
def initialize(self, *args):
Handler.initialize(self, *args)
def initialize(self):
self.init_terminal_state()
self.draw_screen()
@@ -452,26 +452,34 @@ class UnicodeInput(Handler):
self.refresh()
def run_loop(args):
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)]
return loop.return_code
return handler.current_char
if loop.return_code != 0:
raise SystemExit(loop.return_code)
def main(args=sys.argv):
try:
raise SystemExit(run_loop(args))
except Exception:
import traceback
traceback.print_exc()
input(_('Press Enter to quit.'))
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)

File diff suppressed because one or more lines are too long

View File

@@ -9,32 +9,61 @@
static PyObject*
all_words(PYNOARG) {
PyObject *ans = PyTuple_New(arraysz(idx_to_word));
PyObject *ans = PyTuple_New(arraysz(all_words_map));
if (!ans) return NULL;
for (size_t i = 0; i < arraysz(idx_to_word); i++) {
PyObject *w = PyUnicode_FromString(idx_to_word[i]);
for (size_t i = 0; i < arraysz(all_words_map); i++) {
PyObject *w = PyUnicode_FromString(all_words_map[i]);
if (w == NULL) { Py_DECREF(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, w);
}
return ans;
}
static inline void
add_matches(const word_trie *wt, char_type *codepoints, size_t *pos, const size_t sz) {
size_t num = mark_groups[wt->match_offset];
for (size_t i = wt->match_offset + 1; i < wt->match_offset + 1 + num && *pos < sz; i++, (*pos)++) {
codepoints[*pos] = mark_to_cp[mark_groups[i]];
}
}
static void
process_trie_node(const word_trie *wt, char_type *codepoints, size_t *pos, const size_t sz) {
if (wt->match_offset) add_matches(wt, codepoints, pos, sz);
size_t num_children = children_array[wt->children_offset];
if (!num_children) return;
for (size_t c = wt->children_offset + 1; c < wt->children_offset + 1 + num_children; c++) {
if (*pos > sz) return;
uint32_t x = children_array[c];
process_trie_node(&all_trie_nodes[x >> 8], codepoints, pos, sz);
}
}
static inline PyObject*
codepoints_for_word(const char *word, size_t len) {
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
const unsigned short *words = words_for_first_letter[(unsigned)*word];
if (words == NULL) return ans;
for (unsigned short i = 1; i <= words[0]; i++) {
unsigned short word_idx = words[i];
const char *w = idx_to_word[word_idx];
if (strncmp(word, w, len) == 0 && strlen(w) == len) {
const char_type* codepoints = codepoints_for_word_idx[word_idx];
for (char_type i = 1; i <= codepoints[0]; i++) {
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
int ret = PySet_Add(ans, t); Py_DECREF(t); if (ret != 0) { Py_DECREF(ans); return NULL; }
const word_trie *wt = all_trie_nodes;
for (size_t i = 0; i < len; i++) {
unsigned char ch = word[i];
size_t num_children = children_array[wt->children_offset];
if (!num_children) return PyFrozenSet_New(NULL);
bool found = false;
for (size_t c = wt->children_offset + 1; c < wt->children_offset + 1 + num_children; c++) {
uint32_t x = children_array[c];
if ((x & 0xff) == ch) {
found = true;
wt = &all_trie_nodes[x >> 8];
break;
}
break;
}
if (!found) return PyFrozenSet_New(NULL);
}
static char_type codepoints[1024];
size_t cpos = 0;
process_trie_node(wt, codepoints, &cpos, arraysz(codepoints));
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
for (size_t i = 0; i < cpos; i++) {
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
int ret = PySet_Add(ans, t); Py_DECREF(t); if (ret != 0) { Py_DECREF(ans); return NULL; }
}
return ans;
}
@@ -56,12 +85,13 @@ nfc(PyObject *self UNUSED, PyObject *args) {
}
static PyMethodDef module_methods[] = {
METHODB(all_words, METH_NOARGS),
{"all_words", (PyCFunction)all_words, METH_NOARGS, ""},
{"codepoints_for_word", (PyCFunction)cfw, METH_VARARGS, ""},
{"name_for_codepoint", (PyCFunction)nfc, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};
#if PY_VERSION_HEX >= 0x03000000
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "unicode_names", /* name of module */
@@ -79,3 +109,13 @@ PyInit_unicode_names(void) {
if (m == NULL) return NULL;
return m;
}
#else
EXPORTED
initunicode_names(void) {
PyObject *m;
m = Py_InitModule3("unicode_names", module_methods,
""
);
if (m == NULL) return;
}
#endif

View File

@@ -1,247 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import re
import string
import subprocess
import sys
from collections import namedtuple
from functools import lru_cache, partial
from gettext import gettext as _
from kitty.cli import parse_args
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
from kitty.utils import command_for_open
from ..tui.handler import Handler
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, faint, set_cursor_visible, set_window_title, styled
)
Mark = namedtuple('Mark', 'index start end text')
URL_PREFIXES = 'http https file ftp'.split()
HINT_ALPHABET = string.digits + string.ascii_lowercase
@lru_cache(maxsize=2048)
def encode_hint(num):
res = ''
d = len(HINT_ALPHABET)
while not res or num > 0:
num, i = divmod(num, d)
res = HINT_ALPHABET[i] + res
return res
def decode_hint(x):
return int(x, 36)
def render(lines, current_input):
ans = []
def mark(m):
hint = encode_hint(m.index)
text = m.text
if current_input and not hint.startswith(current_input):
return faint(text)
hint = hint[len(current_input):] or ' '
text = text[len(hint):]
return styled(
hint,
fg='black',
bg='green',
bold=True
) + styled(
text, fg='gray', fg_intense=True, bold=True
)
for line, marks in lines:
if not marks:
ans.append(faint(line))
continue
buf = []
for i, m in enumerate(marks):
if i == 0 and m.start:
buf.append(faint(line[:m.start]))
buf.append(mark(m))
if m is not marks[-1]:
buf.append(faint(line[m.end:marks[i + 1].start]))
rest = line[marks[-1].end:]
if rest:
buf.append(faint(rest))
ans.append(''.join(buf))
return '\r\n'.join(ans)
class URLHints(Handler):
def __init__(self, lines, index_map):
self.lines, self.index_map = tuple(lines), index_map
self.current_input = ''
self.current_text = None
self.chosen = None
def init_terminal_state(self):
self.write(set_cursor_visible(False))
self.write(set_window_title(_('Choose URL')))
def initialize(self, *args):
Handler.initialize(self, *args)
self.init_terminal_state()
self.draw_screen()
def on_text(self, text, in_bracketed_paste):
changed = False
for c in text:
if c in HINT_ALPHABET:
self.current_input += c
changed = True
if changed:
matches = [
t for idx, t in self.index_map.items()
if encode_hint(idx).startswith(self.current_input)
]
if len(matches) == 1:
self.chosen = matches[0]
self.quit_loop(0)
return
self.current_text = None
self.draw_screen()
def on_key(self, key_event):
if key_event is backspace_key:
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event is enter_key and self.current_input:
idx = decode_hint(self.current_input)
self.chosen = self.index_map[idx]
self.quit_loop(0)
elif key_event.key is ESCAPE:
self.quit_loop(1)
def on_interrupt(self):
self.quit_loop(1)
def on_eot(self):
self.quit_loop(1)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.draw_screen()
def draw_screen(self):
if self.current_text is None:
self.current_text = render(self.lines, self.current_input)
self.write(clear_screen())
self.write(self.current_text)
def regex_finditer(pat, line):
for m in pat.finditer(line):
s, e = m.span()
if e - s > 2:
yield s, e
def find_urls(pat, line):
for m in pat.finditer(line):
s, e = m.span()
if s > 4 and line[s - 5:s] == 'link:': # asciidoc URLs
url = line[s:e]
idx = url.rfind('[')
if idx > -1:
e -= len(url) - idx
yield s, e
def mark(finditer, line, index_map):
marks = []
for s, e in finditer(line):
idx = len(index_map)
text = line[s:e]
marks.append(Mark(idx, s, e, text))
index_map[idx] = text
return line, marks
def run_loop(args, lines, index_map):
loop = Loop()
handler = URLHints(lines, index_map)
loop.loop(handler)
if handler.chosen and loop.return_code == 0:
cmd = command_for_open(args.program)
ret = subprocess.Popen(cmd + [handler.chosen]).wait()
if ret != 0:
print('URL handler "{}" failed with return code: {}'.format(' '.join(cmd), ret), file=sys.stderr)
input('Press Enter to quit')
loop.return_code = ret
raise SystemExit(loop.return_code)
def run(args, source_file=None):
if source_file is None:
text = sys.stdin.buffer.read().decode('utf-8')
sys.stdin = open('/dev/tty')
else:
with open(source_file, 'r') as f:
text = f.read()
if args.regex is None:
from .url_regex import url_delimiters
url_pat = '(?:{})://[^{}]{{3,}}'.format(
'|'.join(args.url_prefixes.split(',')), url_delimiters
)
finditer = partial(find_urls, re.compile(url_pat))
else:
finditer = partial(regex_finditer, re.compile(args.regex))
lines = []
index_map = {}
for line in text.splitlines():
marked = mark(finditer, line, index_map)
lines.append(marked)
if not index_map:
input(_('No URLs found, press Enter to abort.'))
return
try:
run_loop(args, lines, index_map)
except Exception:
import traceback
traceback.print_exc()
input(_('Press Enter to quit'))
OPTIONS = partial('''\
--program
default=default
What program to use to open matched URLs. Defaults
to the default URL open program for the operating system.
--regex
Instead of searching for URLs search for the specified regular
expression instead.
--url-prefixes
default={0}
Comma separeted list of recognized URL prefixes. Defaults to:
{0}
'''.format, ','.join(sorted(URL_PREFIXES)))
def main(args=sys.argv):
msg = 'Highlight URLs inside the specified text'
try:
args, items = parse_args(args[1:], OPTIONS, '[path to file or omit to use stdin]', msg, 'url_hints')
except SystemExit as e:
print(e.args[0], file=sys.stderr)
input('Press enter to quit...')
return 1
run(args, (items or [None])[0])

View File

@@ -3,6 +3,7 @@ uniform uvec2 viewport;
uniform vec3 default_bg;
uniform vec3 active_border_color;
uniform vec3 inactive_border_color;
uniform vec3 bell_border_color;
in uvec4 rect; // left, top, right, bottom
in uint rect_color;
out vec3 color;
@@ -37,5 +38,5 @@ void main() {
gl_Position = vec4(to_opengl(rect[pos.x], rect[pos.y]), 0, 1);
int rc = int(rect_color);
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
color = float(1 & rc) * default_bg + float((2 & rc) >> 1) * active_border_color + float((4 & rc) >> 2) * inactive_border_color + float((8 & rc) >> 3) * window_bg;
color = float(1 & rc) * default_bg + float((2 & rc) >> 1) * active_border_color + float((4 & rc) >> 2) * inactive_border_color + float((8 & rc) >> 3) * window_bg + float((16 & rc) >> 4) * bell_border_color;
}

View File

@@ -64,7 +64,7 @@ class Borders:
g = w.geometry
if bw > 0 and draw_window_borders:
# Draw the border rectangles
color = 2 if w is active_window else 4
color = 2 if w is active_window else (16 if w.needs_attention else 4)
border(
self.os_window_id, self.tab_id,
color, bw, g.left - fw, g.top - fw, g.right + fw,

View File

@@ -3,8 +3,9 @@
# 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
@@ -13,17 +14,19 @@ from .cli import create_opts, parse_args
from .config import (
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
)
from .constants import appname, editor, set_boss
from .config_utils import to_cmdline
from .constants import appname, config_dir, editor, set_boss
from .fast_data_types import (
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
set_dpi_from_os_window, show_window, toggle_fullscreen,
viewport_for_window
set_dpi_from_os_window, set_in_sequence_mode, show_window,
toggle_fullscreen, viewport_for_window
)
from .fonts.render import prerender, resize_fonts, set_font_family
from .keys import get_shortcut
from .keys import get_shortcut, shortcut_matches
from .remote_control import handle_cmd
from .rgb import Color, color_from_int
from .session import create_session
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
from .utils import (
@@ -39,6 +42,7 @@ def initialize_renderer():
def listen_on(spec):
import socket
family, address, socket_path = parse_address_spec(spec)
s = socket.socket(family)
atexit.register(remove_socket_file, s, socket_path)
@@ -78,6 +82,8 @@ 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
@@ -115,6 +121,7 @@ class Boss:
self.os_window_map[os_window_id] = tm
if dpi_changed:
self.on_dpi_change(os_window_id)
return os_window_id
def list_os_windows(self):
for os_window_id, tm in self.os_window_map.items():
@@ -123,24 +130,45 @@ 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):
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
if field == 'num':
tab = self.active_tab
if tab is not None:
try:
w = tab.get_nth_window(int(exp))
except Exception:
return
if w is not None:
yield w
else:
pat = re.compile(exp)
for window in self.all_windows:
if window.matches(field, pat):
yield window
def tab_for_window(self, window):
for tm in self.os_window_map.values():
for tab in tm:
for w in tab:
if w.id == window.id:
return tab
for tab in self.all_tabs:
for w in tab:
if w.id == window.id:
return tab
def match_tabs(self, match):
try:
@@ -148,14 +176,12 @@ class Boss:
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:
@@ -175,7 +201,7 @@ class Boss:
def _new_os_window(self, args, cwd_from=None):
sw = self.args_to_special_window(args, cwd_from) if args else None
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
self.add_os_window(startup_session)
return self.add_os_window(startup_session)
def new_os_window(self, *args):
self._new_os_window(args)
@@ -191,7 +217,7 @@ class Boss:
def _handle_remote_command(self, cmd, window=None):
response = None
if self.opts.allow_remote_control:
if self.opts.allow_remote_control or getattr(window, 'allow_remote_control', False):
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
@@ -204,7 +230,6 @@ class Boss:
return response
def peer_message_received(self, msg):
import json
msg = msg.decode('utf-8')
cmd_prefix = '\x1bP@kitty-cmd'
if msg.startswith(cmd_prefix):
@@ -220,7 +245,9 @@ class Boss:
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:
log_error('Unknown message received from peer, ignoring')
@@ -352,8 +379,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)
@@ -395,6 +451,12 @@ class Boss:
w = tm.active_window
if w is not None:
w.focus_changed(focused)
tm.mark_tab_bar_dirty()
def update_tab_bar_data(self, os_window_id):
tm = self.os_window_map.get(os_window_id)
if tm is not None:
tm.update_tab_bar_data()
def on_drop(self, os_window_id, paths):
tm = self.os_window_map.get(os_window_id)
@@ -425,64 +487,100 @@ class Boss:
cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + editor + [confpath]
self.new_os_window(*cmd)
def input_unicode_character(self):
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), 'PYTHONWARNINGS': 'ignore'},
overlay_for=w.id))
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
def run_kitten(self, type_of_input, kitten, *args):
import shlex
cmdline = args[0] if args else ''
args = shlex.split(cmdline) if cmdline else []
self._run_kitten(kitten, args, type_of_input)
def on_kitten_finish(self, target_window_id, end_kitten, source_window):
output = self.get_output(source_window, num_lines=None)
from kittens.runner import deserialize
data = deserialize(output)
if data is not None:
end_kitten(data, target_window_id, self)
def input_unicode_character(self):
self._run_kitten('unicode_input')
def set_tab_title(self):
tab = self.active_tab
if tab:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
self._run_kitten('ask', args)
def do_set_tab_title(self, title, tab_id):
tm = self.active_tab_manager
if tm is not None and title:
tab_id = int(tab_id)
for tab in tm.tabs:
if tab.id == tab_id:
tab.set_title(title)
break
def kitty_shell(self, window_type):
cmd = ['kitty', '@']
if window_type == 'tab':
window = self._new_tab(cmd).active_window
elif window_type == 'os_window':
os_window_id = self._new_os_window(cmd)
window = self.os_window_map[os_window_id].active_window
elif window_type == 'overlay':
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
else:
window = None
else:
window = self._new_window(cmd)
if window is not None:
window.allow_remote_control = True
def switch_focus_to(self, window_idx):
tab = self.active_tab
tab.set_active_window_idx(window_idx)
old_focus = tab.active_window
if not old_focus.destroyed:
old_focus.focus_changed(False)
tab.active_window.focus_changed(True)
def open_url(self, url):
def open_url(self, url, program=None, cwd=None):
if url:
open_url(url, self.opts.open_url_with)
if isinstance(program, str):
program = to_cmdline(program)
open_url(url, program or self.opts.open_url_with, cwd=cwd)
def open_url_lines(self, lines):
self.open_url(''.join(lines))
def open_url_lines(self, lines, program=None):
self.open_url(''.join(lines), program)
def destroy(self):
self.shutting_down = True
@@ -578,7 +676,7 @@ class Boss:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager
if tm is not None:
tm.new_tab(special_window=special_window, cwd_from=cwd_from)
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
def new_tab(self, *args):
self._new_tab(args)
@@ -592,9 +690,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)
@@ -615,3 +713,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

@@ -572,6 +572,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
bool needs_render = os_window->needs_render;
os_window->needs_render = false;
if (TD.screen && os_window->num_tabs > 1) {
if (!os_window->tab_bar_data_updated) {
call_boss(update_tab_bar_data, "K", os_window->id);
os_window->tab_bar_data_updated = true;
}
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
}
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
@@ -735,6 +739,30 @@ 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, PyObject *a UNUSED) {
#define main_loop_doc "The main thread loop"
@@ -746,23 +774,8 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
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->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;
}
if (global_state.close_all_windows) close_all_windows();
has_open_windows = process_pending_closes(self);
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;

View File

@@ -2,16 +2,36 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import subprocess
import sys
from collections import deque
from .config import load_config
from .constants import appname, defconf, is_macos, str_version
from .config import defaults, load_config
from .config_utils import resolve_config
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .layout import all_layouts
is_macos
CONFIG_HELP = '''\
Specify a path to the configuration file(s) to use. All configuration files are
merged onto the builtin {conf_name}.conf, overriding the builtin values. This option
can be specified multiple times to read multiple configuration files in
sequence, which are merged. Use the special value NONE to not load a config
file.
If this option is not specified, config files are searched for in the order:
"$XDG_CONFIG_HOME/{appname}/{conf_name}.conf", "~/.config/{appname}/{conf_name}.conf", {macos_confpath}
"$XDG_CONFIG_DIRS/{appname}/{conf_name}.conf". The first one that exists is used as the
config file.
If the environment variable "KITTY_CONFIG_DIRECTORY" is specified, that
directory is always used and the above searching does not happen.
If "/etc/xdg/{appname}/{conf_name}.conf" exists it is merged before (i.e. with lower
priority) than any user config files. It can be used to specify system-wide
defaults for all users.
'''.replace('{macos_confpath}', '~/Library/Preferences/{appname}/{conf_name}.conf, ' if is_macos else '')
OPTIONS = '''
--class
dest=cls
@@ -30,12 +50,9 @@ Set the window title. This will override any title set by the program running in
only use this if you are running a program that does not set titles.
--config
--config -c
type=list
default={config_path}
Specify a path to the configuration file(s) to use.
Can be specified multiple times to read multiple configuration files in sequence, which are merged.
{config_help}
--override -o
type=list
@@ -43,10 +60,6 @@ Override individual configuration options, can be specified multiple times.
Syntax: |_ name=value|. For example: |_ -o font_size=20|
--cmd -c
Run python code in the kitty context
--directory -d
default=.
Change to the specified directory when launching
@@ -122,6 +135,16 @@ Debug OpenGL commands. This will cause all OpenGL calls to check for errors inst
--debug-font-fallback
type=bool-set
Print out information about the selection of fallback fonts for characters not present in the main font.
--debug-config
type=bool-set
Print out information about the system and kitty configuration.
--execute -e
type=bool-set
!
'''
@@ -201,10 +224,10 @@ def parse_option_spec(spec=OPTIONS):
current_cmd['choices'] = {x.strip() for x in current_cmd['choices'].split(',')}
elif state is HELP:
if line:
current_cmd['help'] += ' ' + line
current_cmd['help'] += ' ' + line.lstrip()
else:
if prev_line:
current_cmd['help'] += '\n'
current_cmd['help'] += '\n\n\t'
else:
state = NORMAL
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
@@ -228,8 +251,12 @@ def prettify(text):
return text
def version():
return '{} {} created by {}'.format(italic(appname), green(str_version), title('Kovid Goyal'))
def version(add_rev=False):
rev = ''
from . import fast_data_types
if add_rev and hasattr(fast_data_types, 'KITTY_VCS_REV'):
rev = ' ({})'.format(fast_data_types.KITTY_VCS_REV[:10])
return '{} {}{} created by {}'.format(italic(appname), green(str_version), rev, title('Kovid Goyal'))
def wrap(text, limit=80):
@@ -267,6 +294,7 @@ def wrap(text, limit=80):
def print_help_for_seq(seq, usage, message, appname):
from kitty.icat import screen_size
screen_size.changed = True
try:
linesz = min(screen_size().cols, 76)
except EnvironmentError:
@@ -302,12 +330,15 @@ def print_help_for_seq(seq, usage, message, appname):
if isinstance(opt, str):
a('{}:'.format(title(opt)))
continue
help_text = opt['help']
if help_text == '!':
continue # hidden option
a(' ' + ', '.join(map(green, sorted(opt['aliases']))))
if not opt.get('type', '').startswith('bool-'):
blocks[-1] += '={}'.format(italic(opt['dest'].upper()))
if opt.get('help'):
defval = opt.get('default')
t = opt['help'].replace('%default', str(defval))
t = help_text.replace('%default', str(defval))
wa(prettify(t.strip()), indent=4)
if defval is not None:
wa('Default: {}'.format(defval), indent=4)
@@ -316,7 +347,8 @@ def print_help_for_seq(seq, usage, message, appname):
a('')
text = '\n'.join(blocks) + '\n\n' + version()
if sys.stdout.isatty():
if print_help_for_seq.allow_pager and sys.stdout.isatty():
import subprocess
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
p.communicate(text.encode('utf-8'))
raise SystemExit(p.wait())
@@ -324,6 +356,9 @@ def print_help_for_seq(seq, usage, message, appname):
print(text)
print_help_for_seq.allow_pager = True
def defval_for_opt(opt):
dv = opt.get('default')
typ = opt.get('type', '')
@@ -453,7 +488,7 @@ def parse_cmdline(oc, disabled, args=None):
def options_spec():
if not hasattr(options_spec, 'ans'):
options_spec.ans = OPTIONS.format(
appname=appname, config_path=defconf,
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
window_layout_choices=', '.join(all_layouts)
)
return options_spec.ans
@@ -466,8 +501,95 @@ def parse_args(args=None, ospec=options_spec, usage=None, message=None, appname=
return parse_cmdline(oc, disabled, args=args)
def create_opts(args):
config = args.config or (defconf, )
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
def print_shortcut(key_sequence, action):
if not getattr(print_shortcut, 'maps', None):
from kitty.keys import defines
v = vars(defines)
mmap = {m[len('GLFW_MOD_'):].lower(): x for m, x in v.items() if m.startswith('GLFW_MOD_')}
kmap = {k[len('GLFW_KEY_'):].lower(): x for k, x in v.items() if k.startswith('GLFW_KEY_')}
krmap = {v: k for k, v in kmap.items()}
print_shortcut.maps = mmap, krmap
mmap, krmap = print_shortcut.maps
keys = []
for key in key_sequence:
names = []
mods, key = key
for name, val in mmap.items():
if mods & val:
names.append(name)
if key:
names.append(krmap[key])
keys.append('+'.join(names))
print('\t', ' > '.join(keys), action)
def print_shortcut_changes(defns, text, changes):
if changes:
print(title(text))
for k in sorted(changes):
print_shortcut(k, defns[k])
def compare_keymaps(final, initial):
added = set(final) - set(initial)
removed = set(initial) - set(final)
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
print_shortcut_changes(final, 'Added shortcuts:', added)
print_shortcut_changes(initial, 'Removed shortcuts:', removed)
print_shortcut_changes(final, 'Changed shortcuts:', changed)
def flatten_sequence_map(m):
ans = {}
for k, rest_map in m.items():
for r, action in rest_map.items():
ans[(k,) + (r)] = action
return ans
def compare_opts(opts):
print('\nConfig options different from defaults:')
default_opts = load_config()
for f in sorted(defaults._fields):
if getattr(opts, f) != getattr(defaults, f):
if f in ('key_definitions', 'keymap', 'sequence_map'):
continue
print(title('{:20s}'.format(f)), getattr(opts, f))
final, initial = opts.keymap, default_opts.keymap
final = {(k,): v for k, v in final.items()}
initial = {(k,): v for k, v in initial.items()}
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
final.update(final_s)
initial.update(initial_s)
compare_keymaps(final, initial)
def create_opts(args, debug_config=False):
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
if debug_config:
print(version(add_rev=True))
print(' '.join(os.uname()))
if is_macos:
import subprocess
print(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
else:
print('Running under:', green('Wayland' if is_wayland else 'X11'))
if os.path.exists('/etc/issue'):
print(open('/etc/issue', encoding='utf-8', errors='replace').read().strip())
if os.path.exists('/etc/lsb-release'):
print(open('/etc/lsb-release', encoding='utf-8', errors='replace').read().strip())
config = tuple(x for x in config if os.path.exists(x))
if config:
print(green('Loaded config files:'), ', '.join(config))
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
opts = load_config(*config, overrides=overrides)
if debug_config:
compare_opts(opts)
return opts

549
kitty/cmds.py Normal file
View File

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

View File

@@ -23,8 +23,6 @@ typedef void* rusage_info_t; // needed for libproc.h
#endif
static NSMenuItem* title_menu = NULL;
static bool change_titlebar_color = false;
static color_type titlebar_color = 0;
static NSString*
@@ -207,32 +205,14 @@ cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
}
static inline color_type
color_as_int(PyObject *color) {
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s)
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff;
#undef I
}
static PyObject*
macos_change_titlebar_color(PyObject *self UNUSED, PyObject *val) {
if (val == Py_None) change_titlebar_color = false;
else if (val == Py_True) {
change_titlebar_color = true;
titlebar_color = OPT(background);
} else {
if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return NULL; }
change_titlebar_color = true;
titlebar_color = color_as_int(val);
}
Py_RETURN_NONE;
void
cocoa_set_hide_from_tasks(void) {
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
}
void
cocoa_set_titlebar_color(void *w)
cocoa_set_titlebar_color(void *w, color_type titlebar_color)
{
if (!change_titlebar_color) return;
NSWindow *window = (NSWindow *)w;
double red = ((titlebar_color >> 16) & 0xFF) / 255.0;
@@ -259,7 +239,6 @@ cocoa_set_titlebar_color(void *w)
static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"macos_change_titlebar_color", (PyCFunction)macos_change_titlebar_color, METH_O, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};

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;
@@ -184,6 +226,10 @@ color_table_address(ColorProfile *self, PyObject *a UNUSED) {
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

@@ -6,20 +6,19 @@ import ast
import json
import os
import re
import shlex
import sys
import tempfile
from collections import namedtuple
from contextlib import contextmanager
from . import fast_data_types as defines
from .config_utils import (
init_config, parse_config_base, positive_float, positive_int, to_bool,
to_color, unit_float
init_config, load_config as _load_config, merge_dicts, parse_config_base,
positive_float, positive_int, to_bool, to_cmdline, to_color, unit_float
)
from .constants import cache_dir, defconf
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts
from .rgb import color_as_int, color_from_int
from .utils import log_error
MINIMUM_FONT_SIZE = 4
@@ -47,18 +46,20 @@ def to_cursor_shape(x):
)
def parse_mods(parts):
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER', '': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
def parse_mods(parts, sc):
def map_mod(m):
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.get(m, m)
return mod_map.get(m, m)
mods = 0
for m in parts:
try:
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
except AttributeError:
log_error('Shortcut: {} has unknown modifier, ignoring'.format(
parts.join('+')))
log_error('Shortcut: {} has unknown modifier, ignoring'.format(sc))
return
return mods
@@ -80,12 +81,14 @@ named_keys = {
def parse_shortcut(sc):
parts = sc.split('+')
mods = parse_mods(parts[:-1])
mods = parse_mods(parts[:-1], sc)
if mods is None:
return None, None
key = parts[-1].upper()
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
if key is not None:
return mods, key
return None, None
return mods, None
KeyAction = namedtuple('KeyAction', 'func args')
@@ -107,29 +110,70 @@ 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':
if len(args) > 0:
try:
args[1] = parse_send_text_bytes(args[1])
except Exception:
log_error('Ignoring invalid send_text string: ' + args[1])
args[1] = ''
elif func in ('run_kitten', 'run_simple_kitten'):
if func == 'run_simple_kitten':
func = 'run_kitten'
args = rest.split(' ', 2)
elif func == 'goto_tab':
args = (max(0, int(rest)), )
elif func == 'goto_layout':
elif func == 'goto_layout' or func == 'kitty_shell':
args = [rest]
elif func == 'set_font_size':
args = (float(rest),)
elif func in shlex_actions:
args = shlex.split(rest)
args = to_cmdline(rest)
return KeyAction(func, args)
def parse_key(val, keymap):
all_key_actions = set()
sequence_sep = '>'
class KeyDefinition:
def __init__(self, is_sequence, action, mods, key, rest=()):
self.is_sequence = is_sequence
self.action = action
self.trigger = mods, key
self.rest = rest
def resolve(self, kitty_mod):
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1]
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), key) for mods, key in self.rest)
def parse_key(val, key_definitions):
sc, action = val.partition(' ')[::2]
sc, action = sc.strip(), action.strip()
sc, action = sc.strip().strip(sequence_sep), action.strip()
if not sc or not action:
return
mods, key = parse_shortcut(sc)
if key is None:
log_error('Shortcut: {} has unknown key, ignoring'.format(
val))
return
is_sequence = sequence_sep in sc
if is_sequence:
trigger = None
rest = []
for part in sc.split(sequence_sep):
mods, key = parse_shortcut(part)
if key is None:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
if trigger is None:
trigger = mods, key
else:
rest.append((mods, key))
rest = tuple(rest)
else:
mods, key = parse_shortcut(sc)
if key is None:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
try:
paction = parse_key_action(action)
except Exception:
@@ -137,7 +181,11 @@ def parse_key(val, keymap):
action))
else:
if paction is not None:
keymap[(mods, key)] = paction
all_key_actions.add(paction.func)
if is_sequence:
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], rest))
else:
key_definitions.append(KeyDefinition(False, paction, mods, key))
def parse_symbol_map(val):
@@ -177,7 +225,7 @@ def parse_send_text_bytes(text):
).encode('utf-8')
def parse_send_text(val, keymap):
def parse_send_text(val, key_definitions):
parts = val.split(' ')
def abort(msg):
@@ -190,11 +238,11 @@ def parse_send_text(val, keymap):
mode, sc = parts[:2]
text = ' '.join(parts[2:])
key_str = '{} send_text {} {}'.format(sc, mode, text)
return parse_key(key_str, keymap)
return parse_key(key_str, key_definitions)
def to_modifiers(val):
return parse_mods(val.split('+'))
return parse_mods(val.split('+'), val) or 0
def to_layout_names(raw):
@@ -216,10 +264,10 @@ def adjust_line_height(x):
def macos_titlebar_color(x):
x = x.strip('"')
if x == 'system':
return
return 0
if x == 'background':
return True
return to_color(x)
return 1
return (color_as_int(to_color(x)) << 8) | 2
def box_drawing_scale(x):
@@ -264,7 +312,8 @@ type_map = {
'adjust_line_height': adjust_line_height,
'adjust_column_width': adjust_line_height,
'scrollback_lines': positive_int,
'scrollback_pager': shlex.split,
'scrollback_pager': to_cmdline,
'open_url_with': to_cmdline,
'font_size': to_font_size,
'font_size_delta': positive_float,
'focus_follows_mouse': to_bool,
@@ -290,10 +339,10 @@ type_map = {
'initial_window_width': positive_int,
'initial_window_height': positive_int,
'macos_hide_titlebar': to_bool,
'macos_hide_from_tasks': to_bool,
'macos_option_as_alt': to_bool,
'macos_titlebar_color': macos_titlebar_color,
'box_drawing_scale': box_drawing_scale,
'x11_bell_volume': int,
'background_opacity': unit_float,
'tab_separator': tab_separator,
'active_tab_font_style': tab_font_style,
@@ -301,16 +350,20 @@ type_map = {
'inactive_text_alpha': unit_float,
'url_style': url_style,
'copy_on_select': to_bool,
'window_alert_on_bell': to_bool,
'tab_bar_edge': tab_bar_edge,
'bell_on_tab': to_bool,
'kitty_mod': to_modifiers,
'clear_all_shortcuts': to_bool,
}
for name in (
'foreground background cursor active_border_color inactive_border_color'
' selection_foreground selection_background url_color'
' selection_foreground selection_background url_color bell_border_color'
).split():
type_map[name] = to_color
for i in range(16):
type_map['color%d' % i] = to_color
for i in range(256):
type_map['color{}'.format(i)] = to_color
for a in ('active', 'inactive'):
for b in ('foreground', 'background'):
type_map['%s_tab_%s' % (a, b)] = to_color
@@ -318,15 +371,19 @@ for a in ('active', 'inactive'):
def special_handling(key, val, ans):
if key == 'map':
parse_key(val, ans['keymap'])
parse_key(val, ans['key_definitions'])
return True
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
return True
if key == 'send_text':
# For legacy compatibility
parse_send_text(val, ans['keymap'])
parse_send_text(val, ans['key_definitions'])
return True
if key == 'clear_all_shortcuts':
if to_bool(val):
ans['key_definitions'] = [None]
return
defaults = None
@@ -336,10 +393,7 @@ default_config_path = os.path.join(
def parse_config(lines, check_keys=True):
ans = {
'keymap': {},
'symbol_map': {},
}
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []}
parse_config_base(
lines,
defaults,
@@ -351,65 +405,38 @@ 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 goto_layout set_font_size 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():
f = v.func
if f in no_op_actions:
ans.pop(k, None)
continue
if f in actions:
ans[k] = v
return ans
def merge_dicts(defaults, newvals):
ans = defaults.copy()
ans.update(newvals)
return ans
def merge_configs(defaults, vals):
ans = {}
for k, v in defaults.items():
if isinstance(v, dict):
newvals = vals.get(k, {})
if k == 'keymap':
ans['keymap'] = merge_keymaps(v, newvals)
else:
ans[k] = merge_dicts(v, newvals)
ans[k] = merge_dicts(v, newvals)
elif k == 'key_definitions':
ans['key_definitions'] = v + vals.get('key_definitions', [])
else:
ans[k] = vals.get(k, v)
return ans
def load_config(*paths, overrides=None) -> Options:
ans = defaults._asdict()
for path in paths:
if not path:
continue
try:
f = open(path, encoding='utf-8', errors='replace')
except FileNotFoundError:
continue
with f:
vals = parse_config(f)
ans = merge_configs(ans, vals)
if overrides is not None:
vals = parse_config(overrides)
ans = merge_configs(ans, vals)
return Options(**ans)
def build_ansi_color_table(opts: Options = defaults):
def build_ansi_color_table(opts=defaults):
def as_int(x):
return (x[0] << 16) | (x[1] << 8) | x[2]
@@ -417,10 +444,11 @@ 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):
import tempfile
fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
try:
with os.fdopen(fd, 'wb') as f:
@@ -492,3 +520,45 @@ def prepare_config_file_for_editing():
with open(defconf, 'w') as f:
f.write(commented_out_default_config())
return defconf
def finalize_keys(opts):
defns = []
for d in opts.key_definitions:
if d is None: # clear_all_shortcuts
defns = []
else:
defns.append(d)
for d in defns:
d.resolve(opts.kitty_mod)
keymap = {}
sequence_map = {}
for defn in defns:
is_no_op = defn.action.func in no_op_actions
if defn.is_sequence:
keymap.pop(defn.trigger, None)
s = sequence_map.setdefault(defn.trigger, {})
if is_no_op:
s.pop(defn.rest, None)
if not s:
del sequence_map[defn.trigger]
else:
s[defn.rest] = defn.action
else:
sequence_map.pop(defn.trigger, None)
if is_no_op:
keymap.pop(defn.trigger, None)
else:
keymap[defn.trigger] = defn.action
opts.keymap = keymap
opts.sequence_map = sequence_map
def load_config(*paths, overrides=None):
opts = _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
finalize_keys(opts)
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
log_error('Cannot use both macos_titlebar_color and background_opacity')
opts.macos_titlebar_color = 0
return opts

View File

@@ -2,11 +2,12 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from collections import namedtuple
import shlex
from .utils import log_error
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+(.+)$')
@@ -31,33 +32,136 @@ def to_bool(x):
return x.lower() in 'y yes true'.split()
def to_cmdline(x):
ans = shlex.split(x)
ans[0] = os.path.expandvars(os.path.expanduser(ans[0]))
return ans
def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes):
line = line.strip()
if not line or line.startswith('#'):
return
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if special_handling(key, val, ans):
return
if key == 'include':
val = val.strip()
if not os.path.isabs(val):
val = os.path.join(base_path_for_includes, val)
try:
with open(val, encoding='utf-8', errors='replace') as include:
_parse(include, type_map, special_handling, ans, all_keys)
except FileNotFoundError:
log_error('Could not find included config file: {}, ignoring'.format(val))
except EnvironmentError:
log_error('Could not read from included config file: {}, ignoring'.format(val))
return
if all_keys is not None and key not in all_keys:
log_error('Ignoring unknown config key: {}'.format(key))
return
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
def _parse(lines, type_map, special_handling, ans, all_keys):
name = getattr(lines, 'name', None)
if name:
base_path_for_includes = os.path.dirname(os.path.abspath(name))
else:
from .constants import config_dir
base_path_for_includes = config_dir
for line in lines:
parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes)
def parse_config_base(
lines, defaults, type_map, special_handling, ans, check_keys=True
):
if check_keys:
all_keys = defaults._asdict()
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
all_keys = defaults._asdict() if check_keys else None
_parse(lines, type_map, special_handling, ans, all_keys)
def create_options_class(keys):
keys = tuple(sorted(keys))
slots = keys + ('_fields',)
def __init__(self, kw):
for k, v in kw.items():
setattr(self, k, v)
def __iter__(self):
return iter(keys)
def __len__(self):
return len(keys)
def __getitem__(self, i):
if isinstance(i, int):
i = keys[i]
try:
return getattr(self, i)
except AttributeError:
raise KeyError('No option named: {}'.format(i))
def _asdict(self):
return {k: getattr(self, k) for k in self._fields}
def _replace(self, **kw):
ans = self._asdict()
ans.update(kw)
return self.__class__(ans)
ans = type('Options', (), {
'__slots__': slots, '__init__': __init__, '_asdict': _asdict, '_replace': _replace, '__iter__': __iter__,
'__len__': __len__, '__getitem__': __getitem__
})
ans._fields = keys
return ans
def merge_dicts(defaults, newvals):
ans = defaults.copy()
ans.update(newvals)
return ans
def resolve_config(SYSTEM_CONF, defconf, config_files_on_cmd_line):
if config_files_on_cmd_line:
if 'NONE' not in config_files_on_cmd_line:
yield SYSTEM_CONF
for cf in config_files_on_cmd_line:
yield cf
else:
yield SYSTEM_CONF
yield defconf
def load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=None):
ans = defaults._asdict()
for path in paths:
if not path:
continue
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if special_handling(key, val, ans):
continue
if check_keys:
if key not in all_keys:
log_error('Ignoring unknown config key: {}'.format(key))
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
try:
f = open(path, encoding='utf-8', errors='replace')
except FileNotFoundError:
continue
with f:
vals = parse_config(f)
ans = merge_configs(ans, vals)
if overrides is not None:
vals = parse_config(overrides)
ans = merge_configs(ans, vals)
return Options(ans)
def init_config(defaults_path, parse_config):
with open(defaults_path, encoding='utf-8', errors='replace') as f:
defaults = parse_config(f.read().splitlines(), check_keys=False)
Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults)
defaults = parse_config(f, check_keys=False)
Options = create_options_class(defaults.keys())
defaults = Options(defaults)
return Options, defaults

View File

@@ -11,7 +11,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss
appname = 'kitty'
version = (0, 8, 2)
version = (0, 9, 1)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat
@@ -27,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
@@ -46,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

View File

@@ -193,16 +193,15 @@ PyInit_fast_data_types(void) {
#endif
if (!init_fonts(m)) return NULL;
#define OOF(n) #n, offsetof(Cell, n)
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",
OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc_idx), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
#undef OOF
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT);
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
PyModule_AddStringMacro(m, ERROR_PREFIX);
#ifdef KITTY_VCS_REV
PyModule_AddStringMacro(m, KITTY_VCS_REV);
#endif
PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);

View File

@@ -17,6 +17,7 @@
// Required minimum OpenGL version
#define OPENGL_REQUIRED_VERSION_MAJOR 3
#define OPENGL_REQUIRED_VERSION_MINOR 3
#define GLFW_MOD_KITTY 1024
#define UNUSED __attribute__ ((unused))
#define PYNOARG PyObject *__a1 UNUSED, PyObject *__a2 UNUSED
#define EXPORTED __attribute__ ((visibility ("default")))
@@ -160,15 +161,18 @@ typedef struct {
Line *line;
} LineBuf;
typedef struct {
Cell *cells;
line_attrs_type *line_attrs;
} HistoryBufSegment;
typedef struct {
PyObject_HEAD
Cell *buf;
index_type xnum, ynum;
index_type xnum, ynum, num_segments;
HistoryBufSegment* segments;
Line *line;
index_type start_of_data, count;
line_attrs_type *line_attrs;
} HistoryBuf;
typedef struct {
@@ -265,6 +269,5 @@ void wakeup_io_loop(bool);
void scroll_event(double, double);
void fake_scroll(int, bool);
void set_special_key_combo(int glfw_key, int mods);
void on_text_input(unsigned int codepoint, int mods);
void on_key_input(int key, int scancode, int action, int mods);
void on_key_input(int key, int scancode, int action, int mods, const char*, int);
void request_window_attention(id_type, bool);

2
kitty/emoji.h generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2018-02-09
// unicode data, built from the unicode standard on: 2018-05-01
// see gen-wcwidth.py
#pragma once
#include "data-types.h"

View File

@@ -852,7 +852,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", g + 1 < group->num_glyphs ? G(info)[group->first_glyph_idx + g].codepoint : 0));
PyList_Append(ans, Py_BuildValue("IIIN", group->num_cells, group->num_glyphs, first_glyph, eg));
PyList_Append(ans, Py_BuildValue("IIHN", group->num_cells, group->num_glyphs, first_glyph, eg));
idx++;
}
if (face) { Py_CLEAR(face); free(font); }
@@ -897,6 +897,12 @@ render_line(Line *line) {
ssize_t cell_font_idx = font_for_cell(cell);
if (is_private_use(cell->ch) && i + 1 < line->xnum && (line->cells[i+1].ch == ' ' || line->cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
// We have a private use char followed by a space char, render it as a two cell ligature.
Cell *space_cell = line->cells + i+1;
// Ensure the space cell uses the foreground colors from the PUA cell
// This is needed because there are stupid applications like
// powerline that use PUA+space with different foreground colors
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
space_cell->fg = cell->fg; space_cell->decoration_fg = cell->decoration_fg;
RENDER;
render_run(line->cells + i, 2, cell_font_idx, true);
run_font_idx = NO_FONT;
@@ -982,7 +988,7 @@ static PyObject*
test_sprite_position_for(PyObject UNUSED *self, PyObject *args) {
glyph_index glyph;
ExtraGlyphs extra_glyphs = {{0}};
if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs.data)) return NULL;
if (!PyArg_ParseTuple(args, "H|H", &glyph, &extra_glyphs.data)) return NULL;
int error;
SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, &extra_glyphs, 0, &error);
if (pos == NULL) { sprite_map_set_error(error); return NULL; }

View File

@@ -214,15 +214,20 @@ ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_dat
PyObject*
face_from_descriptor(PyObject *descriptor) {
#define D(key, conv) { PyObject *t = PyDict_GetItemString(descriptor, #key); if (t == NULL) return NULL; key = conv(t); t = NULL; }
char *path;
long index;
bool hinting;
long hint_style;
D(path, PyUnicode_AsUTF8);
D(index, PyLong_AsLong);
D(hinting, PyObject_IsTrue);
D(hint_style, PyLong_AsLong);
#define D(key, conv, missing_ok) { \
PyObject *t = PyDict_GetItemString(descriptor, #key); \
if (t == NULL) { \
if (!missing_ok) { PyErr_SetString(PyExc_KeyError, "font descriptor is missing the key: " #key); return NULL; } \
} else key = conv(t); \
}
char *path = NULL;
long index = 0;
bool hinting = false;
long hint_style = 0;
D(path, PyUnicode_AsUTF8, false);
D(index, PyLong_AsLong, true);
D(hinting, PyObject_IsTrue, true);
D(hint_style, PyLong_AsLong, true);
#undef D
Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0);
if (self != NULL) {
@@ -279,7 +284,7 @@ calc_cell_width(Face *self) {
for (char_type i = 32; i < 128; i++) {
int glyph_index = FT_Get_Char_Index(self->face, i);
if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) {
ans = MAX(ans, (unsigned long)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
ans = MAX(ans, (unsigned int)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
}
}
return ans;
@@ -324,7 +329,6 @@ typedef struct {
unsigned int factor, right_edge;
} ProcessedBitmap;
static inline void
trim_borders(ProcessedBitmap *ans, size_t extra) {
bool column_has_text = false;
@@ -354,9 +358,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;

6265
kitty/gl-wrapper.c generated

File diff suppressed because it is too large Load Diff

43
kitty/gl-wrapper.h generated
View File

@@ -1,7 +1,5 @@
/*
OpenGL loader generated by glad 0.1.15a0 on Wed Nov 8 14:58:53 2017.
OpenGL loader generated by glad 0.1.20a0 on Fri May 4 18:11:55 2018.
Language/Generator: C/C++ Debug
Specification: gl
APIs: gl=3.3
@@ -15,22 +13,17 @@
Loader: True
Local files: False
Omit khrplatform: False
Commandline:
--profile="core" --api="gl=3.3" --generator="c-debug" --spec="gl" --extensions="GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_KHR_debug"
Online:
http://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
*/
#ifndef __glad_h_
#define __glad_h_
#ifdef __gl_h_
#error OpenGL header already included, remove this include, glad already provides it
#endif
#define __gl_h_
#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN 1
@@ -40,25 +33,20 @@
#endif
#include <windows.h>
#endif
#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef APIENTRYP
#define APIENTRYP APIENTRY *
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct gladGLversionStruct {
int major;
int minor;
};
typedef void* (* GLADloadproc)(const char *name);
#ifndef GLAPI
# if defined(GLAD_GLAPI_EXPORT)
# if defined(_WIN32) || defined(__CYGWIN__)
@@ -84,17 +72,11 @@ typedef void* (* GLADloadproc)(const char *name);
# define GLAPI extern
# endif
#endif
GLAPI struct gladGLversionStruct GLVersion;
GLAPI int gladLoadGL(void);
GLAPI int gladLoadGLLoader(GLADloadproc);
#define GLAD_DEBUG
typedef void (* GLADcallback)(const char *name, void *funcptr, int len_args, ...);
GLAPI void glad_set_pre_callback(GLADcallback cb);
GLAPI void glad_set_post_callback(GLADcallback cb);
#include <stddef.h>
@@ -1112,11 +1094,11 @@ typedef void (APIENTRYP PFNGLENABLEPROC)(GLenum cap);
GLAPI PFNGLENABLEPROC glad_glEnable;
GLAPI PFNGLENABLEPROC glad_debug_glEnable;
#define glEnable glad_debug_glEnable
typedef void (APIENTRYP PFNGLFINISHPROC)();
typedef void (APIENTRYP PFNGLFINISHPROC)(void);
GLAPI PFNGLFINISHPROC glad_glFinish;
GLAPI PFNGLFINISHPROC glad_debug_glFinish;
#define glFinish glad_debug_glFinish
typedef void (APIENTRYP PFNGLFLUSHPROC)();
typedef void (APIENTRYP PFNGLFLUSHPROC)(void);
GLAPI PFNGLFLUSHPROC glad_glFlush;
GLAPI PFNGLFLUSHPROC glad_debug_glFlush;
#define glFlush glad_debug_glFlush
@@ -1164,7 +1146,7 @@ typedef void (APIENTRYP PFNGLGETDOUBLEVPROC)(GLenum pname, GLdouble *data);
GLAPI PFNGLGETDOUBLEVPROC glad_glGetDoublev;
GLAPI PFNGLGETDOUBLEVPROC glad_debug_glGetDoublev;
#define glGetDoublev glad_debug_glGetDoublev
typedef GLenum (APIENTRYP PFNGLGETERRORPROC)();
typedef GLenum (APIENTRYP PFNGLGETERRORPROC)(void);
GLAPI PFNGLGETERRORPROC glad_glGetError;
GLAPI PFNGLGETERRORPROC glad_debug_glGetError;
#define glGetError glad_debug_glGetError
@@ -1204,7 +1186,7 @@ typedef GLboolean (APIENTRYP PFNGLISENABLEDPROC)(GLenum cap);
GLAPI PFNGLISENABLEDPROC glad_glIsEnabled;
GLAPI PFNGLISENABLEDPROC glad_debug_glIsEnabled;
#define glIsEnabled glad_debug_glIsEnabled
typedef void (APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble near, GLdouble far);
typedef void (APIENTRYP PFNGLDEPTHRANGEPROC)(GLdouble n, GLdouble f);
GLAPI PFNGLDEPTHRANGEPROC glad_glDepthRange;
GLAPI PFNGLDEPTHRANGEPROC glad_debug_glDepthRange;
#define glDepthRange glad_debug_glDepthRange
@@ -1484,7 +1466,7 @@ typedef void (APIENTRYP PFNGLCOMPILESHADERPROC)(GLuint shader);
GLAPI PFNGLCOMPILESHADERPROC glad_glCompileShader;
GLAPI PFNGLCOMPILESHADERPROC glad_debug_glCompileShader;
#define glCompileShader glad_debug_glCompileShader
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC)();
typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC)(void);
GLAPI PFNGLCREATEPROGRAMPROC glad_glCreateProgram;
GLAPI PFNGLCREATEPROGRAMPROC glad_debug_glCreateProgram;
#define glCreateProgram glad_debug_glCreateProgram
@@ -1884,7 +1866,7 @@ typedef void (APIENTRYP PFNGLBEGINTRANSFORMFEEDBACKPROC)(GLenum primitiveMode);
GLAPI PFNGLBEGINTRANSFORMFEEDBACKPROC glad_glBeginTransformFeedback;
GLAPI PFNGLBEGINTRANSFORMFEEDBACKPROC glad_debug_glBeginTransformFeedback;
#define glBeginTransformFeedback glad_debug_glBeginTransformFeedback
typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC)();
typedef void (APIENTRYP PFNGLENDTRANSFORMFEEDBACKPROC)(void);
GLAPI PFNGLENDTRANSFORMFEEDBACKPROC glad_glEndTransformFeedback;
GLAPI PFNGLENDTRANSFORMFEEDBACKPROC glad_debug_glEndTransformFeedback;
#define glEndTransformFeedback glad_debug_glEndTransformFeedback
@@ -1912,7 +1894,7 @@ typedef void (APIENTRYP PFNGLBEGINCONDITIONALRENDERPROC)(GLuint id, GLenum mode)
GLAPI PFNGLBEGINCONDITIONALRENDERPROC glad_glBeginConditionalRender;
GLAPI PFNGLBEGINCONDITIONALRENDERPROC glad_debug_glBeginConditionalRender;
#define glBeginConditionalRender glad_debug_glBeginConditionalRender
typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC)();
typedef void (APIENTRYP PFNGLENDCONDITIONALRENDERPROC)(void);
GLAPI PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender;
GLAPI PFNGLENDCONDITIONALRENDERPROC glad_debug_glEndConditionalRender;
#define glEndConditionalRender glad_debug_glEndConditionalRender
@@ -2678,7 +2660,7 @@ GLAPI PFNGLSAMPLECOVERAGEARBPROC glad_debug_glSampleCoverageARB;
#ifndef GL_ARB_robustness
#define GL_ARB_robustness 1
GLAPI int GLAD_GL_ARB_robustness;
typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC)();
typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSARBPROC)(void);
GLAPI PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_glGetGraphicsResetStatusARB;
GLAPI PFNGLGETGRAPHICSRESETSTATUSARBPROC glad_debug_glGetGraphicsResetStatusARB;
#define glGetGraphicsResetStatusARB glad_debug_glGetGraphicsResetStatusARB
@@ -2798,7 +2780,7 @@ typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPPROC)(GLenum source, GLuint id, GLsiz
GLAPI PFNGLPUSHDEBUGGROUPPROC glad_glPushDebugGroup;
GLAPI PFNGLPUSHDEBUGGROUPPROC glad_debug_glPushDebugGroup;
#define glPushDebugGroup glad_debug_glPushDebugGroup
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC)();
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPPROC)(void);
GLAPI PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup;
GLAPI PFNGLPOPDEBUGGROUPPROC glad_debug_glPopDebugGroup;
#define glPopDebugGroup glad_debug_glPopDebugGroup
@@ -2842,7 +2824,7 @@ typedef void (APIENTRYP PFNGLPUSHDEBUGGROUPKHRPROC)(GLenum source, GLuint id, GL
GLAPI PFNGLPUSHDEBUGGROUPKHRPROC glad_glPushDebugGroupKHR;
GLAPI PFNGLPUSHDEBUGGROUPKHRPROC glad_debug_glPushDebugGroupKHR;
#define glPushDebugGroupKHR glad_debug_glPushDebugGroupKHR
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC)();
typedef void (APIENTRYP PFNGLPOPDEBUGGROUPKHRPROC)(void);
GLAPI PFNGLPOPDEBUGGROUPKHRPROC glad_glPopDebugGroupKHR;
GLAPI PFNGLPOPDEBUGGROUPKHRPROC glad_debug_glPopDebugGroupKHR;
#define glPopDebugGroupKHR glad_debug_glPopDebugGroupKHR
@@ -2867,11 +2849,8 @@ GLAPI PFNGLGETPOINTERVKHRPROC glad_glGetPointervKHR;
GLAPI PFNGLGETPOINTERVKHRPROC glad_debug_glGetPointervKHR;
#define glGetPointervKHR glad_debug_glGetPointervKHR
#endif
#ifdef __cplusplus
}
#endif
#endif
int init_glad(GLADloadproc, int);

View File

@@ -59,6 +59,7 @@ gl_init() {
ARB_TEST(texture_storage);
#undef ARB_TEST
glad_loaded = true;
if (global_state.debug_gl) printf("GL version string: '%s' Detected version: %d.%d\n", glGetString(GL_VERSION), GLVersion.major, GLVersion.minor);
if (GLVersion.major < OPENGL_REQUIRED_VERSION_MAJOR || (GLVersion.major == OPENGL_REQUIRED_VERSION_MAJOR && GLVersion.minor < OPENGL_REQUIRED_VERSION_MINOR)) {
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", GLVersion.major, GLVersion.minor);
}

12
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());
@@ -363,6 +357,8 @@ load_glfw(const char* path) {
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
*(void **) (&glfwGetX11Display_impl) = dlsym(handle, "glfwGetX11Display");
*(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window");

85
kitty/glfw-wrapper.h generated
View File

@@ -1,6 +1,7 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
/*! @name GLFW version macros
@@ -1133,9 +1134,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 +1150,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 +1558,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 +1686,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;
@@ -1861,6 +1826,10 @@ typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*);
glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl;
#define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl
typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwindow*, GLFWcocoatextinputfilterfun);
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
#define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl
typedef void* (*glfwGetX11Display_func)();
glfwGetX11Display_func glfwGetX11Display_impl;
#define glfwGetX11Display glfwGetX11Display_impl

View File

@@ -9,13 +9,15 @@
#include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w);
extern void cocoa_create_global_menu(void);
extern void cocoa_set_titlebar_color(void *w);
extern void cocoa_set_hide_from_tasks(void);
extern void cocoa_set_titlebar_color(void *w, color_type color);
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
#endif
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) {
@@ -74,9 +76,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;
@@ -84,6 +97,7 @@ 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();
unjam_event_loop();
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
global_state.callback_os_window = NULL;
}
@@ -96,21 +110,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;
}
@@ -231,7 +237,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;
}
@@ -300,12 +310,29 @@ set_dpi_from_os_window(OSWindow *w) {
static bool is_first_window = true;
#ifdef __APPLE__
static int
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED) {
return ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) ? 1 : 0;
}
#endif
void
set_titlebar_color(OSWindow *w, color_type color) {
if (w->handle && (!w->last_titlebar_color || (w->last_titlebar_color & 0xffffff) != (color & 0xffffff))) {
w->last_titlebar_color = (1 << 24) | (color & 0xffffff);
#ifdef __APPLE__
cocoa_set_titlebar_color(glfwGetCocoaWindow(w->handle), color);
#endif
}
}
static PyObject*
create_os_window(PyObject UNUSED *self, PyObject *args) {
int width, height, x = -1, y = -1;
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;
if (!PyArg_ParseTuple(args, "iisss|Oii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
if (is_first_window) {
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
@@ -338,7 +365,7 @@ 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) {
@@ -358,6 +385,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
Py_DECREF(ret);
#ifdef __APPLE__
cocoa_create_global_menu();
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
// This needs to be done only after the first window has been created, because glfw only sets the activation policy once upon initialization.
if (OPT(macos_hide_from_tasks)) cocoa_set_hide_from_tasks();
#endif
is_first_window = false;
}
@@ -375,11 +405,10 @@ 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__
@@ -387,7 +416,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
else log_error("Failed to load glfwGetCocoaWindow");
}
cocoa_set_titlebar_color(glfwGetCocoaWindow(glfw_window));
#endif
double now = monotonic();
w->is_focused = true;
@@ -488,18 +516,6 @@ glfw_terminate(PYNOARG) {
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(PYNOARG) {
glfwPostEmptyEvent();
@@ -604,7 +620,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);
}
}
@@ -613,7 +629,7 @@ request_window_attention(id_type kitty_window_id, bool audio_bell) {
OSWindow *w = os_window_for_kitty_window(kitty_window_id);
if (w) {
if (audio_bell) ring_audio_bell(w);
glfwRequestWindowAttention(w->handle);
if (OPT(window_alert_on_bell)) glfwRequestWindowAttention(w->handle);
glfwPostEmptyEvent();
}
}
@@ -625,9 +641,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
@@ -637,8 +651,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
@@ -771,7 +785,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, ""},
@@ -780,10 +793,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);
@@ -925,6 +947,7 @@ init_glfw(PyObject *m) {
ADDC(GLFW_MOD_CONTROL);
ADDC(GLFW_MOD_ALT);
ADDC(GLFW_MOD_SUPER);
ADDC(GLFW_MOD_KITTY);
// --- Mouse -------------------------------------------------------------------
ADDC(GLFW_MOUSE_BUTTON_1);

View File

@@ -873,9 +873,10 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
static inline PyObject*
image_as_dict(Image *img) {
#define U(x) #x, img->x
return Py_BuildValue("{sI sI sI sI sI sI sH sH sN}",
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt), U(data_loaded),
"is_4byte_aligned", img->load_data.is_4byte_aligned,
return Py_BuildValue("{sI sI sI sI sI sI sO sO sN}",
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt),
"data_loaded", img->data_loaded ? Py_True : Py_False,
"is_4byte_aligned", img->load_data.is_4byte_aligned ? Py_True : Py_False,
"data", Py_BuildValue("y#", img->load_data.data, img->load_data.data_sz)
);
#undef U

View File

@@ -10,10 +10,42 @@
#include <structmember.h>
extern PyTypeObject Line_Type;
#define SEGMENT_SIZE 2048
static inline void
add_segment(HistoryBuf *self) {
self->num_segments += 1;
self->segments = PyMem_Realloc(self->segments, sizeof(HistoryBufSegment) * self->num_segments);
if (self->segments == NULL) fatal("Out of memory allocating new history buffer segment");
HistoryBufSegment *s = self->segments + self->num_segments - 1;
s->cells = PyMem_Calloc(self->xnum * SEGMENT_SIZE, sizeof(Cell));
s->line_attrs = PyMem_Calloc(SEGMENT_SIZE, sizeof(line_attrs_type));
if (s->cells == NULL || s->line_attrs == NULL) fatal("Out of memory allocating new history buffer segment");
}
static inline index_type
segment_for(HistoryBuf *self, index_type y) {
index_type seg_num = y / SEGMENT_SIZE;
while (UNLIKELY(seg_num >= self->num_segments && SEGMENT_SIZE * self->num_segments < self->ynum)) add_segment(self);
if (UNLIKELY(seg_num >= self->num_segments)) fatal("Out of bounds access to history buffer line number: %u", y);
return seg_num;
}
#define seg_ptr(which, stride) { \
index_type seg_num = segment_for(self, y); \
y -= seg_num * SEGMENT_SIZE; \
return self->segments[seg_num].which + y * stride; \
}
static inline Cell*
lineptr(HistoryBuf *linebuf, index_type y) {
return linebuf->buf + y * linebuf->xnum;
lineptr(HistoryBuf *self, index_type y) {
seg_ptr(cells, self->xnum);
}
static inline line_attrs_type*
attrptr(HistoryBuf *self, index_type y) {
seg_ptr(line_attrs, 1);
}
static PyObject *
@@ -32,18 +64,12 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
if (self != NULL) {
self->xnum = xnum;
self->ynum = ynum;
self->buf = PyMem_Calloc(xnum * ynum, sizeof(Cell));
self->line_attrs = PyMem_Calloc(ynum, sizeof(line_attrs_type));
self->line = alloc_line();
if (self->buf == NULL || self->line == NULL || self->line_attrs == NULL) {
PyErr_NoMemory();
PyMem_Free(self->buf); Py_CLEAR(self->line); PyMem_Free(self->line_attrs);
Py_CLEAR(self);
} else {
self->line->xnum = xnum;
for(index_type y = 0; y < self->ynum; y++) {
clear_chars_in_line(lineptr(self, y), self->xnum, BLANK_CHAR);
}
self->num_segments = 0;
add_segment(self);
self->line->xnum = xnum;
for(index_type y = 0; y < self->ynum; y++) {
clear_chars_in_line(lineptr(self, y), self->xnum, BLANK_CHAR);
}
}
@@ -53,8 +79,11 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
static void
dealloc(HistoryBuf* self) {
Py_CLEAR(self->line);
PyMem_Free(self->buf);
PyMem_Free(self->line_attrs);
for (size_t i = 0; i < self->num_segments; i++) {
PyMem_Free(self->segments[i].cells);
PyMem_Free(self->segments[i].line_attrs);
}
PyMem_Free(self->segments);
Py_TYPE(self)->tp_free((PyObject*)self);
}
@@ -71,8 +100,8 @@ static inline void
init_line(HistoryBuf *self, index_type num, Line *l) {
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
l->cells = lineptr(self, num);
l->continued = self->line_attrs[num] & CONTINUED_MASK;
l->has_dirty_text = self->line_attrs[num] & TEXT_DIRTY_MASK ? true : false;
l->continued = *attrptr(self, num) & CONTINUED_MASK;
l->has_dirty_text = *attrptr(self, num) & TEXT_DIRTY_MASK ? true : false;
}
void
@@ -82,12 +111,14 @@ historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) {
void
historybuf_mark_line_clean(HistoryBuf *self, index_type y) {
self->line_attrs[index_of(self, y)] &= ~TEXT_DIRTY_MASK;
line_attrs_type *p = attrptr(self, index_of(self, y));
*p &= ~TEXT_DIRTY_MASK;
}
void
historybuf_mark_line_dirty(HistoryBuf *self, index_type y) {
self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK;
line_attrs_type *p = attrptr(self, index_of(self, y));
*p |= TEXT_DIRTY_MASK;
}
inline void
@@ -105,43 +136,11 @@ historybuf_push(HistoryBuf *self) {
return idx;
}
bool
historybuf_resize(HistoryBuf *self, index_type lines) {
HistoryBuf t = {{0}};
t.xnum=self->xnum;
t.ynum=lines;
if (t.ynum > 0 && t.ynum != self->ynum) {
t.buf = PyMem_Calloc(t.xnum * t.ynum, sizeof(Cell));
if (t.buf == NULL) { PyErr_NoMemory(); return false; }
t.line_attrs = PyMem_Calloc(t.ynum, sizeof(line_attrs_type));
if (t.line_attrs == NULL) { PyMem_Free(t.buf); PyErr_NoMemory(); return false; }
t.count = MIN(self->count, t.ynum);
for (index_type s=0; s < t.count; s++) {
index_type si = index_of(self, s), ti = index_of(&t, s);
copy_cells(lineptr(self, si), lineptr(&t, ti), t.xnum);
t.line_attrs[ti] = self->line_attrs[si];
}
self->count = t.count;
self->start_of_data = t.start_of_data;
self->ynum = t.ynum;
PyMem_Free(self->buf); PyMem_Free(self->line_attrs);
self->buf = t.buf; self->line_attrs = t.line_attrs;
}
return true;
}
void
historybuf_add_line(HistoryBuf *self, const Line *line) {
index_type idx = historybuf_push(self);
copy_line(line, self->line);
self->line_attrs[idx] = (line->continued & CONTINUED_MASK) | (line->has_dirty_text ? TEXT_DIRTY_MASK : 0);
}
static PyObject*
change_num_of_lines(HistoryBuf *self, PyObject *val) {
#define change_num_of_lines_doc "Change the number of lines in this buffer"
if(!historybuf_resize(self, (index_type)PyLong_AsUnsignedLong(val))) return NULL;
Py_RETURN_NONE;
*attrptr(self, idx) = (line->continued & CONTINUED_MASK) | (line->has_dirty_text ? TEXT_DIRTY_MASK : 0);
}
static PyObject*
@@ -188,7 +187,7 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
for(unsigned int i = 0; i < self->count; i++) {
init_line(self, i, &l);
if (i < self->count - 1) {
l.continued = self->line_attrs[index_of(self, i + 1)] & CONTINUED_MASK;
l.continued = *attrptr(self, index_of(self, i + 1)) & CONTINUED_MASK;
} else l.continued = false;
index_type num = line_as_ansi(&l, t, 5120);
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
@@ -219,7 +218,7 @@ 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++) {
if (self->line_attrs[i] & TEXT_DIRTY_MASK) {
if (*attrptr(self, i) & TEXT_DIRTY_MASK) {
PyList_Append(ans, PyLong_FromUnsignedLong(i));
}
}
@@ -232,7 +231,6 @@ static PyObject* rewrap(HistoryBuf *self, PyObject *args);
#define rewrap_doc ""
static PyMethodDef methods[] = {
METHOD(change_num_of_lines, METH_O)
METHOD(line, METH_O)
METHOD(as_ansi, METH_O)
METHODB(as_text, METH_VARARGS),
@@ -275,19 +273,22 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
#define init_src_line(src_y) init_line(src, map_src_index(src_y), src->line);
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (src->line_attrs[map_src_index(src_y + 1)] & CONTINUED_MASK) : false)
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (*attrptr(src, map_src_index(src_y + 1)) & CONTINUED_MASK) : false)
#define next_dest_line(cont) dest->line_attrs[historybuf_push(dest)] = cont & CONTINUED_MASK; dest->line->continued = cont;
#define next_dest_line(cont) *attrptr(dest, historybuf_push(dest)) = cont & CONTINUED_MASK; dest->line->continued = cont;
#define first_dest_line next_dest_line(false);
#include "rewrap.h"
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
// Fast path
while(other->num_segments < self->num_segments) add_segment(other);
if (other->xnum == self->xnum && other->ynum == self->ynum) {
memcpy(other->buf, self->buf, sizeof(Cell) * self->xnum * self->ynum);
memcpy(other->line_attrs, self->line_attrs, sizeof(line_attrs_type) * self->ynum);
// Fast path
for (index_type i = 0; i < self->num_segments; i++) {
memcpy(other->segments[i].cells, self->segments[i].cells, SEGMENT_SIZE * self->xnum * sizeof(Cell));
memcpy(other->segments[i].line_attrs, self->segments[i].line_attrs, SEGMENT_SIZE * sizeof(line_attrs_type));
}
other->count = self->count; other->start_of_data = self->start_of_data;
return;
}
@@ -295,7 +296,7 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
index_type x = 0, y = 0;
if (self->count > 0) {
rewrap_inner(self, other, self->count, NULL, &x, &y);
for (index_type i = 0; i < other->count; i++) other->line_attrs[(other->start_of_data + i) % other->ynum] |= TEXT_DIRTY_MASK;
for (index_type i = 0; i < other->count; i++) *attrptr(other, (other->start_of_data + i) % other->ynum) |= TEXT_DIRTY_MASK;
}
}

View File

@@ -43,22 +43,6 @@ active_window() {
return NULL;
}
void
on_text_input(unsigned int codepoint, int mods) {
Window *w = active_window();
static char buf[16];
unsigned int sz = 0;
if (w != NULL) {
bool is_text = mods <= GLFW_MOD_SHIFT;
if (is_text) sz = encode_utf8(codepoint, buf);
#ifdef __APPLE__
if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods)) sz = encode_utf8(codepoint, buf);
#endif
if (sz) schedule_write_to_child(w->id, buf, sz);
}
}
static inline bool
is_modifier_key(int key) {
switch(key) {
@@ -76,66 +60,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 +76,26 @@ send_key_to_child(Window *w, int key, int mods, int action) {
}
}
static inline bool
is_ascii_control_char(char c) {
return c == 0 || (1 <= 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 &&
key != GLFW_KEY_LEFT_SHIFT && key != GLFW_KEY_RIGHT_SHIFT && key != GLFW_KEY_LEFT_ALT && key != GLFW_KEY_RIGHT_ALT && key != GLFW_KEY_LEFT_CONTROL && key != GLFW_KEY_RIGHT_CONTROL
) 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]);
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 +103,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 +112,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);
}
}
}

1552
kitty/keys.h generated

File diff suppressed because it is too large Load Diff

View File

@@ -37,6 +37,16 @@ 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
SHIFTED_KEYS = {
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
defines.GLFW_KEY_END: key_as_bytes('kEND'),
defines.GLFW_KEY_LEFT: key_as_bytes('kLFT'),
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
defines.GLFW_KEY_UP: key_as_bytes('kri'),
defines.GLFW_KEY_DOWN: key_as_bytes('kind'),
}
for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1',
@@ -58,9 +68,14 @@ for f in range(1, 13):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
SHIFTED_KEYS[kf] = modify_complex_key(kn, 2)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
for f in range(13, 26):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
f = {k: k for k in '0123456789'}
f.update({
'COMMA': ',',
@@ -80,16 +95,6 @@ smkx_key_map[defines.GLFW_KEY_KP_ENTER] = b'\r'
smkx_key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs')
smkx_key_map[defines.GLFW_KEY_TAB] = b'\t'
SHIFTED_KEYS = {
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
defines.GLFW_KEY_END: key_as_bytes('kEND'),
defines.GLFW_KEY_LEFT: key_as_bytes('kLFT'),
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
defines.GLFW_KEY_UP: key_as_bytes('kri'),
defines.GLFW_KEY_DOWN: key_as_bytes('kind'),
}
control_codes.update({
k: (1 + i, )
for i, k in
@@ -251,6 +256,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

43
kitty/khrplatform.h generated
View File

@@ -1,8 +1,7 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2009 The Khronos Group Inc.
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
@@ -23,21 +22,18 @@
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* $Revision: 32517 $ on $Date: 2016-03-11 02:41:19 -0800 (Fri, 11 Mar 2016) $
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by sending them to the public Khronos Bugzilla
* (http://khronos.org/bugzilla) by filing a bug against product
* "Khronos (general)" component "Registry".
*
* A predefined template which fills in some of the bug fields can be
* reached using http://tinyurl.com/khrplatform-h-bugreport, but you
* must create a Bugzilla login first.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
@@ -91,7 +87,6 @@
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
@@ -106,7 +101,6 @@
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
@@ -119,7 +113,6 @@
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
@@ -130,13 +123,10 @@
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
@@ -147,9 +137,7 @@ typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
@@ -160,9 +148,7 @@ typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
@@ -172,9 +158,7 @@ typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
@@ -189,9 +173,7 @@ typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
@@ -199,9 +181,7 @@ typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
@@ -212,10 +192,7 @@ typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
@@ -223,7 +200,6 @@ typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
@@ -240,14 +216,12 @@ typedef unsigned long int khronos_uintptr_t;
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
@@ -261,14 +235,12 @@ typedef float khronos_float_t;
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
@@ -280,5 +252,4 @@ typedef enum {
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

View File

@@ -1,5 +1,10 @@
# 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 of 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,
@@ -96,7 +101,7 @@ 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
open_url_modifiers kitty_mod
# 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.
@@ -161,6 +166,14 @@ visual_bell_duration 0.0
# Enable/disable the audio bell. Useful in environments that require silence.
enable_audio_bell yes
# Request window attention on bell.
# Makes the dock icon bounce on macOS or the taskbar flash on linux.
window_alert_on_bell yes
# Show a bell symbol on the tab if a bell occurs in one of the windows in the
# tab and the window is not the currently focused window
bell_on_tab yes
# }}}
@@ -193,6 +206,9 @@ active_border_color #00ff00
# The color for the border of inactive windows
inactive_border_color #cccccc
# The color for the border of inactive windows in which a bell has occurred
bell_border_color #ff5a00
# 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
@@ -242,7 +258,8 @@ selection_foreground #000000
selection_background #FFFACD
# 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
@@ -313,6 +330,11 @@ 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
# The value of kitty_mod is used as the modifier for all default shortcuts, you
# can change it in your kitty.conf to change the modifiers for all the default
# shortcuts.
kitty_mod ctrl+shift
#
# You can use the special action no_op to unmap a keyboard shortcut that is
# assigned in the default configuration.
@@ -321,63 +343,69 @@ term xterm-kitty
# syntax below:
# map key combine <separator> action1 <separator> action2 <separator> action3 ...
# For example:
# map ctrl+shift+e combine : new_window : next_layout
# map kitty_mod+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 {{{
map ctrl+shift+v paste_from_clipboard
map ctrl+shift+s paste_from_selection
map ctrl+shift+c copy_to_clipboard
map kitty_mod+v paste_from_clipboard
map kitty_mod+s paste_from_selection
map kitty_mod+c copy_to_clipboard
map shift+insert paste_from_selection
# You can also pass the contents of the current selection to any program using
# pass_selection_to_program. By default, the system's open program is used, but
# you can specify your own, for example:
# map ctrl+shift+o pass_selection_to_program firefox
map ctrl+shift+o pass_selection_to_program
# map kitty_mod+o pass_selection_to_program firefox
map kitty_mod+o pass_selection_to_program
# }}}
# Scrolling {{{
map ctrl+shift+up scroll_line_up
map ctrl+shift+down scroll_line_down
map ctrl+shift+k scroll_line_up
map ctrl+shift+j scroll_line_down
map ctrl+shift+page_up scroll_page_up
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
map kitty_mod+up scroll_line_up
map kitty_mod+down scroll_line_down
map kitty_mod+k scroll_line_up
map kitty_mod+j scroll_line_down
map kitty_mod+page_up scroll_page_up
map kitty_mod+page_down scroll_page_down
map kitty_mod+home scroll_home
map kitty_mod+end scroll_end
map kitty_mod+h show_scrollback
# }}}
# Window management {{{
map ctrl+shift+enter new_window
map ctrl+shift+n new_os_window
map ctrl+shift+w close_window
map ctrl+shift+] next_window
map ctrl+shift+[ previous_window
map ctrl+shift+f move_window_forward
map ctrl+shift+b move_window_backward
map ctrl+shift+` move_window_to_top
map ctrl+shift+1 first_window
map ctrl+shift+2 second_window
map ctrl+shift+3 third_window
map ctrl+shift+4 fourth_window
map ctrl+shift+5 fifth_window
map ctrl+shift+6 sixth_window
map ctrl+shift+7 seventh_window
map ctrl+shift+8 eighth_window
map ctrl+shift+9 ninth_window
map ctrl+shift+0 tenth_window
map kitty_mod+enter new_window
map kitty_mod+n new_os_window
map kitty_mod+w close_window
map kitty_mod+] next_window
map kitty_mod+[ previous_window
map kitty_mod+f move_window_forward
map kitty_mod+b move_window_backward
map kitty_mod+` move_window_to_top
map kitty_mod+1 first_window
map kitty_mod+2 second_window
map kitty_mod+3 third_window
map kitty_mod+4 fourth_window
map kitty_mod+5 fifth_window
map kitty_mod+6 sixth_window
map kitty_mod+7 seventh_window
map kitty_mod+8 eighth_window
map kitty_mod+9 ninth_window
map kitty_mod+0 tenth_window
# You can open a new window running an arbitrary program, for example:
# map ctrl+shift+y new_window mutt
# map kitty_mod+y new_window mutt
#
# You can pass the current selection to the new program by using the @selection placeholder
# map ctrl+shift+y new_window less @selection
# map kitty_mod+y new_window less @selection
#
# You can even send the contents of the current screen + history buffer as stdin using
# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes).
# For only the current screen, use @screen or @ansi_screen.
# For example, the following command opens the scrollback buffer in less in a new window.
# map ctrl+shift+y new_window @ansi less +G -R
# map kitty_mod+y new_window @ansi less +G -R
#
# You can open a new window with the current working directory set to the
# working directory of the current window using
@@ -385,13 +413,14 @@ map ctrl+shift+0 tenth_window
# }}}
# Tab management {{{
map ctrl+shift+right next_tab
map ctrl+shift+left previous_tab
map ctrl+shift+t new_tab
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 kitty_mod+right next_tab
map kitty_mod+left previous_tab
map kitty_mod+t new_tab
map kitty_mod+q close_tab
map kitty_mod+l next_layout
map kitty_mod+. move_tab_forward
map kitty_mod+, move_tab_backward
map kitty_mod+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
@@ -407,23 +436,47 @@ map ctrl+shift+, move_tab_backward
# }}}
# Font sizes {{{
map ctrl+shift+equal increase_font_size
map ctrl+shift+minus decrease_font_size
map ctrl+shift+backspace restore_font_size
map kitty_mod+equal increase_font_size
map kitty_mod+minus decrease_font_size
map kitty_mod+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
# map kitty_mod+f6 set_font_size 10.0
# map kitty_mod+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 kitty_mod+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 kitty_mod+p>f run_kitten text hints --type path --program -
# Select a path/filename and open it with the default open program.
map kitty_mod+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 kitty_mod+p>l run_kitten text hints --type line --program -
# Select words and insert into terminal.
map kitty_mod+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
map ctrl+shift+f2 edit_config_file
# Open a currently visible URL using the keyboard. The program used to 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 kitty_mod+f11 toggle_fullscreen
map kitty_mod+u input_unicode_character
map kitty_mod+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 kitty_mod+escape kitty_shell window
# Sending arbitrary text on shortcut key presses
# You can tell kitty to send arbitrary (UTF-8) encoded text to
@@ -443,6 +496,9 @@ map ctrl+shift+e run_simple_kitten text url_hints
# map ctrl+alt+a send_text application Word\x1bOH
# }}}
# You can have kitty remove all shortcut definition seen upto this point. Useful for
# instance, to remove the default shortcuts.
clear_all_shortcuts no
# }}}
@@ -450,8 +506,12 @@ map ctrl+shift+e run_simple_kitten text url_hints
# 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
# the default background color and finally you can use an arbitrary color, such
# as #12af59 or "red".
# the background color of the currently active window and finally you can use
# an arbitrary color, such as #12af59 or "red". WARNING: This option works by
# using a hack, as there is no proper Cocoa API for it. It sets the background
# color of the entire window and makes the titlebar transparent. As such it is
# incompatible with background_opacity. If you want to use both, you are
# probably better off just hiding the titlebar with macos_hide_titlebar.
macos_titlebar_color system
# Hide the kitty window's title bar on macOS.
@@ -463,7 +523,6 @@ macos_hide_titlebar no
# 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
# Hide the kitty window from running tasks (alt-tab) on macOS.
macos_hide_from_tasks no
# }}}

View File

@@ -75,9 +75,11 @@ class Layout:
# this layout, i.e. spaces that are not covered by any window
self.blank_rects = ()
def nth_window(self, all_windows, num):
def nth_window(self, all_windows, num, make_active=True):
windows = process_overlaid_windows(all_windows)[1]
w = windows[min(num, len(windows) - 1)]
if not make_active:
return w
active_window_idx = idx_for_id(w.id, all_windows)
return self.set_active_window(all_windows, active_window_idx)
@@ -252,7 +254,9 @@ def top_blank_rect(w, rects):
def bottom_blank_rect(w, rects):
b = w.geometry.bottom
if b < central.bottom:
# Need to use <= here as otherwise a single pixel row at the bottom of the
# window is sometimes not covered. See https://github.com/kovidgoyal/kitty/issues/506
if b <= central.bottom:
rects.append(Rect(central.left, b, central.right + 1, central.bottom + 1))

View File

@@ -72,7 +72,6 @@ void linebuf_mark_line_dirty(LineBuf *self, index_type y);
void linebuf_mark_line_clean(LineBuf *self, index_type y);
unsigned int linebuf_char_width_at(LineBuf *self, index_type x, index_type y);
void linebuf_refresh_sprite_positions(LineBuf *self);
bool historybuf_resize(HistoryBuf *self, index_type lines);
void historybuf_add_line(HistoryBuf *self, const Line *line);
void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other);
void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);

View File

@@ -39,9 +39,6 @@ def init_graphics():
def run_app(opts, args):
set_scale(opts.box_drawing_scale)
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)
with cached_values_for('main') as cached_values:
w, h = initial_window_size(opts, cached_values)
window_id = create_os_window(w, h, appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders)
@@ -141,15 +138,19 @@ def _main():
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
os.chdir(os.path.expanduser('~'))
args = macos_cmdline()
if not os.path.isdir(os.getcwd()):
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=args)
args.args = rest
if args.debug_config:
create_opts(args, debug_config=True)
return
if getattr(args, 'detach', False):
detach()
if args.cmd:
exec(args.cmd)
return
if args.replay_commands:
from kitty.client import main
main(args.replay_commands)
@@ -158,7 +159,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

View File

@@ -16,13 +16,13 @@
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 int last_multi_clicks = 0;
static inline unsigned int
button_map(int button) {
@@ -91,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)
@@ -118,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);
}
}
@@ -129,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;
@@ -152,12 +181,8 @@ drag_scroll(Window *w, OSWindow *frame) {
static inline void
extend_selection(Window *w) {
Screen *screen = w->render_data.screen;
index_type start, end;
if (screen_has_selection(screen)) {
bool found_selectable_word = screen_selection_range_for_word(screen, w->mouse_cell_x, w->mouse_cell_y, &start, &end);
if (last_multi_clicks >= 2 && found_selectable_word) screen_update_selection(screen, end, w->mouse_cell_y, true);
else screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
call_boss(set_primary_selection, NULL);
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
}
}
@@ -234,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);
}
}
@@ -260,11 +288,9 @@ HANDLER(add_click) {
q->length++;
// Now dispatch the multi-click if any
if (q->length > 2 && N(1).at - N(3).at <= 2 * OPT(click_interval)) {
last_multi_clicks = 3;
multi_click(w, 3);
q->length = 0;
} else if (q->length > 1 && N(1).at - N(2).at <= OPT(click_interval)) {
last_multi_clicks = 2;
multi_click(w, 2);
}
#undef N
@@ -285,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)
@@ -294,14 +320,9 @@ HANDLER(handle_button_event) {
switch(button) {
case GLFW_MOUSE_BUTTON_LEFT:
update_drag(true, w, is_release, modifiers);
last_multi_clicks = 0;
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; }

View File

@@ -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) {
@@ -816,7 +816,7 @@ parse_graphics_code(Screen *screen, PyObject UNUSED *dump_callback) {
enum KEYS key = 'a';
static GraphicsCommand g;
unsigned int i, code;
unsigned long lcode;
uint64_t lcode;
bool is_negative;
memset(&g, 0, sizeof(g));
static uint8_t payload[4096];

View File

@@ -4,470 +4,14 @@
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, parse_address_spec, read_with_timeout
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
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
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
# }}}
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):
@@ -496,6 +40,7 @@ will only work if this process is run within an existing kitty window.
def do_io(to, send, no_response):
import socket
send = ('@kitty-cmd' + json.dumps(send)).encode('ascii')
send = b'\x1bP' + send + b'\x1b\\'
if to:
@@ -548,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]

16
kitty/rgb.py generated
View File

@@ -27,6 +27,22 @@ 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 color_as_sharp(x):
return '#{:02x}{:02x}{:02x}'.format(*x)
def color_as_sgr(x):
return ':2:{}:{}:{}'.format(*x)
def to_color(raw, validate=False):
# See man XParseColor
x = raw.strip().lower()

View File

@@ -212,12 +212,6 @@ screen_rescale_images(Screen *self, unsigned int old_cell_width, unsigned int ol
}
static bool
screen_change_scrollback_size(Screen *self, unsigned int size) {
if (size != self->historybuf->ynum) return historybuf_resize(self->historybuf, size);
return true;
}
static PyObject*
reset_callbacks(Screen *self, PyObject *a UNUSED) {
Py_CLEAR(self->callbacks);
@@ -1066,7 +1060,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 +1070,8 @@ 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();
CALLBACK("on_bell", NULL);
}
void
@@ -1410,7 +1406,7 @@ screen_apply_selection(Screen *self, void *address, size_t 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);
}
@@ -1464,6 +1460,11 @@ as_text(Screen *self, PyObject *args) {
as_text_generic(args, self, visual_line_, self->lines, self->columns, callback, as_ansi);
}
static PyObject*
as_text_non_visual(Screen *self, PyObject *args) {
as_text_generic(args, self, range_line_, self->lines, self->columns, callback, as_ansi);
}
static PyObject*
refresh_sprite_positions(Screen *self, PyObject *a UNUSED) {
self->is_dirty = true;
@@ -1515,6 +1516,52 @@ screen_wcswidth(PyObject UNUSED *self, PyObject *str) {
return PyLong_FromUnsignedLong(ans);
}
static PyObject*
screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) {
PyObject *str; unsigned int num_cells;
if (!PyArg_ParseTuple(args, "OI", &str, &num_cells)) return NULL;
if (PyUnicode_READY(str) != 0) return NULL;
int kind = PyUnicode_KIND(str);
void *data = PyUnicode_DATA(str);
Py_ssize_t len = PyUnicode_GET_LENGTH(str), i;
char_type prev_ch = 0;
int prev_width = 0;
bool in_sgr = false;
unsigned long width_so_far = 0;
for (i = 0; i < len && width_so_far < num_cells; i++) {
char_type ch = PyUnicode_READ(kind, data, i);
if (in_sgr) {
if (ch == 'm') in_sgr = false;
continue;
}
if (ch == 0x1b && i + 1 < len && PyUnicode_READ(kind, data, i + 1) == '[') { in_sgr = true; continue; }
if (ch == 0xfe0f) {
if (is_emoji_presentation_base(prev_ch) && prev_width == 1) {
width_so_far += 1;
prev_width = 2;
} else prev_width = 0;
} else {
int w = wcwidth_std(ch);
switch(w) {
case -1:
case 0:
prev_width = 0; break;
case 2:
prev_width = 2; break;
default:
prev_width = 1; break;
}
if (width_so_far + prev_width > num_cells) { break; }
width_so_far += prev_width;
}
prev_ch = ch;
}
return PyLong_FromUnsignedLong(i);
}
static PyObject*
line(Screen *self, PyObject *val) {
unsigned long y = PyLong_AsUnsignedLong(val);
@@ -1626,14 +1673,14 @@ 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*
change_scrollback_size(Screen *self, PyObject *args) {
unsigned int count = 1;
if (!PyArg_ParseTuple(args, "|I", &count)) return NULL;
if (!screen_change_scrollback_size(self, MAX(self->lines, count))) return NULL;
start_selection(Screen *self, PyObject *args) {
unsigned int x, y;
int rectangle_select = 0, extend_mode = EXTEND_CELL;
if (!PyArg_ParseTuple(args, "II|pi", &x, &y, &rectangle_select, &extend_mode)) return NULL;
screen_start_selection(self, x, y, rectangle_select, extend_mode);
Py_RETURN_NONE;
}
@@ -1643,7 +1690,7 @@ text_for_selection(Screen *self, PyObject *a UNUSED) {
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;
}
@@ -1667,17 +1714,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;
@@ -1733,10 +1794,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
}
@@ -1751,6 +1812,60 @@ 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: {
index_type top_line = extending_leftwards ? self->selection.end_y : self->selection.start_y;
index_type bottom_line = extending_leftwards ? self->selection.start_y : self->selection.end_y;
while(top_line > 0 && visual_line_(self, top_line)->continued) top_line--;
while(bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) bottom_line++;
found = screen_selection_range_for_line(self, top_line, &start, &end);
if (found) {
if (extending_leftwards) {
self->selection.end_x = start; self->selection.end_y = top_line;
} else {
self->selection.start_x = start; self->selection.start_y = top_line;
}
}
found = screen_selection_range_for_line(self, bottom_line, &start, &end);
if (found) {
if (extending_leftwards) {
self->selection.start_x = end; self->selection.start_y = bottom_line;
} else {
self->selection.end_x = end; self->selection.end_y = bottom_line;
}
}
break;
}
case EXTEND_CELL:
break;
}
call_boss(set_primary_selection, NULL);
}
static PyObject*
@@ -1836,7 +1951,6 @@ static PyMethodDef methods[] = {
MND(delete_lines, METH_VARARGS)
MND(insert_characters, METH_VARARGS)
MND(delete_characters, METH_VARARGS)
MND(change_scrollback_size, METH_VARARGS)
MND(erase_characters, METH_VARARGS)
MND(cursor_up, METH_VARARGS)
MND(cursor_up1, METH_VARARGS)
@@ -1845,6 +1959,7 @@ static PyMethodDef methods[] = {
MND(cursor_forward, METH_VARARGS)
{"index", (PyCFunction)xxx_index, METH_VARARGS, ""},
MND(as_text, METH_VARARGS)
MND(as_text_non_visual, METH_VARARGS)
MND(refresh_sprite_positions, METH_NOARGS)
MND(tab, METH_NOARGS)
MND(backspace, METH_NOARGS)
@@ -1920,6 +2035,7 @@ PyTypeObject Screen_Type = {
static PyMethodDef module_methods[] = {
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""},
{"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""},
{NULL} /* Sentinel */
};

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;
@@ -155,8 +158,8 @@ 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

@@ -7,7 +7,6 @@
#include "gl.h"
#include "fonts.h"
#include <sys/sysctl.h>
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, BLIT_PROGRAM, NUM_PROGRAMS };
enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BLIT_UNIT };
@@ -214,9 +213,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 +238,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 +469,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
@@ -489,7 +486,7 @@ draw_cursor(CursorRenderInfo *cursor, bool is_focused) {
// }}}
// Borders {{{
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, NUM_BORDER_UNIFORMS };
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, BORDER_bell_border_color, NUM_BORDER_UNIFORMS };
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
static void
@@ -503,6 +500,7 @@ init_borders_program() {
else if SET_LOC(default_bg);
else if SET_LOC(active_border_color);
else if SET_LOC(inactive_border_color);
else if SET_LOC(bell_border_color);
else { fatal("Unknown uniform in borders program: %s", p->uniforms[i].name); return; }
}
if (left) { fatal("Left over uniforms in borders program"); return; }
@@ -539,6 +537,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
glUniform1f(border_uniform_locations[BORDER_background_opacity], OPT(background_opacity));
glUniform3f(border_uniform_locations[BORDER_active_border_color], CV3(OPT(active_border_color)));
glUniform3f(border_uniform_locations[BORDER_inactive_border_color], CV3(OPT(inactive_border_color)));
glUniform3f(border_uniform_locations[BORDER_bell_border_color], CV3(OPT(bell_border_color)));
}
glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height);
color_type default_bg = num_visible_windows > 1 ? OPT(background) : active_window_bg;

217
kitty/shell.py Normal file
View File

@@ -0,0 +1,217 @@
#!/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, is_macos, version
all_commands = tuple(sorted(cmap))
match_commands = tuple(sorted(all_commands + ('exit', 'help', 'quit')))
def init_readline(readline):
try:
readline.read_init_file()
except EnvironmentError:
if not is_macos:
raise
if 'libedit' in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete")
else:
readline.parse_and_bind('tab: complete')
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)
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):
init_readline(readline)
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

@@ -284,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) {
@@ -302,6 +303,21 @@ repaint_delay(PyObject *val) {
PyObject *key, *value; Py_ssize_t pos = 0; \
while (PyDict_Next(d, &pos, &key, &value))
static int kitty_mod = 0;
static inline int
resolve_mods(int mods) {
if (mods & GLFW_MOD_KITTY) {
mods = (mods & ~GLFW_MOD_KITTY) | kitty_mod;
}
return mods;
}
static int
convert_mods(PyObject *obj) {
return resolve_mods(PyLong_AsLong(obj));
}
static inline void
set_special_keys(PyObject *dict) {
dict_iter(dict) {
@@ -331,6 +347,8 @@ PYWRAP1(set_options) {
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; }
GA(kitty_mod);
kitty_mod = PyLong_AsLong(ret); Py_CLEAR(ret); if (PyErr_Occurred()) return NULL;
S(visual_bell_duration, PyFloat_AsDouble);
S(enable_audio_bell, PyObject_IsTrue);
S(focus_follows_mouse, PyObject_IsTrue);
@@ -338,25 +356,28 @@ 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);
S(open_url_modifiers, PyLong_AsUnsignedLong);
S(rectangle_select_modifiers, PyLong_AsUnsignedLong);
S(open_url_modifiers, convert_mods);
S(rectangle_select_modifiers, convert_mods);
S(click_interval, PyFloat_AsDouble);
S(url_color, color_as_int);
S(background, color_as_int);
S(active_border_color, color_as_int);
S(inactive_border_color, color_as_int);
S(bell_border_color, color_as_int);
S(repaint_delay, repaint_delay);
S(input_delay, repaint_delay);
S(sync_to_monitor, PyObject_IsTrue);
S(close_on_child_death, PyObject_IsTrue);
S(window_alert_on_bell, PyObject_IsTrue);
S(macos_option_as_alt, PyObject_IsTrue);
S(macos_hide_titlebar, PyObject_IsTrue);
S(macos_hide_from_tasks, PyObject_IsTrue);
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
if (chars == NULL) return NULL;
@@ -368,6 +389,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); \
@@ -387,6 +410,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;
@@ -454,6 +479,25 @@ PYWRAP1(mark_os_window_for_close) {
Py_RETURN_FALSE;
}
PYWRAP1(set_titlebar_color) {
id_type os_window_id;
unsigned int color;
PA("KI", &os_window_id, &color);
WITH_OS_WINDOW(os_window_id)
set_titlebar_color(os_window, color);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
}
PYWRAP1(mark_tab_bar_dirty) {
id_type os_window_id = PyLong_AsUnsignedLongLong(args);
WITH_OS_WINDOW(os_window_id)
os_window->tab_bar_data_updated = false;
END_WITH_OS_WINDOW
Py_RETURN_NONE;
}
static inline bool
fix_window_idx(Tab *tab, id_type window_id, unsigned int *window_idx) {
for (id_type fix = 0; fix < tab->num_windows; fix++) {
@@ -540,6 +584,7 @@ PYWRAP1(set_display_state) {
THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window)
PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); }
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); }
PYWRAP0(current_os_window) { OSWindow *w = current_os_window(); if (!w) Py_RETURN_NONE; return PyLong_FromUnsignedLongLong(w->id); }
@@ -556,6 +601,8 @@ 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(resolve_key_mods, METH_VARARGS),
MW(handle_for_window_id, METH_VARARGS),
MW(set_logical_dpi, METH_VARARGS),
MW(pt_to_px, METH_O),
@@ -575,6 +622,8 @@ static PyMethodDef module_methods[] = {
MW(set_window_render_data, METH_VARARGS),
MW(viewport_for_window, METH_VARARGS),
MW(mark_os_window_for_close, METH_VARARGS),
MW(set_titlebar_color, METH_VARARGS),
MW(mark_tab_bar_dirty, METH_O),
MW(update_window_visibility, METH_VARARGS),
MW(set_boss, METH_O),
MW(set_display_state, METH_VARARGS),

View File

@@ -20,18 +20,19 @@ typedef struct {
unsigned int rectangle_select_modifiers;
unsigned int url_style;
char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
color_type url_color, background, active_border_color, inactive_border_color;
color_type url_color, background, active_border_color, inactive_border_color, bell_border_color;
double repaint_delay, input_delay;
bool focus_follows_mouse;
bool macos_option_as_alt, macos_hide_titlebar;
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks;
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;
bool window_alert_on_bell;
} Options;
typedef struct {
@@ -104,6 +105,7 @@ typedef struct {
unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id;
bool focused_at_last_render, needs_render;
ScreenRenderData tab_bar_render_data;
bool tab_bar_data_updated;
bool is_focused;
double cursor_blink_zero_time, last_mouse_activity_at;
double mouse_x, mouse_y;
@@ -115,6 +117,7 @@ typedef struct {
bool has_pending_resizes, is_semi_transparent, shown_once, is_damaged;
uint32_t offscreen_texture_id;
unsigned int clear_count;
color_type last_titlebar_color;
} OSWindow;
@@ -133,11 +136,12 @@ typedef struct {
bool is_wayland;
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_); \
@@ -178,3 +182,4 @@ void update_surface_size(int, int, uint32_t);
void free_texture(uint32_t*);
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
void send_sprite_to_gpu(unsigned int, unsigned int, unsigned int, pixel*);
void set_titlebar_color(OSWindow *w, color_type color);

View File

@@ -11,26 +11,27 @@ from .child import Child
from .config import build_ansi_color_table
from .constants import WindowGeometry, appname, get_boss, is_macos, is_wayland
from .fast_data_types import (
DECAWM, Screen, add_tab, glfw_post_empty_event, remove_tab, remove_window,
set_active_tab, set_tab_bar_render_data, swap_tabs, viewport_for_window,
x11_window_id
DECAWM, Screen, add_tab, glfw_post_empty_event, mark_tab_bar_dirty,
remove_tab, remove_window, set_active_tab, set_tab_bar_render_data,
swap_tabs, viewport_for_window, x11_window_id
)
from .layout import Rect, all_layouts
from .session import resolved_shell
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')
TabbarData = namedtuple('TabbarData', 'title is_active is_last needs_attention')
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: # {{{
def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None):
self._active_window_idx = 0
self.tab_manager_ref = weakref.ref(tab_manager)
self.os_window_id = tab_manager.os_window_id
self.id = add_tab(self.os_window_id)
@@ -41,7 +42,6 @@ class Tab: # {{{
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
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
setattr(self, which + '_window', partial(self.nth_window, num=i))
if session_tab is None:
@@ -66,6 +66,31 @@ class Tab: # {{{
self.new_window(cmd=cmd)
self.set_active_window_idx(session_tab.active_window_idx)
@property
def active_window_idx(self):
return self._active_window_idx
@active_window_idx.setter
def active_window_idx(self, val):
try:
old_active_window = self.windows[self._active_window_idx]
except Exception:
old_active_window = None
self._active_window_idx = max(0, min(val, len(self.windows) - 1))
try:
new_active_window = self.windows[self._active_window_idx]
except Exception:
new_active_window = None
if old_active_window is not new_active_window:
if old_active_window is not None:
old_active_window.focus_changed(False)
if new_active_window is not None:
new_active_window.focus_changed(True)
tm = self.tab_manager_ref()
if tm is not None:
self.relayout_borders()
tm.mark_tab_bar_dirty()
@property
def active_window(self):
return self.windows[self.active_window_idx] if self.windows else None
@@ -78,13 +103,19 @@ class Tab: # {{{
self.name = title or ''
tm = self.tab_manager_ref()
if tm is not None:
tm.title_changed(self.name)
tm.mark_tab_bar_dirty()
def title_changed(self, window):
if window is self.active_window:
tm = self.tab_manager_ref()
if tm is not None:
tm.title_changed(window.title)
tm.mark_tab_bar_dirty()
def on_bell(self, window):
tm = self.tab_manager_ref()
if tm is not None:
self.relayout_borders()
tm.mark_tab_bar_dirty()
def visible_windows(self):
for w in self.windows:
@@ -100,8 +131,11 @@ class Tab: # {{{
tm = self.tab_manager_ref()
if tm is not None:
visible_windows = [w for w in self.windows if w.is_visible_in_layout]
self.borders(visible_windows, self.active_window, self.current_layout,
w = self.active_window
self.borders(visible_windows, w, self.current_layout,
tm.blank_rects, self.current_layout.needs_window_borders and len(visible_windows) > 1)
if w is not None:
w.change_titlebar_color()
def create_layout_object(self, idx):
return all_layouts[idx](self.os_window_id, self.id, self.opts, self.borders.border_width)
@@ -124,25 +158,27 @@ class Tab: # {{{
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):
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)
@@ -181,6 +217,10 @@ class Tab: # {{{
return
self.set_active_window_idx(idx)
def get_nth_window(self, n):
if self.windows:
return self.current_layout.nth_window(self.windows, n, make_active=False)
def nth_window(self, num=0):
if self.windows:
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
@@ -225,7 +265,7 @@ class Tab: # {{{
return False
def __iter__(self):
yield from iter(self.windows)
return iter(self.windows)
def __len__(self):
return len(self.windows)
@@ -247,6 +287,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
@@ -276,6 +317,17 @@ 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))
self.bell_fg = as_rgb(0xff0000)
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)
@@ -309,10 +361,18 @@ class TabBar: # {{{
for t in data:
s.cursor.bg = self.active_bg if t.is_active else 0
s.cursor.fg = self.active_fg if t.is_active else 0
s.cursor.fg = fg = self.active_fg if t.is_active else 0
s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style
before = s.cursor.x
s.draw(' ' * self.leading_spaces + t.title + ' ' * self.trailing_spaces)
if self.leading_spaces:
s.draw(' ' * self.leading_spaces)
if t.needs_attention and self.opts.bell_on_tab:
s.cursor.fg = self.bell_fg
s.draw('🔔 ')
s.cursor.fg = fg
s.draw(t.title)
if self.trailing_spaces:
s.draw(' ' * self.trailing_spaces)
extra = s.cursor.x - before - max_title_length
if extra > 0:
s.cursor.x -= extra + 1
@@ -347,12 +407,36 @@ class TabManager: # {{{
self.opts, self.args = opts, args
self.tabs = []
self.tab_bar = TabBar(self.os_window_id, opts)
self.active_tab_idx = 0
self._active_tab_idx = 0
for t in startup_session.tabs:
self._add_tab(Tab(self, session_tab=t))
self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1)))
self.update_tab_bar()
@property
def active_tab_idx(self):
return self._active_tab_idx
@active_tab_idx.setter
def active_tab_idx(self, val):
try:
old_active_tab = self.tabs[self._active_tab_idx]
except Exception:
old_active_tab = None
self._active_tab_idx = max(0, min(val, len(self.tabs) - 1))
try:
new_active_tab = self.tabs[self._active_tab_idx]
except Exception:
new_active_tab = None
if old_active_tab is not new_active_tab:
if old_active_tab is not None:
w = old_active_tab.active_window
if w is not None:
w.focus_changed(False)
if new_active_tab is not None:
w = new_active_tab.active_window
if w is not None:
w.focus_changed(True)
def refresh_sprite_positions(self):
self.tab_bar.screen.refresh_sprite_positions()
@@ -379,21 +463,24 @@ class TabManager: # {{{
self.resize(only_tabs=True)
glfw_post_empty_event()
def update_tab_bar(self):
def mark_tab_bar_dirty(self):
if len(self.tabs) > 1:
self.tab_bar.update(self.tab_bar_data)
mark_tab_bar_dirty(self.os_window_id)
def update_tab_bar_data(self):
self.tab_bar.update(self.tab_bar_data)
def resize(self, only_tabs=False):
if not only_tabs:
self.tab_bar.layout()
self.update_tab_bar()
self.mark_tab_bar_dirty()
for tab in self.tabs:
tab.relayout()
def set_active_tab_idx(self, idx):
self._set_active_tab(idx)
self.active_tab.relayout_borders()
self.update_tab_bar()
self.mark_tab_bar_dirty()
def set_active_tab(self, tab):
try:
@@ -446,21 +533,19 @@ class TabManager: # {{{
self.tabs[idx], self.tabs[nidx] = self.tabs[nidx], self.tabs[idx]
swap_tabs(self.os_window_id, idx, nidx)
self._set_active_tab(nidx)
self.update_tab_bar()
def title_changed(self, new_title):
self.update_tab_bar()
self.mark_tab_bar_dirty()
def new_tab(self, special_window=None, cwd_from=None):
idx = len(self.tabs)
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
self._set_active_tab(idx)
self.update_tab_bar()
self.mark_tab_bar_dirty()
return self.tabs[idx]
def remove(self, tab):
self._remove_tab(tab)
self._set_active_tab(max(0, min(self.active_tab_idx, len(self.tabs) - 1)))
self.update_tab_bar()
self.mark_tab_bar_dirty()
tab.destroy()
@property
@@ -469,7 +554,12 @@ class TabManager: # {{{
ans = []
for t in self.tabs:
title = (t.name or t.title or appname).strip()
ans.append(TabbarData(title, t is at, t is self.tabs[-1]))
needs_attention = False
for w in t:
if w.needs_attention:
needs_attention = True
break
ans.append(TabbarData(title, t is at, t is self.tabs[-1], needs_attention))
return ans
def activate_tab_at(self, x):

View File

@@ -173,6 +173,19 @@ string_capabilities = {
'kf10': r'\E[21~',
'kf11': r'\E[23~',
'kf12': r'\E[24~',
'kf13': r'\E[1;2P',
'kf14': r'\E[1;2Q',
'kf15': r'\E[1;2R',
'kf16': r'\E[1;2S',
'kf17': r'\E[15;2~',
'kf18': r'\E[17;2~',
'kf19': r'\E[18;2~',
'kf20': r'\E[19;2~',
'kf21': r'\E[20;2~',
'kf22': r'\E[21;2~',
'kf23': r'\E[23;2~',
'kf24': r'\E[24;2~',
'kf25': r'\E[1;5P',
# Home
'khome': r'\EOH',
# End
@@ -345,6 +358,19 @@ termcap_aliases.update({
'k;': 'kf10',
'F1': 'kf11',
'F2': 'kf12',
'F3': 'kf13',
'F4': 'kf14',
'F5': 'kf15',
'F6': 'kf16',
'F7': 'kf17',
'F8': 'kf18',
'F9': 'kf19',
'FA': 'kf20',
'FB': 'kf21',
'FC': 'kf22',
'FD': 'kf23',
'FE': 'kf24',
'FF': 'kf25',
'kh': 'khome',
'@7': 'kend',
'kI': 'kich1',

View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2018-02-09
// unicode data, built from the unicode standard on: 2018-05-01
// see gen-wcwidth.py
#include "data-types.h"
@@ -2335,269 +2335,269 @@ char_type codepoint_for_mark(combining_type m) {
combining_type mark_for_codepoint(char_type c) {
switch(c) { // {{{
case 0: return 0;
case 768 ... 879: return 1 + c - 768;
case 1155 ... 1161: return 113 + c - 1155;
case 1425 ... 1469: return 120 + c - 1425;
case 768: case 769: case 770: case 771: case 772: case 773: case 774: case 775: case 776: case 777: case 778: case 779: case 780: case 781: case 782: case 783: case 784: case 785: case 786: case 787: case 788: case 789: case 790: case 791: case 792: case 793: case 794: case 795: case 796: case 797: case 798: case 799: case 800: case 801: case 802: case 803: case 804: case 805: case 806: case 807: case 808: case 809: case 810: case 811: case 812: case 813: case 814: case 815: case 816: case 817: case 818: case 819: case 820: case 821: case 822: case 823: case 824: case 825: case 826: case 827: case 828: case 829: case 830: case 831: case 832: case 833: case 834: case 835: case 836: case 837: case 838: case 839: case 840: case 841: case 842: case 843: case 844: case 845: case 846: case 847: case 848: case 849: case 850: case 851: case 852: case 853: case 854: case 855: case 856: case 857: case 858: case 859: case 860: case 861: case 862: case 863: case 864: case 865: case 866: case 867: case 868: case 869: case 870: case 871: case 872: case 873: case 874: case 875: case 876: case 877: case 878: case 879: return 1 + c - 768;
case 1155: case 1156: case 1157: case 1158: case 1159: case 1160: case 1161: return 113 + c - 1155;
case 1425: case 1426: case 1427: case 1428: case 1429: case 1430: case 1431: case 1432: case 1433: case 1434: case 1435: case 1436: case 1437: case 1438: case 1439: case 1440: case 1441: case 1442: case 1443: case 1444: case 1445: case 1446: case 1447: case 1448: case 1449: case 1450: case 1451: case 1452: case 1453: case 1454: case 1455: case 1456: case 1457: case 1458: case 1459: case 1460: case 1461: case 1462: case 1463: case 1464: case 1465: case 1466: case 1467: case 1468: case 1469: return 120 + c - 1425;
case 1471: return 165;
case 1473 ... 1474: return 166 + c - 1473;
case 1476 ... 1477: return 168 + c - 1476;
case 1473: case 1474: return 166 + c - 1473;
case 1476: case 1477: return 168 + c - 1476;
case 1479: return 170;
case 1552 ... 1562: return 171 + c - 1552;
case 1611 ... 1631: return 182 + c - 1611;
case 1552: case 1553: case 1554: case 1555: case 1556: case 1557: case 1558: case 1559: case 1560: case 1561: case 1562: return 171 + c - 1552;
case 1611: case 1612: case 1613: case 1614: case 1615: case 1616: case 1617: case 1618: case 1619: case 1620: case 1621: case 1622: case 1623: case 1624: case 1625: case 1626: case 1627: case 1628: case 1629: case 1630: case 1631: return 182 + c - 1611;
case 1648: return 203;
case 1750 ... 1756: return 204 + c - 1750;
case 1759 ... 1764: return 211 + c - 1759;
case 1767 ... 1768: return 217 + c - 1767;
case 1770 ... 1773: return 219 + c - 1770;
case 1750: case 1751: case 1752: case 1753: case 1754: case 1755: case 1756: return 204 + c - 1750;
case 1759: case 1760: case 1761: case 1762: case 1763: case 1764: return 211 + c - 1759;
case 1767: case 1768: return 217 + c - 1767;
case 1770: case 1771: case 1772: case 1773: return 219 + c - 1770;
case 1809: return 223;
case 1840 ... 1866: return 224 + c - 1840;
case 1958 ... 1968: return 251 + c - 1958;
case 2027 ... 2035: return 262 + c - 2027;
case 2070 ... 2073: return 271 + c - 2070;
case 2075 ... 2083: return 275 + c - 2075;
case 2085 ... 2087: return 284 + c - 2085;
case 2089 ... 2093: return 287 + c - 2089;
case 2137 ... 2139: return 292 + c - 2137;
case 2260 ... 2273: return 295 + c - 2260;
case 2275 ... 2307: return 309 + c - 2275;
case 2362 ... 2364: return 342 + c - 2362;
case 2366 ... 2383: return 345 + c - 2366;
case 2385 ... 2391: return 363 + c - 2385;
case 2402 ... 2403: return 370 + c - 2402;
case 2433 ... 2435: return 372 + c - 2433;
case 1840: case 1841: case 1842: case 1843: case 1844: case 1845: case 1846: case 1847: case 1848: case 1849: case 1850: case 1851: case 1852: case 1853: case 1854: case 1855: case 1856: case 1857: case 1858: case 1859: case 1860: case 1861: case 1862: case 1863: case 1864: case 1865: case 1866: return 224 + c - 1840;
case 1958: case 1959: case 1960: case 1961: case 1962: case 1963: case 1964: case 1965: case 1966: case 1967: case 1968: return 251 + c - 1958;
case 2027: case 2028: case 2029: case 2030: case 2031: case 2032: case 2033: case 2034: case 2035: return 262 + c - 2027;
case 2070: case 2071: case 2072: case 2073: return 271 + c - 2070;
case 2075: case 2076: case 2077: case 2078: case 2079: case 2080: case 2081: case 2082: case 2083: return 275 + c - 2075;
case 2085: case 2086: case 2087: return 284 + c - 2085;
case 2089: case 2090: case 2091: case 2092: case 2093: return 287 + c - 2089;
case 2137: case 2138: case 2139: return 292 + c - 2137;
case 2260: case 2261: case 2262: case 2263: case 2264: case 2265: case 2266: case 2267: case 2268: case 2269: case 2270: case 2271: case 2272: case 2273: return 295 + c - 2260;
case 2275: case 2276: case 2277: case 2278: case 2279: case 2280: case 2281: case 2282: case 2283: case 2284: case 2285: case 2286: case 2287: case 2288: case 2289: case 2290: case 2291: case 2292: case 2293: case 2294: case 2295: case 2296: case 2297: case 2298: case 2299: case 2300: case 2301: case 2302: case 2303: case 2304: case 2305: case 2306: case 2307: return 309 + c - 2275;
case 2362: case 2363: case 2364: return 342 + c - 2362;
case 2366: case 2367: case 2368: case 2369: case 2370: case 2371: case 2372: case 2373: case 2374: case 2375: case 2376: case 2377: case 2378: case 2379: case 2380: case 2381: case 2382: case 2383: return 345 + c - 2366;
case 2385: case 2386: case 2387: case 2388: case 2389: case 2390: case 2391: return 363 + c - 2385;
case 2402: case 2403: return 370 + c - 2402;
case 2433: case 2434: case 2435: return 372 + c - 2433;
case 2492: return 375;
case 2494 ... 2500: return 376 + c - 2494;
case 2503 ... 2504: return 383 + c - 2503;
case 2507 ... 2509: return 385 + c - 2507;
case 2494: case 2495: case 2496: case 2497: case 2498: case 2499: case 2500: return 376 + c - 2494;
case 2503: case 2504: return 383 + c - 2503;
case 2507: case 2508: case 2509: return 385 + c - 2507;
case 2519: return 388;
case 2530 ... 2531: return 389 + c - 2530;
case 2561 ... 2563: return 391 + c - 2561;
case 2530: case 2531: return 389 + c - 2530;
case 2561: case 2562: case 2563: return 391 + c - 2561;
case 2620: return 394;
case 2622 ... 2626: return 395 + c - 2622;
case 2631 ... 2632: return 400 + c - 2631;
case 2635 ... 2637: return 402 + c - 2635;
case 2622: case 2623: case 2624: case 2625: case 2626: return 395 + c - 2622;
case 2631: case 2632: return 400 + c - 2631;
case 2635: case 2636: case 2637: return 402 + c - 2635;
case 2641: return 405;
case 2672 ... 2673: return 406 + c - 2672;
case 2672: case 2673: return 406 + c - 2672;
case 2677: return 408;
case 2689 ... 2691: return 409 + c - 2689;
case 2689: case 2690: case 2691: return 409 + c - 2689;
case 2748: return 412;
case 2750 ... 2757: return 413 + c - 2750;
case 2759 ... 2761: return 421 + c - 2759;
case 2763 ... 2765: return 424 + c - 2763;
case 2786 ... 2787: return 427 + c - 2786;
case 2810 ... 2815: return 429 + c - 2810;
case 2817 ... 2819: return 435 + c - 2817;
case 2750: case 2751: case 2752: case 2753: case 2754: case 2755: case 2756: case 2757: return 413 + c - 2750;
case 2759: case 2760: case 2761: return 421 + c - 2759;
case 2763: case 2764: case 2765: return 424 + c - 2763;
case 2786: case 2787: return 427 + c - 2786;
case 2810: case 2811: case 2812: case 2813: case 2814: case 2815: return 429 + c - 2810;
case 2817: case 2818: case 2819: return 435 + c - 2817;
case 2876: return 438;
case 2878 ... 2884: return 439 + c - 2878;
case 2887 ... 2888: return 446 + c - 2887;
case 2891 ... 2893: return 448 + c - 2891;
case 2902 ... 2903: return 451 + c - 2902;
case 2914 ... 2915: return 453 + c - 2914;
case 2878: case 2879: case 2880: case 2881: case 2882: case 2883: case 2884: return 439 + c - 2878;
case 2887: case 2888: return 446 + c - 2887;
case 2891: case 2892: case 2893: return 448 + c - 2891;
case 2902: case 2903: return 451 + c - 2902;
case 2914: case 2915: return 453 + c - 2914;
case 2946: return 455;
case 3006 ... 3010: return 456 + c - 3006;
case 3014 ... 3016: return 461 + c - 3014;
case 3018 ... 3021: return 464 + c - 3018;
case 3006: case 3007: case 3008: case 3009: case 3010: return 456 + c - 3006;
case 3014: case 3015: case 3016: return 461 + c - 3014;
case 3018: case 3019: case 3020: case 3021: return 464 + c - 3018;
case 3031: return 468;
case 3072 ... 3075: return 469 + c - 3072;
case 3134 ... 3140: return 473 + c - 3134;
case 3142 ... 3144: return 480 + c - 3142;
case 3146 ... 3149: return 483 + c - 3146;
case 3157 ... 3158: return 487 + c - 3157;
case 3170 ... 3171: return 489 + c - 3170;
case 3201 ... 3203: return 491 + c - 3201;
case 3072: case 3073: case 3074: case 3075: return 469 + c - 3072;
case 3134: case 3135: case 3136: case 3137: case 3138: case 3139: case 3140: return 473 + c - 3134;
case 3142: case 3143: case 3144: return 480 + c - 3142;
case 3146: case 3147: case 3148: case 3149: return 483 + c - 3146;
case 3157: case 3158: return 487 + c - 3157;
case 3170: case 3171: return 489 + c - 3170;
case 3201: case 3202: case 3203: return 491 + c - 3201;
case 3260: return 494;
case 3262 ... 3268: return 495 + c - 3262;
case 3270 ... 3272: return 502 + c - 3270;
case 3274 ... 3277: return 505 + c - 3274;
case 3285 ... 3286: return 509 + c - 3285;
case 3298 ... 3299: return 511 + c - 3298;
case 3328 ... 3331: return 513 + c - 3328;
case 3387 ... 3388: return 517 + c - 3387;
case 3390 ... 3396: return 519 + c - 3390;
case 3398 ... 3400: return 526 + c - 3398;
case 3402 ... 3405: return 529 + c - 3402;
case 3262: case 3263: case 3264: case 3265: case 3266: case 3267: case 3268: return 495 + c - 3262;
case 3270: case 3271: case 3272: return 502 + c - 3270;
case 3274: case 3275: case 3276: case 3277: return 505 + c - 3274;
case 3285: case 3286: return 509 + c - 3285;
case 3298: case 3299: return 511 + c - 3298;
case 3328: case 3329: case 3330: case 3331: return 513 + c - 3328;
case 3387: case 3388: return 517 + c - 3387;
case 3390: case 3391: case 3392: case 3393: case 3394: case 3395: case 3396: return 519 + c - 3390;
case 3398: case 3399: case 3400: return 526 + c - 3398;
case 3402: case 3403: case 3404: case 3405: return 529 + c - 3402;
case 3415: return 533;
case 3426 ... 3427: return 534 + c - 3426;
case 3458 ... 3459: return 536 + c - 3458;
case 3426: case 3427: return 534 + c - 3426;
case 3458: case 3459: return 536 + c - 3458;
case 3530: return 538;
case 3535 ... 3540: return 539 + c - 3535;
case 3535: case 3536: case 3537: case 3538: case 3539: case 3540: return 539 + c - 3535;
case 3542: return 545;
case 3544 ... 3551: return 546 + c - 3544;
case 3570 ... 3571: return 554 + c - 3570;
case 3544: case 3545: case 3546: case 3547: case 3548: case 3549: case 3550: case 3551: return 546 + c - 3544;
case 3570: case 3571: return 554 + c - 3570;
case 3633: return 556;
case 3636 ... 3642: return 557 + c - 3636;
case 3655 ... 3662: return 564 + c - 3655;
case 3636: case 3637: case 3638: case 3639: case 3640: case 3641: case 3642: return 557 + c - 3636;
case 3655: case 3656: case 3657: case 3658: case 3659: case 3660: case 3661: case 3662: return 564 + c - 3655;
case 3761: return 572;
case 3764 ... 3769: return 573 + c - 3764;
case 3771 ... 3772: return 579 + c - 3771;
case 3784 ... 3789: return 581 + c - 3784;
case 3864 ... 3865: return 587 + c - 3864;
case 3764: case 3765: case 3766: case 3767: case 3768: case 3769: return 573 + c - 3764;
case 3771: case 3772: return 579 + c - 3771;
case 3784: case 3785: case 3786: case 3787: case 3788: case 3789: return 581 + c - 3784;
case 3864: case 3865: return 587 + c - 3864;
case 3893: return 589;
case 3895: return 590;
case 3897: return 591;
case 3902 ... 3903: return 592 + c - 3902;
case 3953 ... 3972: return 594 + c - 3953;
case 3974 ... 3975: return 614 + c - 3974;
case 3981 ... 3991: return 616 + c - 3981;
case 3993 ... 4028: return 627 + c - 3993;
case 3902: case 3903: return 592 + c - 3902;
case 3953: case 3954: case 3955: case 3956: case 3957: case 3958: case 3959: case 3960: case 3961: case 3962: case 3963: case 3964: case 3965: case 3966: case 3967: case 3968: case 3969: case 3970: case 3971: case 3972: return 594 + c - 3953;
case 3974: case 3975: return 614 + c - 3974;
case 3981: case 3982: case 3983: case 3984: case 3985: case 3986: case 3987: case 3988: case 3989: case 3990: case 3991: return 616 + c - 3981;
case 3993: case 3994: case 3995: case 3996: case 3997: case 3998: case 3999: case 4000: case 4001: case 4002: case 4003: case 4004: case 4005: case 4006: case 4007: case 4008: case 4009: case 4010: case 4011: case 4012: case 4013: case 4014: case 4015: case 4016: case 4017: case 4018: case 4019: case 4020: case 4021: case 4022: case 4023: case 4024: case 4025: case 4026: case 4027: case 4028: return 627 + c - 3993;
case 4038: return 663;
case 4139 ... 4158: return 664 + c - 4139;
case 4182 ... 4185: return 684 + c - 4182;
case 4190 ... 4192: return 688 + c - 4190;
case 4194 ... 4196: return 691 + c - 4194;
case 4199 ... 4205: return 694 + c - 4199;
case 4209 ... 4212: return 701 + c - 4209;
case 4226 ... 4237: return 705 + c - 4226;
case 4139: case 4140: case 4141: case 4142: case 4143: case 4144: case 4145: case 4146: case 4147: case 4148: case 4149: case 4150: case 4151: case 4152: case 4153: case 4154: case 4155: case 4156: case 4157: case 4158: return 664 + c - 4139;
case 4182: case 4183: case 4184: case 4185: return 684 + c - 4182;
case 4190: case 4191: case 4192: return 688 + c - 4190;
case 4194: case 4195: case 4196: return 691 + c - 4194;
case 4199: case 4200: case 4201: case 4202: case 4203: case 4204: case 4205: return 694 + c - 4199;
case 4209: case 4210: case 4211: case 4212: return 701 + c - 4209;
case 4226: case 4227: case 4228: case 4229: case 4230: case 4231: case 4232: case 4233: case 4234: case 4235: case 4236: case 4237: return 705 + c - 4226;
case 4239: return 717;
case 4250 ... 4253: return 718 + c - 4250;
case 4957 ... 4959: return 722 + c - 4957;
case 5906 ... 5908: return 725 + c - 5906;
case 5938 ... 5940: return 728 + c - 5938;
case 5970 ... 5971: return 731 + c - 5970;
case 6002 ... 6003: return 733 + c - 6002;
case 6068 ... 6099: return 735 + c - 6068;
case 4250: case 4251: case 4252: case 4253: return 718 + c - 4250;
case 4957: case 4958: case 4959: return 722 + c - 4957;
case 5906: case 5907: case 5908: return 725 + c - 5906;
case 5938: case 5939: case 5940: return 728 + c - 5938;
case 5970: case 5971: return 731 + c - 5970;
case 6002: case 6003: return 733 + c - 6002;
case 6068: case 6069: case 6070: case 6071: case 6072: case 6073: case 6074: case 6075: case 6076: case 6077: case 6078: case 6079: case 6080: case 6081: case 6082: case 6083: case 6084: case 6085: case 6086: case 6087: case 6088: case 6089: case 6090: case 6091: case 6092: case 6093: case 6094: case 6095: case 6096: case 6097: case 6098: case 6099: return 735 + c - 6068;
case 6109: return 767;
case 6155 ... 6157: return 768 + c - 6155;
case 6277 ... 6278: return 771 + c - 6277;
case 6155: case 6156: case 6157: return 768 + c - 6155;
case 6277: case 6278: return 771 + c - 6277;
case 6313: return 773;
case 6432 ... 6443: return 774 + c - 6432;
case 6448 ... 6459: return 786 + c - 6448;
case 6679 ... 6683: return 798 + c - 6679;
case 6741 ... 6750: return 803 + c - 6741;
case 6752 ... 6780: return 813 + c - 6752;
case 6432: case 6433: case 6434: case 6435: case 6436: case 6437: case 6438: case 6439: case 6440: case 6441: case 6442: case 6443: return 774 + c - 6432;
case 6448: case 6449: case 6450: case 6451: case 6452: case 6453: case 6454: case 6455: case 6456: case 6457: case 6458: case 6459: return 786 + c - 6448;
case 6679: case 6680: case 6681: case 6682: case 6683: return 798 + c - 6679;
case 6741: case 6742: case 6743: case 6744: case 6745: case 6746: case 6747: case 6748: case 6749: case 6750: return 803 + c - 6741;
case 6752: case 6753: case 6754: case 6755: case 6756: case 6757: case 6758: case 6759: case 6760: case 6761: case 6762: case 6763: case 6764: case 6765: case 6766: case 6767: case 6768: case 6769: case 6770: case 6771: case 6772: case 6773: case 6774: case 6775: case 6776: case 6777: case 6778: case 6779: case 6780: return 813 + c - 6752;
case 6783: return 842;
case 6832 ... 6846: return 843 + c - 6832;
case 6912 ... 6916: return 858 + c - 6912;
case 6964 ... 6980: return 863 + c - 6964;
case 7019 ... 7027: return 880 + c - 7019;
case 7040 ... 7042: return 889 + c - 7040;
case 7073 ... 7085: return 892 + c - 7073;
case 7142 ... 7155: return 905 + c - 7142;
case 7204 ... 7223: return 919 + c - 7204;
case 7376 ... 7378: return 939 + c - 7376;
case 7380 ... 7400: return 942 + c - 7380;
case 6832: case 6833: case 6834: case 6835: case 6836: case 6837: case 6838: case 6839: case 6840: case 6841: case 6842: case 6843: case 6844: case 6845: case 6846: return 843 + c - 6832;
case 6912: case 6913: case 6914: case 6915: case 6916: return 858 + c - 6912;
case 6964: case 6965: case 6966: case 6967: case 6968: case 6969: case 6970: case 6971: case 6972: case 6973: case 6974: case 6975: case 6976: case 6977: case 6978: case 6979: case 6980: return 863 + c - 6964;
case 7019: case 7020: case 7021: case 7022: case 7023: case 7024: case 7025: case 7026: case 7027: return 880 + c - 7019;
case 7040: case 7041: case 7042: return 889 + c - 7040;
case 7073: case 7074: case 7075: case 7076: case 7077: case 7078: case 7079: case 7080: case 7081: case 7082: case 7083: case 7084: case 7085: return 892 + c - 7073;
case 7142: case 7143: case 7144: case 7145: case 7146: case 7147: case 7148: case 7149: case 7150: case 7151: case 7152: case 7153: case 7154: case 7155: return 905 + c - 7142;
case 7204: case 7205: case 7206: case 7207: case 7208: case 7209: case 7210: case 7211: case 7212: case 7213: case 7214: case 7215: case 7216: case 7217: case 7218: case 7219: case 7220: case 7221: case 7222: case 7223: return 919 + c - 7204;
case 7376: case 7377: case 7378: return 939 + c - 7376;
case 7380: case 7381: case 7382: case 7383: case 7384: case 7385: case 7386: case 7387: case 7388: case 7389: case 7390: case 7391: case 7392: case 7393: case 7394: case 7395: case 7396: case 7397: case 7398: case 7399: case 7400: return 942 + c - 7380;
case 7405: return 963;
case 7410 ... 7412: return 964 + c - 7410;
case 7415 ... 7417: return 967 + c - 7415;
case 7616 ... 7673: return 970 + c - 7616;
case 7675 ... 7679: return 1028 + c - 7675;
case 8400 ... 8432: return 1033 + c - 8400;
case 11503 ... 11505: return 1066 + c - 11503;
case 7410: case 7411: case 7412: return 964 + c - 7410;
case 7415: case 7416: case 7417: return 967 + c - 7415;
case 7616: case 7617: case 7618: case 7619: case 7620: case 7621: case 7622: case 7623: case 7624: case 7625: case 7626: case 7627: case 7628: case 7629: case 7630: case 7631: case 7632: case 7633: case 7634: case 7635: case 7636: case 7637: case 7638: case 7639: case 7640: case 7641: case 7642: case 7643: case 7644: case 7645: case 7646: case 7647: case 7648: case 7649: case 7650: case 7651: case 7652: case 7653: case 7654: case 7655: case 7656: case 7657: case 7658: case 7659: case 7660: case 7661: case 7662: case 7663: case 7664: case 7665: case 7666: case 7667: case 7668: case 7669: case 7670: case 7671: case 7672: case 7673: return 970 + c - 7616;
case 7675: case 7676: case 7677: case 7678: case 7679: return 1028 + c - 7675;
case 8400: case 8401: case 8402: case 8403: case 8404: case 8405: case 8406: case 8407: case 8408: case 8409: case 8410: case 8411: case 8412: case 8413: case 8414: case 8415: case 8416: case 8417: case 8418: case 8419: case 8420: case 8421: case 8422: case 8423: case 8424: case 8425: case 8426: case 8427: case 8428: case 8429: case 8430: case 8431: case 8432: return 1033 + c - 8400;
case 11503: case 11504: case 11505: return 1066 + c - 11503;
case 11647: return 1069;
case 11744 ... 11775: return 1070 + c - 11744;
case 12330 ... 12335: return 1102 + c - 12330;
case 12441 ... 12442: return 1108 + c - 12441;
case 42607 ... 42610: return 1110 + c - 42607;
case 42612 ... 42621: return 1114 + c - 42612;
case 42654 ... 42655: return 1124 + c - 42654;
case 42736 ... 42737: return 1126 + c - 42736;
case 11744: case 11745: case 11746: case 11747: case 11748: case 11749: case 11750: case 11751: case 11752: case 11753: case 11754: case 11755: case 11756: case 11757: case 11758: case 11759: case 11760: case 11761: case 11762: case 11763: case 11764: case 11765: case 11766: case 11767: case 11768: case 11769: case 11770: case 11771: case 11772: case 11773: case 11774: case 11775: return 1070 + c - 11744;
case 12330: case 12331: case 12332: case 12333: case 12334: case 12335: return 1102 + c - 12330;
case 12441: case 12442: return 1108 + c - 12441;
case 42607: case 42608: case 42609: case 42610: return 1110 + c - 42607;
case 42612: case 42613: case 42614: case 42615: case 42616: case 42617: case 42618: case 42619: case 42620: case 42621: return 1114 + c - 42612;
case 42654: case 42655: return 1124 + c - 42654;
case 42736: case 42737: return 1126 + c - 42736;
case 43010: return 1128;
case 43014: return 1129;
case 43019: return 1130;
case 43043 ... 43047: return 1131 + c - 43043;
case 43136 ... 43137: return 1136 + c - 43136;
case 43188 ... 43205: return 1138 + c - 43188;
case 43232 ... 43249: return 1156 + c - 43232;
case 43302 ... 43309: return 1174 + c - 43302;
case 43335 ... 43347: return 1182 + c - 43335;
case 43392 ... 43395: return 1195 + c - 43392;
case 43443 ... 43456: return 1199 + c - 43443;
case 43043: case 43044: case 43045: case 43046: case 43047: return 1131 + c - 43043;
case 43136: case 43137: return 1136 + c - 43136;
case 43188: case 43189: case 43190: case 43191: case 43192: case 43193: case 43194: case 43195: case 43196: case 43197: case 43198: case 43199: case 43200: case 43201: case 43202: case 43203: case 43204: case 43205: return 1138 + c - 43188;
case 43232: case 43233: case 43234: case 43235: case 43236: case 43237: case 43238: case 43239: case 43240: case 43241: case 43242: case 43243: case 43244: case 43245: case 43246: case 43247: case 43248: case 43249: return 1156 + c - 43232;
case 43302: case 43303: case 43304: case 43305: case 43306: case 43307: case 43308: case 43309: return 1174 + c - 43302;
case 43335: case 43336: case 43337: case 43338: case 43339: case 43340: case 43341: case 43342: case 43343: case 43344: case 43345: case 43346: case 43347: return 1182 + c - 43335;
case 43392: case 43393: case 43394: case 43395: return 1195 + c - 43392;
case 43443: case 43444: case 43445: case 43446: case 43447: case 43448: case 43449: case 43450: case 43451: case 43452: case 43453: case 43454: case 43455: case 43456: return 1199 + c - 43443;
case 43493: return 1213;
case 43561 ... 43574: return 1214 + c - 43561;
case 43561: case 43562: case 43563: case 43564: case 43565: case 43566: case 43567: case 43568: case 43569: case 43570: case 43571: case 43572: case 43573: case 43574: return 1214 + c - 43561;
case 43587: return 1228;
case 43596 ... 43597: return 1229 + c - 43596;
case 43643 ... 43645: return 1231 + c - 43643;
case 43596: case 43597: return 1229 + c - 43596;
case 43643: case 43644: case 43645: return 1231 + c - 43643;
case 43696: return 1234;
case 43698 ... 43700: return 1235 + c - 43698;
case 43703 ... 43704: return 1238 + c - 43703;
case 43710 ... 43711: return 1240 + c - 43710;
case 43698: case 43699: case 43700: return 1235 + c - 43698;
case 43703: case 43704: return 1238 + c - 43703;
case 43710: case 43711: return 1240 + c - 43710;
case 43713: return 1242;
case 43755 ... 43759: return 1243 + c - 43755;
case 43765 ... 43766: return 1248 + c - 43765;
case 44003 ... 44010: return 1250 + c - 44003;
case 44012 ... 44013: return 1258 + c - 44012;
case 43755: case 43756: case 43757: case 43758: case 43759: return 1243 + c - 43755;
case 43765: case 43766: return 1248 + c - 43765;
case 44003: case 44004: case 44005: case 44006: case 44007: case 44008: case 44009: case 44010: return 1250 + c - 44003;
case 44012: case 44013: return 1258 + c - 44012;
case 64286: return 1260;
case 65024 ... 65039: return 1261 + c - 65024;
case 65056 ... 65071: return 1277 + c - 65056;
case 65024: case 65025: case 65026: case 65027: case 65028: case 65029: case 65030: case 65031: case 65032: case 65033: case 65034: case 65035: case 65036: case 65037: case 65038: case 65039: return 1261 + c - 65024;
case 65056: case 65057: case 65058: case 65059: case 65060: case 65061: case 65062: case 65063: case 65064: case 65065: case 65066: case 65067: case 65068: case 65069: case 65070: case 65071: return 1277 + c - 65056;
case 66045: return 1293;
case 66272: return 1294;
case 66422 ... 66426: return 1295 + c - 66422;
case 68097 ... 68099: return 1300 + c - 68097;
case 68101 ... 68102: return 1303 + c - 68101;
case 68108 ... 68111: return 1305 + c - 68108;
case 68152 ... 68154: return 1309 + c - 68152;
case 66422: case 66423: case 66424: case 66425: case 66426: return 1295 + c - 66422;
case 68097: case 68098: case 68099: return 1300 + c - 68097;
case 68101: case 68102: return 1303 + c - 68101;
case 68108: case 68109: case 68110: case 68111: return 1305 + c - 68108;
case 68152: case 68153: case 68154: return 1309 + c - 68152;
case 68159: return 1312;
case 68325 ... 68326: return 1313 + c - 68325;
case 69632 ... 69634: return 1315 + c - 69632;
case 69688 ... 69702: return 1318 + c - 69688;
case 69759 ... 69762: return 1333 + c - 69759;
case 69808 ... 69818: return 1337 + c - 69808;
case 69888 ... 69890: return 1348 + c - 69888;
case 69927 ... 69940: return 1351 + c - 69927;
case 68325: case 68326: return 1313 + c - 68325;
case 69632: case 69633: case 69634: return 1315 + c - 69632;
case 69688: case 69689: case 69690: case 69691: case 69692: case 69693: case 69694: case 69695: case 69696: case 69697: case 69698: case 69699: case 69700: case 69701: case 69702: return 1318 + c - 69688;
case 69759: case 69760: case 69761: case 69762: return 1333 + c - 69759;
case 69808: case 69809: case 69810: case 69811: case 69812: case 69813: case 69814: case 69815: case 69816: case 69817: case 69818: return 1337 + c - 69808;
case 69888: case 69889: case 69890: return 1348 + c - 69888;
case 69927: case 69928: case 69929: case 69930: case 69931: case 69932: case 69933: case 69934: case 69935: case 69936: case 69937: case 69938: case 69939: case 69940: return 1351 + c - 69927;
case 70003: return 1365;
case 70016 ... 70018: return 1366 + c - 70016;
case 70067 ... 70080: return 1369 + c - 70067;
case 70090 ... 70092: return 1383 + c - 70090;
case 70188 ... 70199: return 1386 + c - 70188;
case 70016: case 70017: case 70018: return 1366 + c - 70016;
case 70067: case 70068: case 70069: case 70070: case 70071: case 70072: case 70073: case 70074: case 70075: case 70076: case 70077: case 70078: case 70079: case 70080: return 1369 + c - 70067;
case 70090: case 70091: case 70092: return 1383 + c - 70090;
case 70188: case 70189: case 70190: case 70191: case 70192: case 70193: case 70194: case 70195: case 70196: case 70197: case 70198: case 70199: return 1386 + c - 70188;
case 70206: return 1398;
case 70367 ... 70378: return 1399 + c - 70367;
case 70400 ... 70403: return 1411 + c - 70400;
case 70367: case 70368: case 70369: case 70370: case 70371: case 70372: case 70373: case 70374: case 70375: case 70376: case 70377: case 70378: return 1399 + c - 70367;
case 70400: case 70401: case 70402: case 70403: return 1411 + c - 70400;
case 70460: return 1415;
case 70462 ... 70468: return 1416 + c - 70462;
case 70471 ... 70472: return 1423 + c - 70471;
case 70475 ... 70477: return 1425 + c - 70475;
case 70462: case 70463: case 70464: case 70465: case 70466: case 70467: case 70468: return 1416 + c - 70462;
case 70471: case 70472: return 1423 + c - 70471;
case 70475: case 70476: case 70477: return 1425 + c - 70475;
case 70487: return 1428;
case 70498 ... 70499: return 1429 + c - 70498;
case 70502 ... 70508: return 1431 + c - 70502;
case 70512 ... 70516: return 1438 + c - 70512;
case 70709 ... 70726: return 1443 + c - 70709;
case 70832 ... 70851: return 1461 + c - 70832;
case 71087 ... 71093: return 1481 + c - 71087;
case 71096 ... 71104: return 1488 + c - 71096;
case 71132 ... 71133: return 1497 + c - 71132;
case 71216 ... 71232: return 1499 + c - 71216;
case 71339 ... 71351: return 1516 + c - 71339;
case 71453 ... 71467: return 1529 + c - 71453;
case 72193 ... 72202: return 1544 + c - 72193;
case 72243 ... 72249: return 1554 + c - 72243;
case 72251 ... 72254: return 1561 + c - 72251;
case 70498: case 70499: return 1429 + c - 70498;
case 70502: case 70503: case 70504: case 70505: case 70506: case 70507: case 70508: return 1431 + c - 70502;
case 70512: case 70513: case 70514: case 70515: case 70516: return 1438 + c - 70512;
case 70709: case 70710: case 70711: case 70712: case 70713: case 70714: case 70715: case 70716: case 70717: case 70718: case 70719: case 70720: case 70721: case 70722: case 70723: case 70724: case 70725: case 70726: return 1443 + c - 70709;
case 70832: case 70833: case 70834: case 70835: case 70836: case 70837: case 70838: case 70839: case 70840: case 70841: case 70842: case 70843: case 70844: case 70845: case 70846: case 70847: case 70848: case 70849: case 70850: case 70851: return 1461 + c - 70832;
case 71087: case 71088: case 71089: case 71090: case 71091: case 71092: case 71093: return 1481 + c - 71087;
case 71096: case 71097: case 71098: case 71099: case 71100: case 71101: case 71102: case 71103: case 71104: return 1488 + c - 71096;
case 71132: case 71133: return 1497 + c - 71132;
case 71216: case 71217: case 71218: case 71219: case 71220: case 71221: case 71222: case 71223: case 71224: case 71225: case 71226: case 71227: case 71228: case 71229: case 71230: case 71231: case 71232: return 1499 + c - 71216;
case 71339: case 71340: case 71341: case 71342: case 71343: case 71344: case 71345: case 71346: case 71347: case 71348: case 71349: case 71350: case 71351: return 1516 + c - 71339;
case 71453: case 71454: case 71455: case 71456: case 71457: case 71458: case 71459: case 71460: case 71461: case 71462: case 71463: case 71464: case 71465: case 71466: case 71467: return 1529 + c - 71453;
case 72193: case 72194: case 72195: case 72196: case 72197: case 72198: case 72199: case 72200: case 72201: case 72202: return 1544 + c - 72193;
case 72243: case 72244: case 72245: case 72246: case 72247: case 72248: case 72249: return 1554 + c - 72243;
case 72251: case 72252: case 72253: case 72254: return 1561 + c - 72251;
case 72263: return 1565;
case 72273 ... 72283: return 1566 + c - 72273;
case 72330 ... 72345: return 1577 + c - 72330;
case 72751 ... 72758: return 1593 + c - 72751;
case 72760 ... 72767: return 1601 + c - 72760;
case 72850 ... 72871: return 1609 + c - 72850;
case 72873 ... 72886: return 1631 + c - 72873;
case 73009 ... 73014: return 1645 + c - 73009;
case 72273: case 72274: case 72275: case 72276: case 72277: case 72278: case 72279: case 72280: case 72281: case 72282: case 72283: return 1566 + c - 72273;
case 72330: case 72331: case 72332: case 72333: case 72334: case 72335: case 72336: case 72337: case 72338: case 72339: case 72340: case 72341: case 72342: case 72343: case 72344: case 72345: return 1577 + c - 72330;
case 72751: case 72752: case 72753: case 72754: case 72755: case 72756: case 72757: case 72758: return 1593 + c - 72751;
case 72760: case 72761: case 72762: case 72763: case 72764: case 72765: case 72766: case 72767: return 1601 + c - 72760;
case 72850: case 72851: case 72852: case 72853: case 72854: case 72855: case 72856: case 72857: case 72858: case 72859: case 72860: case 72861: case 72862: case 72863: case 72864: case 72865: case 72866: case 72867: case 72868: case 72869: case 72870: case 72871: return 1609 + c - 72850;
case 72873: case 72874: case 72875: case 72876: case 72877: case 72878: case 72879: case 72880: case 72881: case 72882: case 72883: case 72884: case 72885: case 72886: return 1631 + c - 72873;
case 73009: case 73010: case 73011: case 73012: case 73013: case 73014: return 1645 + c - 73009;
case 73018: return 1651;
case 73020 ... 73021: return 1652 + c - 73020;
case 73023 ... 73029: return 1654 + c - 73023;
case 73020: case 73021: return 1652 + c - 73020;
case 73023: case 73024: case 73025: case 73026: case 73027: case 73028: case 73029: return 1654 + c - 73023;
case 73031: return 1661;
case 92912 ... 92916: return 1662 + c - 92912;
case 92976 ... 92982: return 1667 + c - 92976;
case 94033 ... 94078: return 1674 + c - 94033;
case 94095 ... 94098: return 1720 + c - 94095;
case 113821 ... 113822: return 1724 + c - 113821;
case 119141 ... 119145: return 1726 + c - 119141;
case 119149 ... 119154: return 1731 + c - 119149;
case 119163 ... 119170: return 1737 + c - 119163;
case 119173 ... 119179: return 1745 + c - 119173;
case 119210 ... 119213: return 1752 + c - 119210;
case 119362 ... 119364: return 1756 + c - 119362;
case 121344 ... 121398: return 1759 + c - 121344;
case 121403 ... 121452: return 1814 + c - 121403;
case 92912: case 92913: case 92914: case 92915: case 92916: return 1662 + c - 92912;
case 92976: case 92977: case 92978: case 92979: case 92980: case 92981: case 92982: return 1667 + c - 92976;
case 94033: case 94034: case 94035: case 94036: case 94037: case 94038: case 94039: case 94040: case 94041: case 94042: case 94043: case 94044: case 94045: case 94046: case 94047: case 94048: case 94049: case 94050: case 94051: case 94052: case 94053: case 94054: case 94055: case 94056: case 94057: case 94058: case 94059: case 94060: case 94061: case 94062: case 94063: case 94064: case 94065: case 94066: case 94067: case 94068: case 94069: case 94070: case 94071: case 94072: case 94073: case 94074: case 94075: case 94076: case 94077: case 94078: return 1674 + c - 94033;
case 94095: case 94096: case 94097: case 94098: return 1720 + c - 94095;
case 113821: case 113822: return 1724 + c - 113821;
case 119141: case 119142: case 119143: case 119144: case 119145: return 1726 + c - 119141;
case 119149: case 119150: case 119151: case 119152: case 119153: case 119154: return 1731 + c - 119149;
case 119163: case 119164: case 119165: case 119166: case 119167: case 119168: case 119169: case 119170: return 1737 + c - 119163;
case 119173: case 119174: case 119175: case 119176: case 119177: case 119178: case 119179: return 1745 + c - 119173;
case 119210: case 119211: case 119212: case 119213: return 1752 + c - 119210;
case 119362: case 119363: case 119364: return 1756 + c - 119362;
case 121344: case 121345: case 121346: case 121347: case 121348: case 121349: case 121350: case 121351: case 121352: case 121353: case 121354: case 121355: case 121356: case 121357: case 121358: case 121359: case 121360: case 121361: case 121362: case 121363: case 121364: case 121365: case 121366: case 121367: case 121368: case 121369: case 121370: case 121371: case 121372: case 121373: case 121374: case 121375: case 121376: case 121377: case 121378: case 121379: case 121380: case 121381: case 121382: case 121383: case 121384: case 121385: case 121386: case 121387: case 121388: case 121389: case 121390: case 121391: case 121392: case 121393: case 121394: case 121395: case 121396: case 121397: case 121398: return 1759 + c - 121344;
case 121403: case 121404: case 121405: case 121406: case 121407: case 121408: case 121409: case 121410: case 121411: case 121412: case 121413: case 121414: case 121415: case 121416: case 121417: case 121418: case 121419: case 121420: case 121421: case 121422: case 121423: case 121424: case 121425: case 121426: case 121427: case 121428: case 121429: case 121430: case 121431: case 121432: case 121433: case 121434: case 121435: case 121436: case 121437: case 121438: case 121439: case 121440: case 121441: case 121442: case 121443: case 121444: case 121445: case 121446: case 121447: case 121448: case 121449: case 121450: case 121451: case 121452: return 1814 + c - 121403;
case 121461: return 1864;
case 121476: return 1865;
case 121499 ... 121503: return 1866 + c - 121499;
case 121505 ... 121519: return 1871 + c - 121505;
case 122880 ... 122886: return 1886 + c - 122880;
case 122888 ... 122904: return 1893 + c - 122888;
case 122907 ... 122913: return 1910 + c - 122907;
case 122915 ... 122916: return 1917 + c - 122915;
case 122918 ... 122922: return 1919 + c - 122918;
case 125136 ... 125142: return 1924 + c - 125136;
case 125252 ... 125258: return 1931 + c - 125252;
case 917760 ... 917999: return 1938 + c - 917760;
case 121499: case 121500: case 121501: case 121502: case 121503: return 1866 + c - 121499;
case 121505: case 121506: case 121507: case 121508: case 121509: case 121510: case 121511: case 121512: case 121513: case 121514: case 121515: case 121516: case 121517: case 121518: case 121519: return 1871 + c - 121505;
case 122880: case 122881: case 122882: case 122883: case 122884: case 122885: case 122886: return 1886 + c - 122880;
case 122888: case 122889: case 122890: case 122891: case 122892: case 122893: case 122894: case 122895: case 122896: case 122897: case 122898: case 122899: case 122900: case 122901: case 122902: case 122903: case 122904: return 1893 + c - 122888;
case 122907: case 122908: case 122909: case 122910: case 122911: case 122912: case 122913: return 1910 + c - 122907;
case 122915: case 122916: return 1917 + c - 122915;
case 122918: case 122919: case 122920: case 122921: case 122922: return 1919 + c - 122918;
case 125136: case 125137: case 125138: case 125139: case 125140: case 125141: case 125142: return 1924 + c - 125136;
case 125252: case 125253: case 125254: case 125255: case 125256: case 125257: case 125258: return 1931 + c - 125252;
case 917760: case 917761: case 917762: case 917763: case 917764: case 917765: case 917766: case 917767: case 917768: case 917769: case 917770: case 917771: case 917772: case 917773: case 917774: case 917775: case 917776: case 917777: case 917778: case 917779: case 917780: case 917781: case 917782: case 917783: case 917784: case 917785: case 917786: case 917787: case 917788: case 917789: case 917790: case 917791: case 917792: case 917793: case 917794: case 917795: case 917796: case 917797: case 917798: case 917799: case 917800: case 917801: case 917802: case 917803: case 917804: case 917805: case 917806: case 917807: case 917808: case 917809: case 917810: case 917811: case 917812: case 917813: case 917814: case 917815: case 917816: case 917817: case 917818: case 917819: case 917820: case 917821: case 917822: case 917823: case 917824: case 917825: case 917826: case 917827: case 917828: case 917829: case 917830: case 917831: case 917832: case 917833: case 917834: case 917835: case 917836: case 917837: case 917838: case 917839: case 917840: case 917841: case 917842: case 917843: case 917844: case 917845: case 917846: case 917847: case 917848: case 917849: case 917850: case 917851: case 917852: case 917853: case 917854: case 917855: case 917856: case 917857: case 917858: case 917859: case 917860: case 917861: case 917862: case 917863: case 917864: case 917865: case 917866: case 917867: case 917868: case 917869: case 917870: case 917871: case 917872: case 917873: case 917874: case 917875: case 917876: case 917877: case 917878: case 917879: case 917880: case 917881: case 917882: case 917883: case 917884: case 917885: case 917886: case 917887: case 917888: case 917889: case 917890: case 917891: case 917892: case 917893: case 917894: case 917895: case 917896: case 917897: case 917898: case 917899: case 917900: case 917901: case 917902: case 917903: case 917904: case 917905: case 917906: case 917907: case 917908: case 917909: case 917910: case 917911: case 917912: case 917913: case 917914: case 917915: case 917916: case 917917: case 917918: case 917919: case 917920: case 917921: case 917922: case 917923: case 917924: case 917925: case 917926: case 917927: case 917928: case 917929: case 917930: case 917931: case 917932: case 917933: case 917934: case 917935: case 917936: case 917937: case 917938: case 917939: case 917940: case 917941: case 917942: case 917943: case 917944: case 917945: case 917946: case 917947: case 917948: case 917949: case 917950: case 917951: case 917952: case 917953: case 917954: case 917955: case 917956: case 917957: case 917958: case 917959: case 917960: case 917961: case 917962: case 917963: case 917964: case 917965: case 917966: case 917967: case 917968: case 917969: case 917970: case 917971: case 917972: case 917973: case 917974: case 917975: case 917976: case 917977: case 917978: case 917979: case 917980: case 917981: case 917982: case 917983: case 917984: case 917985: case 917986: case 917987: case 917988: case 917989: case 917990: case 917991: case 917992: case 917993: case 917994: case 917995: case 917996: case 917997: case 917998: case 917999: return 1938 + c - 917760;
default: return 0;
} // }}}
}

View File

@@ -8,12 +8,8 @@ import fcntl
import math
import os
import re
import shlex
import socket
import string
import subprocess
import sys
import tempfile
from contextlib import contextmanager
from time import monotonic
@@ -111,22 +107,26 @@ def base64_encode(
def command_for_open(program='default'):
if program == 'default':
if isinstance(program, str):
from .config_utils import to_cmdline
program = to_cmdline(program)
if program == ['default']:
cmd = ['open'] if is_macos else ['xdg-open']
else:
cmd = shlex.split(program)
cmd = program
return cmd
def open_cmd(cmd, arg=None):
def open_cmd(cmd, arg=None, cwd=None):
import subprocess
if arg is not None:
cmd = list(cmd)
cmd.append(arg)
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd or None)
def open_url(url, program='default'):
return open_cmd(command_for_open(program), url)
def open_url(url, program='default', cwd=None):
return open_cmd(command_for_open(program), url, cwd=cwd)
def detach(fork=True, setsid=True, redirect=True):
@@ -199,6 +199,8 @@ def remove_socket_file(s, path=None):
def single_instance_unix(name):
import socket
import tempfile
home = os.path.expanduser('~')
candidates = [tempfile.gettempdir(), home]
if is_macos:
@@ -237,6 +239,7 @@ def single_instance_unix(name):
def single_instance(group_id=None):
import socket
name = '{}-ipc-{}'.format(appname, os.geteuid())
if group_id:
name += '-{}'.format(group_id)
@@ -262,6 +265,7 @@ def single_instance(group_id=None):
def parse_address_spec(spec):
import socket
protocol, rest = spec.split(':', 1)
socket_path = None
if protocol == 'unix':

2
kitty/wcwidth-std.h generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2018-02-09
// unicode data, built from the unicode standard on: 2018-05-01
// see gen-wcwidth.py
#pragma once
#include "data-types.h"

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