Compare commits

..

550 Commits

Author SHA1 Message Date
Kovid Goyal
8c4e426534 Silence some unused parameter warnings 2021-04-19 13:53:27 +05:30
Kovid Goyal
636771e566 Fix test crashing on 32bit machines 2021-04-19 13:48:39 +05:30
Kovid Goyal
363b20bdd2 Add debug symbols when not stripping 2021-04-19 13:29:50 +05:30
Kovid Goyal
b16317e2ca Silence spurious compiler warning 2021-04-19 13:27:17 +05:30
Kovid Goyal
2c5c869228 Print command used to run tests 2021-04-19 13:14:25 +05:30
Kovid Goyal
3f5f218e0b Use correct printf codes 2021-04-19 13:00:48 +05:30
Kovid Goyal
c9cc425aad version 0.20.0
Fixes #3248
Fixes #3287
2021-04-19 10:14:00 +05:30
Kovid Goyal
9a2b0b6995 Wayland CSD: Double clicking on titlebar should toggle window maximize 2021-04-19 08:58:29 +05:30
Kovid Goyal
50f8aeeaf6 Dont output an error when none is used as a modifier 2021-04-18 18:11:56 +05:30
Kovid Goyal
fe291dd769 ... 2021-04-18 10:46:06 +05:30
Kovid Goyal
58850a0e14 Switch to ALE instead of syntastic 2021-04-17 16:53:48 +05:30
Kovid Goyal
518057489c Also output layout state in kitty @ ls 2021-04-17 12:11:56 +05:30
Kovid Goyal
cf0b2389a3 Also report window sizes in kitty @ ls 2021-04-17 11:49:57 +05:30
Kovid Goyal
6179cfc670 Abort parsing of APC and PM escape codes on 0x00 and 0x7f
Correct as per ECMA-48 section 8.3 and matches parsing of other escape
codes
2021-04-15 12:57:58 +05:30
Kovid Goyal
e6ccc2d178 Fix #3485 2021-04-15 08:57:47 +05:30
Kovid Goyal
db719eafac Remove num/caps locks from modifier processing in mouse events 2021-04-14 10:08:33 +05:30
Kovid Goyal
a4db27a807 Ignore the lock modifiers in legacy mode 2021-04-13 21:35:18 +05:30
Kovid Goyal
111c123435 X11: Fix resize_in_steps being applied even when window is maximized
Fixes #3473
2021-04-13 12:48:23 +05:30
Kovid Goyal
75d067e411 Merge branch 'fix-timg-link' of https://github.com/hzeller/kitty 2021-04-13 11:09:31 +05:30
Henner Zeller
fbeb991f7d Fix timg link (underscore was missing).
Signed-off-by: Henner Zeller <h.zeller@acm.org>
2021-04-12 22:27:32 -07:00
Kovid Goyal
ea2db51bbb Output all modifier indices in debug 2021-04-13 10:21:26 +05:30
Kovid Goyal
15f4c476c3 Ignore locked mods for legacy encoding in a few more places 2021-04-13 10:01:16 +05:30
Kovid Goyal
a7c5b8c634 Fix #3471 2021-04-13 09:53:57 +05:30
Kovid Goyal
1aeafcaebf Remove unused code 2021-04-13 09:02:30 +05:30
Kovid Goyal
8d743e9511 Fix is_self 2021-04-13 08:54:45 +05:30
Kovid Goyal
958d4d8a6a kitty @ ls: Show only environment variables that are different for each window, by default.
Makes the output much less noisy, and env vars that are common across
all windows are useless for selection/identification purposes.
2021-04-13 08:44:11 +05:30
Kovid Goyal
05fb09f56c Fix #3470 2021-04-13 08:23:15 +05:30
Kovid Goyal
cdcf8ed3bd Add some colors to keyboad debug output 2021-04-13 07:41:47 +05:30
Kovid Goyal
572466eed1 XKB: Honor numlock state when reporting keypad keys 2021-04-13 07:35:13 +05:30
Kovid Goyal
4c644b8556 Add NumLock and CapsLock reporting to the keyboard protocol 2021-04-13 07:10:00 +05:30
Kovid Goyal
c989a7198b Add support for the keypad Begin key
Fixes #3468
2021-04-11 07:58:27 +05:30
Kovid Goyal
084b028eac Merge branch 'macos-secure-keyboard-entry' of https://github.com/multi/kitty
This is the usual Apple security theatre. Prevent key loggers from
reading key events from the OS, but those same key events are written to
the unsecured tty device from where the keylogger could read them
anyway.

But, since I dont want to have to explain why this is simply security
theatre to every rando on the internet, merging anyway. It only costs
one line in the application menu and maybe it will make some people that
click on it *feel* more secure.
2021-04-10 14:41:54 +05:30
Kovid Goyal
699d88e393 ... 2021-04-10 14:36:31 +05:30
Kovid Goyal
8c6566580a ... 2021-04-10 14:32:13 +05:30
Kovid Goyal
effe30ad3f Add a FAQ entry about memory leaks 2021-04-10 14:23:28 +05:30
Kovid Goyal
e91b8d4556 ... 2021-04-10 14:08:03 +05:30
Kovid Goyal
76fca0641e Fix memory leak in utf_8_strndup test 2021-04-10 13:39:04 +05:30
Kovid Goyal
7ec803222f Fix a mem leak of the disk cache directory path 2021-04-10 13:21:58 +05:30
Kovid Goyal
03f0f065ee Use a single alloc per history buf segment 2021-04-10 13:18:27 +05:30
Kovid Goyal
63e5be6ff0 Dont use the python memory pool for the history buffer
It's not optimized for this use case
2021-04-10 12:11:05 +05:30
Kovid Goyal
4ccd2afd78 Free ChildMonitor at end of dealloc 2021-04-10 11:49:39 +05:30
Georgi Yonchev
98519bf326 [macos] - Add "Secure Keyboard Entry" menu item 2021-04-09 16:39:51 +03:00
Kovid Goyal
82d11b80dd Add a test for filling from scrollback with large continued line 2021-04-09 17:04:55 +05:30
Kovid Goyal
cf3662442f Fix #3460 2021-04-09 16:56:53 +05:30
Kovid Goyal
0be5347e6a Better error message when screen_resize() fails 2021-04-09 10:09:17 +05:30
Kovid Goyal
1b39c42e92 ... 2021-04-08 12:36:44 +05:30
Kovid Goyal
5b4f5a1e2a Add a link to the nb note taking application 2021-04-08 12:06:02 +05:30
Kovid Goyal
4eb8b1db01 Merge branch 'bug3446-fix' of https://github.com/orki/kitty 2021-04-08 08:37:55 +05:30
Ravi R Kiran
b5555b9a8d Handle multiple real modifiers mapping to the same virtual modifier 2021-04-07 21:36:11 -05:00
Kovid Goyal
67bb426eb2 Fix recent Wayland chagelog entries in the incorrect section 2021-04-08 07:59:34 +05:30
Kovid Goyal
cf3a088a53 Allow running under Wayland on GNOME by default 2021-04-08 07:52:28 +05:30
Kovid Goyal
a79d024c09 Accept window_id as an alias for id when matching 2021-04-08 07:19:45 +05:30
Kovid Goyal
253de1eeca Move the CSD metrics initialization into the CSD module 2021-04-07 22:28:04 +05:30
Kovid Goyal
4e6d5d3f1e Fix GNOME breaking when setting window geometry
The current version of GNOME has a regression that causes the compositor
to send incorrect geometry with the next configure event if the CSD
buffers are created after a call to set_window_geometry.

So call set_window_geometry last. And shake head sadly.
2021-04-07 22:14:09 +05:30
Kovid Goyal
82a5733ec5 _glfwPLatformSetWindowSize() should not be called when handling configure events as it does a bunch of work already done before 2021-04-07 19:55:15 +05:30
Kovid Goyal
4822fa989e Wayland: wire up live resize notification 2021-04-07 18:29:28 +05:30
Kovid Goyal
fe0e1adb2a DRYer 2021-04-07 15:26:30 +05:30
Kovid Goyal
850a8218db Allow toggling xdg configure debug output at runtime 2021-04-07 15:24:58 +05:30
Kovid Goyal
bf3fc5fb92 Also implement programmatic window resizing 2021-04-07 15:13:22 +05:30
Kovid Goyal
a19d1fc140 Restore size after all types of docking not just maximize/unmaximize
We make use of the fact that wayland tells us when the window is being
resized to track actual user requested sizes as opposed to compositor
requested ones.
2021-04-07 15:08:44 +05:30
Kovid Goyal
593eb1d952 Focus state needs to be updated always 2021-04-07 14:28:11 +05:30
Kovid Goyal
75ec801941 Get the window geometry with CSD fully correct (I think)
The co-ordinates are in the surface co-ordinate system. When the
compositor specifies sizes it apparently adds one subsurface size
in both x and y directions, not both. Possibly because the origin is
placed after the top and left subsurfaces. So modify it by adding
and extra subsurface size to that.
2021-04-07 14:21:43 +05:30
Kovid Goyal
5a1bd93518 Handle all known xdg top level states 2021-04-07 13:33:06 +05:30
Kovid Goyal
f849f383cb Use the proper number for wayland CSD vertical shadow geometry
Apparently set_wayland_geometry uses buffer co-ordinates not surface
co-ords so the numbers need to be multiplied by the scale
2021-04-06 22:19:17 +05:30
Kovid Goyal
ac3080f0e2 Fix #3446 (I hope) 2021-04-06 21:51:03 +05:30
Kovid Goyal
0935dd053c Modifier indices are unsigned 2021-04-06 21:45:02 +05:30
Kovid Goyal
7e78f30739 Wayland: Fix un-maximizing a window not restoring its size to what it was before being maximized 2021-04-05 13:47:50 +05:30
Kovid Goyal
d0f34e99a6 Fix fringing in shadow due to using integer operations 2021-04-05 11:09:34 +05:30
Kovid Goyal
c781825b00 Make setting sigma independent of kernel size 2021-04-05 10:55:30 +05:30
Kovid Goyal
7199c62b76 ... 2021-04-05 10:13:33 +05:30
Kovid Goyal
2074a0d958 Forgot that the top margin is scaled 2021-04-05 09:57:44 +05:30
Kovid Goyal
3e2b626107 Inform compositor of visible window geometry
The numbers dont make logical sense, but they do what is expected on
GNOME and since only GNOME is stupid enough to insist on CSD, that's
all we care about.
2021-04-05 09:10:47 +05:30
Kovid Goyal
abf515ece9 Mention that w3m now supports the kitty graphics protocol 2021-04-05 08:15:34 +05:30
Kovid Goyal
07de9c021d Decrease shadow intensity when un-focused 2021-04-04 23:04:12 +05:30
Kovid Goyal
ff420b6495 Render top CSD shadow 2021-04-04 22:49:07 +05:30
Kovid Goyal
78908e5b93 Finish rendering of side shadows for CSD 2021-04-04 20:16:13 +05:30
Kovid Goyal
bd6643a523 Use a tile to reduce startup CPU cost of shadows 2021-04-04 18:16:33 +05:30
Kovid Goyal
71fddeb1f5 Start work on shadow based CSD 2021-04-04 17:13:50 +05:30
Kovid Goyal
9f708be8fd Fix name of wttr -> wttr.in 2021-04-04 17:13:17 +05:30
Kovid Goyal
a13f815591 Fix index_selection() not updating empty selections
Newly created selections are empty but must still be indexed.
Fixes #3431
2021-04-03 10:48:43 +05:30
Kovid Goyal
b5ac06e652 Fix #3422 2021-04-03 06:48:10 +05:30
Kovid Goyal
e56d259b8e Also free() when loading fails 2021-04-03 06:41:11 +05:30
Kovid Goyal
11d13c406a Fix a memory leak when setting default window icon or background image 2021-04-03 06:37:57 +05:30
Kovid Goyal
d5448e6902 Add documentation and cleanup previous PR 2021-04-03 06:25:47 +05:30
Kovid Goyal
777b9eb68e Merge branch 'hyper' of https://github.com/orki/kitty 2021-04-03 06:15:55 +05:30
Kovid Goyal
ff1585acfe Unicode input: Make diamond a synonym for gem
Fixes #3437
2021-04-02 12:53:58 +05:30
Kovid Goyal
cb515157b3 Fix the selection getting changed if the screen contents scroll while the selection is in progress
When the selection object was refactored to track the input
co-ordinates, index_selection() was not updated accordingly.
Fixes #3431
2021-04-01 23:17:13 +05:30
Kovid Goyal
da39fb2880 Add a note about when the splits layout was added to kitty
Bloody Ubuntu/Debian and their absurd release policies.
2021-04-01 23:04:13 +05:30
Kovid Goyal
eb3a2126ee Changelog entry for CSD work
Bloody two days of my life I will never get back thanks to those
*&^(*&%*%*& at GNOME.
2021-04-01 22:57:19 +05:30
Kovid Goyal
9c1c399716 ... 2021-04-01 22:52:13 +05:30
Kovid Goyal
e209c3329b Allow specifying a right_margin that is not rendered for the CSD title
Can void double rendering if I ever decide to add button to the titlebar
2021-04-01 22:50:54 +05:30
Kovid Goyal
802147e255 More robust ellipsis width calculation 2021-04-01 22:18:13 +05:30
Kovid Goyal
1a7291276c Add a cat face emoji at the start of the CSD title 2021-04-01 22:07:37 +05:30
Kovid Goyal
6d6273af48 Properly scale emoji rendered in the CSD 2021-04-01 22:07:00 +05:30
Kovid Goyal
b7d967b6bc Dont offset title from left edge 2021-04-01 18:19:59 +05:30
Kovid Goyal
779a06fa72 DRYer 2021-04-01 13:54:20 +05:30
Kovid Goyal
0656a5079a CSD titles: Render ellipsis when too long 2021-04-01 13:48:58 +05:30
Kovid Goyal
a1669267b3 Fix hiding on window decorations not working with new CSD code 2021-04-01 12:32:48 +05:30
Kovid Goyal
bbb40f2a21 Use a little less contrast for CSD title 2021-04-01 12:20:53 +05:30
Kovid Goyal
c5b3e43b6c Use bold for CSD titles 2021-04-01 12:18:48 +05:30
Kovid Goyal
55dc354e68 Improve at exit cleanup functions
Now they are run in a defined order not based on
the order of initialization
2021-04-01 11:48:36 +05:30
Kovid Goyal
0ec7e6c36f Make the freetype text rendering code re-useable in multiple contexts 2021-04-01 11:28:23 +05:30
Kovid Goyal
b1da76eefa Set CSD color based on focus state 2021-04-01 10:54:22 +05:30
Kovid Goyal
5c6e55d37f Vertically center CSD title 2021-04-01 10:34:22 +05:30
Kovid Goyal
5d496216e0 Implement rendering of titles in CSD 2021-04-01 10:23:04 +05:30
Kovid Goyal
e92ed67021 Add a callback glfw can use to request text rendering 2021-04-01 08:22:06 +05:30
Ravi R Kiran
b074533b29 Fix regression 2021-03-31 08:54:10 -05:00
Kovid Goyal
f3665ddfab Cleanup some used memory 2021-03-31 15:29:28 +05:30
Kovid Goyal
dd2b6119b3 Implement rendering of emoji for CSD 2021-03-31 15:23:05 +05:30
Kovid Goyal
ada3f9e547 Add a wcswidth() implementation useable from C code 2021-03-31 15:19:39 +05:30
Kovid Goyal
0372242d12 Use an enum for UTF8 decoder state 2021-03-31 15:19:27 +05:30
Kovid Goyal
9838a336d5 Make wcwidth-std.h more generally includeable 2021-03-31 15:18:19 +05:30
Kovid Goyal
debd9fe419 Dont render glyphs that would overflow the canvas 2021-03-31 11:06:37 +05:30
Kovid Goyal
73a06b0e9e Text rendering for CSD now works 2021-03-31 11:02:35 +05:30
Ravi R Kiran
fdfba4ea04 Fix pyflakes failures 2021-03-30 14:57:41 -05:00
Ravi R Kiran
953253de99 Implementation of hyper and meta 2021-03-30 14:23:17 -05:00
Kovid Goyal
da0009a46b More work on CSD title rendering 2021-03-30 22:12:54 +05:30
Kovid Goyal
d7e1e53450 More work on text rendering for CSD 2021-03-30 08:25:01 +05:30
Kovid Goyal
4a3c57054f Function to get fallback font for a char 2021-03-29 15:35:38 +05:30
Kovid Goyal
41b9495588 More work on text rendering 2021-03-29 14:56:49 +05:30
Kovid Goyal
cdbc096990 Start work on rendering text for CSDs 2021-03-29 12:15:47 +05:30
Kovid Goyal
53f2df115e Hook up re-render of title bar when title changes 2021-03-29 12:15:47 +05:30
Kovid Goyal
0593158a86 Refactor CSD code to avoid un-needed rerenders and use a single shm pool for all CSD buffers 2021-03-29 12:15:47 +05:30
Kovid Goyal
c1f8372efc Hide edges when not focused 2021-03-29 12:15:47 +05:30
Kovid Goyal
6e3db0be3d Rename variable 2021-03-29 12:15:47 +05:30
Kovid Goyal
0f084faca5 Forgot to scale the title buffer 2021-03-29 12:15:47 +05:30
Kovid Goyal
c6829766bb Properly scale the decorations buffers 2021-03-29 12:15:47 +05:30
Kovid Goyal
e7931b0121 Get rid of the no longer needed viewporter protocol 2021-03-29 12:15:47 +05:30
Kovid Goyal
aeb82e7fb0 Refactor the rendering of CSD on Wayland 2021-03-29 12:15:47 +05:30
Kovid Goyal
1e6fe7785a Dont set the EDITOR env var in child processes
It isnt really needed, since the various kittens dont rely on it
anymore, instead calling get_editor() to get the path to the editor.
Has the nice side-effect of not needing to run the shell at startup
to read its environment. Now the shell is only run if the user calls
the edit config file kitten. Fixes #3426
2021-03-29 11:56:48 +05:30
Kovid Goyal
4edbe0ea4f Remove unnecessary opacity marking for decoration buffers 2021-03-25 16:28:49 +05:30
Kovid Goyal
0788b98ae4 Fix header comment 2021-03-25 16:05:14 +05:30
Kovid Goyal
8a267894f3 Make createShmBuffer re-useable 2021-03-25 16:04:12 +05:30
Kovid Goyal
0510cd47f4 Rename buffer -> edge_buffer 2021-03-25 15:19:11 +05:30
Kovid Goyal
5dbbed3dcc Ignore text input done if serial is incorrect 2021-03-25 15:05:48 +05:30
Kovid Goyal
7b130dc220 Double buffer pre-edit and commit events 2021-03-25 14:59:58 +05:30
Kovid Goyal
d28caf15b2 More glyph grouping fixes 2021-03-25 14:06:16 +05:30
Kovid Goyal
44df11c443 Move CSD metrics into the window object
Makes them potentially configurable
2021-03-25 12:52:09 +05:30
Kovid Goyal
63a50ec066 Use the correct mouse cursor theme on GNOME
Relies on a working desktop settings portal (xdg-desktop-portal-gtk)
2021-03-25 12:20:13 +05:30
Kovid Goyal
3d0cff1f47 Merge branch 'fix-rectircle-subpixel-rendering' of https://github.com/jesseleite/kitty 2021-03-25 09:55:05 +05:30
Jesse Leite
97a8b718dd Merge branch 'master' of https://github.com/kovidgoyal/kitty into fix-rectircle-subpixel-rendering 2021-03-25 00:17:47 -04:00
Kovid Goyal
fc8e147e4a Fix mouse handling when using client side decorations
The mouse co-ordinates used by glfw were all wrong.
2021-03-25 09:27:25 +05:30
Jesse Leite
af4083a8b7 Prevent subpixel rendering via floor/ceil on left/right side of box drawn corners. 2021-03-24 23:56:53 -04:00
Kovid Goyal
7c7933efa9 Fix #3416 2021-03-24 13:17:38 +05:30
Kovid Goyal
317ecbc9fa Wayland: Add support for the text input protocol
Fixes #3410
2021-03-24 11:24:37 +05:30
Kovid Goyal
67445a22d3 ... 2021-03-23 16:33:05 +05:30
Kovid Goyal
62997956a0 Start work on Wayland text input protocol support 2021-03-23 16:33:04 +05:30
Kovid Goyal
df7790fdfe ... 2021-03-23 16:31:25 +05:30
Kovid Goyal
34d06fa3e9 Use a struct for IME update events
Allows for easier extension in the future
2021-03-23 16:05:22 +05:30
Kovid Goyal
a981b46ec9 Use an enum for updateimestate as well 2021-03-23 10:52:11 +05:30
Kovid Goyal
84dcf8fd27 Use an enum for ime_state 2021-03-23 10:42:07 +05:30
Kovid Goyal
11268ffa16 Fix marking of text not working on lines that contain zero cells
Zero cells are passed to the regex engine as spaces, so they must
increment the match_pos counter. Fixes #3403
2021-03-23 10:04:58 +05:30
Kovid Goyal
c0ec60c113 Forgot to change a couple of usages of line_as_unicode 2021-03-23 09:56:58 +05:30
Kovid Goyal
ca7587c084 Allow skipping zero cells when converting a line to unicode 2021-03-23 09:53:22 +05:30
Kovid Goyal
33ecfc6f84 ... 2021-03-22 22:33:30 +05:30
Kovid Goyal
83bbcf0aa1 Graphics protocol: Add a control to allow clients to specify that the cursor should not move when displaying an image
Fixes #3411
2021-03-22 22:16:40 +05:30
Kovid Goyal
ba32e481ca ... 2021-03-22 10:46:41 +05:30
Kovid Goyal
af9da2e495 Make pt_to_px useable in state.c 2021-03-21 07:29:48 +05:30
Kovid Goyal
77f7672e98 ... 2021-03-20 13:19:11 +05:30
Kovid Goyal
9dd8185f37 rectciricle -> rectircle 2021-03-20 13:17:17 +05:30
Kovid Goyal
12f61fd24f super sample factor is included in height 2021-03-20 13:11:26 +05:30
Kovid Goyal
c9864c994f Improve rendering of rounded corners by using a rectcircle equation rather than a cubic bezier
Fixes #3409
2021-03-20 12:59:20 +05:30
Kovid Goyal
9e476127d3 macOS: Add native keyboard shortcut for unicode input
Fixes #3407
2021-03-19 14:44:38 +05:30
Kovid Goyal
fae99a24bf Merge branch 'installation-doc-bug' of https://github.com/Dsantra92/kitty 2021-03-18 18:17:14 +05:30
Deeptendu Santra
8d9ab15c40 Fix installation docs
Added `/` at the applications, otherwise applications may be considered a file if the directory is not present. The addition of `/` also ensures user knows that he/she needs to create new directory if it is not present.
2021-03-18 17:55:53 +05:30
Kovid Goyal
ccafdacc6f Ensure cursor position is correct when filling line buffer 2021-03-18 10:39:08 +05:30
Kovid Goyal
4a996c1f76 Use the final cursor position when calculating number of continued lines in resized buffer 2021-03-17 22:29:59 +05:30
Kovid Goyal
d743aff4bc Make the code to copy lines into linebufs general 2021-03-17 22:02:47 +05:30
Kovid Goyal
0f3ff4e2d9 Scrollback filling should happen after cursor position is finalized 2021-03-17 21:59:11 +05:30
Kovid Goyal
a4d9d948ab ... 2021-03-17 14:45:23 +05:30
Kovid Goyal
32e6c0c6a6 Add a security policy 2021-03-17 14:38:57 +05:30
Kovid Goyal
ba2efaacd8 Fix regression in launching executables as command line arguments without full paths 2021-03-17 14:32:43 +05:30
Kovid Goyal
37e3e29c8c Fix a crash on systems using musl as libc
Fixes #3395
2021-03-17 14:13:50 +05:30
Kovid Goyal
90722ecbe7 historybuf should be used only when current screen is the main linebuf 2021-03-17 13:51:30 +05:30
Kovid Goyal
5a2eaa24d0 Add changelog entry for last PR 2021-03-17 13:49:24 +05:30
Kovid Goyal
071986138b Cleanup previous merge 2021-03-17 13:43:26 +05:30
Kovid Goyal
d61c4a9569 Merge branch 'scrollback-fill-enlarged-window' of https://github.com/elebow/kitty 2021-03-17 12:22:12 +05:30
Eddie Lebow
9fff829ab4 Implement scrollback_fill_enlarged_window 2021-03-17 02:27:59 -04:00
Eddie Lebow
4682da83c1 Add linebuf_continued_lines_count() 2021-03-17 02:27:58 -04:00
Eddie Lebow
c93c87e8fe Add linebuf_add_line_to_top() 2021-03-17 00:09:03 -04:00
Kovid Goyal
f3e2a42c29 Fix #3400 2021-03-16 20:59:54 +05:30
Kovid Goyal
b063c8cda1 Fix inactive tab closing causing active tab to change
Fixes #3398
2021-03-16 18:06:01 +05:30
Kovid Goyal
d360d077d1 Handle the XTMODKEYS escape code
Treat the XTerm specific modifyOtherKeys escape codes as putting the
disambiguate mode on/off the stack. The escape code causes XTerm to use
a (broken, naturally) variant of CSI u encoding for many keys, so we
treat it as going into the disambiguate mode.
2021-03-16 17:46:11 +05:30
Kovid Goyal
a548c31d50 Add links to rasterm and notcurses in the graphics protocol docs 2021-03-16 17:11:01 +05:30
Eddie Lebow
6c44b4f451 Add historybuf_pop_line() 2021-03-15 23:59:26 -04:00
Eddie Lebow
b76491ba82 Add tests for scrollback_fill_enlarged_window 2021-03-15 23:58:11 -04:00
Eddie Lebow
e10919496f Add scrollback_fill_enlarged_window config option 2021-03-15 23:57:43 -04:00
Kovid Goyal
543b7dcf94 Increase the default set of recognized URL prefixes 2021-03-15 12:17:15 +05:30
Kovid Goyal
271c39cc9d Fix reading image index not working on older ImageMagick
Fixes #3384
2021-03-12 12:00:44 +05:30
Kovid Goyal
18186da90e Better error message for people running icat on systems with outdated/broken ImageMagick 2021-03-12 11:28:57 +05:30
Kovid Goyal
02d29d4816 Better error message when ImageMagick does not write correct output filenames 2021-03-12 11:07:42 +05:30
Kovid Goyal
82934b84d6 ... 2021-03-12 08:25:19 +05:30
Kovid Goyal
6177a7d71f Fix duplicated line in graphics protocol delete table
Fixes #3383
2021-03-12 08:06:13 +05:30
Kovid Goyal
1f4fdb174a Fix rendering of the new infinite length ligatures on CoreText
Apparently on CoreText, harfbuzz gives incorrect values for glyph
positions. So we use it only for selection and grouping of glyphs.
Actual positioning is done using CoreText. This means sophisticated
positioning using GPOS tables is probably broken, but that isn't really
useable in a character grid anyway.

Also remove the hack where glyph_centering was done for calt ligatures
as it seems to not be needed with modern FiraCode and CoreText
rendering. Fixes #3372
2021-03-11 20:11:40 +05:30
Kovid Goyal
a94b94cab0 Fix macos-freeze building everything twice 2021-03-11 13:03:45 +05:30
Kovid Goyal
399c700939 Prevent Cocoa from sending us open file events from command line args
We process command line args ourselves, thank you very much.
2021-03-11 10:59:03 +05:30
Kovid Goyal
077097c112 Also implement openFiles 2021-03-10 21:55:00 +05:30
Kovid Goyal
f70c9842f5 macOS: Allow opening script and command
Fixes #3366
2021-03-10 21:33:29 +05:30
Kovid Goyal
0f020d5b37 When passing a directory or a non-executable file as the program to run to kitty open it with the shell, instead of just failing.
Allows using kitty as the program to open directories or shell scripts
from desktop environments.
2021-03-10 14:27:07 +05:30
Kovid Goyal
2eccc81fee Show a message about how to exit --hold 2021-03-10 14:26:11 +05:30
Kovid Goyal
9fdaef1da6 Graphics protocol: Fix suppression of responses not working for chunked transmission
Now the starting escape codes q value is used unless the last escape
code specifies a non-zero q value of its own. Fixes #3375
2021-03-10 09:58:49 +05:30
Kovid Goyal
eb42edff09 Use builtin rfc linking 2021-03-10 08:30:13 +05:30
Kovid Goyal
07f27d0f54 Add a link to RFC 1950 2021-03-10 08:22:59 +05:30
Kovid Goyal
48d2aa14c8 Install libx11-xcb-dev on CI
Looks like it is no longer pre-installed
2021-03-10 08:20:35 +05:30
Kovid Goyal
3c1c8ecd20 Merge branch 'dankamongmen/deflate-docs' of https://github.com/dankamongmen/kitty 2021-03-10 08:07:32 +05:30
nick black
dcb8fcece4 Add some notes to graphics protocol docs
Clarify that RFC1950-style zlib-wrapped deflate (as opposed
to RFC1951-style raw deflate) payloads are sent in the
graphics protocol, and that base64 encoding follows
compression (implied by remarks elsewhere, but go ahead and
make it explicit here).
2021-03-09 18:25:18 -05:00
Kovid Goyal
958ccadc09 Also detect gemini:// URLs when hovering with the mouse
Fixes #3370
2021-03-06 11:12:45 +05:30
Kovid Goyal
c48a42bec5 Merge branch 'add-timg-to-graphics-docs' of https://github.com/hzeller/kitty 2021-03-06 10:48:54 +05:30
Henner Zeller
50858322d6 Add timg (http://timg.sh/) to graphics protocol users program list.
Signed-off-by: Henner Zeller <h.zeller@acm.org>
2021-03-05 20:45:32 -08:00
Kovid Goyal
62912a800a Ban a bunch of unsafe C functions from the codebase
List is taken from the git codebase
2021-03-05 08:26:05 +05:30
Kovid Goyal
1ded78a98c A new command to show the env vars of the kitty process 2021-03-03 16:04:08 +05:30
Kovid Goyal
90c6c2a366 ... 2021-03-03 15:11:05 +05:30
Kovid Goyal
fbe84698c4 Add a FAQ entry about env vars 2021-03-03 15:06:30 +05:30
Kovid Goyal
2c642406d8 Dont exclude glfw.c from LoC as it no longer is largely auto-generated 2021-03-01 10:59:57 +05:30
Kovid Goyal
b811976018 Ensure global shortcuts are never sent to children
This was happening with ctrl+tab and ctrl+shift+tab because cocoa eats
these and so glfw has a workaround to always send them. If they are
added as global shortcuts, for exmaple for next/previous tab they were
then being sent to children
2021-02-28 21:42:57 +05:30
Kovid Goyal
3ee7e5f800 Fix another use after free caused by a re-assigned pointer 2021-02-28 12:30:12 +05:30
Kovid Goyal
945cbca387 hints kitten: When using the linenumber action with a background action, preserve the working directory
Fixes #3352
2021-02-27 09:14:12 +05:30
Kovid Goyal
30c7ff341f DRYer 2021-02-27 09:10:16 +05:30
Kovid Goyal
12d7c7314a Only warn about Wayland's inadequacy once 2021-02-26 19:21:48 +05:30
Kovid Goyal
ce3cd691cc macOS: Add global menu action to detach current tab 2021-02-25 12:27:10 +05:30
Kovid Goyal
81f144df15 macOS: Add entries to global menu bar for creating and changing tabs 2021-02-25 11:54:53 +05:30
Kovid Goyal
78854d4a10 macOS: Disable cocoa tabs
kitty has its own tabs, and macOS inserts confusing menu entries for its
non-functional tabs into the global menu. So disable tabs. Fixes #3325
2021-02-25 11:26:55 +05:30
Kovid Goyal
dc3ad80d24 Window may not be in current os window if triggerred shortcut is detach_window 2021-02-25 07:20:07 +05:30
Kovid Goyal
ed1188dc61 Forgot modifier mask 2021-02-25 07:12:00 +05:30
Kovid Goyal
52347ced85 macOS: Make the global keybinding used for preferences also configurable 2021-02-25 06:58:20 +05:30
Kovid Goyal
5fd6c6b9a1 typos in docs 2021-02-24 14:17:28 +05:30
Kovid Goyal
9e7031f5d8 Dont call window_for_id() unnecessarily 2021-02-24 13:38:38 +05:30
Kovid Goyal
cca590c20a Fix a use-after-free introduced in the recent key handling refactor 2021-02-24 13:32:27 +05:30
Kovid Goyal
863fff8620 Cleanup previous PR 2021-02-24 12:32:26 +05:30
Kovid Goyal
18f42fe4c1 Merge branch 'tab_powerline' of https://github.com/slfotg/kitty 2021-02-24 12:23:11 +05:30
Sam Foster
2a3cc71641 Made powerline tab_bar_style have different display options 2021-02-24 00:14:00 -06:00
Kovid Goyal
bd67814485 Fix building with asan on macOS 2021-02-23 17:06:00 +05:30
Kovid Goyal
40dbf7f1c8 Use log_error instead of print 2021-02-22 08:33:26 +05:30
Kovid Goyal
42a7023203 Fix #3332 2021-02-21 13:18:48 +05:30
Kovid Goyal
14b33a845e Avoid compiler warning about using main as a variable name 2021-02-21 11:20:10 +05:30
Kovid Goyal
56fcbb95ab Key encoding: in legacy mode use legacy encoding for a few more combinations
Some legacy terminal applications get confused seeing CSI u escape
codes. Since it is relatively common to press ctrl or shift and
space/enter/tab/backspace, emit the same bytes as traditional terminals
do for these common keys.
2021-02-21 11:14:25 +05:30
Kovid Goyal
de100ac67c DRYer 2021-02-20 11:39:31 +05:30
Kovid Goyal
2a96d2621c Use cleanup in a few more places to simplify the code 2021-02-19 18:53:30 +05:30
Kovid Goyal
a097f5df23 Use the gcc cleanup attribute in launcher 2021-02-19 18:34:12 +05:30
Kovid Goyal
6a901ea048 Forgot that wayland is not built on CI 2021-02-19 18:09:12 +05:30
Kovid Goyal
dbc1ade5a9 Fallback to importlib_resources on python 3.6 2021-02-19 18:02:15 +05:30
Kovid Goyal
c2a924a5ea Run all tests on the full frozen build using the frozen launcher
Much more comprehensive test coverage at the cost of slightly increasing
the frozen build size.
2021-02-19 17:57:59 +05:30
Kovid Goyal
e06d40cb31 Fix test suite getting type checked accidentally 2021-02-19 15:19:04 +05:30
Kovid Goyal
45d89cfe55 Install importlib.resources on python 3.6 2021-02-19 15:08:47 +05:30
Kovid Goyal
d45a8309cb Dont use VLAs in launcher.c 2021-02-19 15:04:49 +05:30
Kovid Goyal
9cd8637e9c Remove another use of __file__ 2021-02-19 14:49:07 +05:30
Kovid Goyal
38992e25d7 Make the tests into a package
Useful to make the tests runnable in a frozen build.
2021-02-19 14:35:25 +05:30
Kovid Goyal
f25b3c2aee Canonicalize resource pathnames in the frozen build 2021-02-19 13:59:33 +05:30
Kovid Goyal
e3e2261005 Fix resources path in macOS bundle 2021-02-19 13:57:58 +05:30
Kovid Goyal
38a7e77058 Add build test for filesystem locations 2021-02-19 13:42:20 +05:30
Kovid Goyal
a920919b13 Workaround for bug in libxkbcommon/Debian/Ubuntu on en_IN locale
See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=903373
2021-02-19 12:35:59 +05:30
Kovid Goyal
625f0f77bb Ensure disk cache is cleared 2021-02-19 09:43:57 +05:30
Kovid Goyal
12604072fc Allow querying number cached in ram from c code as well 2021-02-19 09:28:56 +05:30
Kovid Goyal
0341b64748 Allow caching of disk cache entries in RAM 2021-02-18 10:22:42 +05:30
Kovid Goyal
e0620fbb1d Also implement the bypy freeze on macOS 2021-02-17 22:01:09 +05:30
Kovid Goyal
9114bda24c Get the new bypy based freezing process working with linux builds 2021-02-17 15:50:53 +05:30
Kovid Goyal
4cf73204a2 Forgot to exclude modules 2021-02-16 13:57:23 +05:30
Kovid Goyal
55e7f69768 Use importlib.resources to enumerate kitten names 2021-02-16 13:48:02 +05:30
Kovid Goyal
d2a16bc8e8 ... 2021-02-16 13:14:36 +05:30
Kovid Goyal
5fdeb3b3b4 Use a temp dir as cache dir when running tests during freezing on linux 2021-02-16 13:07:45 +05:30
Kovid Goyal
9ad5ef8b2d Fix compilation on ancient Linux distros without sys/random.h 2021-02-16 12:39:18 +05:30
Kovid Goyal
c244bcd978 Allow overriding the location of the kitty base directory in frozen builds 2021-02-16 10:29:19 +05:30
Kovid Goyal
7eb8e1df79 Use importlib to load the shaders 2021-02-16 10:23:18 +05:30
Kovid Goyal
74c4e94c3d ... 2021-02-16 10:21:28 +05:30
Kovid Goyal
275b8ce2e7 Fix #3322 2021-02-15 21:27:52 +05:30
Kovid Goyal
ff1b0a7e1b Maybe it's uuid-dev instead 2021-02-15 16:44:59 +05:30
Kovid Goyal
39026bc45c Apparently fontconfig now depends on uuid 2021-02-15 16:39:07 +05:30
Kovid Goyal
3d9a95d0cd ... 2021-02-15 16:24:39 +05:30
Kovid Goyal
085bc09dfb Micro-optimization 2021-02-15 16:24:04 +05:30
Kovid Goyal
886309850f Dont add alpha when rendering alpha mask on canvas
Instead use the larger of the two alphas. This gives better results
for overlapping text, such as the infinite length ligature glyphs used
by FiraCode. I dont think adding ever gave better results anyway.
2021-02-15 16:08:06 +05:30
Kovid Goyal
ba005e991a Improve handling of infinite length ligatures in newer versions of FiraCode and CascadiaCode.
Now such ligatures are detected based on glyph naming convention.
This removes the gap in the ligatures at cell boundaries. However, at
least in Fira Code the infinite length ligature glyphs define a negative
left side bearing. This means they overlap when drawn consecutively,
leading to unsightly bumps at the joins.

Fixes #2695
2021-02-15 15:49:09 +05:30
Kovid Goyal
912c46fc57 Fix rendering of ligatures in the latest release of Cascadia code
For some reason it puts empty glyphs after the ligature glyph rather than before it.
There is a possibility this fix might break something else, we will see.
Fixes #3313
2021-02-15 11:57:57 +05:30
Kovid Goyal
5675944c0c Recognize remaining ASCII keys for legacy mode as well 2021-02-15 05:33:57 +05:30
Kovid Goyal
6b9c280214 Add a test for encoding of : as a legacy ascii key 2021-02-15 05:27:32 +05:30
Kovid Goyal
19b882a0bd Also handle : as a legacy ASCII key
Fixes #3315
2021-02-15 05:24:21 +05:30
Kovid Goyal
323d5dba7e SingleKey parsing requires ASCII lowercase 2021-02-15 04:55:51 +05:30
Kovid Goyal
cd9755ffb3 Sphinx: replace use of deprecated css_files 2021-02-15 04:35:24 +05:30
Kovid Goyal
fbcdf352ac Allow resizing of OS Window framebuffers to a single cell
This is much smaller than before and essentially works around the issue
of the text being scaled when the window is resized to small sizes. Also
has the nice side effect of making the code simpler for the panel
kitten. Fixes #3307
2021-02-14 22:17:41 +05:30
Kovid Goyal
015fe9054e Fix shifted keys not matching shortcuts in kittens
Broke after keyboard refactoring for new keyboard protocol. Now a key
event will match a shortcut specification if either the mods and key
match or mods without shift and shifted key match. Fixes #3314
2021-02-14 07:52:08 +05:30
Kovid Goyal
9a031c5b0c Bump versions for bundled deps 2021-02-12 12:29:38 +05:30
Kovid Goyal
de0f225c45 Fix #3299 2021-02-09 22:10:40 +05:30
Kovid Goyal
41533bffdd Merge branch 'build_doc' of https://github.com/spenczar/kitty 2021-02-09 07:45:25 +05:30
Spencer Nelson
cdf7c4bf97 Add two more build-time dependencies to the docs 2021-02-08 10:40:15 -08:00
Kovid Goyal
57db384953 Add packaging status to README 2021-02-06 12:10:30 +05:30
Kovid Goyal
9f0785a160 Add a link to the RFC 2021-02-05 11:36:03 +05:30
Kovid Goyal
4541cf8807 Another CodeQL warning 2021-02-05 10:41:35 +05:30
Kovid Goyal
fe07306ff1 Use a nicer decorator for functions that only need to be run once 2021-02-05 10:40:52 +05:30
Kovid Goyal
726d736aac Type check return value of cache_dir 2021-02-05 10:34:17 +05:30
Kovid Goyal
db0360dc81 Fix a bunch of CodeQL warnings 2021-02-05 10:05:54 +05:30
Kovid Goyal
4ba35d4a93 Clear cache_dir lru cache when testing 2021-02-05 09:56:17 +05:30
Kovid Goyal
1d51fca879 Python 3.6 compat 2021-02-05 09:49:46 +05:30
Kovid Goyal
67ca1902a0 Use a cache dir in the cwd on CI 2021-02-05 09:42:01 +05:30
Kovid Goyal
1ef326e220 Better error message 2021-02-05 09:29:56 +05:30
Kovid Goyal
0e483f81ac Compat for python < 3.8 2021-02-05 09:23:54 +05:30
Kovid Goyal
e969dea3b7 Remove unused function 2021-02-05 09:20:58 +05:30
Kovid Goyal
89b4f59efa Update changelog to note animation support 2021-02-05 09:20:02 +05:30
Kovid Goyal
95dc87cf04 Prevent stack overflows when recursing to coalesce frames 2021-02-05 09:12:08 +05:30
Kovid Goyal
3fc29e0d11 ... 2021-02-05 09:03:39 +05:30
Kovid Goyal
cc3b71154d Document the animation protocol 2021-02-05 09:00:19 +05:30
Kovid Goyal
e4e9e29949 Add a note about broadcast kitten to index 2021-02-05 06:33:48 +05:30
Kovid Goyal
7a1785e1e8 Get chunked loading working with frames 2021-02-04 21:04:44 +05:30
Kovid Goyal
5d2baaac38 ... 2021-02-04 17:52:18 +05:30
Kovid Goyal
b479510f6c Allow controlling the number of loops in icat 2021-02-04 17:47:53 +05:30
Kovid Goyal
8033e9fef5 Fix error checking when rendering single frame 2021-02-04 17:16:52 +05:30
Kovid Goyal
83b8c8ea12 Reduce frequency of key frames slightly 2021-02-04 17:08:06 +05:30
Kovid Goyal
a447ba7d76 Fix typo in coalesce function causing incorrect coalesce for more than one level of recursion 2021-02-04 16:50:13 +05:30
Kovid Goyal
450e1c2f95 Use key frames for performance 2021-02-04 16:20:30 +05:30
Kovid Goyal
cf3a01f502 Handle images whose first frame is smaller than the canvas 2021-02-04 15:57:49 +05:30
Kovid Goyal
3925e18964 Use rgba data for opaque animations that uses blended frames 2021-02-04 14:39:06 +05:30
Kovid Goyal
2650c7dd9e Avoid calculation when over px is transparent 2021-02-04 14:26:10 +05:30
Kovid Goyal
0f18fedf13 Add support for animations to icat 2021-02-04 14:18:47 +05:30
Kovid Goyal
f18a56682f Fix animations not resuming when OS window is hidden and then shown 2021-02-04 13:34:27 +05:30
Kovid Goyal
af94e07618 Cache exe lookups 2021-02-04 11:36:59 +05:30
Kovid Goyal
7eba3b6cbc Use the magick executable from IMv7 preferentially 2021-02-04 11:34:32 +05:30
Kovid Goyal
e5ef9d9062 Ensure IM outputs multiple images in rgba format 2021-02-04 11:23:54 +05:30
Kovid Goyal
b72dbc973d Allow controlling how many times an animation will loop 2021-02-03 20:54:43 +05:30
Kovid Goyal
55d0e23c38 ... 2021-02-03 19:29:46 +05:30
Kovid Goyal
d3489712a6 Store mode data per frame 2021-02-03 18:00:36 +05:30
Kovid Goyal
56193f6902 Handle resizing of animated images with blended frames correctly 2021-02-03 17:51:38 +05:30
Kovid Goyal
2dad179285 ... 2021-02-03 16:00:31 +05:30
Kovid Goyal
19f0e2303d Refactor to store frame deltas instead of fully coalesced frames
Trades CPU usage for cache/memory space
2021-02-03 15:49:33 +05:30
Kovid Goyal
c152624634 Rename state variable to be closer to its actual purpose 2021-02-02 09:00:28 +05:30
Kovid Goyal
212906cf7d gap should be gap to next frame not gap from previous frame
Matches usage in the GIF format and makes the concept of gapless
clearer.
2021-02-01 17:08:59 +05:30
Kovid Goyal
bb5c5a8e4f Allow gapless frames 2021-02-01 16:22:36 +05:30
Kovid Goyal
9cf5348c36 Modify ImageMagick wrapper to handle animated images 2021-02-01 14:12:26 +05:30
Kovid Goyal
edf4e14e4c Work on animation support for icat 2021-01-31 20:58:43 +05:30
Kovid Goyal
8fa737f01c Rescan for animated images on various events 2021-01-31 20:58:43 +05:30
Kovid Goyal
f16e22fbec Forgot to convert gaps to monotonic_t 2021-01-31 20:58:43 +05:30
Kovid Goyal
19e0406459 Wire up animation state tracking 2021-01-31 20:58:43 +05:30
Kovid Goyal
354d7c2740 ... 2021-01-31 20:58:42 +05:30
Kovid Goyal
00edb6058e Start work on implementing frame transitions
Also ensure that the correct OS Window is current when uploading images
to GPU.
2021-01-31 20:58:42 +05:30
Kovid Goyal
597267d1d0 root frame gap should be set to the gap of the first received frame 2021-01-31 20:58:42 +05:30
Kovid Goyal
ad4665e638 Add support for newer keys to GraphicsCommand serializer 2021-01-31 20:58:42 +05:30
Kovid Goyal
b21850dd15 Implement updating of current frame on GPU 2021-01-31 20:58:42 +05:30
Kovid Goyal
4098f2d383 Add quota enforcement testing 2021-01-31 20:58:42 +05:30
Kovid Goyal
5eba754c60 Implement animation control escape codes 2021-01-31 20:58:42 +05:30
Kovid Goyal
b35084062d Remove old data from cache when re-using image objects 2021-01-31 20:58:42 +05:30
Kovid Goyal
1d20472759 Avoid a cache move when deleting frames 2021-01-31 20:58:42 +05:30
Kovid Goyal
c0e336a971 DRYer 2021-01-31 20:58:42 +05:30
Kovid Goyal
d76e757d53 Implement delete for frames 2021-01-31 20:58:42 +05:30
Kovid Goyal
43ea58ef48 Start work on delete for frames 2021-01-31 20:58:42 +05:30
Kovid Goyal
17f485d614 Basic frame loading works 2021-01-31 20:58:41 +05:30
Kovid Goyal
35f3312a1e Use shorter cache keys 2021-01-31 20:58:41 +05:30
Kovid Goyal
ca3c49099f Start work on tests for frame loading 2021-01-31 20:58:41 +05:30
Kovid Goyal
6631bb0eac Make the image dict more complete 2021-01-31 20:58:41 +05:30
Kovid Goyal
0adbbc6d62 Implement loading of frame animation data 2021-01-31 20:58:41 +05:30
Kovid Goyal
cc4f9ddad8 More work an animation handling 2021-01-31 20:58:41 +05:30
Kovid Goyal
5a182d3d13 Store image data in a disk cache 2021-01-31 20:58:41 +05:30
Kovid Goyal
c60d3af60b Add links to locations for discussion to README
Proof of reddit account:
1e2b9113-2d30-48f0-8f2f-f7a2bcbd4895
2021-01-31 20:54:21 +05:30
Kovid Goyal
4e67a92ec4 DRYer 2021-01-31 12:26:54 +05:30
Kovid Goyal
e43c3118d4 ... 2021-01-31 12:14:36 +05:30
Kovid Goyal
ffc0919790 Allow setting colors when creating windows using the launch command. 2021-01-31 12:13:27 +05:30
Kovid Goyal
2af145879e Make color spec parsing re-useable 2021-01-31 12:10:45 +05:30
Kovid Goyal
35517d3e6f Allow using the full launch command in session files
Note this is slightly backward incompatible.
2021-01-31 11:37:00 +05:30
Kovid Goyal
e9e8ef7210 ssh kitten: Allow using python instead of the shell on the server 2021-01-31 09:04:46 +05:30
Kovid Goyal
ee198ca863 Add versionadded notes 2021-01-31 07:42:01 +05:30
Kovid Goyal
0681e6a9b6 ... 2021-01-31 07:35:58 +05:30
Kovid Goyal
965a6dfd30 Add some more kittens to the list 2021-01-31 07:18:27 +05:30
Kovid Goyal
d55fde9eea Use EINTR safe wrappers for open() and shm_open() 2021-01-31 07:02:11 +05:30
Kovid Goyal
231f054bc3 Merge branch 'master' of https://github.com/dlh/kitty 2021-01-31 06:47:01 +05:30
Daniel Lee Harple
43fafd85f8 Allow cancellation of "detach_window ask" 2021-01-30 19:21:11 -05:00
Kovid Goyal
8a5a7b8807 Merge branch 'master' of https://github.com/dlh/kitty 2021-01-30 10:07:16 +05:30
Daniel Lee Harple
2c24cc1341 Allow quoted args to toggle_marker 2021-01-29 17:52:24 -05:00
Kovid Goyal
0fc93118f2 Make the error message when failing to get monitor display name more informative 2021-01-29 12:45:22 +05:30
Kovid Goyal
ac2a33d09f Silence the errors about OSC 7
Up to now this poorly designed and completely unnecessary escape code
was relegated to only GNOME, however, off late Apple has started using
it as well, so silently ignore it, instead of spamming error messages
for it.
2021-01-29 12:43:41 +05:30
Kovid Goyal
68423b3603 Fix compiler warning on newer Xcode
Fixes #3271
2021-01-29 11:29:29 +05:30
Kovid Goyal
6d18223f17 Use an id for keyboard focus tracking as well 2021-01-29 11:22:39 +05:30
Kovid Goyal
0443e5ad7f Wayland: Use window ids rather than pointers to track windows for key repeat
Might fix #3266
2021-01-29 08:43:00 +05:30
Kovid Goyal
4619921780 Add a link to the thread with discussion on using vim as pager 2021-01-29 08:35:15 +05:30
Kovid Goyal
df89266c03 diff kitten: Implement recursive diff over SSH
Fixes #3268
2021-01-28 14:23:56 +05:30
Kovid Goyal
36ca3838a6 Fix #3265 2021-01-27 17:19:06 +05:30
davidak
60ce88cf25 Update Nix package link 2021-01-27 13:43:21 +05:30
Kovid Goyal
5a2f2767ad Fix window icon not working on X11 with 64bits
Apparently on X11 the maximum icon size is 128x128. 256x256 is too
large for the X11 protocol because the X server unserializes the icons
using "unsigned long" which is 64 bits on Linux. So we have to use
64bits per pixel instead of 32, with 32bits padded to 0.

While there I also got rid of the kitty.rgba file replacing it with a
128x128 PNG file.

Fixes #3260
2021-01-25 20:54:19 +05:30
Kovid Goyal
6e73d3fac8 Fix extra space at bottom of OS window when using the fat layout with the tab bar at the top
Fix #3258
2021-01-25 09:30:14 +05:30
Kovid Goyal
d08ae5466d Dont use a function 2021-01-24 12:57:39 +05:30
Kovid Goyal
fc175732e1 Fix CodeQL warning 2021-01-24 12:48:47 +05:30
Kovid Goyal
dfe41217f8 Start work on allowing images to have multiple frames 2021-01-24 12:37:21 +05:30
Kovid Goyal
4870d07cbb Refactor handle_add_command to make it more readable 2021-01-24 12:00:35 +05:30
Kovid Goyal
2dd9fd83a3 Add some text to the control reference 2021-01-23 13:44:10 +05:30
Kovid Goyal
0b93153656 Add the I key to the control data reference 2021-01-23 13:20:32 +05:30
Kovid Goyal
714d64c417 Get type checking working with mypy 0.80
Add workaround for https://github.com/python/typeshed/issues/4964
2021-01-23 09:25:31 +05:30
Kovid Goyal
b823af00ff Get rid of a bunch of noqas 2021-01-22 22:04:53 +05:30
Kovid Goyal
77f456bd9c Fix #3253 2021-01-22 21:06:58 +05:30
Kovid Goyal
874fe2ed72 ... 2021-01-22 20:36:39 +05:30
Kovid Goyal
786f8c7b1f macOS: Ensure font size is correct if the OS moves the newly created window to a different monitor after it is shown 2021-01-22 20:22:09 +05:30
Kovid Goyal
0c784346cc Update blurb 2021-01-21 12:14:44 +05:30
Kovid Goyal
2ab0ff74c6 ... 2021-01-21 11:34:21 +05:30
Kovid Goyal
22cb9b9798 Add a note about kbs = backspace 2021-01-21 11:29:01 +05:30
Kovid Goyal
2ee1a366fd Also assert non-negative params for SET_MODE 2021-01-21 07:21:23 +05:30
Kovid Goyal
f9844ba3b0 Allow negative numbers in CSI codes 2021-01-21 07:06:43 +05:30
Kovid Goyal
7cea233de3 - is not a CSI start modifier 2021-01-21 06:44:18 +05:30
Kovid Goyal
e4d8aac3d5 Make CSI parsing a bit more spec compliant
Now the full list of secondary characters (intermediate bytes) is used from ECMA 48
Also the codes that accept only one parameter now report errors when
multiple parameters are passed.
2021-01-21 06:41:27 +05:30
Kovid Goyal
354d381372 Oops forgot to regen glfw wrapper 2021-01-21 05:37:13 +05:30
Kovid Goyal
60e589f775 Recognize Hyper key
Also use only one implementation for detecting if a key is a modifier
key
2021-01-21 05:33:14 +05:30
Kovid Goyal
f6d4b8e0b0 typo 2021-01-20 18:30:22 +05:30
Kovid Goyal
5fafd70b0f Fix detection of ASCII control chars in key event text 2021-01-20 17:35:24 +05:30
Kovid Goyal
dcfeab8461 Remove un-needed code
Space is now handled as legacy ascii key
2021-01-20 16:08:15 +05:30
Kovid Goyal
2a8e8d4b38 Fix #3252 2021-01-20 16:02:28 +05:30
Kovid Goyal
e6644aebcb Add a test to ensure caps lock does not produce bytes in legacy mode 2021-01-19 09:19:34 +05:30
Kovid Goyal
654cd54363 Add the ISO level 3 and 5 shift keys to functional keys 2021-01-19 09:11:44 +05:30
Kovid Goyal
16005d43e8 Dont send unknown keys that dont generate text
Fixes #3249
2021-01-19 09:01:52 +05:30
Kovid Goyal
b08c93032b ... 2021-01-19 08:58:06 +05:30
Kovid Goyal
dd08896c70 Lock keys should not be sent to applications in legacy mode 2021-01-19 08:55:01 +05:30
Kovid Goyal
f0ae5dfc73 ... 2021-01-18 16:31:09 +05:30
Kovid Goyal
0adc7e4d4b fix typo 2021-01-18 16:30:02 +05:30
Kovid Goyal
5c2056a7cb Fix parsing of + as a shortcut 2021-01-17 19:56:32 +05:30
Kovid Goyal
f0e7d26409 Fix typo 2021-01-17 19:12:12 +05:30
Kovid Goyal
95ebeebb84 Mention alternate screen mode in the quickstart 2021-01-17 11:41:35 +05:30
Kovid Goyal
a88875f98e ... 2021-01-17 11:17:31 +05:30
Kovid Goyal
39da92d8ab Now that glfw reports shifted keys, use it when encoding key events 2021-01-17 11:14:44 +05:30
Kovid Goyal
089d358031 ... 2021-01-17 09:06:03 +05:30
Kovid Goyal
6a4885df17 Add changelog entries for the new keyboard features 2021-01-17 08:52:10 +05:30
Kovid Goyal
d45d553eaf When using a non-US keyboard layout and pressing ctrl+key when the key matches an English key in the default layout, send that to the program running in the terminal automatically
See #2000
2021-01-17 08:25:19 +05:30
Kovid Goyal
2cbbd84987 Clean up some formatting 2021-01-17 07:34:35 +05:30
Kovid Goyal
1016586611 Fix compiler warning 2021-01-17 07:28:22 +05:30
Kovid Goyal
6bbf014152 ... 2021-01-17 07:24:12 +05:30
Kovid Goyal
4fea7bd8ef Fix building on systems with xkbcommon < 1.0 2021-01-17 07:22:21 +05:30
Kovid Goyal
aa63bf71cf macOS: Add menu items to close the OS window and the current tab
Fixes #3246
2021-01-17 06:49:25 +05:30
Kovid Goyal
4c9bd368c6 Implement alternate keys on macOS 2021-01-16 20:52:16 +05:30
Kovid Goyal
b1c1c497e4 More character key name aliases 2021-01-16 20:52:16 +05:30
Kovid Goyal
86ce72e725 Port the Cocoa backend to use unicode key numbers 2021-01-16 20:52:16 +05:30
Kovid Goyal
ae37b4b09b Add more character key aliases 2021-01-16 20:52:15 +05:30
Kovid Goyal
5ffbee1e8c Use shifted keys to match shortcuts as well 2021-01-16 20:52:15 +05:30
Kovid Goyal
19a3635c64 Dont send release events for kitty shortcuts to children 2021-01-16 20:52:15 +05:30
Kovid Goyal
3c648c05ec Report shifted and alternate keys from the XKB backend 2021-01-16 20:52:15 +05:30
Kovid Goyal
5607a3c2ed ... 2021-01-16 20:52:15 +05:30
Kovid Goyal
cf04a5a036 Add tests for encode/decode roundtripping 2021-01-16 20:52:15 +05:30
Kovid Goyal
c12680895e Fix key state tracking regression 2021-01-16 20:52:15 +05:30
Kovid Goyal
0163bf3edb Fix enter/tab/backspace not being reported in all keys mode 2021-01-16 20:52:15 +05:30
Kovid Goyal
af0a24c702 Make key demo output prettier 2021-01-16 20:52:15 +05:30
Kovid Goyal
7ea1deba8f Clean up code tables 2021-01-16 20:52:15 +05:30
Kovid Goyal
b60c84ecdc Add note on detection of support 2021-01-16 20:52:15 +05:30
Kovid Goyal
e09ed038e2 Port key handling in more kittens
Also fix decoding of enter/f3 key events
2021-01-16 20:52:15 +05:30
Kovid Goyal
9c6ba213f9 Fix key handling in diff kitten 2021-01-16 20:52:14 +05:30
Kovid Goyal
66341111e6 keyboard mode change should happen only in alternate screen 2021-01-16 20:52:14 +05:30
Kovid Goyal
abc1e3f289 Remove the function used to init glfw key events
Instead use C99 struct initializers. Much less error prone.
2021-01-16 20:52:14 +05:30
Kovid Goyal
027c5a57f1 Work on porting kittens to use new key infrastructure
Also move type definitions into their own module
2021-01-16 20:52:14 +05:30
Kovid Goyal
0714fd376b Fix handling of ctrl key in legacy mode
Also change the glfw constants used for the modifiers to match those
used in the terminal encoding. Less likely to make mistakes translating
that way.
2021-01-16 20:52:14 +05:30
Kovid Goyal
39f41faf9f Dont send events for modifier keys unless in all key mode 2021-01-16 20:52:14 +05:30
Kovid Goyal
6b2ffc774f Port shortcut and sequence matching code 2021-01-16 20:52:14 +05:30
Kovid Goyal
f94a5f4e5c Add a Quickstart section to the protocol documentation 2021-01-16 20:52:14 +05:30
Kovid Goyal
c421fd56be Ensure typing in shell works in disambiguate mode 2021-01-16 20:52:14 +05:30
Kovid Goyal
11de87d9d9 It's ok that Enter and F3 share a number
Enter has u has trailer, F3 has either ~ or 1R
2021-01-16 20:52:14 +05:30
Kovid Goyal
8f5d42fa11 Python wrapper for key events from window system 2021-01-16 20:52:14 +05:30
Kovid Goyal
e14fcf5590 Clean up docs 2021-01-16 20:52:14 +05:30
Kovid Goyal
f5b81c7ed6 ... 2021-01-16 20:52:13 +05:30
Kovid Goyal
47a901385f Implement reporting of all keys as escape codes with text 2021-01-16 20:52:13 +05:30
Kovid Goyal
819bd5cd70 Tests for alternate key reporting 2021-01-16 20:52:13 +05:30
Kovid Goyal
753ad68ca9 Add tests for event type reporting 2021-01-16 20:52:13 +05:30
Kovid Goyal
5498f4e526 ... 2021-01-16 20:52:13 +05:30
Kovid Goyal
83a01b6bf4 Add tests for the disambiguate flag 2021-01-16 20:52:13 +05:30
Kovid Goyal
c519013b20 Add tests for legacy letter key encodings 2021-01-16 20:52:13 +05:30
Kovid Goyal
78d45eb161 Port another use of the old key API 2021-01-16 20:52:13 +05:30
Kovid Goyal
eeacac227a Add tests for functional KP keys 2021-01-16 20:52:13 +05:30
Kovid Goyal
529e9f83f7 Finish up functional key tests 2021-01-16 20:52:13 +05:30
Kovid Goyal
5297f7474d Remove another use of GLFW_KEY 2021-01-16 20:52:13 +05:30
Kovid Goyal
e06b48a344 Identify SS3 in the docs 2021-01-16 20:52:13 +05:30
Kovid Goyal
7ba57ebf62 esc also needs a low number 2021-01-16 20:52:12 +05:30
Kovid Goyal
1ec9a8b777 Fix parsing of shortcuts to support the new keys 2021-01-16 20:52:12 +05:30
Kovid Goyal
1690718710 More removal of GLFW_KEY_ constants 2021-01-16 20:52:12 +05:30
Kovid Goyal
774a6c8c8b Remove glfw key constants 2021-01-16 20:52:12 +05:30
Kovid Goyal
f06eee8fe1 Define special numbers in one place only 2021-01-16 20:52:12 +05:30
Kovid Goyal
2b12bcc07f Docs for legacy functional encoding 2021-01-16 20:52:12 +05:30
Kovid Goyal
a30ea2b7f8 Implement progressive enhancement of key event reporting 2021-01-16 20:52:12 +05:30
Kovid Goyal
295e8db04c remove unused code 2021-01-16 20:52:12 +05:30
Kovid Goyal
b63ae10a09 Start documenting new keyboard protocol 2021-01-16 20:52:12 +05:30
Kovid Goyal
c8a9336160 Code to encode key events 2021-01-16 20:52:12 +05:30
Kovid Goyal
c0b6078438 Add more keypad keys 2021-01-16 20:52:11 +05:30
Kovid Goyal
ccce8c32a3 Mapping to unicode and back for XKB keys 2021-01-16 20:52:11 +05:30
Kovid Goyal
8f8be9c2b1 Add notice to not edit generated code 2021-01-16 20:52:11 +05:30
Kovid Goyal
eb8e2225e5 Add some more functional keys
Also define aliases for XKB codes
2021-01-16 20:52:11 +05:30
Kovid Goyal
397d7d044b Change key data type in some functions 2021-01-16 20:52:11 +05:30
Kovid Goyal
a681162326 Start work on supporting arbitrary unicode keys 2021-01-16 20:52:11 +05:30
Kovid Goyal
f00ec0ff60 Merge branch 'm1-build-fix' of https://github.com/pjjw/kitty 2021-01-15 10:52:01 +05:30
Peter Woodman
35982a9e58 Make is_arm predicate work on macos 2021-01-15 00:09:24 -05:00
Kovid Goyal
cf0bdf4d59 Add another couple of integrations 2021-01-14 06:36:18 +05:30
Kovid Goyal
8a9d43e84d Changelog item for last PR 2021-01-13 06:58:01 +05:30
Kovid Goyal
578bf963e3 Merge branch 'master' of https://github.com/quentin-fox/kitty 2021-01-13 06:57:25 +05:30
Quentin Fox
292c4ce450 add menu item to close window 2021-01-12 19:55:51 -05:00
Kovid Goyal
8ea589e5a3 Change blurb wording slightly 2021-01-12 20:01:44 +05:30
Kovid Goyal
ad09ccb0f2 Panel kitten: Allow setting WM_CLASS
Fixes #3233
2021-01-12 04:49:45 +05:30
Kovid Goyal
edfc6903ce Remove unused code 2021-01-08 13:32:52 +05:30
Kovid Goyal
b94d2b27f4 Refactor single key config parsing to use a special type 2021-01-07 18:07:17 +05:30
Kovid Goyal
31cb68840a Merge branch 'bugfix-unicode-names-input' of https://github.com/purxiz/kitty
Fixes #3221
2021-01-07 14:18:24 +05:30
purxiz
89c62377da fix type error
I moved the location of the check for an overflowing index to avoid a type error that was revealed by mypy. I also think this is a better place to put it from a code standpoint in general...
2021-01-06 13:24:38 -06:00
purxiz
85afda8832 fixes incorrect hint base
The bases for the hint were being decoded as base 16, despite being displayed as base 36 (0-9, a-z). This fix makes it so typing 'search string .index' returns the unicode char at the expected index, based on what is displayed by the program.

It also slightly changes the behavior. Before, only the selected result would appear. For example, musical note `musical note .18` would only show a single result, since there was only a single result at index 18. Now, searching by index doesn't remove codepoints/results, it simply highlights the result at the correct index for you.
2021-01-06 13:07:41 -06:00
Kovid Goyal
5184e4bde3 Fix #3217 2021-01-06 22:31:09 +05:30
Kovid Goyal
133b580262 Avoid error prints when rendering the space glyph and freetype fails to load it
Also a micro-optimization, since there is no need to actually render the
space glyph
2021-01-05 12:21:06 +05:30
Kovid Goyal
3e76cf87ce Linux: Fix rendering of emoji followed by the graphics variation selector not being colored with some fonts
Harfbuzz, for some reason has started rendering variation selector
glyphs. These fail to load for some fonts. https://github.com/harfbuzz/harfbuzz/issues/2804

Fixes #3211
2021-01-05 09:35:21 +05:30
Kovid Goyal
f72ff73344 Correct return type 2021-01-05 09:17:17 +05:30
Kovid Goyal
9aae346ab5 Better error message when failing to load glyph 2021-01-05 09:10:35 +05:30
Kovid Goyal
ca65ad6fa3 Inline qsort
Cleaner, more local code and faster sorting by virtue of inlining the
comparisons. What's not to like.
2021-01-04 14:14:15 +05:30
Kovid Goyal
0f4156c564 ... 2021-01-04 13:15:29 +05:30
Kovid Goyal
3a0d3dc0b3 Slightly improved command line 2021-01-04 13:14:14 +05:30
Kovid Goyal
d68c338304 Wait for write and check data, fixes test failure 2021-01-04 12:27:26 +05:30
Kovid Goyal
82f8c59bc7 ... 2021-01-04 12:18:42 +05:30
Kovid Goyal
c55f485166 Add an example of using the remote control protocol from a shell script 2021-01-04 12:17:20 +05:30
Kovid Goyal
2762488c81 Remove double increment 2021-01-03 21:10:37 +05:30
Kovid Goyal
ac13ae727f Fix failing test
We cant know what the size of the disk cache file is,
since defragmentation occurs asynchronously
2021-01-03 21:05:08 +05:30
Kovid Goyal
1b59a1c421 Finish tests of disk cache 2021-01-03 17:36:40 +05:30
Kovid Goyal
df00e9545d Add some more ensure_state() 2021-01-03 12:13:08 +05:30
Kovid Goyal
d1496f1eb5 Use O_TMPFILE when available 2021-01-03 07:07:50 +05:30
Kovid Goyal
14d9bcb08a Go back to nanosleep since it is in POSIX 2021-01-03 06:45:53 +05:30
Kovid Goyal
c869774c58 More tests for the disk cache 2021-01-03 06:22:48 +05:30
Kovid Goyal
18679348b2 Use usleep rather than nanosleep 2021-01-03 05:00:40 +05:30
Kovid Goyal
b3ed4c3f40 Simplify implementation of REP
Also make it align more with the standard by using the actual last drawn
graphics character rather than the character before the cursor
2021-01-03 04:38:02 +05:30
Kovid Goyal
8f58140419 Start work on tests for disk cache 2021-01-03 04:28:48 +05:30
Kovid Goyal
bc8a351558 Add repeat character to client.py 2021-01-02 12:49:33 +05:30
Kovid Goyal
9ae198ef8f Double clicking on empty tab bar area now opens a new tab
Fixes #3201
2021-01-02 11:45:18 +05:30
Kovid Goyal
bc86bc91f7 Auto-defrag cache file 2021-01-02 11:32:38 +05:30
Kovid Goyal
a60f347648 Merge branch 'kitten-diff-select-fg-none' of https://github.com/tomaskrizek/kitty 2021-01-02 09:01:25 +05:30
Tomas Krizek
dbe44c9e4b kitten/diff: allow specifying none color for select_fg
Allow diff kitten to not override foreground color for selections.

Related #1358
2021-01-01 23:25:06 +01:00
Tomas Krizek
fb8b9b3933 kitty/config_data: remove duplicate function
selection_foreground() serves the exact same purpose as
to_color_or_none()
2021-01-01 23:23:47 +01:00
Kovid Goyal
56ebc9fa92 Add a link to ctx.graphics 2021-01-01 15:20:47 +05:30
Kovid Goyal
7f73609c77 Update total_size when removing cache items 2021-01-01 14:31:57 +05:30
Kovid Goyal
3ac70042c1 Expose grman disk cache to python 2021-01-01 14:19:10 +05:30
Kovid Goyal
61637a7a74 Simplified API t read from disk cache
Avoids the need to pass a custom allocator by using malloc
2021-01-01 14:16:26 +05:30
Kovid Goyal
49562e96cc Python wrappers for the disk cache methods 2021-01-01 14:08:23 +05:30
Kovid Goyal
58b1868536 When cache is emptied clear disk file to avoid fragmentation 2021-01-01 13:15:21 +05:30
Kovid Goyal
1cad40e977 ... 2021-01-01 12:03:09 +05:30
Kovid Goyal
9fbf23fdcc Implement simple defrag for on-disk cache 2021-01-01 11:54:13 +05:30
Kovid Goyal
55e53db184 ... 2020-12-31 15:38:57 +05:30
Kovid Goyal
f790e8bd47 DRYer 2020-12-31 15:33:04 +05:30
Kovid Goyal
54de2d177e Enforce max key size
Also allocate it dynamically
2020-12-31 15:30:55 +05:30
Kovid Goyal
ec96771e4c track total size and since each cache has its own writer thread store current key per thread 2020-12-31 15:24:25 +05:30
Kovid Goyal
99d2647335 Code to write to cache file 2020-12-31 15:18:30 +05:30
Kovid Goyal
c346cbc252 Use a single file for the disk cache
This has the advantage that on crash it is automatically cleaned
2020-12-31 13:54:33 +05:30
Kovid Goyal
3c77290c2c Code to get random bytes in C, cross-platform 2020-12-31 11:26:22 +05:30
Kovid Goyal
72d193e852 Fix python3.7 fork registration not working 2020-12-31 10:38:26 +05:30
Kovid Goyal
1f7499e841 Detect more types of BSD 2020-12-31 10:26:15 +05:30
Kovid Goyal
c486fbb7c3 Add some aliases for shift key shenanigans 2020-12-30 21:09:03 +05:30
Kovid Goyal
fe1050fc0e More work on disk cache 2020-12-30 14:26:38 +05:30
Kovid Goyal
3162d391cc Start work on disk cache 2020-12-29 19:16:21 +05:30
Kovid Goyal
e2d61ab5be Fix error handling for pthread create 2020-12-29 15:53:40 +05:30
Kovid Goyal
2a026de526 More sophisticated PATH resolution for the launch command 2020-12-29 11:51:17 +05:30
Kovid Goyal
c1b7023e3a Look in ~/.local/bin preferentially 2020-12-29 11:50:11 +05:30
Kovid Goyal
175ff4b955 DRYer 2020-12-29 11:36:30 +05:30
Kovid Goyal
65361d56c4 Use find_exe() for resolving editor as well
Avoids having to exec the shell in most cases
2020-12-29 11:33:42 +05:30
Kovid Goyal
08fae7a0ac Add sbin to default paths 2020-12-29 10:50:31 +05:30
Kovid Goyal
a7e9030c12 Make finding ImageMagick a bit more robust on macOS 2020-12-29 10:48:26 +05:30
Kovid Goyal
aa8282f8f2 Update actual terminfo files from last PR
Also add entry to changelog
2020-12-29 10:03:56 +05:30
Kovid Goyal
7155400eb5 Merge branch 'bawr/terminfo-u6789' of https://github.com/bawr/kitty 2020-12-29 10:02:17 +05:30
Kovid Goyal
6760e38d93 Callback to create tempdir in cache 2020-12-29 10:00:34 +05:30
Bartosz Wróblewski
6bbe995d64 Generate u[6789] with terminfo.py
Should not hurt anything, it's just documenting kitty's existing VT220 compatibility.
2020-12-28 14:05:25 -08:00
Kovid Goyal
85bc8b61df Make wm_class/wm_name available in @ ls output 2020-12-26 10:46:07 +05:30
Kovid Goyal
a235509fa1 Make aliasing ssh kitten more prominent 2020-12-24 13:56:17 +05:30
Kovid Goyal
2e5b595a82 Clarify color table docs 2020-12-24 13:54:09 +05:30
Kovid Goyal
5f8dee8384 Add support for the color settings stack that XTerm copied from us without acknowledgement and decided to use incompatible escape codes for.
Completely in keeping with that project's past behavior.
See https://github.com/kovidgoyal/kitty/issues/879

XTerm announcement:
https://www.mail-archive.com/xorg@lists.x.org/msg06419.html
2020-12-21 21:39:05 +05:30
Kovid Goyal
e97f1a4310 Have the save/restore colors escape codes also save restore the ANSI color table 2020-12-21 19:38:03 +05:30
Kovid Goyal
c3c5a5446f Add window_id and window_title fields to --match-tab 2020-12-20 19:39:09 +05:30
196 changed files with 28517 additions and 43843 deletions

1
.gitattributes vendored
View File

@@ -1,6 +1,5 @@
kitty/wcwidth-std.h linguist-generated=true
kitty/emoji.h linguist-generated=true
kitty/keys.h linguist-generated=true
kitty/charsets.c linguist-generated=true
kitty/key_encoding.py linguist-generated=true
kitty/unicode-data.c linguist-generated=true

View File

@@ -35,15 +35,18 @@ def install_deps():
else:
run('sudo apt-get update')
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev'
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev'
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev')
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev'
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev uuid-dev')
if is_bundle:
install_bundle()
else:
if is_macos:
# needed for zlib for pillow, should not be needed after pillow 8.0
os.environ['PKG_CONFIG_PATH'] = '/usr/local/opt/zlib/lib/pkgconfig'
run('pip3 install Pillow pygments')
cmd = 'pip3 install Pillow pygments'
if sys.version_info[:2] < (3, 7):
cmd += ' importlib-resources'
run(cmd)
def build_kitty():

View File

@@ -1,5 +1,12 @@
= kitty - the fast, featureful, GPU based, terminal emulator
= kitty - the fast, feature-rich, cross-platform, GPU based terminal
See https://sw.kovidgoyal.net/kitty/
image:https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg["Build status", link="https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI"]
To ask questions about kitty usage, use either the https://github.com/kovidgoyal/kitty/discussions/[discussions on GitHub] or the
https://www.reddit.com/r/KittyTerminal[Reddit community]
Packaging status in various repositories:
image:https://repology.org/badge/vertical-allrepos/kitty.svg[https://repology.org/project/kitty/versions]

12
SECURITY.md Normal file
View File

@@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
There are no security specific releases of kitty. Security bugs are fixed
and released just like all other bugs.
## Reporting a vulnerability
Preferably send an email to kovid at kovidgoyal.net or open an issue in the
GitHub repository, though the latter means you are disclosing the vulnerability
publicly before it can be fixed.

View File

@@ -30,8 +30,14 @@ def runpy(args: List[str]) -> None:
def hold(args: List[str]) -> None:
import subprocess
from contextlib import suppress
import tty
ret = subprocess.Popen(args[1:]).wait()
sys.stdin.read()
with suppress(BaseException):
print('\n\x1b[1;32mPress any key to exit', end='', flush=True)
with suppress(BaseException):
tty.setraw(sys.stdin.fileno())
sys.stdin.buffer.read(1)
raise SystemExit(ret)

View File

@@ -4,15 +4,17 @@
import os
import re
import shlex
import shutil
import subprocess
import sys
import tempfile
from contextlib import suppress
from bypy.constants import (
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
)
from bypy.utils import run_shell
from bypy.utils import run_shell, walk
def read_src_file(name):
@@ -40,52 +42,66 @@ def run(*args, **extra_env):
if ismacos:
env['PKGCONFIG_EXE'] = os.path.join(PREFIX, 'bin', 'pkg-config')
cwd = env.pop('cwd', KITTY_DIR)
print(' '.join(map(shlex.quote, args)), flush=True)
return subprocess.call(list(args), env=env, cwd=cwd)
def build_frozen_launcher(extra_include_dirs):
inc_dirs = [f'--extra-include-dirs={x}' for x in extra_include_dirs]
cmd = [PYTHON, 'setup.py', '--prefix', build_frozen_launcher.prefix] + inc_dirs + ['build-frozen-launcher']
if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0:
print('Building of frozen kitty launcher failed', file=sys.stderr)
os.chdir(KITTY_DIR)
run_shell()
raise SystemExit('Building of kitty launcher failed')
return build_frozen_launcher.writeable_src_dir
def run_tests(kitty_exe):
with tempfile.TemporaryDirectory() as tdir:
env = {
'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'),
'KITTY_CACHE_DIRECTORY': os.path.join(tdir, 'cache')
}
[os.mkdir(x) for x in env.values()]
cmd = [kitty_exe, '+runpy', 'from kitty_tests.main import run_tests; run_tests()']
print(*map(shlex.quote, cmd), flush=True)
if subprocess.call(cmd, env=env) != 0:
print('Checking of kitty build failed', file=sys.stderr)
os.chdir(os.path.dirname(kitty_exe))
run_shell()
raise SystemExit('Checking of kitty build failed')
def sanitize_source_folder(path: str) -> None:
for q in walk(path):
if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf'):
os.unlink(q)
def build_c_extensions(ext_dir, args):
writeable_src_dir = os.path.join(ext_dir, 'src')
build_frozen_launcher.writeable_src_dir = writeable_src_dir
shutil.copytree(
KITTY_DIR, writeable_src_dir, symlinks=True,
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o'))
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o', '*.so', '*.dylib', '*.pyd'))
# Build the launcher as it is needed for the spawn test
with suppress(FileNotFoundError):
os.remove(os.path.join(writeable_src_dir, 'kitty', 'launcher', 'kitty'))
if run(PYTHON, 'setup.py', 'build-launcher', cwd=writeable_src_dir) != 0:
print('Building of kitty launcher failed', file=sys.stderr)
os.chdir(KITTY_DIR)
run_shell()
raise SystemExit('Building of kitty launcher failed')
os.unlink(os.path.join(writeable_src_dir, 'kitty', 'launcher', 'kitty'))
cmd = [PYTHON, 'setup.py']
bundle = 'macos-freeze' if ismacos else 'linux-freeze'
cmd.append(bundle)
cmd = [PYTHON, 'setup.py', 'macos-freeze' if ismacos else 'linux-freeze']
if args.dont_strip:
cmd.append('--debug')
dest = kitty_constants['appname'] + ('.app' if ismacos else '')
dest = os.path.join(ext_dir, dest)
cmd += ['--prefix', dest]
dest = build_frozen_launcher.prefix = os.path.join(ext_dir, dest)
cmd += ['--prefix', dest, '--full']
if run(*cmd, cwd=writeable_src_dir) != 0:
print('Building of kitty package failed', file=sys.stderr)
os.chdir(KITTY_DIR)
os.chdir(writeable_src_dir)
run_shell()
raise SystemExit('Building of kitty package failed')
return ext_dir
def run_tests(path_to_kitty, cwd_on_failure):
kw = {'cwd': cwd_on_failure}
if not ismacos:
# this is needed for the spawn test which starts an interpreter
# using the kitty launcher.
kw['PYTHONHOME'] = PREFIX
ret = run(PYTHON, 'test.py', **kw)
if ret != 0:
os.chdir(cwd_on_failure)
print(
'running kitty tests failed with return code:', ret, file=sys.stderr)
run_shell()
raise SystemExit('running kitty tests failed')
if __name__ == 'program':
kitty_constants = initialize_constants()

View File

@@ -13,12 +13,14 @@ import time
from bypy.constants import (
OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
)
from bypy.utils import get_dll_path, py_compile, walk
from bypy.freeze import (
extract_extension_modules, freeze_python, path_to_freeze_dir
)
from bypy.utils import get_dll_path, mkdtemp, py_compile, walk
j = os.path.join
self_dir = os.path.dirname(os.path.abspath(__file__))
arch = 'x86_64' if is64bit else 'i686'
self_dir = os.path.dirname(os.path.abspath(__file__))
py_ver = '.'.join(map(str, python_major_minor_version()))
iv = globals()['init_env']
kitty_constants = iv['kitty_constants']
@@ -26,12 +28,8 @@ kitty_constants = iv['kitty_constants']
def binary_includes():
return tuple(map(get_dll_path, (
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2',
# dont include freetype because fontconfig is closely coupled to it
# and also distros often patch freetype
# 'iconv.so.2', 'pcre.so.1', 'graphite2.so.3', 'glib-2.0.so.0', 'freetype.so.6',
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'crypt',
'iconv', 'pcre', 'graphite2', 'glib-2.0', 'freetype',
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
'ncursesw', 'readline',
))) + (
@@ -48,6 +46,7 @@ class Env:
self.py_dir = j(self.lib_dir, 'python' + py_ver)
os.makedirs(self.py_dir)
self.bin_dir = j(self.base, 'bin')
self.obj_dir = mkdtemp('launchers-')
def ignore_in_lib(base, items, ignored_dirs=None):
@@ -75,7 +74,7 @@ def import_site_packages(srcdir, dest):
if ext in ('py', 'so'):
shutil.copy2(f, dest)
elif ext == 'pth' and x != 'setuptools.pth':
for line in open(f, 'rb').read().splitlines():
for line in open(f):
src = os.path.abspath(j(srcdir, line))
if os.path.exists(src) and os.path.isdir(src):
import_site_packages(src, dest)
@@ -105,10 +104,31 @@ def copy_python(env):
shutil.copy2(y, env.py_dir)
srcdir = j(srcdir, 'site-packages')
dest = j(env.py_dir, 'site-packages')
import_site_packages(srcdir, dest)
site_packages_dir = j(env.py_dir, 'site-packages')
import_site_packages(srcdir, site_packages_dir)
pdir = os.path.join(env.lib_dir, 'kitty-extensions')
os.makedirs(pdir, exist_ok=True)
kitty_dir = os.path.join(env.lib_dir, 'kitty')
bases = ('kitty', 'kittens', 'kitty_tests')
for x in bases:
dest = os.path.join(env.py_dir, x)
os.rename(os.path.join(kitty_dir, x), dest)
if x == 'kitty':
shutil.rmtree(os.path.join(dest, 'launcher'))
os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(env.py_dir, 'kitty_main.py'))
shutil.rmtree(os.path.join(kitty_dir, '__pycache__'))
print('Extracting extension modules from', env.py_dir, 'to', pdir)
ext_map = extract_extension_modules(env.py_dir, pdir)
shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(env.py_dir, 'site.py'))
for x in bases:
iv['sanitize_source_folder'](os.path.join(env.py_dir, x))
py_compile(env.py_dir)
py_compile(os.path.join(env.base, 'lib', 'kitty'))
freeze_python(env.py_dir, pdir, env.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM')
def build_launcher(env):
iv['build_frozen_launcher']([path_to_freeze_dir(), env.obj_dir])
def is_elf(path):
@@ -192,16 +212,16 @@ def create_tarfile(env, compression_level='9'):
def main():
args = globals()['args']
ext_dir = globals()['ext_dir']
if not args.skip_tests:
run_tests = iv['run_tests']
run_tests(None, os.path.join(ext_dir, 'src'))
env = Env(os.path.join(ext_dir, kitty_constants['appname']))
copy_libs(env)
copy_python(env)
build_launcher(env)
files = find_binaries(env)
fix_permissions(files)
if not args.dont_strip:
strip_binaries(files)
if not args.skip_tests:
iv['run_tests'](os.path.join(env.base, 'bin', 'kitty'))
create_tarfile(env, args.compression_level)

View File

@@ -13,16 +13,23 @@ import tempfile
import zipfile
from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version
from bypy.freeze import (
extract_extension_modules, freeze_python, path_to_freeze_dir
)
from bypy.macos_sign import (
codesign, create_entitlements_file, make_certificate_useable, notarize_app,
verify_signature
)
from bypy.utils import current_dir, py_compile, run_shell, timeit, walk
from bypy.utils import (
current_dir, mkdtemp, py_compile, run_shell, timeit, walk
)
iv = globals()['init_env']
kitty_constants = iv['kitty_constants']
self_dir = os.path.dirname(os.path.abspath(__file__))
join = os.path.join
basename = os.path.basename
dirname = os.path.dirname
abspath = os.path.abspath
APPNAME = kitty_constants['appname']
VERSION = kitty_constants['version']
@@ -134,8 +141,9 @@ class Freeze(object):
FID = '@executable_path/../Frameworks'
def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False):
def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False, skip_tests=False):
self.build_dir = build_dir
self.skip_tests = skip_tests
self.sign_installers = sign_installers
self.notarize = notarize
self.dont_strip = dont_strip
@@ -147,6 +155,7 @@ class Freeze(object):
self.py_ver = py_ver
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', 'python' + self.py_ver)
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
self.obj_dir = mkdtemp('launchers-')
self.run()
@@ -160,10 +169,11 @@ class Freeze(object):
self.add_site_packages()
self.add_stdlib()
self.add_misc_libraries()
self.compile_py_modules()
self.fix_dependencies_in_kitty()
self.freeze_python()
if not self.dont_strip:
self.strip_files()
if not self.skip_tests:
self.run_tests()
# self.run_shell()
ret = self.makedmg(self.build_dir, APPNAME + '-' + VERSION)
@@ -175,6 +185,10 @@ class Freeze(object):
print('\nStripping files...')
strip_files(self.to_strip)
@flush
def run_tests(self):
iv['run_tests'](os.path.join(self.contents_dir, 'MacOS', 'kitty'))
@flush
def set_id(self, path_to_lib, new_id):
old_mode = flipwritable(path_to_lib)
@@ -239,7 +253,6 @@ class Freeze(object):
self.set_id(
join(currd, 'Python'),
self.FID + '/Python.framework/Versions/%s/Python' % basename(curr))
self.fix_dependencies_in_lib(join(self.contents_dir, 'MacOS', 'kitty'))
# The following is needed for codesign
with current_dir(x):
os.symlink(basename(curr), 'Versions/Current')
@@ -265,6 +278,7 @@ class Freeze(object):
'lcms2.2',
'crypto.1.1',
'ssl.1.1',
'ffi.7',
):
print('\nAdding', x)
x = 'lib%s.dylib' % x
@@ -273,11 +287,6 @@ class Freeze(object):
dest = join(self.frameworks_dir, x)
self.set_id(dest, self.FID + '/' + x)
self.fix_dependencies_in_lib(dest)
base = join(self.frameworks_dir, 'kitty')
for lib in walk(base):
if lib.endswith('.so'):
self.set_id(lib, self.FID + '/' + os.path.relpath(lib, self.frameworks_dir))
self.fix_dependencies_in_lib(lib)
@flush
def add_package_dir(self, x, dest=None):
@@ -295,21 +304,10 @@ class Freeze(object):
dest = self.site_packages
dest = join(dest, basename(x))
shutil.copytree(x, dest, symlinks=True, ignore=ignore)
self.postprocess_package(x, dest)
for f in walk(dest):
if f.endswith('.so'):
self.fix_dependencies_in_lib(f)
@flush
def fix_dependencies_in_kitty(self):
for f in walk(join(self.resources_dir, 'kitty')):
if f.endswith('.so'):
self.fix_dependencies_in_lib(f)
@flush
def postprocess_package(self, src_path, dest_path):
pass
@flush
def add_stdlib(self):
print('\nAdding python stdlib')
@@ -331,6 +329,35 @@ class Freeze(object):
if dest2.endswith('.so'):
self.fix_dependencies_in_lib(dest2)
@flush
def freeze_python(self):
print('\nFreezing python')
kitty_dir = join(self.resources_dir, 'kitty')
bases = ('kitty', 'kittens', 'kitty_tests')
for x in bases:
dest = os.path.join(self.python_stdlib, x)
os.rename(os.path.join(kitty_dir, x), dest)
if x == 'kitty':
shutil.rmtree(os.path.join(dest, 'launcher'))
os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(self.python_stdlib, 'kitty_main.py'))
shutil.rmtree(os.path.join(kitty_dir, '__pycache__'))
pdir = os.path.join(dirname(self.python_stdlib), 'kitty-extensions')
os.mkdir(pdir)
print('Extracting extension modules from', self.python_stdlib, 'to', pdir)
ext_map = extract_extension_modules(self.python_stdlib, pdir)
shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(self.python_stdlib, 'site.py'))
for x in bases:
iv['sanitize_source_folder'](os.path.join(self.python_stdlib, x))
self.compile_py_modules()
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM')
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
os.rename(join(dirname(self.contents_dir), 'bin', 'kitty'), join(self.contents_dir, 'MacOS', 'kitty'))
shutil.rmtree(join(dirname(self.contents_dir), 'bin'))
self.fix_dependencies_in_lib(join(self.contents_dir, 'MacOS', 'kitty'))
for f in walk(pdir):
if f.endswith('.so') or f.endswith('.dylib'):
self.fix_dependencies_in_lib(f)
@flush
def add_site_packages(self):
print('\nAdding site-packages')
@@ -389,11 +416,8 @@ class Freeze(object):
@flush
def compile_py_modules(self):
print('\nCompiling Python modules')
self.remove_bytecode(join(self.resources_dir, 'Python'))
py_compile(join(self.resources_dir, 'Python'))
self.remove_bytecode(join(self.resources_dir, 'kitty'))
py_compile(join(self.resources_dir, 'kitty'))
@flush
def makedmg(self, d, volname, internet_enable=True, format='ULFO'):
@@ -445,14 +469,12 @@ class Freeze(object):
def main():
args = globals()['args']
ext_dir = globals()['ext_dir']
if not args.skip_tests:
run_tests = iv['run_tests']
run_tests(None, os.path.join(ext_dir, 'src'))
Freeze(
os.path.join(ext_dir, kitty_constants['appname'] + '.app'),
dont_strip=args.dont_strip,
sign_installers=args.sign_installers,
notarize=args.notarize
notarize=args.notarize,
skip_tests=args.skip_tests
)

31
bypy/site.py Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import builtins
import sys
import _sitebuiltins
def set_quit():
eof = 'Ctrl-D (i.e. EOF)'
builtins.quit = _sitebuiltins.Quitter('quit', eof)
builtins.exit = _sitebuiltins.Quitter('exit', eof)
def set_helper():
builtins.help = _sitebuiltins._Helper()
def main():
sys.argv[0] = sys.calibre_basename
set_helper()
set_quit()
mod = __import__(sys.calibre_module, fromlist=[1])
func = getattr(mod, sys.calibre_function)
return func()
if __name__ == '__main__':
main()

View File

@@ -32,9 +32,20 @@
{
"name": "expat",
"unix": {
"filename": "expat-2.2.6.tar.bz2",
"hash": "sha256:17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2",
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_2_6/{filename}"]
"filename": "expat-2.2.10.tar.bz2",
"hash": "sha256:b2c160f1b60e92da69de8e12333096aeb0c3bf692d41c60794de278af72135a5",
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_2_10/{filename}"]
}
},
{
"name": "libxml2",
"os": "linux",
"unix": {
"filename": "libxml2-2.9.10.tar.gz",
"hash": "sha256:aafee193ffb8fe0c82d4afef6ef91972cbaf5feea100edc2f262750611b4be1f",
"urls": ["ftp://xmlsoft.org/libxml2/{filename}"]
}
},
@@ -43,8 +54,8 @@
"name": "xkbcommon",
"os": "linux",
"unix": {
"filename": "libxkbcommon-0.10.0.tar.xz",
"hash": "sha256:57c3630cdc38fb4734cd57fa349e92244f5ae3862813e533cedbd86721a0b6f2",
"filename": "libxkbcommon-1.0.3.tar.xz",
"hash": "sha256:a2202f851e072b84e64a395212cbd976ee18a8ee602008b0bad02a13247dbc52",
"urls": ["http://xkbcommon.org/download/{filename}"]
}
},
@@ -52,9 +63,9 @@
{
"name": "sqlite",
"unix": {
"filename": "sqlite-autoconf-3310100.tar.gz",
"hash": "sha1:0c30f5b22152a8166aa3bebb0f4bc1f3e9cc508b",
"urls": ["https://www.sqlite.org/2020/{filename}"]
"filename": "sqlite-autoconf-3340100.tar.gz",
"hash": "sha256:2a3bca581117b3b88e5361d0ef3803ba6d8da604b1c1a47d902ef785c1b53e89",
"urls": ["https://www.sqlite.org/2021/{filename}"]
}
},
@@ -62,17 +73,17 @@
{
"name": "libffi",
"unix": {
"filename": "libffi-3.2.1.tar.gz",
"hash": "sha1:280c265b789e041c02e5c97815793dfc283fb1e6",
"urls": ["ftp://sourceware.org/pub/libffi/{filename}"]
"filename": "libffi-3.3.0.tar.gz",
"hash": "sha256:72fba7922703ddfa7a028d513ac15a85c8d54c8d67f55fa5a4802885dc652056",
"urls": ["https://github.com/libffi/libffi/releases/download/v3.3/libffi-3.3.tar.gz"]
}
},
{
"name": "openssl",
"unix": {
"filename": "openssl-1.1.1g.tar.gz",
"hash": "sha256:ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46",
"filename": "openssl-1.1.1i.tar.gz",
"hash": "sha256:e8be6a35fe41d10603c3cc635e93289ed00bf34b79671a3a4de64fcee00d5242",
"urls": ["https://www.openssl.org/source/{filename}"]
}
},
@@ -91,15 +102,14 @@
"name": "readline",
"os": "linux",
"unix": {
"filename": "readline-8.0.tar.gz",
"hash": "sha256:e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461",
"filename": "readline-8.1.tar.gz",
"hash": "sha256:f8ceb4ee131e3232226a17f51b164afc46cd0b9e6cef344be87c65962cb82b02",
"urls": ["http://ftp.gnu.org/gnu/readline/{filename}"]
}
},
{
"name": "xz",
"os": "macos,linux",
"unix": {
"filename": "xz-5.2.5.tar.gz",
"hash": "sha256:f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10",
@@ -107,20 +117,30 @@
}
},
{
"name": "xcrypt",
"os": "linux",
"unix": {
"filename": "xcrypt-4.4.17.tar.gz",
"hash": "sha256:7665168d0409574a03f7b484682e68334764c29c21ca5df438955a381384ca07",
"urls": ["https://github.com/besser82/libxcrypt/archive/v4.4.17.tar.gz"]
}
},
{
"name": "python",
"unix": {
"filename": "Python-3.8.3.tar.xz",
"hash": "sha256:dfab5ec723c218082fe3d5d7ae17ecbdebffa9a1aea4d64aa3a2ecdd2e795864",
"urls": ["https://www.python.org/ftp/python/3.8.3/{filename}"]
"filename": "Python-3.9.1.tar.xz",
"hash": "sha256:991c3f8ac97992f3d308fefeb03a64db462574eadbff34ce8bc5bb583d9903ff",
"urls": ["https://www.python.org/ftp/python/3.9.1/{filename}"]
}
},
{
"name": "setuptools",
"unix": {
"filename": "setuptools-41.0.1.zip",
"hash": "sha256:a222d126f5471598053c9a77f4b5d4f26eaa1f150ad6e01dcf1a42e185d05613",
"filename": "setuptools-53.0.0.tar.gz",
"hash": "sha256:1b18ef17d74ba97ac9c0e4b4265f123f07a8ae85d9cd093949fa056d3eeeead5",
"urls": ["pypi"]
}
},
@@ -128,8 +148,8 @@
{
"name": "pygments",
"unix": {
"filename": "Pygments-2.6.1.tar.gz",
"hash": "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
"filename": "Pygments-2.7.4.tar.gz",
"hash": "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337",
"urls": ["pypi"]
}
},
@@ -138,9 +158,9 @@
"name": "cmake",
"os": "macos",
"unix": {
"filename": "cmake-3.6.0.tar.gz",
"hash": "sha1:6b0d2d7d93c2e74f8890c4b8b824deb3b36d24b6",
"urls": ["https://cmake.org/files/v3.6/{filename}"]
"filename": "cmake-3.19.4.tar.gz",
"hash": "sha256:7d0232b9f1c57e8de81f38071ef8203e6820fe7eec8ae46a1df125d88dbcc2e1",
"urls": ["https://cmake.org/files/v3.19/{filename}"]
}
},
@@ -156,9 +176,9 @@
{
"name": "lcms2",
"unix": {
"filename": "lcms2-2.11.tar.gz",
"hash": "sha256:478c9c3938d7a91b1171de4616f8b04308a8676d73eadc19505b7ace41327f28",
"urls": ["https://github.com/mm2/Little-CMS/archive/2.11/{filename}"]
"filename": "lcms2-2.12.tar.gz",
"hash": "sha256:e501f1482fc424550ef3abbf86bf1c66090e1661249e89552d39ed5bf935df66",
"urls": ["https://github.com/mm2/Little-CMS/archive/2.12/{filename}"]
}
},
@@ -166,8 +186,8 @@
"name": "graphite",
"os": "linux",
"unix": {
"filename": "graphite2-1.3.5.tgz",
"hash": "md5:5b8d22a8bbf031838e31432868c0109c",
"filename": "graphite2-1.3.14.tgz",
"hash": "sha256:f99d1c13aa5fa296898a181dff9b82fb25f6cc0933dbaa7a475d8109bd54209d",
"urls": ["http://downloads.sourceforge.net/silgraphite/{filename}"]
}
},
@@ -176,8 +196,8 @@
"name": "pcre",
"os": "linux",
"unix": {
"filename": "pcre-8.42.tar.bz2",
"hash": "md5:085b6aa253e0f91cae70b3cdbe8c1ac2",
"filename": "pcre-8.44.tar.bz2",
"hash": "sha256:19108658b23b3ec5058edc9f66ac545ea19f9537234be1ec62b714c84399366d",
"urls": ["https://ftp.pcre.org/pub/pcre/{filename}"]
}
},
@@ -196,9 +216,9 @@
"name": "glib",
"os": "linux",
"unix": {
"filename": "glib-2.65.1.tar.xz",
"hash": "sha256:bc63bf6c32713e0ee1dddc28e03f23b4a20c78bcb9a2c5b0f4eea41e46fb9cee",
"urls": ["https://ftp.gnome.org/pub/GNOME/sources/glib/2.65/{filename}"]
"filename": "glib-2.66.6.tar.xz",
"hash": "sha256:80fff9c63d2725834328071c42003c311f77f91caf2285195c587c62f5638329",
"urls": ["https://ftp.gnome.org/pub/GNOME/sources/glib/2.66/{filename}"]
}
},
@@ -206,8 +226,8 @@
"name": "freetype",
"os": "linux",
"unix": {
"filename": "freetype-2.9.1.tar.bz2",
"hash": "md5:60ef7d8160cd4bf8cb118ee9d65367ca",
"filename": "freetype-2.10.4.tar.xz",
"hash": "sha256:86a854d8905b19698bbc8f23b860bc104246ce4854dcea8e3b0fb21284f75784",
"urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"]
}
},
@@ -216,8 +236,8 @@
"name": "fontconfig",
"os": "linux",
"unix": {
"filename": "fontconfig-2.12.0.tar.bz2",
"hash": "sha256:b433e4efff1f68fdd8aac221ed1df3ff1e580ffedbada020a703fe64017d8224",
"filename": "fontconfig-2.13.1.tar.bz2",
"hash": "sha256:f655dd2a986d7aa97e052261b36aa67b0a64989496361eca8d604e6414006741",
"urls": ["http://www.fontconfig.org/release/{filename}"]
}
},
@@ -225,19 +245,9 @@
{
"name": "harfbuzz",
"unix": {
"filename": "harfbuzz-1.7.2.tar.bz2",
"hash": "sha256:a790585e35c1a87f0dcc23580c84b7cc2324e6f67a2946178d278c2a36c790cb",
"urls": ["https://www.freedesktop.org/software/harfbuzz/release/{filename}"]
}
},
{
"name": "libxml2",
"os": "linux",
"unix": {
"filename": "libxml2-2.9.4.tar.gz",
"hash": "sha1:958ae70baf186263a4bd801a81dd5d682aedd1db",
"urls": ["ftp://xmlsoft.org/libxml2/{filename}"]
"filename": "harfbuzz-2.7.4.tar.xz",
"hash": "sha256:6ad11d653347bd25d8317589df4e431a2de372c0cf9be3543368e07ec23bb8e7",
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/2.7.4/{filename}"]
}
},
@@ -245,8 +255,8 @@
"name": "wayland",
"os": "linux",
"unix": {
"filename": "wayland-1.18.0.tar.xz",
"hash": "sha256:4675a79f091020817a98fd0484e7208c8762242266967f55a67776936c2e294d",
"filename": "wayland-1.19.0.tar.xz",
"hash": "sha256:baccd902300d354581cd5ad3cc49daa4921d55fb416a5883e218750fef166d15",
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
}
},

View File

@@ -4,8 +4,6 @@ import subprocess
files_to_exclude = '''\
kitty/wcwidth-std.h
kitty/glfw.c
kitty/keys.h
kitty/charsets.c
kitty/unicode-data.c
kitty/key_encoding.py

View File

@@ -44,7 +44,7 @@ particular desktop, but it should work for most major desktop environments.
# your PATH)
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
# Place the kitty.desktop file somewhere it can be found by the OS
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
# Update the path to the kitty icon in the kitty.desktop file
sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty.desktop

View File

@@ -30,7 +30,7 @@ Build-time dependencies:
* gcc or clang
* pkg-config
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev``, and ``libpython3-dev``,
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev``, ``libx11-xcb-dev``, ``liblcms2-dev``, and ``libpython3-dev``,
if they are not already installed by your distro.
Install and run from source

View File

@@ -1,9 +1,133 @@
Changelog
==============
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.20.0 [2021-04-19]
----------------------
- Support display of animated images ``kitty +kitten icat animation.gif``. See
:ref:`animation_protocol` for details on animation support in the kitty
graphics protocol.
- A new keyboard reporting protocol with various advanced features that can be
used by full screen terminal programs and even games, see
:doc:`keyboard-protocol` (:iss:`3248`)
- **Backward incompatibility**: Session files now use the full :doc:`launch <launch>`
command with all its capabilities. However, the syntax of the command is
slightly different from before. In particular watchers are now specified
directly on launch and environment variables are set using ``--env``.
- Allow setting colors when creating windows using the :doc:`launch <launch>` command.
- A new option :opt:`tab_powerline_style` to control the appearance of the tab
bar when using the powerline tab bar style.
- A new option :opt:`scrollback_fill_enlarged_window` to fill extra lines in
the window when the window is expanded with lines from the scrollback
(:pull:`3371`)
- diff kitten: Implement recursive diff over SSH (:iss:`3268`)
- ssh kitten: Allow using python instead of the shell on the server, useful if
the shell used is a non-POSIX compliant one, such as fish (:iss:`3277`)
- Add support for the color settings stack that XTerm copied from us without
acknowledgement and decided to use incompatible escape codes for.
- Add entries to the terminfo file for some user capabilities that are shared
with XTerm (:pull:`3193`)
- The launch command now does more sophisticated resolving of executables to
run. The system-wide PATH is used first, then system specific default paths,
and finally the PATH inside the shell.
- Double clicking on empty tab bar area now opens a new tab (:iss:`3201`)
- kitty @ ls: Show only environment variables that are different for each
window, by default.
- When passing a directory or a non-executable file as the program to run to
kitty opens it with the shell or by parsing the shebang, instead of just failing.
- Linux: Fix rendering of emoji followed by the graphics variation selector not
being colored with some fonts (:iss:`3211`)
- Unicode input: Fix using index in select by name mode not working for indices
larger than 16. Also using an index does not filter the list of matches. (:pull:`3219`)
- Wayland: Add support for the text input protocol (:iss:`3410`)
- Wayland: Fix mouse handling when using client side decorations
- Wayland: Fix un-maximizing a window not restoring its size to what it was
before being maximized
- GNOME/Wayland: Improve window decorations the titlebar now shows the window
title. Allow running under Wayland on GNOME by default. (:iss:`3284`)
- Panel kitten: Allow setting WM_CLASS (:iss:`3233`)
- macOS: Add menu items to close the OS window and the current tab (:pull:`3240`, :iss:`3246`)
- macOS: Allow opening script and command files with kitty (:iss:`3366`)
- Also detect ``gemini://`` URLs when hovering with the mouse (:iss:`3370`)
- When using a non-US keyboard layout and pressing :kbd:`ctrl+key` when
the key matches an English key, send that to the program running in the
terminal automatically (:iss:`2000`)
- When matching shortcuts, also match on shifted keys, so a shortcut defined as
:kbd:`ctrl+plus` will match a keyboard where you have to press
:kbd:`shift+equal` to get the plus key (:iss:`2000`)
- Fix extra space at bottom of OS window when using the fat layout with the tab bar at the
top (:iss:`3258`)
- Fix window icon not working on X11 with 64bits (:iss:`3260`)
- Fix OS window sizes under 100px resulting in scaled display (:iss:`3307`)
- Fix rendering of ligatures in the latest release of Cascadia code, which for
some reason puts empty glyphs after the ligature glyph rather than before it
(:iss:`3313`)
- Improve handling of infinite length ligatures in newer versions of FiraCode
and CascadiaCode. Now such ligatures are detected based on glyph naming
convention. This removes the gap in the ligatures at cell boundaries (:iss:`2695`)
- macOS: Disable the native operating system tabs as they are non-functional
and can be confusing (:iss:`3325`)
- hints kitten: When using the linenumber action with a background action,
preserve the working directory (:iss:`3352`)
- Graphics protocol: Fix suppression of responses not working for chunked
transmission (:iss:`3375`)
- Fix inactive tab closing causing active tab to change (:iss:`3398`)
- Fix a crash on systems using musl as libc (:iss:`3395`)
- Improve rendering of rounded corners by using a rectircle equation rather
than a cubic bezier (:iss:`3409`)
- Graphics protocol: Add a control to allow clients to specify that the cursor
should not move when displaying an image (:iss:`3411`)
- Fix marking of text not working on lines that contain zero cells
(:iss:`3403`)
- Fix the selection getting changed if the screen contents scroll while
the selection is in progress (:iss:`3431`)
- X11: Fix :opt:`resize_in_steps` being applied even when window is maximized
(:iss:`3473`)
0.19.3 [2020-12-19]
-------------------

View File

@@ -140,9 +140,6 @@ html_theme_options = {
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static', '../logo/kitty.png']
html_context = {
'css_files': ['_static/custom.css']
}
html_favicon = '../logo/kitty.png'
# Custom sidebar templates, must be a dictionary that maps document names
@@ -182,7 +179,7 @@ man_pages = [
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'kitty', 'kitty Documentation',
author, 'kitty', 'A cross-platform, fast, feature full, GPU based terminal emulator',
author, 'kitty', 'Cross-platform, fast, feature-rich, GPU based terminal',
'Miscellaneous'),
]
# }}}
@@ -604,6 +601,7 @@ def setup(app: Any) -> None:
write_cli_docs(kn)
write_remote_control_protocol_docs()
write_conf_docs(app, kn)
app.add_css_file('custom.css')
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
app.add_role('link', link_role)
app.add_role('iss', partial(num_role, 'issues'))

View File

@@ -49,10 +49,17 @@ terminfo files to the server::
This ssh kitten takes all the same command line arguments
as ssh, you can alias it to ssh in your shell's rc files to avoid having to
type it each time.
type it each time::
alias ssh="kitty +kitten ssh"
If for some reason that does not work (typically because the server is using a
non POSIX compliant shell), you can use the following one-liner instead (it
non POSIX compliant shell), you can try using it with python instead::
kitty +kitten ssh use-python myserver
If that also fails, perhaps because python is not installed on the remote
server, use the following one-liner instead (it
is slower as it needs to ssh into the server twice, but will work with most
servers)::
@@ -253,6 +260,33 @@ as the current window. The :doc:`launch command <launch>` is very powerful,
explore :doc:`its documentation <launch>`.
Things behave differently when running kitty from system launcher vs. from another terminal?
-----------------------------------------------------------------------------------------------
This will be because of environment variables. When you run kitty from the
system launcher, it gets a default set of system environment variables. When
you run kitty from another terminal, you are actually running it from a shell,
and the shell's rc files will have setup a whole different set of environment
variables which kitty will now inherit.
You need to make sure that the environment variables you define in your shell's
rc files are either also defined system wide or via the :opt:`env` directive in
:file:`kitty.conf`. Common environment variables that cause issues are those
related to localization, such as ``LANG, LC_*`` and loading of configuration
files such as ``XDG_*, KITTY_CONFIG_DIRECTORY``.
To see the environment variables that kitty sees, you can add the following
mapping to :file:`kitty.conf`::
map f1 show_kitty_env_vars
then pressing :kbd:`F1` will show you the environment variables kitty sees.
This problem is most common on macOS, as Apple makes it exceedingly difficult to
setup environment variables system-wide, so people end up putting them in all
sorts of places where they may or may not work.
I am using tmux and have a problem
--------------------------------------
@@ -278,3 +312,40 @@ If you use any of the advanced features that kitty has innovated, such as
styled underlines, desktop notifications, extended keyboard support, etc.
they may or may not work, depending on the whims of tmux's maintainer, your
version of tmux, etc.
I opened and closed a lot of windows/tabs and top shows kitty's memory usage is very high?
-------------------------------------------------------------------------------------------
``top`` is not a good way to measure process memory usage. That is because on
modern systems, when allocating memory to a process, the C library functions
will typically allocate memory in large blocks, and give the process chunks of
these blocks. When the process frees a chunk, the C library will not
necessarily release the underlying block back to the OS. So even though the
application has released the memory, ``top`` will still claim the process is
using it.
To check for memory leaks, instead use a tool like ``valgrind``. Run::
PYTHONMALLOC=malloc valgrind --tool=massif kitty
Now open lots of tabs/windows, generate lots of output using tools like find/yes
etc. Then close all but one window. Do some random work for a few seconds in
that window, maybe run yes or find again. Then quit kitty and run::
massif-visualizer massif.out.*
You will see the allocations graph goes up when you opened the windows, then
goes back down when you closed them, indicating there were no memory leaks.
For those interested, you can get a similar profile out of ``valgrind`` as you get
with ``top`` by adding ``--pages-as-heap=yes`` then you will see that memory
allocated in malloc is not freed in free. This can be further refined if you
use `glibc`` as your C library by setting the environment variable
``MALLOC_MMAP_THRESHOLD_=64``. This will cause free to actually free memory
allocated in sizes of more than 64 bytes. With this set, memory usage will
climb high, then fall when closing windows, but not fall all the way back. The
remaining used memory can be investigated using valgrind again, and it will
come from arenas in the GPU drivers and the per thread arenas glibc's malloc
maintains. These too allocate memory in large blocks and dont release it back
to the OS immediately.

View File

@@ -28,7 +28,7 @@ alpha-blending and text over graphics.
:alt: Demo of graphics rendering in kitty
:align: center
Some programs that use the kitty graphics protocol:
Some programs and libraries that use the kitty graphics protocol:
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with
@@ -39,6 +39,10 @@ Some programs that use the kitty graphics protocol:
information tool
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
* `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
* `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the the terminal
.. contents::
@@ -144,6 +148,8 @@ The meaning of the payload is interpreted based on the control data.
The first step is to transmit the actual image data.
.. _transferring_pixel_data:
Transferring pixel data
--------------------------
@@ -190,16 +196,17 @@ compression, then you must provide the ``S`` key with the size of the PNG data.
Compression
~~~~~~~~~~~~~
The client can send compressed image data to the terminal emulator, by specifying the
``o`` key. Currently, only zlib based deflate compression is supported, which is specified using
``o=z``. For example::
The client can send compressed image data to the terminal emulator, by
specifying the ``o`` key. Currently, only :rfc:`1950` ZLIB based deflate
compression is supported, which is specified using ``o=z``. For example::
<ESC>_Gf=24,s=10,v=20,o=z;<payload><ESC>\
This is the same as the example from the RGB data section, except that the
payload is now compressed using deflate. The terminal emulator will decompress
it before rendering. You can specify compression for any format. The terminal
emulator will decompress before interpreting the pixel data.
payload is now compressed using deflate (this occurs prior to base64-encoding).
The terminal emulator will decompress it before rendering. You can specify
compression for any format. The terminal emulator will decompress before
interpreting the pixel data.
The transmission medium
@@ -265,7 +272,7 @@ sequence of escape codes to the terminal emulator::
<ESC>_Gm=0;<encoded pixel data last chunk><ESC>\
Note that only the first escape code needs to have the full set of control
codes such as width, height, format etc. Subsequent chunks must have
codes such as width, height, format etc. Subsequent chunks **must** have
only the ``m`` key. The client **must** finish sending all chunks for a single image
before sending any other graphics related escape codes. Note that the cursor
position used to display the image **must** be the position when the final chunk is
@@ -394,6 +401,12 @@ colors.
number of rows in the image placement rectangle. If either of these cause
the cursor to leave either the screen or the scroll area, the exact
positioning of the cursor is undefined, and up to implementations.
The client can ask the terminal emulator to not move the cursor at all
by specifying ``C=1`` in the command, which sets the cursor movement policy
to no movement for placing the current image.
.. versionadded:: 0.20.0
Support for the C=1 cursor movement policy
Deleting images
@@ -417,7 +430,7 @@ Value of ``d`` Meaning
``n`` or ``N`` Delete newest image with the specified number, specified using the ``I`` key. If you specify a ``p`` key for the
placement id as well, then only the placement with the specified number and placement id will be deleted.
``c`` or ``C`` Delete all placements that intersect with the current cursor position.
``c`` or ``C`` Delete all placements that intersect with the current cursor position.
``f`` or ``F`` Delete animation frames.
``p`` or ``P`` Delete all placements that intersect a specific cell, the cell is specified using the ``x`` and ``y`` keys
``q`` or ``Q`` Delete all placements that intersect a specific cell having a specific z-index. The cell and z-index is specified using the ``x``, ``y`` and ``z`` keys.
``x`` or ``X`` Delete all placements that intersect the specified column, specified using the ``x`` key.
@@ -428,8 +441,8 @@ Value of ``d`` Meaning
Note when all placements for an image have been deleted, the image is also
deleted, if the capital letter form above is specified. Also, when the terminal
is running out of quota space for image, images without placements will be
preferentially deleted.
is running out of quota space for new images, existing images without
placements will be preferentially deleted.
Some examples::
@@ -441,7 +454,7 @@ Some examples::
Suppressing responses from the terminal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------------------------
If you are using the graphics protocol from a limited client, such as a shell
script, it might be useful to avoid having to process responses from the
@@ -453,7 +466,7 @@ terminal. For this, you can use the ``q`` key. Set it to ``1`` to suppress
Requesting image ids from the terminal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-------------------------------------------
If you are writing a program that is going to share the screen with other
programs and you still want to use image ids, it is not possible to know
@@ -484,14 +497,146 @@ use the ``i`` key with the image id for all future communication.
The ability to use image numbers (see :doc:`kittens/query_terminal` to query kitty version)
.. _animation_protocol:
Animation
-------------------------------------------
.. versionadded:: 0.20.0
Animation support (see :doc:`kittens/query_terminal` to query kitty version)
When designing support for animation, the two main considerations were:
#. There should be a way for both client and terminal driven animations.
Since there is unknown and variable latency between client and terminal,
especially over SSH, client driven animations are not sufficient.
#. Animations often consist of small changes from one frame to the next, the
protocol should thus allow transmitting these deltas for efficiency and
performance reasons.
Animation support is added to the protocol by adding two new modes for the
``a`` (action) key. A ``f`` mode for transmitting frame data and an ``a`` mode
for controlling the animation of an image. Animation proceeds in two steps,
first a normal image is created as described earlier. Then animation frames are
added to the image to make it into an animation. Since every animation is
associated with a single image, all animation escape codes must specify either
the ``i`` or ``I`` keys to identify the image being operated on.
Transferring animation frame data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Transferring animation frame data is very similar to
:ref:`transferring_pixel_data` above. The main difference is that the image
the frame belongs to must be specified and it is possible to transmit data for
only part of a frame, declaring the rest of the frame to be filled in by data
from a previous frame, or left blank. To transfer frame data the ``a=f``
key must be used in all escape codes.
First, to transfer a simple frame that has data for the full image area, the
escape codes used are exactly the same as for transferring image data, with the
addition of: ``a=f,i=<image id>`` or ``a=f,I=<image number>``.
If the frame has data for only a part of the image, you can specify the
rectangle for it using the ``x, y, s, v`` keys, for example::
x=10,y=5,s=100,v=200 # A 100x200 rectangle with its top left corner at (10, 5)
Frames are created by composing the transmitted data onto a background canvas.
This canvas can be either a single color, or the pixels from a previous frame.
The composition can be of two types, either a simple replacement (``X=1``) key
or a full alpha blend (the default).
To use a background color for the canvas, specify the ``Y`` key as a 32-bit
RGBA color. For example::
Y=4278190335 # 0xff0000ff opaque red
Y=16711816 # 0x00ff0088 translucent green (alpha=0.53)
The default background color when none is specified is ``0`` i.e. a black,
transparent pixel.
To use the data from a previous frame, specify the ``c`` key which is a 1-based
frame number. Thus ``c=1`` refers to the root frame (the base image data),
``c=2`` refers to the second frame and so on.
If the frame is composed of multiple rectangular blocks, these can be expressed
by using the ``r`` key. When specifying the ``r`` key the data for an existing
frame is edited. The same composition operation as above happens, but now the
background canvas is the existing frame itself. ``r`` is a 1-based index, so
``r=1`` is the root frame (base image data), ``r=2`` is the second frame and so
on.
Finally, while transferring frame data, the frame *gap* can also be specified
using the ``z`` key. The gap is the number of milliseconds to wait before
displaying the next frame when the animation is running. A value of ``z=0`` is
ignored, ``z=positive number`` sets the gap to the specified number of
milliseconds and ``z=negative number`` creates a *gapless* frame. Gapless
frames are not displayed to the user since they are instantly skipped over,
however they can be useful as the base data for subsequent frames. For example,
for an animation where the background remains the same and a small object or two
move.
Controlling animations
~~~~~~~~~~~~~~~~~~~~~~~~~~
Clients can control animations by using the ``a=a`` key in the escape code sent
to the terminal.
The simplest is client driven animations, where the client transmits the frame
data and the also instructs the terminal to make a particular frame the current
frame. To change the current frame, use the ``c`` key::
<ESC>_Ga=a,i=3,c=7<ESC>\
This will make the seventh frame in the image with id ``3`` the current frame.
However, client driven animations can be sub-optimal, since the latency between
the client and terminal is unknown and variable especially over the network.
Also they require the client to remain running for the lifetime of the
animation, which is not desirable for cat like utilities.
Terminal driven animations are achieved by the client specifying *gaps* (time
in milliseconds) between frames and instructing the terminal to stop or start
the animation.
The animation state is controlled by the ``s`` key. ``s=1`` stops the
animation. ``s=2`` runs the animation, but in *loading* mode, in this mode when
reaching the last frame, instead of looping, the terminal will wait for the
arrival of more frames. ``s=3`` runs the animation normally, after the last
frame, the terminal loops back to the first frame. The number of loops can be
controlled by the ``v`` key. ``v=0`` is ignored, ``v=1`` is loop infinitely,
and any other positive number is loop ``number - 1`` times. Note that stopping
the animation resets the loop counter.
Finally, the *gap* for frames can be set using the ``z`` key. This can be
specified either when the frame is created as part of the transmit escape code
or separately using the animation control escape code. The *gap* is the time in
milliseconds to wait before displaying the next frame in the animation.
For example::
<ESC>_Ga=a,i=7,r=3,z=48<ESC>\
This sets the gap for the third frame of the image with id ``7`` to ``48``
milliseconds. Note that *gapless* frames are not displayed to the user since
the next frame comes immediately, however they can be useful to store base data
for subsequent frames, such as in an animation with an object moving against a
static background.
In particular, the first frame or *root frame* is created with the base image
data and has no gap, so its gap must be set using this control code.
Image persistence and storage quotas
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----------------------------------------
In order to avoid *Denial-of-Service* attacks, terminal emulators should have a
maximum storage quota for image data. It should allow at least a few full
screen images. For example the quota in kitty is 320MB per buffer. When adding
a new image, if the total size exceeds the quota, the terminal emulator should
delete older images to make space for the new one.
delete older images to make space for the new one. In kitty, for animations,
the additional frame data is stored on disk and has a separate, larger quota of
five times the base quota.
Control data reference
@@ -504,7 +649,10 @@ take, and the default value they take when missing. All integers are 32-bit.
Key Value Default Description
======= ==================== ========= =================
``a`` Single character. ``t`` The overall action this graphics command is performing.
``(t, T, q, p, d)``
``(t, T, q, p, d)`` ``t`` - transmit data, ``T`` - transmit data and display image,
``q`` - query terminal, ``p`` - put (display) previous transmitted image,
``d`` - delete image, ``f`` - transmit data for animation frames,
``a`` - control animation
``q`` ``0, 1, 2`` ``0`` Suppress responses from the terminal to this graphics command.
@@ -520,22 +668,57 @@ Key Value Default Description
``O`` Positive integer. ``0`` The offset from which to read data from a file.
``i`` Positive integer.
``(0 - 4294967295)`` ``0`` The image id
``I`` Positive integer.
``(0 - 4294967295)`` ``0`` The image number
``p`` Positive integer.
``(0 - 4294967295)`` ``0`` The placement id
``o`` Single character. ``null`` The type of data compression.
``only z``
``m`` zero or one ``0`` Whether there is more chunked data available.
**Keys for image display**
-----------------------------------------------------------
``x`` Positive integer ``0`` The left edge (in pixels) of the image area to display
``y`` Positive integer ``0`` The top edge (in pixels) of the image area to display
``w`` Positive integer ``0`` The width (in pixels) of the image area to display. By default, the entire width is used.
``w`` Positive integer ``0`` The width (in pixels) of the image area to display. By default, the entire width is used
``h`` Positive integer ``0`` The height (in pixels) of the image area to display. By default, the entire height is used
``X`` Positive integer ``0`` The x-offset within the first cell at which to start displaying the image
``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image
``c`` Positive integer ``0`` The number of columns to display the image over
``r`` Positive integer ``0`` The number of rows to display the image over
``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image.
``1`` is to not move the cursor at all when placing the image.
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
**Keys for animation frame loading**
-----------------------------------------------------------
``x`` Positive integer ``0`` The left edge (in pixels) of where the frame data should be updated
``y`` Positive integer ``0`` The top edge (in pixels) of where the frame data should be updated
``c`` Positive integer ``0`` The 1-based frame number of the frame whose image data serves as the base data
when creating a new frame, by default the base data is black, fully transparent pixels
``r`` Positive integer ``0`` The 1-based frame number of the frame that is being edited. By default, a new frame is created
``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the next one. A value of
zero is ignored. Negative values create a *gapless* frame. If not specified,
frames have a default gap of ``40ms``. The root frame defaults to zero gap.
``X`` Positive integer ``0`` The composition mode for blending pixels when creating a new frame or
editing a frame's data. The default is full alpha blending. ``1`` means a
simple overwrite.
``Y`` Positive integer ``0`` The background color for pixels not
specified in the frame data. Must be in 32-bit RGBA format
**Keys for animation control**
-----------------------------------------------------------
``s`` Positive integer ``0`` ``1`` - stop animation, ``2`` - run animation, but wait for new frames, ``3`` - run animation
``r`` Positive integer ``0`` The 1-based frame number of the frame that is being affected
``z`` 32-bit integer ``0`` The gap (in milliseconds) of this frame from the next one. A value of
zero is ignored. Negative values create a *gapless* frame.
``c`` Positive integer ``0`` The 1-based frame number of the frame that should be made the current frame
``v`` Positive integer ``0`` The number of loops to play. ``0`` is
ignored, ``1`` is play infinite and is the default and larger number
means play that number ``-1`` loops
**Keys for deleting images**
-----------------------------------------------------------
``d`` Single character. ``a`` What to delete.

View File

@@ -61,7 +61,7 @@ See the :doc:`binary install instructions </binary>`. You can
You can also use your favorite package manager to install the |kitty| package.
|kitty| packages are available for:
`macOS with Homebrew (Cask) <https://formulae.brew.sh/cask/kitty>`_,
`macOS and Linux with Nix <https://nixos.org/nixos/packages.html?channel=nixpkgs-unstable&query=kitty>`_,
`macOS and Linux with Nix <https://search.nixos.org/packages?channel=unstable&show=kitty&sort=relevance&query=kitty>`_,
`Ubuntu <https://launchpad.net/ubuntu/+source/kitty>`_,
`Debian <https://packages.debian.org/buster/kitty>`_,
`openSUSE <https://build.opensuse.org/package/show/X11:terminals/kitty>`_,
@@ -214,7 +214,7 @@ Decrease font size :sc:`decrease_font_size` (also :kbd:`⌘+-`
Restore font size :sc:`reset_font_size` (also :kbd:`⌘+0` on macOS)
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`^+⌘+f` on macOS)
Toggle maximized :sc:`toggle_maximized`
Input unicode character :sc:`input_unicode_character`
Input unicode character :sc:`input_unicode_character` (also :kbd:`^+⌘+space` on macOS)
Click URL using the keyboard :sc:`open_url`
Reset the terminal :sc:`reset_terminal`
Pass current selection to program :sc:`pass_selection_to_program`
@@ -278,13 +278,30 @@ Some prominent kittens:
:doc:`Hints <kittens/hints>`
Select and open/paste/insert arbitrary text snippets such as URLs,
filenames, words, lines, etc from the terminal screen.
filenames, words, lines, etc. from the terminal screen.
:doc:`Remote file <kittens/remote_file>`
Edit, open, or download remote files over SSH easily, by simply clicking on
the filename.
:doc:`Hyperlinked grep <kittens/hyperlinked_grep>`
Search your files using `ripgrep <https://github.com/BurntSushi/ripgrep>`_
and open the results directly in your favorite editor in the terminal,
at the line containing the search result, simply by clicking on the result you want.
:doc:`Broadcast <kittens/broadcast>`
Type in one kitty window and have it broadcast to all (or a subset) of
other kitty windows.
:doc:`Panel <kittens/panel>`
Draw a GPU accelerated dock panel on your desktop showing the output
from an arbitrary terminal program.
:doc:`Clipboard <kittens/clipboard>`
Copy/paste to the clipboard from shell scripts, even over SSH.
@@ -328,7 +345,7 @@ For example:
launch zsh
# Create a window with some environment variables set and run
# vim in it
launch env FOO=BAR vim
launch --env FOO=BAR vim
# Set the title for the next window
title Chat with x
launch irssi --profile x
@@ -355,14 +372,9 @@ For example:
focus
launch emacs
# Add a watcher that will be called with various events that occur
# on all subsequent windows. See the documentation of the launch command
# for details on watchers.
watcher /some/python/file.py
launch mpd
launch irssi
# Remove the watcher for further windows
watcher clear
.. note::
The :doc:`launch <launch>` command when used in a session file
cannot create new OS windows, or tabs.
Mouse features
@@ -416,6 +428,10 @@ arbitrary, command running in a new window, tab or overlay, for example::
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
key. See :sc:`show_scrollback` for details.
If you want to use it with an editor such as vim to get more powerful features,
you can see tips for doing so, in
`this thread <https://github.com/kovidgoyal/kitty/issues/719>`_.
If you wish to store very large amounts of scrollback to view using the piping or
:sc:`show_scrollback` features, you can use the :opt:`scrollback_pager_history_size`
option.

View File

@@ -28,11 +28,23 @@ images and other types of documents directly in your terminal, even over SSH.
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
`koneko <https://github.com/twenty5151/koneko>`_
browse images from the pixiv artist community directly in kitty
browse images from the pixiv artist community directly in kitty.
`viu <https://github.com/atanunq/viu>`_
view images in the terminal, similar to kitty's icat, however also supports
displaying animated GIFs
view images in the terminal, similar to kitty's icat.
`nb <https://github.com/xwmx/nb>`_
command line and local web note-taking, bookmarking, archiving, and
knowledge base application that uses kitty's graphics protocol for images.
`w3m <https://github.com/tats/w3m>`_
A text mode WWW browser that supports kitty's graphics protocol to display
images.
`timg <https://github.com/hzeller/timg>`_
A terminal image and video viewer, that displays static and animated
images or plays videos. Fast multi-threaded loading, JPEG exif rotation,
grid view and connecting to the webcam make it a versatile terminal utility.
System and data visualisation tools
@@ -70,9 +82,15 @@ System and data visualisation tools
`onefetch <https://github.com/o2sh/onefetch>`_
a tool to fetch information about your git repositories
`wttr <https://github.com/chubin/wttr.in>`_
`wttr.in <https://github.com/chubin/wttr.in>`_
a tool to display weather information in your terminal with curl
`wl-clipboard-manager <https://github.com/maximbaz/wl-clipboard-manager>`_
view and manage the system clipboard under Wayland in your kitty terminal
`dmenu-term <https://github.com/maximbaz/dmenu-term>`_
run applications on your system with fuzzy find inside a kitty window
Editor integration
-----------------------

View File

@@ -1,128 +0,0 @@
Key encoding for extended keyboard protocol
===============================================
See :ref:`extended-key-protocol` for more information and `this table in JSON
format <https://github.com/kovidgoyal/kitty/blob/master/key_encoding.json>`_.
===================== ======================
Name Encoded representation
0 ``G``
1 ``H``
2 ``I``
3 ``J``
4 ``K``
5 ``L``
6 ``M``
7 ``N``
8 ``O``
9 ``P``
A ``S``
APOSTROPHE ``B``
B ``T``
BACKSLASH ``t``
BACKSPACE ``1``
C ``U``
CAPS LOCK ``:``
COMMA ``C``
D ``V``
DELETE ``3``
DOWN ``6``
E ``W``
END ``-``
ENTER ``z``
EQUAL ``R``
ESCAPE ``y``
F ``X``
F1 ``/``
F10 ``]``
F11 ``{``
F12 ``}``
F13 ``@``
F14 ``%``
F15 ``$``
F16 ``#``
F17 ``BA``
F18 ``BB``
F19 ``BC``
F2 ``*``
F20 ``BD``
F21 ``BE``
F22 ``BF``
F23 ``BG``
F24 ``BH``
F25 ``BI``
F3 ``?``
F4 ``&``
F5 ``<``
F6 ``>``
F7 ``(``
F8 ``)``
F9 ``[``
G ``Y``
GRAVE ACCENT ``v``
H ``Z``
HOME ``.``
I ``a``
INSERT ``2``
J ``b``
K ``c``
KP 0 ``BJ``
KP 1 ``BK``
KP 2 ``BL``
KP 3 ``BM``
KP 4 ``BN``
KP 5 ``BO``
KP 6 ``BP``
KP 7 ``BQ``
KP 8 ``BR``
KP 9 ``BS``
KP ADD ``BX``
KP DECIMAL ``BT``
KP DIVIDE ``BU``
KP ENTER ``BY``
KP EQUAL ``BZ``
KP MULTIPLY ``BV``
KP SUBTRACT ``BW``
L ``d``
LEFT ``5``
LEFT ALT ``Bc``
LEFT BRACKET ``s``
LEFT CONTROL ``Bb``
LEFT SHIFT ``Ba``
LEFT SUPER ``Bd``
M ``e``
MINUS ``D``
N ``f``
NUM LOCK ``=``
O ``g``
P ``h``
PAGE DOWN ``9``
PAGE UP ``8``
PAUSE ``!``
PERIOD ``E``
PRINT SCREEN ``^``
Q ``i``
R ``j``
RIGHT ``4``
RIGHT ALT ``Bg``
RIGHT BRACKET ``u``
RIGHT CONTROL ``Bf``
RIGHT SHIFT ``Be``
RIGHT SUPER ``Bh``
S ``k``
SCROLL LOCK ``+``
SEMICOLON ``Q``
SLASH ``F``
SPACE ``A``
T ``l``
TAB ``0``
U ``m``
UP ``7``
V ``n``
W ``o``
WORLD 1 ``w``
WORLD 2 ``x``
X ``p``
Y ``q``
Z ``r``
===================== ======================

654
docs/keyboard-protocol.rst Normal file
View File

@@ -0,0 +1,654 @@
A protocol for comprehensive keyboard handling in terminals
=================================================================
There are various problems with the current state of keyboard handling in
terminals. They include:
* No way to use modifiers other than ``ctrl`` and ``alt``
* No way to reliably use multiple modifier keys, other than, ``shift+alt`` and
``ctrl+alt``.
* Many of the existing escape codes used to encode these events are ambiguous
with different key presses mapping to the same escape code.
* No way to handle different types of keyboard events, such as press, release or repeat
* No reliable way to distinguish single ``Esc`` key presses from the start of a
escape sequence. Currently, client programs use fragile timing related hacks
for this, leading to bugs, for example:
`neovim #2035 <https://github.com/neovim/neovim/issues/2035>`_.
To solve these issues and others, kitty has created a new keyboard protocol,
that is backward compatible but allows applications to opt-in to support more
advanced usages. The protocol is based on initial work in `fixterms
<http://www.leonerd.org.uk/hacks/fixterms/>`_, however, it corrects various
issues in that proposal, listed at the :ref:`bottom of this document
<fixterms_bugs>`. For public discussion of this spec, see :iss:`3248`.
You can see this protocol with all enhancements in action by running::
kitty +kitten key_demo
inside the kitty terminal to report key events.
.. versionadded:: 0.20.0
Quickstart
---------------
If you are an application or library developer just interested in using this
protocol to make keyboard handling simpler and more robust in your application,
without too many changes, do the following:
#. Emit the escape code ``CSI > 1 u`` at application startup or when entering
alternate screen mode
#. All key events will now be sent in only a few forms to your application,
that are easy to parse unambiguously.
#. Emit the escape sequence ``CSI < u`` at application exit or just before
leaving alternate screen mode to restore the previously used keyboard mode.
Key events will all be delivered to your application either as plain UTF-8
text, or using the following escape codes, for those keys that do not produce
text (``CSI`` is the bytes ``0x1b 0x5b``)::
CSI number ; modifiers [u~]
CSI 1; modifiers [ABCDEFHPQRS]
0x0d - for the Enter key
0x7f or 0x08 - for Backspace
0x09 - for Tab
The ``number`` in the first form above will be either the Unicode codepoint for a
key, such as ``97`` for the :kbd:`a` key, or one of the numbers from the
:ref:`functional` table below. The ``modifiers`` optional parameter encodes any
modifiers pressed for the key event. The encoding is described in the
:ref:`modifiers` section.
The second form is used for a few functional keys, such as the :kbd:`Home, End,
Arrow keys and F1-F4`, they are enumerated in the :ref:`functional` table below.
Note that if no modifiers are present the parameters are omitted entirely
giving an escape code of the form ``CSI [ABCDEFHPQRS]``.
If you want support for more advanced features such as repeat and release
events, alternate keys for shortcut matching et cetera, these can be turned on
using :ref:`progressive_enhancement` as documented in the rest of this
specification.
An overview
------------------
Key events are divided into two types, those that produce text and those that
do not. When a key event produces text, the text is sent directly as UTF-8
encoded bytes. This is safe as UTF-8 contains no C0 control codes.
When the key event does not have text, the key event is encoded as an escape code. In
legacy compatibility mode (the default) this uses legacy escape codes, so old terminal
applications continue to work. Key events that could not be represented in
legacy mode are encoded using a ``CSI u`` escape code, that most terminal
programs should just ignore. For more advanced features, such as release/repeat
reporting etc., applications can tell the terminal they want this information by
sending an escape code to :ref:`progressively enhance <progressive_enhancement>` the data reported for
key events.
The central escape code used to encode key events is::
CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ; text-as-codepoints u
Spaces in the above definition are present for clarity and should be ignored.
``CSI`` is the bytes ``0x1b 0x5b``. All parameters are decimal numbers. Fields
are separated by the semi-colon and sub-fields by the colon. Only the
``unicode-key-code`` field is mandatory, everything else is optional. The
escape code is terminated by the ``u`` character (the byte ``0x75``).
.. _key_codes:
Key codes
~~~~~~~~~~~~~~
The ``unicode-key-code`` above is the Unicode codepoint representing the key, as a
decimal number. For example, the :kbd:`A` key is represented as ``97`` which is
the unicode code for lowercase ``a``. Note that the codepoint used is *always*
the lower-case (or more technically, un-shifted) version of the key. If the
user presses, for example, :kbd:`ctrl+shift+a` the escape code would be ``CSI
97;modifiers u``. It *must not* by ``CSI 65; modifiers u``.
If *alternate key reporting* is requested by the program running in the
terminal, the terminal can send two additional Unicode codepoints, the
*shifted key* and *base layout key*, separated by colons.
The shifted key is simply the upper-case version of ``unicode-codepoint``, or
more technically, the shifted version. So `a` becomes `A` and so on, based on
the current keyboard layout. This is needed to be able to match against a
shortcut such as :kbd:`ctrl+plus` which depending on the type of keyboard could
be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. Note that the shifted
key must be present only if shift is also present in the modifiers.
The *base layout key* is the key corresponding to the physical key in the
standard PC-101 key layout. So for example, if the user is using a Cyrillic
keyboard with a Cyrillic keyboard layout pressing the :kbd:`ctrl+С` key will
be :kbd:`ctrl+c` in the standard layout. So the terminal should send the *base
layout key* as ``99`` corresponding to the ``c`` key.
If only one alternate key is present, it is the *shifted key* if the terminal
wants to send only a base layout key but no shifted key, it must use an empty
sub-field for the shifted key, like this::
CSI unicode-key-code::base-layout-key
.. _modifiers:
Modifiers
~~~~~~~~~~~~~~
This protocol supports six modifier keys, :kbd:`shift, alt, ctrl, super, hyper
and meta` as well as :kbd:`num_lock and caps_lock`. Here :kbd:`super` is either
the *Windows/Linux* key or the *Cmd* key on mac keyboards. :kbd:`hyper` and
:kbd:`meta` are typically present only on X11 based systems with special XKB
rules. Modifiers are encoded as a bit field with::
shift 0b1 (1)
alt 0b10 (2)
ctrl 0b100 (4)
super 0b1000 (8)
hyper 0b10000 (16)
meta 0b100000 (32)
caps_lock 0b1000000 (64)
num_lock 0b10000000 (128)
In the escape code, the modifier value is encoded as a decimal number which is
``1 + actual modifiers``. So to represent :kbd:`shift` only, the value would be ``1 +
1 = 2``, to represent :kbd:`ctrl+shift` the value would be ``1 + 0b101 = 6``
and so on. If the modifier field is not present in the escape code, its default
value is ``1`` which means no modifiers.
.. _event_types:
Event types
~~~~~~~~~~~~~~~~
There are three key event types: ``press, repeat and release``. They are
reported (if requested ``0b10``) as a sub-field of the modifiers field
(separated by a colon). If no modifiers are present, the modifiers field must
have the value ``1`` and the event type sub-field the type of event. The
``press`` event type has value ``1`` and is the default if no event type sub
field is present. The ``repeat`` type is ``2`` and the ``release`` type is
``3``. So for example::
CSI key-code # this is a press event
CSI key-code;modifier # this is a press event
CSI key-code;modifier:1 # this is a press event
CSI key-code;modifier:2 # this is a repeat event
CSI key-code;modifier:3 # this is a release event
.. note:: Key events that result in text are reported as plain UTF-8 text, so
events are not supported for them, unless the application requests *key
report mode*, see below.
.. _text_as_codepoints:
Text as code points
~~~~~~~~~~~~~~~~~~~~~
The terminal can optionally send the text associated with key events as a
sequence of Unicode code points. This behavior is opt-in by the :ref:`progressive
enhancement <progressive_enhancement>` mechanism described below. Some examples::
shift+a -> CSI 97 ; 2 ; 65 u # The text 'A' is reported as 65
option+a -> CSI 97 ; ; 229 u # The text 'å' is reported as 229
If multiple code points are present, they must be separated by colons.
If no known key is associated with the text the key number ``0`` must be used.
Non-Unicode keys
~~~~~~~~~~~~~~~~~~~~~~~
There are many keys that don't correspond to letters from human languages, and
thus aren't represented in Unicode. Think of functional keys, such as
:kbd:`Escape, Play, Pause, F1, Home, etc`. These are encoded using Unicode code
points from the Private Use Area (``57344 - 63743``). The mapping of key
names to code points for these keys is in the
:ref:`Functional key definition table below <functional>`.
.. _progressive_enhancement:
Progressive enhancement
--------------------------
While, in theory, every key event could be completely represented by this
protocol and all would be hunk-dory, in reality there is a vast universe of
existing terminal programs that expect legacy control codes for key events and
that are not likely to ever be updated. To support these, in default mode,
the terminal will emit legacy escape codes for compatibility. If a terminal
program wants more robust key handling, it can request it from the terminal,
via the mechanism described here. Each enhancement is described in detail
below. The escape code for requesting enhancements is::
CSI = flags ; mode u
Here ``flags`` is a decimal encoded integer to specify a set of bit-flags. The
meanings of the flags are given below. The second, ``mode`` parameter is
optional (defaulting to ``1``) and specifies how the flags are applied.
The value ``1`` means all set bits are set and all unset bits are reset.
The value ``2`` means all set bits are set, unset bits are left unchanged.
The value ``3`` means all set bits are reset, unset bits are left unchanged.
.. csv-table:: The progressive enhancement flags
:header: "Bit", "Meaning"
"0b1 (1)", ":ref:`disambiguate`"
"0b10 (2)", ":ref:`report_events`"
"0b100 (4)", ":ref:`report_alternates`"
"0b1000 (8)", ":ref:`report_all_keys`"
"0b10000 (16)", ":ref:`report_text`"
The program running in the terminal can query the terminal for the
current values of the flags by sending::
CSI ? u
The terminal will reply with::
CSI ? flags u
The program can also push/pop the current flags onto a stack in the
terminal with::
CSI > flags u # for push, if flags ommitted default to zero
CSI < number u # to pop number entries, defaulting to 1 if unspecified
Terminals should limit the size of the stack as appropriate, to prevent
Denial-of-Service attacks. Terminals must maintain separate stacks for the main
and alternate screens. If a pop request is received that empties the stack,
all flags are reset. If a push request is received and the stack is full, the
oldest entry from the stack must be evicted.
.. note:: In the interests of interoperation, the XTerm specific sequences
`CSI > 4; 1 m` and `CSI > 4; 0 m` are treated as `CSI > 1 u` and `CSI < 1 u`.
These codes cause XTerm to use the CSI u encoding for more keys and are therefore
treated as similar to the disambiguate progressive enhancement.
.. _disambiguate:
Disambiguate escape codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This type of progressive enhancement (``0b1``) fixes the problem of some legacy key press
encodings overlapping with other control codes. For instance, pressing the
:kbd:`Esc` key generates the byte ``0x1b`` which also is used to indicate the
start of an escape code. Similarly pressing the key :kbd:`alt+[` will generate
the bytes used for CSI control codes.
Turning on this flag will cause the terminal to report the :kbd:`Esc, alt+key,
ctrl+key, ctrl+alt+key, shift+alt+key` keys using ``CSI u`` sequences instead
of legacy ones. Here key is any ASCII key as described in :ref:`legacy_text`.
Additionally, all keypad keys will be reported as separate keys with ``CSI u``
encoding, using dedicated numbers from the :ref:`table below <functional>`.
With this flag turned on, all key events that do not generate text are
represented in one of the following two forms::
CSI number; modifier u
CSI 1; modifier [~ABCDEFHPQRS]
This makes it very easy to parse key events in an application. In particular,
:kbd:`ctrl+c` will no longer generate the ``SIGINT`` signal, but instead be
delivers as a ``CSI u`` escape code. This has the nice side effect of making it
much easier to integrate into the application event loop. The only exceptions
are the :kbd:`Enter, Tab and Backspace` keys which still generate the same
bytes as in legacy mode this is to allow the user to type and execute commands
in the shell such as ``reset`` after a program that sets this mode crashes
without clearing it.
.. _report_events:
Report event types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b10``) causes the terminal to report key repeat
and key release events. Normally only key press events are reported and key
repeat events are treated as key press events. See :ref:`event_types` for
details on how these are reported.
.. _report_alternates:
Report alternate keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b100``) causes the terminal to report
alternate key values in addition to the main value, to aid in shortcut
matching. See :ref:`key_codes` for details on how these are reported.
.. _report_all_keys:
Report all keys as escape codes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Key events that generate text, such as plain key presses without modifiers,
result in just the text being sent, in the legacy protocol. There is no way to
be notified of key repeat/release events. These types of events are needed for
some applications, such as games (think of movement using the ``WASD`` keys).
This progressive enhancement (``0b1000``) turns on key reporting even for key
events that generate text. When it is enabled, text will not be sent, instead
only key events are sent. If the text is needed as well, combine with the
Report associated text enhancement below.
Additionally, with this mode, events for pressing modifier keys are reported.
Note that *all* keys are reported as escape codes, including :kbd:`Enter, Tab,
Backspace` etc.
.. _report_text:
Report associated text
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This progressive enhancement (``0b10000``) causes key events that generate text
to be reported as ``CSI u`` escape codes with the text embedded in the escape
code. See :ref:`text_as_codepoints` above for details on the mechanism.
.. _detection:
Detection of support for this protocol
------------------------------------------
An application can query the terminal for support of this protocol by sending
the escape code querying for the :ref:`current progressive enhancement
<progressive_enhancement>` status
followed by request for the `primary device attributes
<https://vt100.net/docs/vt510-rm/DA1.html>`. If an answer for the device
attributes is received without getting back an answer for the progressive
enhancement the terminal does not support this protocol.
Legacy key event encoding
--------------------------------
In the default mode, the terminal uses a legacy encoding for key events. In
this encoding, only key press and repeat events are sent and there is no
way to distinguish between them. Text is sent directly as UTF-8 bytes.
Any key events not described in this section are sent using the standard
``CSI u`` encoding. This includes keys that are not encodable in the legacy
encoding, thereby increasing the space of usable key combinations even without
progressive enhancement.
Legacy functional keys
~~~~~~~~~~~~~~~~~~~~~~~~
These keys are encoded using three schemes::
CSI number ; modifier ~
CSI 1 ; modifier {ABCDEFHPQRS}
SS3 {ABCDEFHPQRS}
In the above, if there are no modifiers, the modifier parameter is omitted.
The modifier value is encoded as described in the :ref:`modifiers` section,
above. When the second form is used, the number is always ``1`` and must be
omitted if the modifiers field is also absent. The third form becomes the
second form when modifiers are present (``SS3 is the bytes 0x1b 0x4f``).
These sequences must match entries in the terminfo database for maximum
compatibility. The table below lists the key, its terminfo entry name and
the escape code used for it by kitty. A different terminal would use whatever
escape code is present in its terminfo database for the key.
Some keys have an alternate representation when the terminal is in *cursor key
mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in
*cursor key mode* and only when no modifiers are present.
.. csv-table:: Legacy functional encoding
:header: "Name", "Terminfo name", "Escape code"
"INSERT", "kich1", "CSI 2 ~"
"DELETE", "kdch1", "CSI 3 ~"
"PAGE_UP", "kpp", "CSI 5 ~"
"PAGE_DOWN", "knp", "CSI 6 ~"
"UP", "cuu1,kcuu1", "CSI A, SS3 A"
"DOWN", "cud1,kcud1", "CSI B, SS3 B"
"RIGHT", "cuf1,kcuf1", "CSI C, SS3 C"
"LEFT", "cub1,kcub1", "CSI D, SS3 D"
"HOME", "home,khome", "CSI H, SS3 H"
"END", "-,kend", "CSI F, SS3 F"
"F1", "kf1", "SS3 P"
"F2", "kf2", "SS3 Q"
"F3", "kf3", "SS3 R"
"F4", "kf4", "SS3 S"
"F5", "kf5", "CSI 15 ~"
"F6", "kf6", "CSI 17 ~"
"F7", "kf7", "CSI 18 ~"
"F8", "kf8", "CSI 19 ~"
"F9", "kf9", "CSI 20 ~"
"F10", "kf10", "CSI 21 ~"
"F11", "kf11", "CSI 23 ~"
"F12", "kf12", "CSI 24 ~"
There are a few more functional keys that have special cased legacy encodings.
These are present because they are commonly used and for the sake of legacy
terminal applications that get confused when seeing CSI u escape codes:
.. csv-table:: C0 controls
:header: "Key", "No mods", "Ctrl", "Alt", "Shift", "Ctrl + Shift", "Alt + Shift", "Ctrl + Alt"
"Enter", "0xd", "0xd", "0x1b 0xd", "0xd", "0xd", "0x1b 0xd", "0x1b 0xd"
"Escape", "0x1b", "0x1b", "0x1b 0x1b", "0x1b", "0x1b", "0x1b 0x1b", "0x1b 0x1b"
"Backspace", "0x7f", "0x8", "0x1b 0x7f", "0x7f", "0x8", "0x1b 0x7f", "0x1b 0x8"
"Tab", "0x9", "0x9", "0x1b 0x9", "CSI Z", "CSI Z", "0x1b CSI Z", "0x1b 0x9"
"Space", "0x20", "0x0", "0x1b 0x20", "0x20", "0x0", "0x1b 0x20", "0x1b 0x0"
Note that :kbd:`Backspace` and :kbd:`ctrl+Backspace` are swapped in some
terminals, this can be detected using the ``kbs`` terminfo property that
must correspond to the :kbd:`Backspace` key.
All keypad keys are reported as their equivalent non-keypad keys. To
distinguish these, use the :ref:`disambiguate <disambiguate>` flag.
.. _legacy_text:
Legacy text keys
~~~~~~~~~~~~~~~~~~~
For legacy compatibility, the keys
:kbd:`a-z 0-9 \` - = [ ] \ ; ' , . /` with the modifiers
:kbd:`shift, alt, ctrl, shift+alt, ctrl+alt` are output using the
following algorithm:
#. If the :kbd:`alt` key is pressed output the byte for ``ESC (0x1b)``
#. If the :kbd:`ctrl` modifier is pressed map the key using the table
in :ref:`ctrl_mapping`.
#. Otherwise, if the :kbd:`shift` modifier is pressed, output the shifted key,
for example, ``A`` for ``a`` and ``$`` for ``4``.
#. Otherwise, output the key unmodified
Additionally, :kbd:`ctrl+space` is output as the NULL byte ``(0x0)``.
Any other combination of modifiers with these keys is output as the appropriate
``CSI u`` escape code.
.. csv-table:: Example encodings
:header: "Key", "Plain", "shift", "alt", "ctrl", "shift+alt", "alt+ctrl", "ctrl+shift"
"i", "i (105)", "I (73)", "ESC i", ") (41)", "ESC I", "ESC )", "CSI 105; 6 u"
"3", "3 (51)", "# (35)", "ESC 3", "3 (51)", "ESC #", "ESC 3", "CSI 51; 6 u"
";", "; (59)", ": (58)", "ESC ;", "; (59)", "ESC :", "ESC ;", "CSI 59; 6 u"
.. note::
Many of the legacy escape codes are ambiguous with multiple different key
presses yielding the same escape code(s), for example, :kbd:`ctrl+i` is the
same as :kbd:`tab`, :kbd:`ctrl+m` is the same as :kbd:`Enter`, :kbd:`ctrl+r`
is the same :kbd:`ctrl+shift+r`, etc. To resolve these use the
:ref:`disambiguate progressive enhancement <disambiguate>`.
.. _functional:
Functional key definitions
----------------------------
All numbers are in the Unicode Private Use Area (``57344 - 63743``) except
for a handful of keys that use numbers under 32 and 127 (C0 control codes) for legacy
compatibility reasons.
.. {{{
.. start functional key table (auto generated by gen-key-constants.py do not edit)
.. csv-table:: Functional key codes
:header: "Name", "CSI", "Name", "CSI"
"ESCAPE", "``27 u``", "ENTER", "``13 u``"
"TAB", "``9 u``", "BACKSPACE", "``127 u``"
"INSERT", "``2 ~``", "DELETE", "``3 ~``"
"LEFT", "``1 D``", "RIGHT", "``1 C``"
"UP", "``1 A``", "DOWN", "``1 B``"
"PAGE_UP", "``5 ~``", "PAGE_DOWN", "``6 ~``"
"HOME", "``1 H or 7 ~``", "END", "``1 F or 8 ~``"
"CAPS_LOCK", "``57358 u``", "SCROLL_LOCK", "``57359 u``"
"NUM_LOCK", "``57360 u``", "PRINT_SCREEN", "``57361 u``"
"PAUSE", "``57362 u``", "MENU", "``57363 u``"
"F1", "``1 P or 11 ~``", "F2", "``1 Q or 12 ~``"
"F3", "``1 R or 13 ~``", "F4", "``1 S or 14 ~``"
"F5", "``15 ~``", "F6", "``17 ~``"
"F7", "``18 ~``", "F8", "``19 ~``"
"F9", "``20 ~``", "F10", "``21 ~``"
"F11", "``23 ~``", "F12", "``24 ~``"
"F13", "``57376 u``", "F14", "``57377 u``"
"F15", "``57378 u``", "F16", "``57379 u``"
"F17", "``57380 u``", "F18", "``57381 u``"
"F19", "``57382 u``", "F20", "``57383 u``"
"F21", "``57384 u``", "F22", "``57385 u``"
"F23", "``57386 u``", "F24", "``57387 u``"
"F25", "``57388 u``", "F26", "``57389 u``"
"F27", "``57390 u``", "F28", "``57391 u``"
"F29", "``57392 u``", "F30", "``57393 u``"
"F31", "``57394 u``", "F32", "``57395 u``"
"F33", "``57396 u``", "F34", "``57397 u``"
"F35", "``57398 u``", "KP_0", "``57399 u``"
"KP_1", "``57400 u``", "KP_2", "``57401 u``"
"KP_3", "``57402 u``", "KP_4", "``57403 u``"
"KP_5", "``57404 u``", "KP_6", "``57405 u``"
"KP_7", "``57406 u``", "KP_8", "``57407 u``"
"KP_9", "``57408 u``", "KP_DECIMAL", "``57409 u``"
"KP_DIVIDE", "``57410 u``", "KP_MULTIPLY", "``57411 u``"
"KP_SUBTRACT", "``57412 u``", "KP_ADD", "``57413 u``"
"KP_ENTER", "``57414 u``", "KP_EQUAL", "``57415 u``"
"KP_SEPARATOR", "``57416 u``", "KP_LEFT", "``57417 u``"
"KP_RIGHT", "``57418 u``", "KP_UP", "``57419 u``"
"KP_DOWN", "``57420 u``", "KP_PAGE_UP", "``57421 u``"
"KP_PAGE_DOWN", "``57422 u``", "KP_HOME", "``57423 u``"
"KP_END", "``57424 u``", "KP_INSERT", "``57425 u``"
"KP_DELETE", "``57426 u``", "KP_BEGIN", "``1 E or 57427 ~``"
"MEDIA_PLAY", "``57428 u``", "MEDIA_PAUSE", "``57429 u``"
"MEDIA_PLAY_PAUSE", "``57430 u``", "MEDIA_REVERSE", "``57431 u``"
"MEDIA_STOP", "``57432 u``", "MEDIA_FAST_FORWARD", "``57433 u``"
"MEDIA_REWIND", "``57434 u``", "MEDIA_TRACK_NEXT", "``57435 u``"
"MEDIA_TRACK_PREVIOUS", "``57436 u``", "MEDIA_RECORD", "``57437 u``"
"LOWER_VOLUME", "``57438 u``", "RAISE_VOLUME", "``57439 u``"
"MUTE_VOLUME", "``57440 u``", "LEFT_SHIFT", "``57441 u``"
"LEFT_CONTROL", "``57442 u``", "LEFT_ALT", "``57443 u``"
"LEFT_SUPER", "``57444 u``", "LEFT_HYPER", "``57445 u``"
"LEFT_META", "``57446 u``", "RIGHT_SHIFT", "``57447 u``"
"RIGHT_CONTROL", "``57448 u``", "RIGHT_ALT", "``57449 u``"
"RIGHT_SUPER", "``57450 u``", "RIGHT_HYPER", "``57451 u``"
"RIGHT_META", "``57452 u``", "ISO_LEVEL3_SHIFT", "``57453 u``"
"ISO_LEVEL5_SHIFT", "``57454 u``"
.. end functional key table
.. }}}
Note that the escape codes above of the form ``CSI 1 letter`` will omit the
``1`` if there are no modifiers, since ``1`` is the default value.
.. _ctrl_mapping:
Legacy :kbd:`ctrl` mapping of ASCII keys
------------------------------------------
When the :kbd:`ctrl` key and another key are pressed on the keyboard, terminals
map the result *for some keys* to a *C0 control code* i.e. an value from ``0 -
31``. This mapping was historically dependent on the layout of hardware
terminal keyboards and is not specified anywhere, completely. The best known
reference is `Table 3-5 in the VT-100 docs <https://vt100.net/docs/vt100-ug/chapter3.html>`_.
The table below provides a mapping that is a commonly used superset of the table above.
Any ASCII keys not in the table must be left untouched by :kbd:`ctrl`.
.. {{{
.. start ctrl mapping (auto generated by gen-key-constants.py do not edit)
.. csv-table:: Emitted bytes when :kbd:`ctrl` is held down and a key is pressed
:header: "Key", "Byte", "Key", "Byte", "Key", "Byte"
"SPC ", "0", "/", "31", "0", "48"
"1", "49", "2", "0", "3", "27"
"4", "28", "5", "29", "6", "30"
"7", "31", "8", "127", "9", "57"
"?", "127", "@", "0", "[", "27"
"\\", "28", "]", "29", "^", "30"
"_", "31", "a", "1", "b", "2"
"c", "3", "d", "4", "e", "5"
"f", "6", "g", "7", "h", "8"
"i", "9", "j", "10", "k", "11"
"l", "12", "m", "13", "n", "14"
"o", "15", "p", "16", "q", "17"
"r", "18", "s", "19", "t", "20"
"u", "21", "v", "22", "w", "23"
"x", "24", "y", "25", "z", "26"
"~", "30"
.. end ctrl mapping
.. }}}
.. _fixterms_bugs:
Bugs in fixterms
-------------------
The following is a list of errata in the `original fixterms proposal
<http://www.leonerd.org.uk/hacks/fixterms/>`_, corrected in this
specification.
* No way to disambiguate :kbd:`Esc` key presses, other than using 8-bit controls
which are undesirable for other reasons
* Incorrectly claims special keys are sometimes encoded using ``CSI letter`` encodings when it
is actually ``SS3 letter`` in all terminals newer than a VT-52, which is
pretty much everything.
* :kbd:`ctrl+shift+tab` should be ``CSI 9 ; 6 u`` not ``CSI 1 ; 5 Z``
(shift+tab is not a separate key from tab)
* No support for the :kbd:`super` modifier.
* Makes no mention of cursor key mode and how it changes encodings
* Incorrectly encoding shifted keys when shift modifier is used, for instance,
for :kbd:`ctrl+shift+i` is encoded as :kbd:`ctrl+I`.
* No way to have non-conflicting escape codes for :kbd:`alt+letter,
ctrl+letter, ctrl+alt+letter` key presses
* No way to specify both shifted and unshifted keys for robust shortcut
matching (think matching :kbd:`ctrl+shift+equal` and :kbd:`ctrl+plus`)
* No way to specify alternate layout key. This is useful for keyboard layouts
such as Cyrillic where you want the shortcut :kbd:`ctrl+c` to work when
pressing the :kbd:`ctrl+С` on the keyboard.
* No way to report repeat and release key events, only key press events
* No way to report key events for presses that generate text, useful for
gaming. Think of using the :kbd:`WASD` keys to control movement.
* Only a small subset of all possible functional keys are assigned numbers.
* Claims the ``CSI u`` escape code has no fixed meaning, but has been used for
decades as ``SCORC`` for instance by xterm and ansi.sys and `DECSMBV
<https://vt100.net/docs/vt510-rm/DECSMBV.html>`_ by the VT-510 hardware
terminal. This doesn't really matter since these uses are for communication
to the terminal not from the terminal.
* Handwaves that :kbd:`ctrl` *tends to* mask with ``0x1f``. In actual fact it
does this only for some keys. The action of :kbd:`ctrl` is not specified and
varies between terminals, historically because of different keyboard layouts.

View File

@@ -1,11 +1,14 @@
Hyperlinked grep
=================
This kitten allows you to search your files using `ripgrep
<https://www.google.com/search?q=ripgrep>`_ and open the results
directly in your favorite editor in the terminal, at the line containing
the search result, simply by clicking on the result you want.
.. versionadded:: 0.19.0
To set it up, first create :file:`~/.config/kitty/open-actions.conf` with the
following contents:

View File

@@ -21,6 +21,8 @@ to *Edit* it in which case kitty will download it and open it locally in your
to the remote computer. Note that this happens without needing to install *any*
special software on the server, beyond ``ls`` that supports hyperlinks.
.. versionadded:: 0.19.0
.. note::
Nested SSH sessions are not supported. The kitten will always try to copy
remote files from the first SSH host. This is because there is no way for

View File

@@ -18,10 +18,10 @@ typing a leading period and then the two character index and pressing Enter.
The up and down arrow keys can be used to choose the previous and next unicode
symbol respectively.
In :guilabel:`Name` mode you instead type words from the character name and use the arrow
keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you don't like to use arrow
keys.
In :guilabel:`Name` mode you instead type words from the character name and use
the arrow keys/tab to select the character from the displayed matches. You can
also type a space followed by a period and the index for the match if you don't
like to use arrow keys.
You can switch between modes using either the function keys or by pressing
:kbd:`Ctrl+[` and :kbd:`Ctrl+]`.

View File

@@ -93,6 +93,16 @@ in the window and ``window.id`` is the internal kitty ``id`` of the
window.
Finding executables
-----------------------
When you specify a command to run as just a name rather than an absolute path,
it is searched for in the system-wide ``PATH`` environment variable. Note that
this **may not** be the value of ``PATH`` inside a shell, as shell startup scripts
often change the value of this variable. If it is not found there, then a
system specific list of default paths is searched. If it is still not found,
then your shell is run and the value of ``PATH`` inside the shell is used.
Syntax reference
------------------

View File

@@ -171,6 +171,8 @@ are placed into vertical or horizontal splits, by default::
│ │ │
└──────────────┴───────────────┘
.. versionadded:: 0.17.0
The Splits layout
The Horizontal Layout

View File

@@ -69,91 +69,9 @@ of this protocol to enable drawing of arbitrary raster images in the terminal.
Keyboard handling
-------------------
There are various problems with the current state of keyboard handling. They
include:
* No way to use modifiers other than ``Ctrl`` and ``Alt``
* No way to reliably use multiple modifier keys, other than, ``Shift+Alt``.
* No way to handle different types of keyboard events, such as press, release or repeat
* No reliable way to distinguish single ``Esc`` keypresses from the start of a
escape sequence. Currently, client programs use fragile timing related hacks
for this, leading to bugs, for example:
`neovim #2035 <https://github.com/neovim/neovim/issues/2035>`_.
There are already two distinct keyboard handling modes, *normal mode* and
*application mode*. These modes generate different escape sequences for the
various special keys (arrow keys, function keys, home/end etc.) Most terminals
start out in normal mode, however, most shell programs like ``bash`` switch them to
application mode. We propose adding a third mode, named *full mode* that addresses
the shortcomings listed above.
Switching to the new *full mode* is accomplished using the standard private
mode DECSET escape sequence::
<ESC>[?2017h
and to leave *full mode*, use DECRST::
<ESC>[?2017l
The number ``2017`` above is not used for any existing modes, as far as I know.
Client programs can query if the terminal emulator is in *full mode* by using
the standard `DECRQM <https://vt100.net/docs/vt510-rm/DECRQM.html>`_ escape sequence.
The new mode works as follows:
* All printable key presses without modifier keys are sent just as in the
*normal mode*. This means all printable ASCII characters and in addition,
``Enter``, ``Space`` and ``Backspace``. Also any unicode characters generated by
platform specific extended input modes, such as using the ``AltGr`` key. This
is done so that client programs that are not aware of this mode can still
handle basic text entry, so if a *full mode* using program crashes and does
not reset, the user can still issue a ``reset`` command in the shell to restore
normal key handling. Note that this includes pressing the ``Shift`` modifier
and printable keys. Note that this means there are no repeat and release
events for these keys and also for the left and right shift keys.
* For non printable keys and key combinations including one or more modifiers,
an escape sequence encoding the key event is sent. For details on the
escape sequence, see below.
The escape sequence encodes the following properties:
* Type of event: ``press,repeat,release``
* Modifiers pressed at the time of the event
* The actual key being pressed
Schematically::
<ESC>_K<type><modifiers><key><ESC>\
Where ``<type>`` is one of ``p`` -- press, ``r`` -- release and ``t`` -- repeat.
Modifiers is a bitmask represented as a single base64 digit. Shift -- ``0x1``,
Alt -- ``0x2``, Control -- ``0x4`` and Super -- ``0x8``. ``<key>`` is a number
(encoded in base85) corresponding to the key pressed. The key name to number
mapping is defined in :doc:`this table <key-encoding>`.
Client programs must ignore events for keys they do not know. The mapping in
the above table is stable and will never change, however, new codes might be
added to it in the future, for new keys.
For example::
<ESC>_KpGp<ESC>\ is <Ctrl>+<Alt>+x (press)
<ESC>_KrP8<ESC>\ is <Ctrl>+<Alt>+<Shift>+<Super>+PageUp (release)
This encoding means each key event is represented by 8 or 9 printable ascii
only bytes, for maximum robustness.
To see the full mode in action, run::
kitty +kitten key_demo
Support for this mode is indicated by the ``fullkbd`` boolean capability
in the terminfo database, in case querying for it via DECQRM is inconvenient.
kitty has a :doc:`keyboard protocol <keyboard-protocol>` for reporting key
presses to terminal applications that solves all key handling issues in
terminal applications.
.. _ext_styles:
@@ -180,23 +98,32 @@ rectangular region of the screen from (3, 4) to (10, 11), you use::
<ESC>[2*x<ESC>[4;3;11;10;44$r<ESC>[*x
Saving and restoring the default foreground/background/selection/cursor colors
Saving and restoring colors
---------------------------------------------------------------------------------
It is often useful for a full screen application with its own color themes
to set the default foreground, background, selection and cursor colors. This
allows for various performance optimizations when drawing the screen. The
problem is that if the user previously used the escape codes to change these
colors herself, then running the full screen application will lose her
changes even after it exits. To avoid this, kitty introduces a new pair of
*OSC* escape codes to push and pop the current color values from a stack::
It is often useful for a full screen application with its own color themes to
set the default foreground, background, selection and cursor colors and the
ANSI color table. This allows for various performance optimizations when
drawing the screen. The problem is that if the user previously used the escape
codes to change these colors herself, then running the full screen application
will lose her changes even after it exits. To avoid this, kitty introduces a
new pair of *OSC* escape codes to push and pop the current color values from a
stack::
<ESC>]30001<ESC>\ # push onto stack
<ESC>]30101<ESC>\ # pop from stack
These escape codes save/restore the so called *dynamic colors*, default
These escape codes save/restore the colors, default
background, default foreground, selection background, selection foreground and
cursor color.
cursor color and the 256 colors of the ANSI color table.
.. note:: In July 2020, after several years, XTerm copied this protocol
extension, without acknowledgement, and using incompatible escape codes
(XTPUSHCOLORS, XTPOPCOLORS, XTREPORTCOLORS). And they decided to save not
just the dynamic colors but the entire ANSI color table. In the interests of
promoting interoperability, kitty added support for XTerm's escape codes as
well, and changed this extension to also save/restore the entire ANSI color
table.
Pasting to clipboard

View File

@@ -12,7 +12,7 @@ Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form::
{
"cmd": "command name",
"version": <kitty version>,
"no_response": Optional Boolean,
"no_response": <Optional Boolean>,
"payload": <Optional JSON object>,
}
@@ -26,4 +26,16 @@ Set ``no_response`` to ``true`` if you don't want a response from kitty.
The optional payload is a JSON object that is specific to the actual command being sent.
The fields in the object for every command are documented below.
As a quick example showing how easy to use this protocol is, we will implement
the ``@ ls`` command from the shell using only shell tools. First, run kitty
as::
kitty -o allow_remote_control=socket-only --listen-on unix:/tmp/test
Now, in a different terminal, you can get the pretty printed ``@ ls`` output
with the following command line::
echo -en '\eP@kitty-cmd{"cmd":"ls","version":[0,14,2]}\e\' | socat - unix:/tmp/test | awk '{ print substr($0, 13, length($0) - 14) }' | jq -c '.data | fromjson' | jq .
.. include:: generated/rc.rst

View File

@@ -250,8 +250,8 @@ def write_header(text: str, path: str) -> None:
def graphics_parser() -> None:
flag = frozenset
keymap: KeymapType = {
'a': ('action', flag('tTqpd')),
'd': ('delete_action', flag('aAiIcCnNpPqQxXyYzZ')),
'a': ('action', flag('tTqpdfa')),
'd': ('delete_action', flag('aAiIcCfFnNpPqQxXyYzZ')),
't': ('transmission_type', flag('dfts')),
'o': ('compressed', flag('z')),
'f': ('format', 'uint'),
@@ -273,6 +273,7 @@ def graphics_parser() -> None:
'X': ('cell_x_offset', 'uint'),
'Y': ('cell_y_offset', 'uint'),
'z': ('z_index', 'int'),
'C': ('cursor_movement', 'uint'),
}
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
write_header(text, 'kitty/parse-graphics-command.h')

412
gen-key-constants.py Executable file
View File

@@ -0,0 +1,412 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import string
from typing import Dict, List, Any
from pprint import pformat
functional_key_defs = '''# {{{
# kitty XKB macVK macU
escape Escape 0x35 -
enter Return 0x24 NSCarriageReturnCharacter
tab Tab 0x30 NSTabCharacter
backspace BackSpace 0x33 NSBackspaceCharacter
insert Insert 0x72 Insert
delete Delete 0x75 Delete
left Left 0x7B LeftArrow
right Right 0x7C RightArrow
up Up 0x7E UpArrow
down Down 0x7D DownArrow
page_up Page_Up 0x74 PageUp
page_down Page_Down 0x79 PageDown
home Home 0x73 Home
end End 0x77 End
caps_lock Caps_Lock 0x39 -
scroll_lock Scroll_Lock - ScrollLock
num_lock Num_Lock 0x47 ClearLine
print_screen Print - PrintScreen
pause Pause - Pause
menu Menu 0x6E Menu
f1 F1 0x7A F1
f2 F2 0x78 F2
f3 F3 0x63 F3
f4 F4 0x76 F4
f5 F5 0x60 F5
f6 F6 0x61 F6
f7 F7 0x62 F7
f8 F8 0x64 F8
f9 F9 0x65 F9
f10 F10 0x6D F10
f11 F11 0x67 F11
f12 F12 0x6F F12
f13 F13 0x69 F13
f14 F14 0x6B F14
f15 F15 0x71 F15
f16 F16 0x6A F16
f17 F17 0x40 F17
f18 F18 0x4F F18
f19 F19 0x50 F19
f20 F20 0x5A F20
f21 F21 - F21
f22 F22 - F22
f23 F23 - F23
f24 F24 - F24
f25 F25 - F25
f26 F26 - F26
f27 F27 - F27
f28 F28 - F28
f29 F29 - F29
f30 F30 - F30
f31 F31 - F31
f32 F32 - F32
f33 F33 - F33
f34 F34 - F34
f35 F35 - F35
kp_0 KP_0 0x52 -
kp_1 KP_1 0x53 -
kp_2 KP_2 0x54 -
kp_3 KP_3 0x55 -
kp_4 KP_4 0x56 -
kp_5 KP_5 0x57 -
kp_6 KP_6 0x58 -
kp_7 KP_7 0x59 -
kp_8 KP_8 0x5B -
kp_9 KP_9 0x5C -
kp_decimal KP_Decimal 0x41 -
kp_divide KP_Divide 0x4B -
kp_multiply KP_Multiply 0x43 -
kp_subtract KP_Subtract 0x4E -
kp_add KP_Add 0x45 -
kp_enter KP_Enter 0x4C NSEnterCharacter
kp_equal KP_Equal 0x51 -
kp_separator KP_Separator - -
kp_left KP_Left - -
kp_right KP_Right - -
kp_up KP_Up - -
kp_down KP_Down - -
kp_page_up KP_Page_Up - -
kp_page_down KP_Page_Down - -
kp_home KP_Home - -
kp_end KP_End - -
kp_insert KP_Insert - -
kp_delete KP_Delete - -
kp_begin KP_Begin - -
media_play XF86AudioPlay - -
media_pause XF86AudioPause - -
media_play_pause - - -
media_reverse - - -
media_stop XF86AudioStop - -
media_fast_forward XF86AudioForward - -
media_rewind XF86AudioRewind - -
media_track_next XF86AudioNext - -
media_track_previous XF86AudioPrev - -
media_record XF86AudioRecord - -
lower_volume XF86AudioLowerVolume - -
raise_volume XF86AudioRaiseVolume - -
mute_volume XF86AudioMute - -
left_shift Shift_L 0x38 -
left_control Control_L 0x3B -
left_alt Alt_L 0x3A -
left_super Super_L 0x37 -
left_hyper Hyper_L - -
left_meta Meta_L - -
right_shift Shift_R 0x3C -
right_control Control_R 0x3E -
right_alt Alt_R 0x3D -
right_super Super_R 0x36 -
right_hyper Hyper_R - -
right_meta Meta_R - -
iso_level3_shift ISO_Level3_Shift - -
iso_level5_shift ISO_Level5_Shift - -
''' # }}}
shift_map = {x[0]: x[1] for x in '`~ 1! 2@ 3# 4$ 5% 6^ 7& 8* 9( 0) -_ =+ [{ ]} \\| ;: \'" ,< .> /?'.split()}
shift_map.update({x: x.upper() for x in string.ascii_lowercase})
functional_encoding_overrides = {
'insert': 2, 'delete': 3, 'page_up': 5, 'page_down': 6,
'home': 7, 'end': 8, 'tab': 9, 'f1': 11, 'f2': 12, 'f3': 13, 'enter': 13, 'f4': 14,
'f5': 15, 'f6': 17, 'f7': 18, 'f8': 19, 'f9': 20, 'f10': 21,
'f11': 23, 'f12': 24, 'escape': 27, 'backspace': 127
}
different_trailer_functionals = {
'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'kp_begin': 'E', 'end': 'F', 'home': 'H',
'f1': 'P', 'f2': 'Q', 'f3': 'R', 'f4': 'S', 'enter': 'u', 'tab': 'u',
'backspace': 'u', 'escape': 'u'
}
macos_ansi_key_codes = { # {{{
0x1D: ord('0'),
0x12: ord('1'),
0x13: ord('2'),
0x14: ord('3'),
0x15: ord('4'),
0x17: ord('5'),
0x16: ord('6'),
0x1A: ord('7'),
0x1C: ord('8'),
0x19: ord('9'),
0x00: ord('a'),
0x0B: ord('b'),
0x08: ord('c'),
0x02: ord('d'),
0x0E: ord('e'),
0x03: ord('f'),
0x05: ord('g'),
0x04: ord('h'),
0x22: ord('i'),
0x26: ord('j'),
0x28: ord('k'),
0x25: ord('l'),
0x2E: ord('m'),
0x2D: ord('n'),
0x1F: ord('o'),
0x23: ord('p'),
0x0C: ord('q'),
0x0F: ord('r'),
0x01: ord('s'),
0x11: ord('t'),
0x20: ord('u'),
0x09: ord('v'),
0x0D: ord('w'),
0x07: ord('x'),
0x10: ord('y'),
0x06: ord('z'),
0x27: ord('\''),
0x2A: ord('\\'),
0x2B: ord(','),
0x18: ord('='),
0x32: ord('`'),
0x21: ord('['),
0x1B: ord('-'),
0x2F: ord('.'),
0x1E: ord(']'),
0x29: ord(';'),
0x2C: ord('/'),
0x31: ord(' '),
} # }}}
functional_key_names: List[str] = []
name_to_code: Dict[str, int] = {}
name_to_xkb: Dict[str, str] = {}
name_to_vk: Dict[str, int] = {}
name_to_macu: Dict[str, str] = {}
start_code = 0xe000
for line in functional_key_defs.splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
parts = line.split()
name = parts[0]
functional_key_names.append(name)
name_to_code[name] = len(name_to_code) + start_code
if parts[1] != '-':
name_to_xkb[name] = parts[1]
if parts[2] != '-':
name_to_vk[name] = int(parts[2], 16)
if parts[3] != '-':
val = parts[3]
if not val.startswith('NS'):
val = f'NS{val}FunctionKey'
name_to_macu[name] = val
last_code = start_code + len(functional_key_names) - 1
ctrl_mapping = {
' ': 0, '@': 0, 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7,
'h': 8, 'i': 9, 'j': 10, 'k': 11, 'l': 12, 'm': 13, 'n': 14, 'o': 15, 'p': 16,
'q': 17, 'r': 18, 's': 19, 't': 20, 'u': 21, 'v': 22, 'w': 23, 'x': 24,
'y': 25, 'z': 26, '[': 27, '\\': 28, ']': 29, '^': 30, '~': 30, '/': 31,
'_': 31, '?': 127, '0': 48, '1': 49, '2': 0, '3': 27, '4': 28,
'5': 29, '6': 30, '7': 31, '8': 127, '9': 57
}
def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_marker: str = ' */') -> None:
simple_start_q = f'{start_marker}start {what}{end_marker}'
start_q = f'{start_marker}start {what} (auto generated by gen-key-constants.py do not edit){end_marker}'
end_q = f'{start_marker}end {what}{end_marker}'
with open(path, 'r+') as f:
raw = f.read()
try:
start = raw.index(start_q)
except ValueError:
try:
start = raw.index(simple_start_q)
except ValueError:
raise SystemExit(f'Failed to find "{simple_start_q}" in {path}')
try:
end = raw.index(end_q)
except ValueError:
raise SystemExit(f'Failed to find "{end_q}" in {path}')
raw = raw[:start] + start_q + '\n' + text + '\n' + raw[end:]
f.seek(0)
f.truncate(0)
f.write(raw)
def serialize_dict(x: dict) -> str:
return pformat(x, indent=4).replace('{', '{\n ', 1)
def generate_glfw_header() -> None:
lines = [
'typedef enum {',
f' GLFW_FKEY_FIRST = 0x{start_code:x}u,',
]
klines, pyi, names, knames = [], [], [], []
for name, code in name_to_code.items():
lines.append(f' GLFW_FKEY_{name.upper()} = 0x{code:x}u,')
klines.append(f' ADDC(GLFW_FKEY_{name.upper()});')
pyi.append(f'GLFW_FKEY_{name.upper()}: int')
names.append(f' case GLFW_FKEY_{name.upper()}: return "{name.upper()}";')
knames.append(f' case GLFW_FKEY_{name.upper()}: return PyUnicode_FromString("{name}");')
lines.append(f' GLFW_FKEY_LAST = 0x{last_code:x}u')
lines.append('} GLFWFunctionKey;')
patch_file('glfw/glfw3.h', 'functional key names', '\n'.join(lines))
patch_file('kitty/glfw.c', 'glfw functional keys', '\n'.join(klines))
patch_file('kitty/fast_data_types.pyi', 'glfw functional keys', '\n'.join(pyi), start_marker='# ', end_marker='')
patch_file('glfw/input.c', 'functional key names', '\n'.join(names))
patch_file('kitty/glfw.c', 'glfw functional key names', '\n'.join(knames))
def generate_xkb_mapping() -> None:
lines, rlines = [], []
for name, xkb in name_to_xkb.items():
lines.append(f' case XKB_KEY_{xkb}: return GLFW_FKEY_{name.upper()};')
rlines.append(f' case GLFW_FKEY_{name.upper()}: return XKB_KEY_{xkb};')
patch_file('glfw/xkb_glfw.c', 'xkb to glfw', '\n'.join(lines))
patch_file('glfw/xkb_glfw.c', 'glfw to xkb', '\n'.join(rlines))
def generate_functional_table() -> None:
lines = [
'',
'.. csv-table:: Functional key codes',
' :header: "Name", "CSI", "Name", "CSI"',
''
]
line_items = []
enc_lines = []
tilde_trailers = set()
for name, code in name_to_code.items():
if name in functional_encoding_overrides or name in different_trailer_functionals:
trailer = different_trailer_functionals.get(name, '~')
if trailer == '~':
tilde_trailers.add(code)
code = oc = functional_encoding_overrides.get(name, code)
code = code if trailer in '~u' else 1
enc_lines.append((' ' * 8) + f"case GLFW_FKEY_{name.upper()}: S({code}, '{trailer}');")
if code == 1 and name not in ('up', 'down', 'left', 'right'):
trailer += f' or {oc} ~'
else:
trailer = 'u'
line_items.append(name.upper())
line_items.append(f'``{code}\xa0{trailer}``')
for li in chunks(line_items, 4):
lines.append(' ' + ', '.join(f'"{x}"' for x in li))
lines.append('')
patch_file('docs/keyboard-protocol.rst', 'functional key table', '\n'.join(lines), start_marker='.. ', end_marker='')
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
code_to_name = {v: k.upper() for k, v in name_to_code.items()}
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
letter_trailer_codes = {
v: functional_encoding_overrides.get(k, name_to_code.get(k))
for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'}
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
text += f'\ncsi_number_to_functional_number_map = {serialize_dict(csi_map)}'
text += f'\nletter_trailer_to_csi_number_map = {letter_trailer_codes!r}'
text += f'\ntilde_trailers = {tilde_trailers!r}'
patch_file('kitty/key_encoding.py', 'csi mapping', text, start_marker='# ', end_marker='')
def generate_legacy_text_key_maps() -> None:
tests = []
tp = ' ' * 8
shift, alt, ctrl = 1, 2, 4
def simple(c: str) -> None:
shifted = shift_map.get(c, c)
ctrled = chr(ctrl_mapping.get(c, ord(c)))
call = f'enc(ord({c!r}), shifted_key=ord({shifted!r})'
for m in range(16):
if m == 0:
tests.append(f'{tp}ae({call}), {c!r})')
elif m == shift:
tests.append(f'{tp}ae({call}, mods=shift), {shifted!r})')
elif m == alt:
tests.append(f'{tp}ae({call}, mods=alt), "\\x1b" + {c!r})')
elif m == ctrl:
tests.append(f'{tp}ae({call}, mods=ctrl), {ctrled!r})')
elif m == shift | alt:
tests.append(f'{tp}ae({call}, mods=shift | alt), "\\x1b" + {shifted!r})')
elif m == ctrl | alt:
tests.append(f'{tp}ae({call}, mods=ctrl | alt), "\\x1b" + {ctrled!r})')
for k in shift_map:
simple(k)
patch_file('kitty_tests/keys.py', 'legacy letter tests', '\n'.join(tests), start_marker='# ', end_marker='')
def chunks(lst: List, n: int) -> Any:
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
def generate_ctrl_mapping() -> None:
lines = [
'.. csv-table:: Emitted bytes when :kbd:`ctrl` is held down and a key is pressed',
' :header: "Key", "Byte", "Key", "Byte", "Key", "Byte"',
''
]
items = []
mi = []
for k in sorted(ctrl_mapping):
prefix = '\\' if k == '\\' else ('SPC' if k == ' ' else '')
items.append(prefix + k)
val = str(ctrl_mapping[k])
items.append(val)
if k in "\\'":
k = '\\' + k
mi.append(f" case '{k}': return {val};")
for line_items in chunks(items, 6):
lines.append(' ' + ', '.join(f'"{x}"' for x in line_items))
lines.append('')
patch_file('docs/keyboard-protocol.rst', 'ctrl mapping', '\n'.join(lines), start_marker='.. ', end_marker='')
patch_file('kitty/key_encoding.c', 'ctrl mapping', '\n'.join(mi))
def generate_macos_mapping() -> None:
lines = []
for k in sorted(macos_ansi_key_codes):
v = macos_ansi_key_codes[k]
lines.append(f' case 0x{k:x}: return 0x{v:x};')
patch_file('glfw/cocoa_window.m', 'vk to unicode', '\n'.join(lines))
lines = []
for name, vk in name_to_vk.items():
lines.append(f' case 0x{vk:x}: return GLFW_FKEY_{name.upper()};')
patch_file('glfw/cocoa_window.m', 'vk to functional', '\n'.join(lines))
lines = []
for name, mac in name_to_macu.items():
lines.append(f' case {mac}: return GLFW_FKEY_{name.upper()};')
patch_file('glfw/cocoa_window.m', 'macu to functional', '\n'.join(lines))
lines = []
for name, mac in name_to_macu.items():
lines.append(f' case GLFW_FKEY_{name.upper()}: return {mac};')
patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines))
def main() -> None:
generate_glfw_header()
generate_xkb_mapping()
generate_functional_table()
generate_legacy_text_key_maps()
generate_ctrl_mapping()
generate_macos_mapping()
if __name__ == '__main__':
main()

View File

@@ -114,6 +114,7 @@ def parse_ucd() -> None:
word_search_map['bee'] |= word_search_map['honeybee']
word_search_map['lambda'] |= word_search_map['lamda']
word_search_map['lamda'] |= word_search_map['lambda']
word_search_map['diamond'] |= word_search_map['gem']
def parse_range_spec(spec: str) -> Set[int]:
@@ -504,7 +505,7 @@ def gen_wcwidth() -> None:
p('\t\t// }}}\n')
with create_header('kitty/wcwidth-std.h') as p:
p('static int\nwcwidth_std(int32_t code) {')
p('static inline int\nwcwidth_std(int32_t code) {')
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) return 1;')
p('\tswitch(code) {')

70
glfw/backend_utils.c vendored
View File

@@ -8,6 +8,7 @@
#define _GNU_SOURCE
#include "backend_utils.h"
#include "internal.h"
#include "memfd.h"
#include <stdlib.h>
#include <string.h>
@@ -350,3 +351,72 @@ GLFWAPI char* utf_8_strndup(const char* source, size_t max_length) {
result[length] = 0;
return result;
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
* CLOEXEC. The file is immediately suitable for mmap()'ing
* the given size at offset zero.
*
* The file should not have a permanent backing store like a disk,
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
*
* The file name is deleted from the file system.
*
* The file is suitable for buffer sharing between processes by
* transmitting the file descriptor over Unix sockets using the
* SCM_RIGHTS methods.
*
* posix_fallocate() is used to guarantee that disk space is available
* for the file at the given size. If disk space is insufficient, errno
* is set to ENOSPC. If posix_fallocate() is not supported, program may
* receive SIGBUS on accessing mmap()'ed file contents instead.
*/
int createAnonymousFile(off_t size) {
int ret, fd = -1, shm_anon = 0;
#ifdef HAS_MEMFD_CREATE
fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd < 0) return -1;
// We can add this seal before calling posix_fallocate(), as the file
// is currently zero-sized anyway.
//
// There is also no need to check for the return value, we couldnt do
// anything with it anyway.
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
#elif defined(SHM_ANON)
fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600);
if (fd < 0) return -1;
shm_anon = 1;
#else
static const char template[] = "/glfw-shared-XXXXXX";
const char* path;
char* name;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}
name = calloc(strlen(path) + sizeof(template), 1);
strcpy(name, path);
strcat(name, template);
fd = createTmpfileCloexec(name);
free(name);
if (fd < 0)
return -1;
#endif
// posix_fallocate does not work on SHM descriptors
ret = shm_anon ? ftruncate(fd, size) : posix_fallocate(fd, 0, size);
if (ret != 0)
{
close(fd);
errno = ret;
return -1;
}
return fd;
}

View File

@@ -1,7 +1,7 @@
//========================================================================
// GLFW 3.4 Wayland - www.glfw.org
// GLFW 3.4
//------------------------------------------------------------------------
// Copyright (c) 2014 Jonas Ådahl <jadahl@gmail.com>
// Copyright (c) 2014 Kovid Goyal
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
@@ -29,6 +29,7 @@
#include <poll.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/types.h>
#ifdef __has_include
#if __has_include(<sys/eventfd.h>)
@@ -96,3 +97,4 @@ void finalizePollData(EventLoopData *eld);
bool initPollData(EventLoopData *eld, int display_fd);
void wakeupEventLoop(EventLoopData *eld);
char* utf_8_strndup(const char* source, size_t max_length);
int createAnonymousFile(off_t size);

View File

@@ -175,138 +175,6 @@ static void createMenuBar(void)
[NSApp performSelector:setAppleMenuSelector withObject:appMenu];
}
// Create key code translation tables
//
static void createKeyTables(void)
{
int keycode;
memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
memset(_glfw.ns.key_to_keycode, -1, sizeof(_glfw.ns.key_to_keycode));
_glfw.ns.keycodes[0x1D] = GLFW_KEY_0;
_glfw.ns.keycodes[0x12] = GLFW_KEY_1;
_glfw.ns.keycodes[0x13] = GLFW_KEY_2;
_glfw.ns.keycodes[0x14] = GLFW_KEY_3;
_glfw.ns.keycodes[0x15] = GLFW_KEY_4;
_glfw.ns.keycodes[0x17] = GLFW_KEY_5;
_glfw.ns.keycodes[0x16] = GLFW_KEY_6;
_glfw.ns.keycodes[0x1A] = GLFW_KEY_7;
_glfw.ns.keycodes[0x1C] = GLFW_KEY_8;
_glfw.ns.keycodes[0x19] = GLFW_KEY_9;
_glfw.ns.keycodes[0x00] = GLFW_KEY_A;
_glfw.ns.keycodes[0x0B] = GLFW_KEY_B;
_glfw.ns.keycodes[0x08] = GLFW_KEY_C;
_glfw.ns.keycodes[0x02] = GLFW_KEY_D;
_glfw.ns.keycodes[0x0E] = GLFW_KEY_E;
_glfw.ns.keycodes[0x03] = GLFW_KEY_F;
_glfw.ns.keycodes[0x05] = GLFW_KEY_G;
_glfw.ns.keycodes[0x04] = GLFW_KEY_H;
_glfw.ns.keycodes[0x22] = GLFW_KEY_I;
_glfw.ns.keycodes[0x26] = GLFW_KEY_J;
_glfw.ns.keycodes[0x28] = GLFW_KEY_K;
_glfw.ns.keycodes[0x25] = GLFW_KEY_L;
_glfw.ns.keycodes[0x2E] = GLFW_KEY_M;
_glfw.ns.keycodes[0x2D] = GLFW_KEY_N;
_glfw.ns.keycodes[0x1F] = GLFW_KEY_O;
_glfw.ns.keycodes[0x23] = GLFW_KEY_P;
_glfw.ns.keycodes[0x0C] = GLFW_KEY_Q;
_glfw.ns.keycodes[0x0F] = GLFW_KEY_R;
_glfw.ns.keycodes[0x01] = GLFW_KEY_S;
_glfw.ns.keycodes[0x11] = GLFW_KEY_T;
_glfw.ns.keycodes[0x20] = GLFW_KEY_U;
_glfw.ns.keycodes[0x09] = GLFW_KEY_V;
_glfw.ns.keycodes[0x0D] = GLFW_KEY_W;
_glfw.ns.keycodes[0x07] = GLFW_KEY_X;
_glfw.ns.keycodes[0x10] = GLFW_KEY_Y;
_glfw.ns.keycodes[0x06] = GLFW_KEY_Z;
_glfw.ns.keycodes[0x27] = GLFW_KEY_APOSTROPHE;
_glfw.ns.keycodes[0x2A] = GLFW_KEY_BACKSLASH;
_glfw.ns.keycodes[0x2B] = GLFW_KEY_COMMA;
_glfw.ns.keycodes[0x18] = GLFW_KEY_EQUAL;
_glfw.ns.keycodes[0x32] = GLFW_KEY_GRAVE_ACCENT;
_glfw.ns.keycodes[0x21] = GLFW_KEY_LEFT_BRACKET;
_glfw.ns.keycodes[0x1B] = GLFW_KEY_MINUS;
_glfw.ns.keycodes[0x2F] = GLFW_KEY_PERIOD;
_glfw.ns.keycodes[0x1E] = GLFW_KEY_RIGHT_BRACKET;
_glfw.ns.keycodes[0x29] = GLFW_KEY_SEMICOLON;
_glfw.ns.keycodes[0x2C] = GLFW_KEY_SLASH;
_glfw.ns.keycodes[0x0A] = GLFW_KEY_WORLD_1;
_glfw.ns.keycodes[0x33] = GLFW_KEY_BACKSPACE;
_glfw.ns.keycodes[0x39] = GLFW_KEY_CAPS_LOCK;
_glfw.ns.keycodes[0x75] = GLFW_KEY_DELETE;
_glfw.ns.keycodes[0x7D] = GLFW_KEY_DOWN;
_glfw.ns.keycodes[0x77] = GLFW_KEY_END;
_glfw.ns.keycodes[0x24] = GLFW_KEY_ENTER;
_glfw.ns.keycodes[0x35] = GLFW_KEY_ESCAPE;
_glfw.ns.keycodes[0x7A] = GLFW_KEY_F1;
_glfw.ns.keycodes[0x78] = GLFW_KEY_F2;
_glfw.ns.keycodes[0x63] = GLFW_KEY_F3;
_glfw.ns.keycodes[0x76] = GLFW_KEY_F4;
_glfw.ns.keycodes[0x60] = GLFW_KEY_F5;
_glfw.ns.keycodes[0x61] = GLFW_KEY_F6;
_glfw.ns.keycodes[0x62] = GLFW_KEY_F7;
_glfw.ns.keycodes[0x64] = GLFW_KEY_F8;
_glfw.ns.keycodes[0x65] = GLFW_KEY_F9;
_glfw.ns.keycodes[0x6D] = GLFW_KEY_F10;
_glfw.ns.keycodes[0x67] = GLFW_KEY_F11;
_glfw.ns.keycodes[0x6F] = GLFW_KEY_F12;
_glfw.ns.keycodes[0x69] = GLFW_KEY_F13;
_glfw.ns.keycodes[0x6B] = GLFW_KEY_F14;
_glfw.ns.keycodes[0x71] = GLFW_KEY_F15;
_glfw.ns.keycodes[0x6A] = GLFW_KEY_F16;
_glfw.ns.keycodes[0x40] = GLFW_KEY_F17;
_glfw.ns.keycodes[0x4F] = GLFW_KEY_F18;
_glfw.ns.keycodes[0x50] = GLFW_KEY_F19;
_glfw.ns.keycodes[0x5A] = GLFW_KEY_F20;
_glfw.ns.keycodes[0x73] = GLFW_KEY_HOME;
_glfw.ns.keycodes[0x72] = GLFW_KEY_INSERT;
_glfw.ns.keycodes[0x7B] = GLFW_KEY_LEFT;
_glfw.ns.keycodes[0x3A] = GLFW_KEY_LEFT_ALT;
_glfw.ns.keycodes[0x3B] = GLFW_KEY_LEFT_CONTROL;
_glfw.ns.keycodes[0x38] = GLFW_KEY_LEFT_SHIFT;
_glfw.ns.keycodes[0x37] = GLFW_KEY_LEFT_SUPER;
_glfw.ns.keycodes[0x6E] = GLFW_KEY_MENU;
_glfw.ns.keycodes[0x47] = GLFW_KEY_NUM_LOCK;
_glfw.ns.keycodes[0x79] = GLFW_KEY_PAGE_DOWN;
_glfw.ns.keycodes[0x74] = GLFW_KEY_PAGE_UP;
_glfw.ns.keycodes[0x7C] = GLFW_KEY_RIGHT;
_glfw.ns.keycodes[0x3D] = GLFW_KEY_RIGHT_ALT;
_glfw.ns.keycodes[0x3E] = GLFW_KEY_RIGHT_CONTROL;
_glfw.ns.keycodes[0x3C] = GLFW_KEY_RIGHT_SHIFT;
_glfw.ns.keycodes[0x36] = GLFW_KEY_RIGHT_SUPER;
_glfw.ns.keycodes[0x31] = GLFW_KEY_SPACE;
_glfw.ns.keycodes[0x30] = GLFW_KEY_TAB;
_glfw.ns.keycodes[0x7E] = GLFW_KEY_UP;
_glfw.ns.keycodes[0x52] = GLFW_KEY_KP_0;
_glfw.ns.keycodes[0x53] = GLFW_KEY_KP_1;
_glfw.ns.keycodes[0x54] = GLFW_KEY_KP_2;
_glfw.ns.keycodes[0x55] = GLFW_KEY_KP_3;
_glfw.ns.keycodes[0x56] = GLFW_KEY_KP_4;
_glfw.ns.keycodes[0x57] = GLFW_KEY_KP_5;
_glfw.ns.keycodes[0x58] = GLFW_KEY_KP_6;
_glfw.ns.keycodes[0x59] = GLFW_KEY_KP_7;
_glfw.ns.keycodes[0x5B] = GLFW_KEY_KP_8;
_glfw.ns.keycodes[0x5C] = GLFW_KEY_KP_9;
_glfw.ns.keycodes[0x45] = GLFW_KEY_KP_ADD;
_glfw.ns.keycodes[0x41] = GLFW_KEY_KP_DECIMAL;
_glfw.ns.keycodes[0x4B] = GLFW_KEY_KP_DIVIDE;
_glfw.ns.keycodes[0x4C] = GLFW_KEY_KP_ENTER;
_glfw.ns.keycodes[0x51] = GLFW_KEY_KP_EQUAL;
_glfw.ns.keycodes[0x43] = GLFW_KEY_KP_MULTIPLY;
_glfw.ns.keycodes[0x4E] = GLFW_KEY_KP_SUBTRACT;
for (keycode = 0; keycode < 256; keycode++)
{
// Store the reverse translation for faster key name lookup
if (_glfw.ns.keycodes[keycode] >= 0)
_glfw.ns.key_to_keycode[_glfw.ns.keycodes[keycode]] = keycode;
}
}
// Retrieve Unicode data for the current keyboard layout
//
static bool updateUnicodeDataNS(void)
@@ -478,10 +346,40 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
finish_launching_callback();
}
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
(void)theApplication;
if (!filename || !_glfw.ns.file_open_callback) return NO;
const char *path = NULL;
@try {
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
} @catch(NSException *exc) {
NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason);
return NO;
}
if (!path) return NO;
return _glfw.ns.file_open_callback(path);
}
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
(void)sender;
if (!_glfw.ns.file_open_callback || !filenames) return;
for (id x in filenames) {
NSString *filename = x;
const char *path = NULL;
@try {
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
} @catch(NSException *exc) {
NSLog(@"Converting openFiles filename: %@ failed with error: %@", filename, exc.reason);
}
if (path) _glfw.ns.file_open_callback(path);
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
(void)notification;
[NSApp stop:nil];
if (_glfw.ns.file_open_callback) _glfw.ns.file_open_callback(":cocoa::application launched::");
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
_glfwCocoaPostEmptyEvent();
@@ -648,8 +546,12 @@ int _glfwPlatformInit(void)
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
NSDictionary* defaults = @{
// Press and Hold prevents some keys from emitting repeated characters
@"ApplePressAndHoldEnabled": @NO,
// Dont generate openFile events from command line arguments
@"NSTreatUnknownArgumentsAsOpen": @"NO",
};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
[[NSNotificationCenter defaultCenter]
@@ -658,8 +560,6 @@ int _glfwPlatformInit(void)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
createKeyTables();
_glfw.ns.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
if (!_glfw.ns.eventSource)
return false;

View File

@@ -89,7 +89,7 @@ static char* getDisplayName(CGDirectDisplayID displayID)
if (!service)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to find service port for display");
"Cocoa: Failed to find service port for display, cannot get its name, using Unknown");
return NULL;
}
@@ -326,7 +326,7 @@ static CVReturn displayLinkCallback(
const CVTimeStamp* now UNUSED, const CVTimeStamp* outputTime UNUSED,
CVOptionFlags flagsIn UNUSED, CVOptionFlags* flagsOut UNUSED, void* userInfo)
{
CGDirectDisplayID displayID = (CGDirectDisplayID)userInfo;
CGDirectDisplayID displayID = (uintptr_t)userInfo;
NSNumber *arg = [NSNumber numberWithUnsignedInt:displayID];
[NSApp performSelectorOnMainThread:@selector(render_frame_received:) withObject:arg waitUntilDone:NO];
[arg release];

View File

@@ -67,6 +67,7 @@ typedef void* CVDisplayLinkRef;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandlefileopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
@@ -178,8 +179,6 @@ typedef struct _GLFWlibraryNS
char keyName[64];
char text[256];
short int keycodes[256];
short int key_to_keycode[GLFW_KEY_LAST + 1];
char* clipboardString;
CGPoint cascadePoint;
// Where to place the cursor when re-enabled
@@ -199,6 +198,8 @@ typedef struct _GLFWlibraryNS
_GLFWDisplayLinkNS entries[256];
size_t count;
} displayLinks;
// the callback to handle file open events
GLFWhandlefileopen file_open_callback;
} _GLFWlibraryNS;

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@ def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
return 'wayland-{}-client-protocol.{}'.format(base, ext)
def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_compile: Callable, module: str = 'x11') -> Env:
def init_env(env: Env, pkg_config: Callable, pkg_version: Callable, at_least_version: Callable, test_compile: Callable, module: str = 'x11') -> Env:
ans = env.copy()
ans.cflags.append('-fPIC')
ans.cppflags.append('-D_GLFW_' + module.upper())
@@ -76,7 +76,11 @@ def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_co
ans.ldpaths.extend('-pthread -lm'.split())
if not is_openbsd:
ans.ldpaths.extend('-lrt -ldl'.split())
at_least_version('xkbcommon', 0, 5)
major, minor = pkg_version('xkbcommon')
if (major, minor) < (0, 5):
raise SystemExit('libxkbcommon >= 0.5 required')
if major < 1:
ans.cflags.append('-DXKB_HAS_NO_UTF32')
if module == 'x11':
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
@@ -85,7 +89,7 @@ def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_co
elif module == 'cocoa':
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
for f_ in 'Cocoa Carbon IOKit CoreFoundation CoreVideo'.split():
ans.ldpaths.extend(('-framework', f_))
elif module == 'wayland':
@@ -175,13 +179,12 @@ class Function:
)
def load(self) -> str:
ans = '*(void **) (&{name}_impl) = dlsym(handle, "{name}");'.format(
name=self.name
)
ans = f'*(void **) (&{self.name}_impl) = dlsym(handle, "{self.name}");'
ans += f'\n if ({self.name}_impl == NULL) '
if self.check_fail:
ans += '\n if ({name}_impl == NULL) fail("Failed to load glfw function {name} with error: %s", dlerror());'.format(
name=self.name
)
ans += f'fail("Failed to load glfw function {self.name} with error: %s", dlerror());'
else:
ans += 'dlerror(); // clear error indicator'
return ans
@@ -203,10 +206,11 @@ def generate_wrappers(glfw_header: str) -> None:
void* glfwGetNSGLContext(GLFWwindow *window)
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback)
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char* cocoa_key, size_t key_sz, int* cocoa_mods)
uint32_t glfwGetCocoaKeyEquivalent(uint32_t glfw_key, int glfw_mods, int* cocoa_mods)
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)
@@ -244,6 +248,7 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef bool (* GLFWhandlefileopen)(const char*);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);

518
glfw/glfw3.h vendored
View File

@@ -300,31 +300,6 @@ extern "C" {
#define GLFW_VERSION_REVISION 0
/*! @} */
/*! @name Key and button actions
* @{ */
/*! @brief The key or mouse button was released.
*
* The key or mouse button was released.
*
* @ingroup input
*/
#define GLFW_RELEASE 0
/*! @brief The key or mouse button was pressed.
*
* The key or mouse button was pressed.
*
* @ingroup input
*/
#define GLFW_PRESS 1
/*! @brief The key was held down until it repeated.
*
* The key was held down until it repeated.
*
* @ingroup input
*/
#define GLFW_REPEAT 2
/*! @} */
/*! @defgroup hat_state Joystick hat states
* @brief Joystick hat states.
*
@@ -367,200 +342,123 @@ extern "C" {
* @{
*/
/* The unknown key */
#define GLFW_KEY_UNKNOWN -1
/* Printable keys */
#define GLFW_KEY_SPACE 32
#define GLFW_KEY_EXCLAM 33 /* ! */
#define GLFW_KEY_DOUBLE_QUOTE 34 /* " */
#define GLFW_KEY_NUMBER_SIGN 35 /* # */
#define GLFW_KEY_DOLLAR 36 /* $ */
#define GLFW_KEY_AMPERSAND 38 /* & */
#define GLFW_KEY_APOSTROPHE 39 /* ' */
#define GLFW_KEY_PARENTHESIS_LEFT 40 /* ( */
#define GLFW_KEY_PARENTHESIS_RIGHT 41 /* ) */
#define GLFW_KEY_PLUS 43 /* + */
#define GLFW_KEY_COMMA 44 /* , */
#define GLFW_KEY_MINUS 45 /* - */
#define GLFW_KEY_PERIOD 46 /* . */
#define GLFW_KEY_SLASH 47 /* / */
#define GLFW_KEY_0 48
#define GLFW_KEY_1 49
#define GLFW_KEY_2 50
#define GLFW_KEY_3 51
#define GLFW_KEY_4 52
#define GLFW_KEY_5 53
#define GLFW_KEY_6 54
#define GLFW_KEY_7 55
#define GLFW_KEY_8 56
#define GLFW_KEY_9 57
#define GLFW_KEY_COLON 58 /* : */
#define GLFW_KEY_SEMICOLON 59 /* ; */
#define GLFW_KEY_LESS 60 /* < */
#define GLFW_KEY_EQUAL 61 /* = */
#define GLFW_KEY_GREATER 62 /* > */
#define GLFW_KEY_AT 64 /* @ */
#define GLFW_KEY_A 65
#define GLFW_KEY_B 66
#define GLFW_KEY_C 67
#define GLFW_KEY_D 68
#define GLFW_KEY_E 69
#define GLFW_KEY_F 70
#define GLFW_KEY_G 71
#define GLFW_KEY_H 72
#define GLFW_KEY_I 73
#define GLFW_KEY_J 74
#define GLFW_KEY_K 75
#define GLFW_KEY_L 76
#define GLFW_KEY_M 77
#define GLFW_KEY_N 78
#define GLFW_KEY_O 79
#define GLFW_KEY_P 80
#define GLFW_KEY_Q 81
#define GLFW_KEY_R 82
#define GLFW_KEY_S 83
#define GLFW_KEY_T 84
#define GLFW_KEY_U 85
#define GLFW_KEY_V 86
#define GLFW_KEY_W 87
#define GLFW_KEY_X 88
#define GLFW_KEY_Y 89
#define GLFW_KEY_Z 90
#define GLFW_KEY_LEFT_BRACKET 91 /* [ */
#define GLFW_KEY_BACKSLASH 92 /* \ */
#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */
#define GLFW_KEY_CIRCUMFLEX 94 /* ^ */
#define GLFW_KEY_UNDERSCORE 95 /* _ */
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
#define GLFW_KEY_PARAGRAPH 167 /* § */
#define GLFW_KEY_MASCULINE 186 /* º */
#define GLFW_KEY_A_GRAVE 192 /* À */
#define GLFW_KEY_A_DIAERESIS 196 /* Ä */
#define GLFW_KEY_A_RING 197 /* Å */
#define GLFW_KEY_AE 198 /* Æ */
#define GLFW_KEY_C_CEDILLA 199 /* Ç */
#define GLFW_KEY_E_GRAVE 200 /* È */
#define GLFW_KEY_E_ACUTE 201 /* É */
#define GLFW_KEY_I_GRAVE 204 /* Ì */
#define GLFW_KEY_N_TILDE 209 /* Ñ */
#define GLFW_KEY_O_GRAVE 210 /* Ò */
#define GLFW_KEY_O_DIAERESIS 214 /* Ö */
#define GLFW_KEY_O_SLASH 216 /* Ø */
#define GLFW_KEY_U_GRAVE 217 /* Ù */
#define GLFW_KEY_U_DIAERESIS 220 /* Ü */
#define GLFW_KEY_S_SHARP 222 /* ß */
#define GLFW_KEY_CYRILLIC_A 223 /* А */
#define GLFW_KEY_CYRILLIC_BE 224 /* Б */
#define GLFW_KEY_CYRILLIC_VE 225 /* В */
#define GLFW_KEY_CYRILLIC_GHE 226 /* Г */
#define GLFW_KEY_CYRILLIC_DE 227 /* Д */
#define GLFW_KEY_CYRILLIC_IE 228 /* Е */
#define GLFW_KEY_CYRILLIC_ZHE 229 /* Ж */
#define GLFW_KEY_CYRILLIC_ZE 230 /* З */
#define GLFW_KEY_CYRILLIC_I 231 /* И */
#define GLFW_KEY_CYRILLIC_SHORT_I 232 /* Й */
#define GLFW_KEY_CYRILLIC_KA 233 /* К */
#define GLFW_KEY_CYRILLIC_EL 234 /* Л */
#define GLFW_KEY_CYRILLIC_EM 235 /* М */
#define GLFW_KEY_CYRILLIC_EN 236 /* Н */
#define GLFW_KEY_CYRILLIC_O 237 /* О */
#define GLFW_KEY_CYRILLIC_PE 238 /* П */
#define GLFW_KEY_CYRILLIC_ER 239 /* Р */
#define GLFW_KEY_CYRILLIC_ES 240 /* С */
#define GLFW_KEY_CYRILLIC_TE 241 /* Т */
#define GLFW_KEY_CYRILLIC_U 242 /* У */
#define GLFW_KEY_CYRILLIC_EF 243 /* Ф */
#define GLFW_KEY_CYRILLIC_HA 244 /* Х */
#define GLFW_KEY_CYRILLIC_TSE 245 /* Ц */
#define GLFW_KEY_CYRILLIC_CHE 246 /* Ч */
#define GLFW_KEY_CYRILLIC_SHA 247 /* Ш */
#define GLFW_KEY_CYRILLIC_SHCHA 248 /* Щ */
#define GLFW_KEY_CYRILLIC_HARD_SIGN 249 /* Ъ */
#define GLFW_KEY_CYRILLIC_YERU 250 /* Ы */
#define GLFW_KEY_CYRILLIC_SOFT_SIGN 251 /* Ь */
#define GLFW_KEY_CYRILLIC_E 252 /* Э */
#define GLFW_KEY_CYRILLIC_YU 253 /* Ю */
#define GLFW_KEY_CYRILLIC_YA 254 /* Я */
#define GLFW_KEY_CYRILLIC_IO 255 /* Ё */
#define GLFW_KEY_LAST_PRINTABLE GLFW_KEY_CYRILLIC_IO
/* Function keys */
#define GLFW_KEY_ESCAPE 256
#define GLFW_KEY_ENTER 257
#define GLFW_KEY_TAB 258
#define GLFW_KEY_BACKSPACE 259
#define GLFW_KEY_INSERT 260
#define GLFW_KEY_DELETE 261
#define GLFW_KEY_RIGHT 262
#define GLFW_KEY_LEFT 263
#define GLFW_KEY_DOWN 264
#define GLFW_KEY_UP 265
#define GLFW_KEY_PAGE_UP 266
#define GLFW_KEY_PAGE_DOWN 267
#define GLFW_KEY_HOME 268
#define GLFW_KEY_END 269
#define GLFW_KEY_CAPS_LOCK 280
#define GLFW_KEY_SCROLL_LOCK 281
#define GLFW_KEY_NUM_LOCK 282
#define GLFW_KEY_PRINT_SCREEN 283
#define GLFW_KEY_PAUSE 284
#define GLFW_KEY_F1 290
#define GLFW_KEY_F2 291
#define GLFW_KEY_F3 292
#define GLFW_KEY_F4 293
#define GLFW_KEY_F5 294
#define GLFW_KEY_F6 295
#define GLFW_KEY_F7 296
#define GLFW_KEY_F8 297
#define GLFW_KEY_F9 298
#define GLFW_KEY_F10 299
#define GLFW_KEY_F11 300
#define GLFW_KEY_F12 301
#define GLFW_KEY_F13 302
#define GLFW_KEY_F14 303
#define GLFW_KEY_F15 304
#define GLFW_KEY_F16 305
#define GLFW_KEY_F17 306
#define GLFW_KEY_F18 307
#define GLFW_KEY_F19 308
#define GLFW_KEY_F20 309
#define GLFW_KEY_F21 310
#define GLFW_KEY_F22 311
#define GLFW_KEY_F23 312
#define GLFW_KEY_F24 313
#define GLFW_KEY_F25 314
#define GLFW_KEY_KP_0 320
#define GLFW_KEY_KP_1 321
#define GLFW_KEY_KP_2 322
#define GLFW_KEY_KP_3 323
#define GLFW_KEY_KP_4 324
#define GLFW_KEY_KP_5 325
#define GLFW_KEY_KP_6 326
#define GLFW_KEY_KP_7 327
#define GLFW_KEY_KP_8 328
#define GLFW_KEY_KP_9 329
#define GLFW_KEY_KP_DECIMAL 330
#define GLFW_KEY_KP_DIVIDE 331
#define GLFW_KEY_KP_MULTIPLY 332
#define GLFW_KEY_KP_SUBTRACT 333
#define GLFW_KEY_KP_ADD 334
#define GLFW_KEY_KP_ENTER 335
#define GLFW_KEY_KP_EQUAL 336
#define GLFW_KEY_LEFT_SHIFT 340
#define GLFW_KEY_LEFT_CONTROL 341
#define GLFW_KEY_LEFT_ALT 342
#define GLFW_KEY_LEFT_SUPER 343
#define GLFW_KEY_RIGHT_SHIFT 344
#define GLFW_KEY_RIGHT_CONTROL 345
#define GLFW_KEY_RIGHT_ALT 346
#define GLFW_KEY_RIGHT_SUPER 347
#define GLFW_KEY_MENU 348
#define GLFW_KEY_LAST GLFW_KEY_MENU
/* start functional key names (auto generated by gen-key-constants.py do not edit) */
typedef enum {
GLFW_FKEY_FIRST = 0xe000u,
GLFW_FKEY_ESCAPE = 0xe000u,
GLFW_FKEY_ENTER = 0xe001u,
GLFW_FKEY_TAB = 0xe002u,
GLFW_FKEY_BACKSPACE = 0xe003u,
GLFW_FKEY_INSERT = 0xe004u,
GLFW_FKEY_DELETE = 0xe005u,
GLFW_FKEY_LEFT = 0xe006u,
GLFW_FKEY_RIGHT = 0xe007u,
GLFW_FKEY_UP = 0xe008u,
GLFW_FKEY_DOWN = 0xe009u,
GLFW_FKEY_PAGE_UP = 0xe00au,
GLFW_FKEY_PAGE_DOWN = 0xe00bu,
GLFW_FKEY_HOME = 0xe00cu,
GLFW_FKEY_END = 0xe00du,
GLFW_FKEY_CAPS_LOCK = 0xe00eu,
GLFW_FKEY_SCROLL_LOCK = 0xe00fu,
GLFW_FKEY_NUM_LOCK = 0xe010u,
GLFW_FKEY_PRINT_SCREEN = 0xe011u,
GLFW_FKEY_PAUSE = 0xe012u,
GLFW_FKEY_MENU = 0xe013u,
GLFW_FKEY_F1 = 0xe014u,
GLFW_FKEY_F2 = 0xe015u,
GLFW_FKEY_F3 = 0xe016u,
GLFW_FKEY_F4 = 0xe017u,
GLFW_FKEY_F5 = 0xe018u,
GLFW_FKEY_F6 = 0xe019u,
GLFW_FKEY_F7 = 0xe01au,
GLFW_FKEY_F8 = 0xe01bu,
GLFW_FKEY_F9 = 0xe01cu,
GLFW_FKEY_F10 = 0xe01du,
GLFW_FKEY_F11 = 0xe01eu,
GLFW_FKEY_F12 = 0xe01fu,
GLFW_FKEY_F13 = 0xe020u,
GLFW_FKEY_F14 = 0xe021u,
GLFW_FKEY_F15 = 0xe022u,
GLFW_FKEY_F16 = 0xe023u,
GLFW_FKEY_F17 = 0xe024u,
GLFW_FKEY_F18 = 0xe025u,
GLFW_FKEY_F19 = 0xe026u,
GLFW_FKEY_F20 = 0xe027u,
GLFW_FKEY_F21 = 0xe028u,
GLFW_FKEY_F22 = 0xe029u,
GLFW_FKEY_F23 = 0xe02au,
GLFW_FKEY_F24 = 0xe02bu,
GLFW_FKEY_F25 = 0xe02cu,
GLFW_FKEY_F26 = 0xe02du,
GLFW_FKEY_F27 = 0xe02eu,
GLFW_FKEY_F28 = 0xe02fu,
GLFW_FKEY_F29 = 0xe030u,
GLFW_FKEY_F30 = 0xe031u,
GLFW_FKEY_F31 = 0xe032u,
GLFW_FKEY_F32 = 0xe033u,
GLFW_FKEY_F33 = 0xe034u,
GLFW_FKEY_F34 = 0xe035u,
GLFW_FKEY_F35 = 0xe036u,
GLFW_FKEY_KP_0 = 0xe037u,
GLFW_FKEY_KP_1 = 0xe038u,
GLFW_FKEY_KP_2 = 0xe039u,
GLFW_FKEY_KP_3 = 0xe03au,
GLFW_FKEY_KP_4 = 0xe03bu,
GLFW_FKEY_KP_5 = 0xe03cu,
GLFW_FKEY_KP_6 = 0xe03du,
GLFW_FKEY_KP_7 = 0xe03eu,
GLFW_FKEY_KP_8 = 0xe03fu,
GLFW_FKEY_KP_9 = 0xe040u,
GLFW_FKEY_KP_DECIMAL = 0xe041u,
GLFW_FKEY_KP_DIVIDE = 0xe042u,
GLFW_FKEY_KP_MULTIPLY = 0xe043u,
GLFW_FKEY_KP_SUBTRACT = 0xe044u,
GLFW_FKEY_KP_ADD = 0xe045u,
GLFW_FKEY_KP_ENTER = 0xe046u,
GLFW_FKEY_KP_EQUAL = 0xe047u,
GLFW_FKEY_KP_SEPARATOR = 0xe048u,
GLFW_FKEY_KP_LEFT = 0xe049u,
GLFW_FKEY_KP_RIGHT = 0xe04au,
GLFW_FKEY_KP_UP = 0xe04bu,
GLFW_FKEY_KP_DOWN = 0xe04cu,
GLFW_FKEY_KP_PAGE_UP = 0xe04du,
GLFW_FKEY_KP_PAGE_DOWN = 0xe04eu,
GLFW_FKEY_KP_HOME = 0xe04fu,
GLFW_FKEY_KP_END = 0xe050u,
GLFW_FKEY_KP_INSERT = 0xe051u,
GLFW_FKEY_KP_DELETE = 0xe052u,
GLFW_FKEY_KP_BEGIN = 0xe053u,
GLFW_FKEY_MEDIA_PLAY = 0xe054u,
GLFW_FKEY_MEDIA_PAUSE = 0xe055u,
GLFW_FKEY_MEDIA_PLAY_PAUSE = 0xe056u,
GLFW_FKEY_MEDIA_REVERSE = 0xe057u,
GLFW_FKEY_MEDIA_STOP = 0xe058u,
GLFW_FKEY_MEDIA_FAST_FORWARD = 0xe059u,
GLFW_FKEY_MEDIA_REWIND = 0xe05au,
GLFW_FKEY_MEDIA_TRACK_NEXT = 0xe05bu,
GLFW_FKEY_MEDIA_TRACK_PREVIOUS = 0xe05cu,
GLFW_FKEY_MEDIA_RECORD = 0xe05du,
GLFW_FKEY_LOWER_VOLUME = 0xe05eu,
GLFW_FKEY_RAISE_VOLUME = 0xe05fu,
GLFW_FKEY_MUTE_VOLUME = 0xe060u,
GLFW_FKEY_LEFT_SHIFT = 0xe061u,
GLFW_FKEY_LEFT_CONTROL = 0xe062u,
GLFW_FKEY_LEFT_ALT = 0xe063u,
GLFW_FKEY_LEFT_SUPER = 0xe064u,
GLFW_FKEY_LEFT_HYPER = 0xe065u,
GLFW_FKEY_LEFT_META = 0xe066u,
GLFW_FKEY_RIGHT_SHIFT = 0xe067u,
GLFW_FKEY_RIGHT_CONTROL = 0xe068u,
GLFW_FKEY_RIGHT_ALT = 0xe069u,
GLFW_FKEY_RIGHT_SUPER = 0xe06au,
GLFW_FKEY_RIGHT_HYPER = 0xe06bu,
GLFW_FKEY_RIGHT_META = 0xe06cu,
GLFW_FKEY_ISO_LEVEL3_SHIFT = 0xe06du,
GLFW_FKEY_ISO_LEVEL5_SHIFT = 0xe06eu,
GLFW_FKEY_LAST = 0xe06eu
} GLFWFunctionKey;
/* end functional key names */
/*! @} */
@@ -577,33 +475,44 @@ extern "C" {
* If this bit is set one or more Shift keys were held down.
*/
#define GLFW_MOD_SHIFT 0x0001
/*! @brief If this bit is set one or more Control keys were held down.
*
* If this bit is set one or more Control keys were held down.
*/
#define GLFW_MOD_CONTROL 0x0002
/*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/
#define GLFW_MOD_ALT 0x0004
#define GLFW_MOD_ALT 0x0002
/*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/
#define GLFW_MOD_CONTROL 0x0004
/*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.
*/
#define GLFW_MOD_SUPER 0x0008
/*! @brief If this bit is set one or more Hyper keys were held down.
*
* If this bit is set one or more Hyper keys were held down.
*/
#define GLFW_MOD_HYPER 0x0010
/*! @brief If this bit is set one or more Meta keys were held down.
*
* If this bit is set one or more Meta keys were held down.
*/
#define GLFW_MOD_META 0x0020
/*! @brief If this bit is set the Caps Lock key is enabled.
*
* If this bit is set the Caps Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_CAPS_LOCK 0x0010
#define GLFW_MOD_CAPS_LOCK 0x0040
/*! @brief If this bit is set the Num Lock key is enabled.
*
* If this bit is set the Num Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_NUM_LOCK 0x0020
#define GLFW_MOD_NUM_LOCK 0x0080
#define GLFW_LOCK_MASK (GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK)
/*! @} */
@@ -1202,6 +1111,7 @@ typedef enum {
*/
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
#define GLFW_DEBUG_KEYBOARD 0x00050003
#define GLFW_DEBUG_RENDERING 0x00050004
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
@@ -1285,16 +1195,43 @@ typedef struct GLFWwindow GLFWwindow;
*/
typedef struct GLFWcursor GLFWcursor;
typedef enum {
GLFW_RELEASE = 0,
GLFW_PRESS = 1,
GLFW_REPEAT = 2
} GLFWKeyAction;
typedef enum {
GLFW_IME_NONE,
GLFW_IME_PREEDIT_CHANGED,
GLFW_IME_COMMIT_TEXT
} GLFWIMEState;
typedef enum {
GLFW_IME_UPDATE_FOCUS = 1,
GLFW_IME_UPDATE_CURSOR_POSITION = 2
} GLFWIMEUpdateType;
typedef struct GLFWIMEUpdateEvent {
GLFWIMEUpdateType type;
const char *before_text, *at_text, *after_text;
bool focused;
struct {
int left, top, width, height;
} cursor;
} GLFWIMEUpdateEvent;
typedef struct GLFWkeyevent
{
// The [keyboard key](@ref keys) that was pressed or released.
int key;
uint32_t key, shifted_key, alternate_key;
// The platform-specific identifier of the key.
int native_key;
// The event action. Either `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
int action;
GLFWKeyAction action;
// Bit field describing which [modifier keys](@ref mods) were held down.
int mods;
@@ -1303,9 +1240,9 @@ typedef struct GLFWkeyevent
const char *text;
// Used for Input Method events. Zero for normal key events.
// A value of 1 means the pre-edit text for the input event has been changed.
// A value of 2 means the text should be committed.
int ime_state;
// A value of GLFW_IME_PREEDIT_CHANGED means the pre-edit text for the input event has been changed.
// A value of GLFW_IME_COMMIT_TEXT means the text should be committed.
GLFWIMEState ime_state;
} GLFWkeyevent;
/*! @brief The function pointer type for error callbacks.
@@ -1661,7 +1598,7 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int);
* This is the function pointer type for key callbacks. A keyboard
* key callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int key, int native_key, int action, int mods)
* void function_name(GLFWwindow* window, uint32_t key, int native_key, int action, int mods)
* @endcode
* 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.
@@ -1761,6 +1698,7 @@ typedef void (* GLFWjoystickfun)(int,int);
typedef void (* GLFWuserdatafun)(unsigned long long, void*);
typedef void (* GLFWtickcallback)(void*);
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
/*! @brief Video mode type.
*
@@ -1917,6 +1855,7 @@ GLFWAPI void glfwStopMainLoop(void);
GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void * callback_data, GLFWuserdatafun free_callback);
GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled);
GLFWAPI void glfwRemoveTimer(unsigned long long);
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
/*! @brief Terminates the GLFW library.
*
@@ -4300,108 +4239,19 @@ GLFWAPI int glfwRawMouseMotionSupported(void);
* __Do not use this function__ for [text input](@ref input_char). You will
* break text input for many languages even if it happens to work for yours.
*
* If the key is `GLFW_KEY_UNKNOWN`, the keycode is used to identify the key,
* If the key is `0`, the keycode is used to identify the key,
* otherwise the keycode is ignored. If you specify a non-printable key, or
* `GLFW_KEY_UNKNOWN` and a keycode that maps to a non-printable key, this
* `0` and a keycode that maps to a non-printable key, this
* function returns `NULL` but does not emit an error.
*
* This behavior allows you to always pass in the arguments in the
* [key callback](@ref input_key) without modification.
*
* The printable keys are:
* - `GLFW_KEY_SPACE`
* - `GLFW_KEY_EXCLAM`
* - `GLFW_KEY_DOUBLE_QUOTE`
* - `GLFW_KEY_NUMBER_SIGN`
* - `GLFW_KEY_DOLLAR`
* - `GLFW_KEY_AMPERSAND`
* - `GLFW_KEY_APOSTROPHE`
* - `GLFW_KEY_PARENTHESIS_LEFT`
* - `GLFW_KEY_PARENTHESIS_RIGHT`
* - `GLFW_KEY_PLUS`
* - `GLFW_KEY_COMMA`
* - `GLFW_KEY_MINUS`
* - `GLFW_KEY_PERIOD`
* - `GLFW_KEY_SLASH`
* - `GLFW_KEY_0` to `GLFW_KEY_9`
* - `GLFW_KEY_COLON`
* - `GLFW_KEY_SEMICOLON`
* - `GLFW_KEY_LESS`
* - `GLFW_KEY_EQUAL`
* - `GLFW_KEY_GREATER`
* - `GLFW_KEY_AT`
* - `GLFW_KEY_A` to `GLFW_KEY_Z`
* - `GLFW_KEY_LEFT_BRACKET`
* - `GLFW_KEY_BACKSLASH`
* - `GLFW_KEY_RIGHT_BRACKET`
* - `GLFW_KEY_CIRCUMFLEX`
* - `GLFW_KEY_UNDERSCORE`
* - `GLFW_KEY_GRAVE_ACCENT`
* - `GLFW_KEY_WORLD_1`
* - `GLFW_KEY_WORLD_2`
* - `GLFW_KEY_PARAGRAPH`
* - `GLFW_KEY_MASCULINE`
* - `GLFW_KEY_A_GRAVE`
* - `GLFW_KEY_A_DIAERESIS`
* - `GLFW_KEY_A_RING`
* - `GLFW_KEY_AE`
* - `GLFW_KEY_C_CEDILLA`
* - `GLFW_KEY_E_GRAVE`
* - `GLFW_KEY_E_ACUTE`
* - `GLFW_KEY_I_GRAVE`
* - `GLFW_KEY_N_TILDE`
* - `GLFW_KEY_O_GRAVE`
* - `GLFW_KEY_O_DIAERESIS`
* - `GLFW_KEY_O_SLASH`
* - `GLFW_KEY_U_GRAVE`
* - `GLFW_KEY_U_DIAERESIS`
* - `GLFW_KEY_S_SHARP`
* - `GLFW_KEY_CYRILLIC_A`
* - `GLFW_KEY_CYRILLIC_BE`
* - `GLFW_KEY_CYRILLIC_VE`
* - `GLFW_KEY_CYRILLIC_GHE`
* - `GLFW_KEY_CYRILLIC_DE`
* - `GLFW_KEY_CYRILLIC_IE`
* - `GLFW_KEY_CYRILLIC_ZHE`
* - `GLFW_KEY_CYRILLIC_ZE`
* - `GLFW_KEY_CYRILLIC_I`
* - `GLFW_KEY_CYRILLIC_SHORT_I`
* - `GLFW_KEY_CYRILLIC_KA`
* - `GLFW_KEY_CYRILLIC_EL`
* - `GLFW_KEY_CYRILLIC_EM`
* - `GLFW_KEY_CYRILLIC_EN`
* - `GLFW_KEY_CYRILLIC_O`
* - `GLFW_KEY_CYRILLIC_PE`
* - `GLFW_KEY_CYRILLIC_ER`
* - `GLFW_KEY_CYRILLIC_ES`
* - `GLFW_KEY_CYRILLIC_TE`
* - `GLFW_KEY_CYRILLIC_U`
* - `GLFW_KEY_CYRILLIC_EF`
* - `GLFW_KEY_CYRILLIC_HA`
* - `GLFW_KEY_CYRILLIC_TSE`
* - `GLFW_KEY_CYRILLIC_CHE`
* - `GLFW_KEY_CYRILLIC_SHA`
* - `GLFW_KEY_CYRILLIC_SHCHA`
* - `GLFW_KEY_CYRILLIC_HARD_SIGN`
* - `GLFW_KEY_CYRILLIC_YERU`
* - `GLFW_KEY_CYRILLIC_SOFT_SIGN`
* - `GLFW_KEY_CYRILLIC_E`
* - `GLFW_KEY_CYRILLIC_YU`
* - `GLFW_KEY_CYRILLIC_YA`
* - `GLFW_KEY_CYRILLIC_IO`
* - `GLFW_KEY_KP_0` to `GLFW_KEY_KP_9`
* - `GLFW_KEY_KP_DECIMAL`
* - `GLFW_KEY_KP_DIVIDE`
* - `GLFW_KEY_KP_MULTIPLY`
* - `GLFW_KEY_KP_SUBTRACT`
* - `GLFW_KEY_KP_ADD`
* - `GLFW_KEY_KP_EQUAL`
*
* Names for printable keys depend on keyboard layout, while names for
* non-printable keys are the same across layouts but depend on the application
* language and should be localized along with other user interface text.
*
* @param[in] key The key to query, or `GLFW_KEY_UNKNOWN`.
* @param[in] key The key to query, or `0`.
* @param[in] native_key The platform-specifc identifier of the key to query.
* @return The UTF-8 encoded, layout-specific name of the key, or `NULL`.
*
@@ -4422,13 +4272,13 @@ GLFWAPI int glfwRawMouseMotionSupported(void);
*
* @ingroup input
*/
GLFWAPI const char* glfwGetKeyName(int key, int native_key);
GLFWAPI const char* glfwGetKeyName(uint32_t key, int native_key);
/*! @brief Returns the platform-specific identifier of the specified key.
*
* This function returns the platform-specific identifier of the specified key.
*
* If the key is `GLFW_KEY_UNKNOWN` or does not exist on the keyboard this
* If the key is `0` or does not exist on the keyboard this
* method will return `-1`.
*
* @param[in] key Any [named key](@ref keys).
@@ -4446,7 +4296,7 @@ GLFWAPI const char* glfwGetKeyName(int key, int native_key);
*
* @ingroup input
*/
GLFWAPI int glfwGetNativeKeyForKey(int key);
GLFWAPI int glfwGetNativeKeyForKey(uint32_t key);
/*! @brief Returns the last reported state of a keyboard key for the specified
* window.
@@ -4470,7 +4320,7 @@ GLFWAPI int glfwGetNativeKeyForKey(int key);
* __Do not use this function__ to implement [text input](@ref input_char).
*
* @param[in] window The desired window.
* @param[in] key The desired [keyboard key](@ref keys). `GLFW_KEY_UNKNOWN` is
* @param[in] key The desired [keyboard key](@ref keys). `0` is
* not a valid key for this function.
* @return One of `GLFW_PRESS` or `GLFW_RELEASE`.
*
@@ -4486,7 +4336,7 @@ GLFWAPI int glfwGetNativeKeyForKey(int key);
*
* @ingroup input
*/
GLFWAPI int glfwGetKey(GLFWwindow* window, int key);
GLFWAPI GLFWKeyAction glfwGetKey(GLFWwindow* window, uint32_t key);
/*! @brief Returns the last reported state of a mouse button for the specified
* window.
@@ -4722,16 +4572,12 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboard
* Used to notify the IME system of changes in state such as focus gained/lost
* and text cursor position.
*
* @param which: What data to notify. 1 means focus and 2 means cursor position.
* @param a, b, c, d: Interpreted based on the value of which. When which is 1
* a is interpreted as a boolean indicating focus gained/lost. When which is 2
* a, b, c, d are the cursor x, y, width and height values (in the window co-ordinate
* system).
* @param ev: What data to notify.
*
* @ingroup input
* @since Added in version 4.0
*/
GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int c, int d);
GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, const GLFWIMEUpdateEvent *ev);
/*! @brief Sets the mouse button callback.

10
glfw/ibus_glfw.c vendored
View File

@@ -107,11 +107,10 @@ get_ibus_text_from_message(DBusMessage *msg) {
}
static inline void
send_text(const char *text, int ime_state) {
send_text(const char *text, GLFWIMEState ime_state) {
_GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) {
GLFWkeyevent fake_ev;
_glfwInitializeKeyEvent(&fake_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0);
GLFWkeyevent fake_ev = {.action = GLFW_PRESS};
fake_ev.text = text;
fake_ev.ime_state = ime_state;
w->callbacks.keyboard((GLFWwindow*) w, &fake_ev);
@@ -131,11 +130,11 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data)
case 0:
text = get_ibus_text_from_message(msg);
debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)");
send_text(text, 2);
send_text(text, GLFW_IME_COMMIT_TEXT);
break;
case 1:
text = get_ibus_text_from_message(msg);
send_text(text, 1);
send_text(text, GLFW_IME_PREEDIT_CHANGED);
debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)");
break;
case 2:
@@ -390,6 +389,7 @@ ibus_key_state(unsigned int glfw_modifiers, int action) {
M(ALT, IBUS_MOD1_MASK);
M(NUM_LOCK, IBUS_MOD2_MASK);
M(SUPER, IBUS_MOD4_MASK);
/* To do: figure out how to get super/hyper/meta */
#undef M
return ans;
}

26
glfw/init.c vendored
View File

@@ -50,14 +50,14 @@ _GLFWlibrary _glfw = { false };
//
static _GLFWerror _glfwMainThreadError;
static GLFWerrorfun _glfwErrorCallback;
static _GLFWinitconfig _glfwInitHints =
{
true, // hat buttons
GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend
false, // debug keyboard
{
true, // macOS menu bar
true // macOS bundle chdir
static _GLFWinitconfig _glfwInitHints = {
.hatButtons = true,
.angleType = GLFW_ANGLE_PLATFORM_TYPE_NONE,
.debugKeyboard = false,
.debugRendering = false,
.ns = {
.menubar = true, // macOS menu bar
.chdir = true // macOS bundle chdir
}
};
@@ -285,6 +285,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
case GLFW_DEBUG_KEYBOARD:
_glfwInitHints.debugKeyboard = value;
return;
case GLFW_DEBUG_RENDERING:
_glfwInitHints.debugRendering = value;
return;
case GLFW_COCOA_CHDIR_RESOURCES:
_glfwInitHints.ns.chdir = value;
return;
@@ -375,3 +378,10 @@ GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationc
_GLFW_SWAP_POINTERS(_glfw.callbacks.application_close, cbfun);
return cbfun;
}
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.draw_text, cbfun);
return cbfun;
}

455
glfw/input.c vendored
View File

@@ -273,34 +273,62 @@ static bool parseMapping(_GLFWmapping* mapping, const char* string)
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods)
{
ev->key = key;
ev->native_key = native_key;
ev->action = action;
ev->mods = mods;
ev->text = NULL;
ev->ime_state = 0;
static void
set_key_action(_GLFWwindow *window, uint32_t key, int val, int idx) {
const unsigned sz = arraysz(window->activated_keys);
if (idx < 0) {
for (unsigned i = 0; i < sz; i++) {
if (window->activated_keys[i].key == 0) {
idx = i;
break;
}
}
if (idx < 0) {
idx = sz - 1;
memmove(window->activated_keys, window->activated_keys + 1, sizeof(window->activated_keys[0]) * (sz - 1));
window->activated_keys[sz - 1].key = key;
}
}
if (val == GLFW_RELEASE) {
memset(window->activated_keys + idx, 0, sizeof(window->activated_keys[0]));
if (idx < (int)sz - 1) {
memmove(window->activated_keys + idx, window->activated_keys + idx + 1, sizeof(window->activated_keys[0]) * (sz - 1 - idx));
memset(window->activated_keys + sz - 1, 0, sizeof(window->activated_keys[0]));
}
} else {
window->activated_keys[idx].key = key;
window->activated_keys[idx].action = val;
}
}
// Notifies shared code of a physical key event
//
void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev)
{
if (ev->key >= 0 && ev->key <= GLFW_KEY_LAST)
if (ev->key > 0)
{
bool repeated = false;
int idx = -1;
int current_action = GLFW_RELEASE;
const unsigned sz = arraysz(window->activated_keys);
for (unsigned i = 0; i < sz; i++) {
if (window->activated_keys[i].key == ev->key) {
idx = i;
current_action = window->activated_keys[i].action;
break;
}
}
if (ev->action == GLFW_RELEASE && window->keys[ev->key] == GLFW_RELEASE)
if (ev->action == GLFW_RELEASE && current_action == GLFW_RELEASE)
return;
if (ev->action == GLFW_PRESS && window->keys[ev->key] == GLFW_PRESS)
if (ev->action == GLFW_PRESS && current_action == GLFW_PRESS)
repeated = true;
if (ev->action == GLFW_RELEASE && window->stickyKeys)
window->keys[ev->key] = _GLFW_STICK;
set_key_action(window, ev->key, _GLFW_STICK, idx);
else
window->keys[ev->key] = (char) ev->action;
set_key_action(window, ev->key, ev->action, idx);
if (repeated)
ev->action = GLFW_REPEAT;
@@ -463,200 +491,156 @@ void _glfwFreeJoystick(_GLFWjoystick* js)
memset(js, 0, sizeof(_GLFWjoystick));
}
const char* _glfwGetKeyName(int key)
unsigned int
encode_utf8(uint32_t ch, char* dest) {
if (ch < 0x80) {
dest[0] = (char)ch;
return 1;
}
if (ch < 0x800) {
dest[0] = (ch>>6) | 0xC0;
dest[1] = (ch & 0x3F) | 0x80;
return 2;
}
if (ch < 0x10000) {
dest[0] = (ch>>12) | 0xE0;
dest[1] = ((ch>>6) & 0x3F) | 0x80;
dest[2] = (ch & 0x3F) | 0x80;
return 3;
}
if (ch < 0x110000) {
dest[0] = (ch>>18) | 0xF0;
dest[1] = ((ch>>12) & 0x3F) | 0x80;
dest[2] = ((ch>>6) & 0x3F) | 0x80;
dest[3] = (ch & 0x3F) | 0x80;
return 4;
}
return 0;
}
const char*
_glfwGetKeyName(int key)
{
switch (key)
{
// Printable keys
case GLFW_KEY_SPACE: return "SPACE";
case GLFW_KEY_EXCLAM: return "EXCLAM";
case GLFW_KEY_DOUBLE_QUOTE: return "DOUBLE_QUOTE";
case GLFW_KEY_NUMBER_SIGN: return "NUMBER_SIGN";
case GLFW_KEY_DOLLAR: return "DOLLAR";
case GLFW_KEY_AMPERSAND: return "AMPERSAND";
case GLFW_KEY_APOSTROPHE: return "APOSTROPHE";
case GLFW_KEY_PARENTHESIS_LEFT: return "PARENTHESIS_LEFT";
case GLFW_KEY_PARENTHESIS_RIGHT: return "PARENTHESIS_RIGHT";
case GLFW_KEY_PLUS: return "PLUS";
case GLFW_KEY_COMMA: return "COMMA";
case GLFW_KEY_MINUS: return "MINUS";
case GLFW_KEY_PERIOD: return "PERIOD";
case GLFW_KEY_SLASH: return "SLASH";
case GLFW_KEY_0: return "0";
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_COLON: return "COLON";
case GLFW_KEY_SEMICOLON: return "SEMICOLON";
case GLFW_KEY_LESS: return "LESS";
case GLFW_KEY_EQUAL: return "EQUAL";
case GLFW_KEY_GREATER: return "GREATER";
case GLFW_KEY_AT: return "AT";
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_LEFT_BRACKET: return "LEFT_BRACKET";
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT_BRACKET";
case GLFW_KEY_CIRCUMFLEX: return "CIRCUMFLEX";
case GLFW_KEY_UNDERSCORE: return "UNDERSCORE";
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE_ACCENT";
case GLFW_KEY_WORLD_1: return "WORLD_1";
case GLFW_KEY_WORLD_2: return "WORLD_2";
case GLFW_KEY_PARAGRAPH: return "PARAGRAPH";
case GLFW_KEY_MASCULINE: return "MASCULINE";
case GLFW_KEY_A_GRAVE: return "A_GRAVE";
case GLFW_KEY_A_DIAERESIS: return "A_DIAERESIS";
case GLFW_KEY_A_RING: return "A_RING";
case GLFW_KEY_AE: return "AE";
case GLFW_KEY_C_CEDILLA: return "C_CEDILLA";
case GLFW_KEY_E_GRAVE: return "E_GRAVE";
case GLFW_KEY_E_ACUTE: return "E_ACUTE";
case GLFW_KEY_I_GRAVE: return "I_GRAVE";
case GLFW_KEY_N_TILDE: return "N_TILDE";
case GLFW_KEY_O_GRAVE: return "O_GRAVE";
case GLFW_KEY_O_DIAERESIS: return "O_DIAERESIS";
case GLFW_KEY_O_SLASH: return "O_SLASH";
case GLFW_KEY_U_GRAVE: return "U_GRAVE";
case GLFW_KEY_U_DIAERESIS: return "U_DIAERESIS";
case GLFW_KEY_S_SHARP: return "S_SHARP";
case GLFW_KEY_CYRILLIC_A: return "CYRILLIC_A";
case GLFW_KEY_CYRILLIC_BE: return "CYRILLIC_BE";
case GLFW_KEY_CYRILLIC_VE: return "CYRILLIC_VE";
case GLFW_KEY_CYRILLIC_GHE: return "CYRILLIC_GHE";
case GLFW_KEY_CYRILLIC_DE: return "CYRILLIC_DE";
case GLFW_KEY_CYRILLIC_IE: return "CYRILLIC_IE";
case GLFW_KEY_CYRILLIC_ZHE: return "CYRILLIC_ZHE";
case GLFW_KEY_CYRILLIC_ZE: return "CYRILLIC_ZE";
case GLFW_KEY_CYRILLIC_I: return "CYRILLIC_I";
case GLFW_KEY_CYRILLIC_SHORT_I: return "CYRILLIC_SHORT_I";
case GLFW_KEY_CYRILLIC_KA: return "CYRILLIC_KA";
case GLFW_KEY_CYRILLIC_EL: return "CYRILLIC_EL";
case GLFW_KEY_CYRILLIC_EM: return "CYRILLIC_EM";
case GLFW_KEY_CYRILLIC_EN: return "CYRILLIC_EN";
case GLFW_KEY_CYRILLIC_O: return "CYRILLIC_O";
case GLFW_KEY_CYRILLIC_PE: return "CYRILLIC_PE";
case GLFW_KEY_CYRILLIC_ER: return "CYRILLIC_ER";
case GLFW_KEY_CYRILLIC_ES: return "CYRILLIC_ES";
case GLFW_KEY_CYRILLIC_TE: return "CYRILLIC_TE";
case GLFW_KEY_CYRILLIC_U: return "CYRILLIC_U";
case GLFW_KEY_CYRILLIC_EF: return "CYRILLIC_EF";
case GLFW_KEY_CYRILLIC_HA: return "CYRILLIC_HA";
case GLFW_KEY_CYRILLIC_TSE: return "CYRILLIC_TSE";
case GLFW_KEY_CYRILLIC_CHE: return "CYRILLIC_CHE";
case GLFW_KEY_CYRILLIC_SHA: return "CYRILLIC_SHA";
case GLFW_KEY_CYRILLIC_SHCHA: return "CYRILLIC_SHCHA";
case GLFW_KEY_CYRILLIC_HARD_SIGN: return "CYRILLIC_HARD_SIGN";
case GLFW_KEY_CYRILLIC_YERU: return "CYRILLIC_YERU";
case GLFW_KEY_CYRILLIC_SOFT_SIGN: return "CYRILLIC_SOFT_SIGN";
case GLFW_KEY_CYRILLIC_E: return "CYRILLIC_E";
case GLFW_KEY_CYRILLIC_YU: return "CYRILLIC_YU";
case GLFW_KEY_CYRILLIC_YA: return "CYRILLIC_YA";
case GLFW_KEY_CYRILLIC_IO: return "CYRILLIC_IO";
// 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 MULTIPLY";
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";
/* start functional key names (auto generated by gen-key-constants.py do not edit) */
case GLFW_FKEY_ESCAPE: return "ESCAPE";
case GLFW_FKEY_ENTER: return "ENTER";
case GLFW_FKEY_TAB: return "TAB";
case GLFW_FKEY_BACKSPACE: return "BACKSPACE";
case GLFW_FKEY_INSERT: return "INSERT";
case GLFW_FKEY_DELETE: return "DELETE";
case GLFW_FKEY_LEFT: return "LEFT";
case GLFW_FKEY_RIGHT: return "RIGHT";
case GLFW_FKEY_UP: return "UP";
case GLFW_FKEY_DOWN: return "DOWN";
case GLFW_FKEY_PAGE_UP: return "PAGE_UP";
case GLFW_FKEY_PAGE_DOWN: return "PAGE_DOWN";
case GLFW_FKEY_HOME: return "HOME";
case GLFW_FKEY_END: return "END";
case GLFW_FKEY_CAPS_LOCK: return "CAPS_LOCK";
case GLFW_FKEY_SCROLL_LOCK: return "SCROLL_LOCK";
case GLFW_FKEY_NUM_LOCK: return "NUM_LOCK";
case GLFW_FKEY_PRINT_SCREEN: return "PRINT_SCREEN";
case GLFW_FKEY_PAUSE: return "PAUSE";
case GLFW_FKEY_MENU: return "MENU";
case GLFW_FKEY_F1: return "F1";
case GLFW_FKEY_F2: return "F2";
case GLFW_FKEY_F3: return "F3";
case GLFW_FKEY_F4: return "F4";
case GLFW_FKEY_F5: return "F5";
case GLFW_FKEY_F6: return "F6";
case GLFW_FKEY_F7: return "F7";
case GLFW_FKEY_F8: return "F8";
case GLFW_FKEY_F9: return "F9";
case GLFW_FKEY_F10: return "F10";
case GLFW_FKEY_F11: return "F11";
case GLFW_FKEY_F12: return "F12";
case GLFW_FKEY_F13: return "F13";
case GLFW_FKEY_F14: return "F14";
case GLFW_FKEY_F15: return "F15";
case GLFW_FKEY_F16: return "F16";
case GLFW_FKEY_F17: return "F17";
case GLFW_FKEY_F18: return "F18";
case GLFW_FKEY_F19: return "F19";
case GLFW_FKEY_F20: return "F20";
case GLFW_FKEY_F21: return "F21";
case GLFW_FKEY_F22: return "F22";
case GLFW_FKEY_F23: return "F23";
case GLFW_FKEY_F24: return "F24";
case GLFW_FKEY_F25: return "F25";
case GLFW_FKEY_F26: return "F26";
case GLFW_FKEY_F27: return "F27";
case GLFW_FKEY_F28: return "F28";
case GLFW_FKEY_F29: return "F29";
case GLFW_FKEY_F30: return "F30";
case GLFW_FKEY_F31: return "F31";
case GLFW_FKEY_F32: return "F32";
case GLFW_FKEY_F33: return "F33";
case GLFW_FKEY_F34: return "F34";
case GLFW_FKEY_F35: return "F35";
case GLFW_FKEY_KP_0: return "KP_0";
case GLFW_FKEY_KP_1: return "KP_1";
case GLFW_FKEY_KP_2: return "KP_2";
case GLFW_FKEY_KP_3: return "KP_3";
case GLFW_FKEY_KP_4: return "KP_4";
case GLFW_FKEY_KP_5: return "KP_5";
case GLFW_FKEY_KP_6: return "KP_6";
case GLFW_FKEY_KP_7: return "KP_7";
case GLFW_FKEY_KP_8: return "KP_8";
case GLFW_FKEY_KP_9: return "KP_9";
case GLFW_FKEY_KP_DECIMAL: return "KP_DECIMAL";
case GLFW_FKEY_KP_DIVIDE: return "KP_DIVIDE";
case GLFW_FKEY_KP_MULTIPLY: return "KP_MULTIPLY";
case GLFW_FKEY_KP_SUBTRACT: return "KP_SUBTRACT";
case GLFW_FKEY_KP_ADD: return "KP_ADD";
case GLFW_FKEY_KP_ENTER: return "KP_ENTER";
case GLFW_FKEY_KP_EQUAL: return "KP_EQUAL";
case GLFW_FKEY_KP_SEPARATOR: return "KP_SEPARATOR";
case GLFW_FKEY_KP_LEFT: return "KP_LEFT";
case GLFW_FKEY_KP_RIGHT: return "KP_RIGHT";
case GLFW_FKEY_KP_UP: return "KP_UP";
case GLFW_FKEY_KP_DOWN: return "KP_DOWN";
case GLFW_FKEY_KP_PAGE_UP: return "KP_PAGE_UP";
case GLFW_FKEY_KP_PAGE_DOWN: return "KP_PAGE_DOWN";
case GLFW_FKEY_KP_HOME: return "KP_HOME";
case GLFW_FKEY_KP_END: return "KP_END";
case GLFW_FKEY_KP_INSERT: return "KP_INSERT";
case GLFW_FKEY_KP_DELETE: return "KP_DELETE";
case GLFW_FKEY_KP_BEGIN: return "KP_BEGIN";
case GLFW_FKEY_MEDIA_PLAY: return "MEDIA_PLAY";
case GLFW_FKEY_MEDIA_PAUSE: return "MEDIA_PAUSE";
case GLFW_FKEY_MEDIA_PLAY_PAUSE: return "MEDIA_PLAY_PAUSE";
case GLFW_FKEY_MEDIA_REVERSE: return "MEDIA_REVERSE";
case GLFW_FKEY_MEDIA_STOP: return "MEDIA_STOP";
case GLFW_FKEY_MEDIA_FAST_FORWARD: return "MEDIA_FAST_FORWARD";
case GLFW_FKEY_MEDIA_REWIND: return "MEDIA_REWIND";
case GLFW_FKEY_MEDIA_TRACK_NEXT: return "MEDIA_TRACK_NEXT";
case GLFW_FKEY_MEDIA_TRACK_PREVIOUS: return "MEDIA_TRACK_PREVIOUS";
case GLFW_FKEY_MEDIA_RECORD: return "MEDIA_RECORD";
case GLFW_FKEY_LOWER_VOLUME: return "LOWER_VOLUME";
case GLFW_FKEY_RAISE_VOLUME: return "RAISE_VOLUME";
case GLFW_FKEY_MUTE_VOLUME: return "MUTE_VOLUME";
case GLFW_FKEY_LEFT_SHIFT: return "LEFT_SHIFT";
case GLFW_FKEY_LEFT_CONTROL: return "LEFT_CONTROL";
case GLFW_FKEY_LEFT_ALT: return "LEFT_ALT";
case GLFW_FKEY_LEFT_SUPER: return "LEFT_SUPER";
case GLFW_FKEY_LEFT_HYPER: return "LEFT_HYPER";
case GLFW_FKEY_LEFT_META: return "LEFT_META";
case GLFW_FKEY_RIGHT_SHIFT: return "RIGHT_SHIFT";
case GLFW_FKEY_RIGHT_CONTROL: return "RIGHT_CONTROL";
case GLFW_FKEY_RIGHT_ALT: return "RIGHT_ALT";
case GLFW_FKEY_RIGHT_SUPER: return "RIGHT_SUPER";
case GLFW_FKEY_RIGHT_HYPER: return "RIGHT_HYPER";
case GLFW_FKEY_RIGHT_META: return "RIGHT_META";
case GLFW_FKEY_ISO_LEVEL3_SHIFT: return "ISO_LEVEL3_SHIFT";
case GLFW_FKEY_ISO_LEVEL5_SHIFT: return "ISO_LEVEL5_SHIFT";
/* end functional key names */
case 0: return "UNKNOWN";
}
static char buf[16];
encode_utf8(key, buf);
return buf;
}
// Center the cursor in the content area of the specified window
@@ -736,13 +720,15 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
if (!value)
{
int i;
// Release all sticky keys
for (i = 0; i <= GLFW_KEY_LAST; i++)
for (unsigned i = arraysz(window->activated_keys) - 1; i-- > 0;)
{
if (window->keys[i] == _GLFW_STICK)
window->keys[i] = GLFW_RELEASE;
if (window->activated_keys[i].action == _GLFW_STICK) {
if (i < arraysz(window->activated_keys) - 1) {
memmove(window->activated_keys + i, window->activated_keys + i + 1, sizeof(window->activated_keys[0]) * (arraysz(window->activated_keys) - 1 - i));
}
memset(window->activated_keys + arraysz(window->activated_keys) - 1, 0, sizeof(window->activated_keys[0]));
}
}
}
@@ -798,59 +784,50 @@ GLFWAPI int glfwRawMouseMotionSupported(void)
return _glfwPlatformRawMouseMotionSupported();
}
GLFWAPI const char* glfwGetKeyName(int key, int native_key)
GLFWAPI const char* glfwGetKeyName(uint32_t key, int native_key)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (key) return _glfwGetKeyName(key);
if (key != GLFW_KEY_UNKNOWN)
{
if (key != GLFW_KEY_KP_EQUAL &&
(key < GLFW_KEY_KP_0 || key > GLFW_KEY_KP_ADD) &&
(key < GLFW_KEY_APOSTROPHE || key > GLFW_KEY_LAST_PRINTABLE))
{
return NULL;
}
native_key = _glfwPlatformGetNativeKeyForKey(key);
}
native_key = _glfwPlatformGetNativeKeyForKey(key);
return _glfwPlatformGetNativeKeyName(native_key);
}
GLFWAPI int glfwGetNativeKeyForKey(int key)
GLFWAPI int glfwGetNativeKeyForKey(uint32_t key)
{
_GLFW_REQUIRE_INIT_OR_RETURN(-1);
if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
return GLFW_RELEASE;
}
return _glfwPlatformGetNativeKeyForKey(key);
}
GLFWAPI int glfwGetKey(GLFWwindow* handle, int key)
GLFWAPI GLFWKeyAction glfwGetKey(GLFWwindow* handle, uint32_t key)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_RELEASE);
if (!key) return GLFW_RELEASE;
if (key < GLFW_KEY_SPACE || key > GLFW_KEY_LAST)
{
_glfwInputError(GLFW_INVALID_ENUM, "Invalid key %i", key);
return GLFW_RELEASE;
int current_action = GLFW_RELEASE;
const unsigned sz = arraysz(window->activated_keys);
int idx = -1;
for (unsigned i = 0; i < sz; i++) {
if (window->activated_keys[i].key == key) {
idx = i;
current_action = window->activated_keys[i].action;
break;
}
}
if (window->keys[key] == _GLFW_STICK)
if (current_action == _GLFW_STICK)
{
// Sticky mode: release key now
window->keys[key] = GLFW_RELEASE;
return GLFW_PRESS;
set_key_action(window, key, GLFW_RELEASE, idx);
current_action = GLFW_PRESS;
}
return (int) window->keys[key];
return current_action;
}
GLFWAPI int glfwGetMouseButton(GLFWwindow* handle, int button)
@@ -1036,13 +1013,13 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboard
return cbfun;
}
GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, int which, int a, int b, int c, int d) {
GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, const GLFWIMEUpdateEvent *ev) {
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND) || defined(_GLFW_COCOA)
_glfwPlatformUpdateIMEState(window, which, a, b, c, d);
_glfwPlatformUpdateIMEState(window, ev);
#else
(void)window; (void)which; (void)a; (void)b; (void)c; (void)d;
#endif

15
glfw/internal.h vendored
View File

@@ -276,6 +276,7 @@ struct _GLFWinitconfig
bool hatButtons;
int angleType;
bool debugKeyboard;
bool debugRendering;
struct {
bool menubar;
bool chdir;
@@ -401,6 +402,11 @@ struct _GLFWcontext
_GLFWcontextOSMesa osmesa;
};
typedef struct GLFWKeyState {
uint32_t key;
char action;
} GLFWKeyState;
// Window and context structure
//
struct _GLFWwindow
@@ -431,7 +437,7 @@ struct _GLFWwindow
bool lockKeyMods;
int cursorMode;
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
char keys[GLFW_KEY_LAST + 1];
GLFWKeyState activated_keys[16];
// Virtual cursor position when cursor is disabled
double virtualCursorPosX, virtualCursorPosY;
bool rawMouseMotion;
@@ -609,6 +615,7 @@ struct _GLFWlibrary
GLFWmonitorfun monitor;
GLFWjoystickfun joystick;
GLFWapplicationclosefun application_close;
GLFWdrawtextfun draw_text;
} callbacks;
@@ -649,7 +656,7 @@ void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
const char* _glfwPlatformGetNativeKeyName(int native_key);
int _glfwPlatformGetNativeKeyForKey(int key);
int _glfwPlatformGetNativeKeyForKey(uint32_t key);
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor);
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
@@ -723,7 +730,8 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev);
void _glfwPlatformChangeCursorTheme(void);
void _glfwPlatformPollEvents(void);
void _glfwPlatformWaitEvents(void);
@@ -771,7 +779,6 @@ void _glfwInputWindowDamage(_GLFWwindow* window);
void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods);
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);

109
glfw/linux_desktop_settings.c vendored Normal file
View File

@@ -0,0 +1,109 @@
/*
* linux_cursor_settings.c
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "linux_desktop_settings.h"
#include <stdlib.h>
#include <strings.h>
#include <string.h>
static const char *DESKTOP_SERVICE = "org.freedesktop.portal.Desktop";
static const char *DESKTOP_PATH = "/org/freedesktop/portal/desktop";
static const char *DESKTOP_INTERFACE = "org.freedesktop.portal.Settings";
static const char *GNOME_DESKTOP_NAMESPACE = "org.gnome.desktop.interface";
static char theme_name[64] = {0};
static int theme_size = -1;
static bool gnome_cursor_theme_read = false, gnome_cursor_size_read = false;
static bool
parse_dbus_message_for_type(DBusMessage *const reply, const char *errmsg, const int type, void *value) {
DBusMessageIter iter[3];
dbus_message_iter_init(reply, &iter[0]);
#define FAIL { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", errmsg); return false; }
if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) FAIL;
dbus_message_iter_recurse(&iter[0], &iter[1]);
if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) FAIL;
dbus_message_iter_recurse(&iter[1], &iter[2]);
if (dbus_message_iter_get_arg_type(&iter[2]) != type) FAIL;
dbus_message_iter_get_basic(&iter[2], value);
return true;
#undef FAIL
}
#define HANDLER(name) void name(DBusMessage *msg, const char* errmsg, void *data) { \
(void)data; \
if (errmsg) { \
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \
return; \
}
HANDLER(on_gnome_cursor_theme_read)
const char *name;
if (!parse_dbus_message_for_type(msg, "Failed to get cursor theme name from reply", DBUS_TYPE_STRING, &name)) return;
if (name && name[0]) {
gnome_cursor_theme_read = true;
strncpy(theme_name, name, sizeof(theme_name) - 1);
if (gnome_cursor_size_read) _glfwPlatformChangeCursorTheme();
}
}
HANDLER(on_gnome_cursor_size_read)
int32_t sz;
if (!parse_dbus_message_for_type(msg, "Failed to get cursor theme size from reply", DBUS_TYPE_INT32, &sz)) return;
gnome_cursor_size_read = true;
theme_size = sz;
if (gnome_cursor_theme_read) _glfwPlatformChangeCursorTheme();
}
#undef HANDLER
static bool
call_read(DBusConnection *session_bus, dbus_pending_callback callback, const char *namespace, const char *key) {
return glfw_dbus_call_method_with_reply(
session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "Read", DBUS_TIMEOUT_USE_DEFAULT,
callback, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
}
static void
get_from_gnome(void) {
theme_size = 32;
DBusConnection *session_bus = glfw_dbus_session_bus();
if (session_bus) {
const char *theme_key = "cursor-theme";
call_read(session_bus, on_gnome_cursor_theme_read, GNOME_DESKTOP_NAMESPACE, theme_key);
const char *size_key = "cursor-size";
call_read(session_bus, on_gnome_cursor_size_read, GNOME_DESKTOP_NAMESPACE, size_key);
}
}
void
glfw_current_cursor_theme(const char **theme, int *size) {
*theme = theme_name[0] ? theme_name : NULL;
*size = (theme_size > 0 && theme_size < 2048) ? theme_size : 32;
}
static void
get_cursor_theme_from_env(void) {
const char *q = getenv("XCURSOR_THEME");
if (q) strncpy(theme_name, q, sizeof(theme_name)-1);
const char *env = getenv("XCURSOR_SIZE");
theme_size = 32;
if (env) {
const int retval = atoi(env);
if (retval > 0 && retval < 2048) theme_size = retval;
}
}
void
glfw_initialize_desktop_settings(void) {
get_cursor_theme_from_env();
const char *desktop = getenv("XDG_CURRENT_DESKTOP");
bool is_gnome = desktop && strncasecmp(desktop, "GNOME", sizeof("GNOME") - 1) == 0;
if (is_gnome) get_from_gnome();
}

14
glfw/linux_desktop_settings.h vendored Normal file
View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "dbus_glfw.h"
#include "internal.h"
void glfw_initialize_desktop_settings(void);
void glfw_current_cursor_theme(const char **theme, int *size);

View File

@@ -55,6 +55,7 @@
"wl_platform.h",
"posix_thread.h",
"wl_cursors.h",
"wl_text_input.h",
"xkb_glfw.h",
"dbus_glfw.h",
"ibus_glfw.h",
@@ -64,6 +65,8 @@
"linux_joystick.h",
"null_joystick.h",
"linux_notify.h",
"linux_desktop_settings.h",
"wl_client_side_decorations.h",
"main_loop.h"
],
"protocols": [
@@ -73,13 +76,16 @@
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
"unstable/primary-selection/primary-selection-unstable-v1.xml"
"unstable/primary-selection/primary-selection-unstable-v1.xml",
"unstable/text-input/text-input-unstable-v3.xml"
],
"sources": [
"wl_init.c",
"wl_monitor.c",
"wl_window.c",
"wl_cursors.c",
"wl_text_input.c",
"wl_client_side_decorations.c",
"posix_thread.c",
"xkb_glfw.c",
"dbus_glfw.c",
@@ -88,6 +94,7 @@
"osmesa_context.c",
"backend_utils.c",
"linux_joystick.c",
"linux_desktop_settings.c",
"null_joystick.c",
"linux_notify.c"
]

12
glfw/window.c vendored
View File

@@ -50,21 +50,19 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused)
if (!focused)
{
int key, button;
_glfw.focusedWindowId = 0;
for (key = 0; key <= GLFW_KEY_LAST; key++)
for (unsigned i = 0; i < arraysz(window->activated_keys); i++)
{
if (window->keys[key] == GLFW_PRESS)
if (window->activated_keys[i].key > 0 && window->activated_keys[i].action == GLFW_PRESS)
{
const int native_key = _glfwPlatformGetNativeKeyForKey(key);
GLFWkeyevent ev;
_glfwInitializeKeyEvent(&ev, key, native_key, GLFW_RELEASE, 0);
const int native_key = _glfwPlatformGetNativeKeyForKey(window->activated_keys[i].key);
GLFWkeyevent ev = {.key = window->activated_keys[i].key, .native_key = native_key, .action = GLFW_RELEASE};
_glfwInputKeyboard(window, &ev);
}
}
for (button = 0; button <= GLFW_MOUSE_BUTTON_LAST; button++)
for (int button = 0; button <= GLFW_MOUSE_BUTTON_LAST; button++)
{
if (window->mouseButtons[button] == GLFW_PRESS)
_glfwInputMouseClick(window, button, GLFW_RELEASE, 0);

415
glfw/wl_client_side_decorations.c vendored Normal file
View File

@@ -0,0 +1,415 @@
/*
* wl_client_side_decorations.c
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "wl_client_side_decorations.h"
#include "backend_utils.h"
#include <sys/mman.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
#define decs window->wl.decorations
#define ARGB(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b))
#define A(x) (((x) >> 24) & 0xff)
#define R(x) (((x) >> 16) & 0xff)
#define G(x) (((x) >> 8) & 0xff)
#define B(x) ((x) & 0xff)
#define SWAP(x, y) do { __typeof__(x) SWAP = x; x = y; y = SWAP; } while (0)
static const uint32_t passive_bg_color = 0xffeeeeee;
static const uint32_t active_bg_color = 0xffdddad6;
typedef float kernel_type;
static void
build_blur_kernel(kernel_type *blur_kernel, const size_t size, kernel_type sigma) {
// 1D Normalized Gaussian
const kernel_type half = size / (kernel_type)2;
kernel_type sum = 0;
for (size_t i = 0; i < size; i++) {
kernel_type f = (i - half);
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
sum += blur_kernel[i];
}
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
}
static void
blur_mask(kernel_type *image_data, ssize_t width, ssize_t height, ssize_t kernel_size, kernel_type sigma, kernel_type *scratch, kernel_type *blur_kernel, ssize_t margin) {
(void)margin;
build_blur_kernel(blur_kernel, kernel_size, sigma);
const size_t half = kernel_size / 2;
for (ssize_t y = 0; y < height; y++) {
kernel_type *s = image_data + y * width, *d = scratch + y * width;
for (ssize_t x = 0; x < width; x++) {
kernel_type a = 0;
for (ssize_t k = 0; k < kernel_size; k++) {
const ssize_t px = x + k - half;
if (0 <= px && px < width) a += s[px] * blur_kernel[k];
}
d[x] = a;
}
}
for (ssize_t y = 0; y < height; y++) {
kernel_type *d = image_data + y * width;
for (ssize_t x = 0; x < width; x++) {
kernel_type a = 0;
for (ssize_t k = 0; k < kernel_size; k++) {
const ssize_t py = y + k - half;
if (0 <= py && py < height) {
kernel_type *s = scratch + py * width;
a += s[x] * blur_kernel[k];
}
}
d[x] = a;
}
}
}
static kernel_type*
create_shadow_mask(size_t width, size_t height, size_t margin, size_t kernel_size, kernel_type base_alpha, kernel_type sigma) {
kernel_type *mask = calloc(sizeof(kernel_type), 2 * width * height + kernel_size);
if (!mask) return NULL;
for (size_t y = margin; y < height - margin; y++) {
kernel_type *row = mask + y * width;
for (size_t x = margin; x < width - margin; x++) row[x] = base_alpha;
}
blur_mask(mask, width, height, kernel_size, sigma, mask + width * height, (kernel_type*)(mask + 2 * width * height), margin);
return mask;
}
static void
swap_buffers(_GLFWWaylandBufferPair *pair) {
SWAP(pair->front, pair->back);
SWAP(pair->data.front, pair->data.back);
}
static size_t
init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, unsigned scale) {
memset(pair, 0, sizeof(_GLFWWaylandBufferPair));
pair->width = width * scale;
pair->height = height * scale;
pair->stride = 4 * pair->width;
pair->size_in_bytes = pair->stride * pair->height;
return 2 * pair->size_in_bytes;
}
static void
alloc_buffer_pair(_GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
pair->data.a = data + *offset;
pair->a = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
*offset += pair->size_in_bytes;
pair->data.b = data + *offset;
pair->b = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
*offset += pair->size_in_bytes;
pair->front = pair->a; pair->back = pair->b;
pair->data.front = pair->data.a; pair->data.back = pair->data.b;
}
#define st decs.shadow_tile
void
initialize_csd_metrics(_GLFWwindow *window) {
decs.metrics.width = 12;
decs.metrics.top = 36;
decs.metrics.visible_titlebar_height = window->wl.decorations.metrics.top - window->wl.decorations.metrics.width;
decs.metrics.horizontal = 2 * window->wl.decorations.metrics.width;
decs.metrics.vertical = window->wl.decorations.metrics.width + window->wl.decorations.metrics.top;
}
static size_t
create_shadow_tile(_GLFWwindow *window) {
const size_t margin = decs.bottom.buffer.height;
if (st.data && st.for_decoration_size == margin) return margin;
st.for_decoration_size = margin;
free(st.data);
st.segments = 7;
st.stride = st.segments * margin;
st.corner_size = margin * (st.segments - 1) / 2;
kernel_type* mask = create_shadow_mask(st.stride, st.stride, margin, 2 * margin + 1, (kernel_type)0.7, 32 * margin);
st.data = malloc(sizeof(uint32_t) * st.stride * st.stride);
if (st.data) for (size_t i = 0; i < st.stride * st.stride; i++) st.data[i] = ((uint8_t)(mask[i] * 255)) << 24;
free(mask);
return margin;
}
static void
render_title_bar(_GLFWwindow *window, bool to_front_buffer) {
const bool is_focused = window->id == _glfw.focusedWindowId;
uint32_t bg_color = is_focused ? active_bg_color : passive_bg_color;
uint8_t *output = to_front_buffer ? decs.top.buffer.data.front : decs.top.buffer.data.back;
// render shadow part
const size_t margin = create_shadow_tile(window);
const size_t edge_segment_size = st.corner_size - margin;
const uint8_t divisor = is_focused ? 1 : 2;
for (size_t y = 0; y < margin; y++) {
// left segment
uint32_t *s = st.data + y * st.stride + margin;
uint32_t *d = (uint32_t*)(output + y * decs.top.buffer.stride);
for (size_t x = 0; x < edge_segment_size; x++) d[x] = (A(s[x]) / divisor) << 24;
// middle segment
s += edge_segment_size;
size_t limit = decs.top.buffer.width > edge_segment_size ? decs.top.buffer.width - edge_segment_size : 0;
for (size_t x = edge_segment_size, sx = 0; x < limit; x++, sx = (sx + 1) % margin) d[x] = (A(s[sx]) / divisor) << 24;
// right segment
s += margin;
for (size_t x = limit; x < decs.top.buffer.width; x++, s++) d[x] = (A(*s) / divisor) << 24;
}
// render text part
output += decs.top.buffer.stride * margin;
if (window->wl.title && window->wl.title[0] && _glfw.callbacks.draw_text) {
uint32_t fg_color = is_focused ? 0xff444444 : 0xff888888;
if (_glfw.callbacks.draw_text((GLFWwindow*)window, window->wl.title, fg_color, bg_color, output, decs.top.buffer.width, decs.top.buffer.height - margin, 0, 0, 0)) return;
}
for (uint32_t *px = (uint32_t*)output, *end = (uint32_t*)(output + decs.top.buffer.size_in_bytes); px < end; px++) {
*px = bg_color;
}
}
static void
update_title_bar(_GLFWwindow *window) {
render_title_bar(window, false);
swap_buffers(&decs.top.buffer);
}
static void
render_edges(_GLFWwindow *window) {
const size_t margin = create_shadow_tile(window);
if (!st.data) return; // out of memory
// bottom edge
uint32_t *src = st.data + (st.segments - 1) * margin * st.stride;
for (size_t y = 0; y < margin; y++) {
uint32_t *d = (uint32_t*)(decs.bottom.buffer.data.front + y * decs.bottom.buffer.stride);
uint32_t *s = src + st.stride * y;
// left corner
for (size_t x = 0; x < st.corner_size && x < decs.bottom.buffer.width; x++) d[x] = s[x];
// middle
size_t pos = st.corner_size, limit = decs.bottom.buffer.width > st.corner_size ? decs.bottom.buffer.width - st.corner_size : 0;
s += st.corner_size;
while (pos < limit) {
uint32_t *p = d + pos;
for (size_t x = 0; x < margin && pos + x < limit; x++) p[x] = s[x];
pos += margin;
}
// right corner
s += margin;
for (size_t x = 0; x < st.corner_size && limit + x < decs.bottom.buffer.width; x++) d[limit + x] = s[x];
}
// upper corners
for (size_t y = 0; y < st.corner_size && y < decs.left.buffer.height; y++) {
uint32_t *left_src = st.data + st.stride * y;
uint32_t *left_dest = (uint32_t*)(decs.left.buffer.data.front + y * decs.left.buffer.stride);
memcpy(left_dest, left_src, margin * sizeof(uint32_t));
uint32_t *right_src = left_src + 2 * st.corner_size;
uint32_t *right_dest = (uint32_t*)(decs.right.buffer.data.front + y * decs.right.buffer.stride);
memcpy(right_dest, right_src, margin * sizeof(uint32_t));
}
// lower corners
size_t src_height = st.corner_size - margin;
size_t dest_top = decs.left.buffer.height > src_height ? decs.left.buffer.height - src_height : 0;
size_t src_top = st.corner_size + margin;
for (size_t src_y = src_top, dest_y = dest_top; src_y < src_top + src_height && dest_y < decs.left.buffer.height; src_y++, dest_y++) {
uint32_t *s = st.data + st.stride * src_y;
uint32_t *d = (uint32_t*)(decs.left.buffer.data.front + dest_y * decs.left.buffer.stride);
memcpy(d, s, margin * sizeof(uint32_t));
s += 2 * st.corner_size;
d = (uint32_t*)(decs.right.buffer.data.front + dest_y * decs.left.buffer.stride);
memcpy(d, s, margin * sizeof(uint32_t));
}
// sides
size_t limit = decs.left.buffer.height > src_height ? decs.left.buffer.height - src_height : 0;
for (size_t dest_y = st.corner_size, src_y = 0; dest_y < limit; dest_y++, src_y = (src_y + 1) % margin) {
uint32_t *src = st.data + (st.corner_size + src_y) * st.stride;
uint32_t *left_dest = (uint32_t*)(decs.left.buffer.data.front + dest_y * decs.left.buffer.stride);
memcpy(left_dest, src, margin * sizeof(uint32_t));
src += 2 * st.corner_size;
uint32_t *right_dest = (uint32_t*)(decs.right.buffer.data.front + dest_y * decs.right.buffer.stride);
memcpy(right_dest, src, margin * sizeof(uint32_t));
}
#define copy(which) for (uint32_t *src = (uint32_t*)decs.which.buffer.data.front, *dest = (uint32_t*)decs.which.buffer.data.back; src < (uint32_t*)(decs.which.buffer.data.front + decs.which.buffer.size_in_bytes); src++, dest++) *dest = (A(*src) / 2 ) << 24;
copy(left); copy(bottom); copy(right);
#undef copy
}
#undef st
static bool
create_shm_buffers(_GLFWwindow* window) {
const unsigned scale = window->wl.scale >= 1 ? window->wl.scale : 1;
const size_t vertical_width = decs.metrics.width, vertical_height = window->wl.height + decs.metrics.top;
const size_t horizontal_height = decs.metrics.width, horizontal_width = window->wl.width + 2 * decs.metrics.width;
decs.mapping.size = 0;
decs.mapping.size += init_buffer_pair(&decs.top.buffer, window->wl.width, decs.metrics.top, scale);
decs.mapping.size += init_buffer_pair(&decs.left.buffer, vertical_width, vertical_height, scale);
decs.mapping.size += init_buffer_pair(&decs.bottom.buffer, horizontal_width, horizontal_height, scale);
decs.mapping.size += init_buffer_pair(&decs.right.buffer, vertical_width, vertical_height, scale);
int fd = createAnonymousFile(decs.mapping.size);
if (fd < 0) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Creating a buffer file for %zu B failed: %s",
decs.mapping.size, strerror(errno));
return false;
}
decs.mapping.data = mmap(NULL, decs.mapping.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (decs.mapping.data == MAP_FAILED) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: mmap failed: %s", strerror(errno));
close(fd);
return false;
}
struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, decs.mapping.size);
close(fd);
size_t offset = 0;
#define a(which) alloc_buffer_pair(&decs.which.buffer, pool, decs.mapping.data, &offset)
a(top); a(left); a(bottom); a(right);
#undef a
wl_shm_pool_destroy(pool);
create_shadow_tile(window);
render_title_bar(window, true);
render_edges(window);
debug("Created decoration buffers at scale: %u vertical_height: %zu horizontal_width: %zu\n", scale, vertical_height, horizontal_width);
return true;
}
void
free_csd_surfaces(_GLFWwindow *window) {
#define d(which) {\
if (decs.which.subsurface) wl_subsurface_destroy(decs.which.subsurface); \
decs.which.subsurface = NULL; \
if (decs.which.surface) wl_surface_destroy(decs.which.surface); \
decs.which.surface = NULL; \
}
d(left); d(top); d(right); d(bottom);
#undef d
}
static void
free_csd_buffers(_GLFWwindow *window) {
#define d(which) { \
if (decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
if (decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \
}
d(left); d(top); d(right); d(bottom);
#undef d
if (decs.mapping.data) munmap(decs.mapping.data, decs.mapping.size);
decs.mapping.data = NULL; decs.mapping.size = 0;
}
static void
position_csd_surface(_GLFWWaylandCSDEdge *s, int x, int y, int scale) {
wl_surface_set_buffer_scale(s->surface, scale);
s->x = x; s->y = y;
wl_subsurface_set_position(s->subsurface, s->x, s->y);
}
static void
create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDEdge *s) {
s->surface = wl_compositor_create_surface(_glfw.wl.compositor);
s->subsurface = wl_subcompositor_get_subsurface(_glfw.wl.subcompositor, s->surface, window->wl.surface);
}
#define damage_csd(which, xbuffer) \
wl_surface_attach(decs.which.surface, xbuffer, 0, 0); \
wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \
wl_surface_commit(decs.which.surface)
bool
ensure_csd_resources(_GLFWwindow *window) {
if (!window->decorated || window->wl.decorations.serverSide) return false;
const bool is_focused = window->id == _glfw.focusedWindowId;
const bool focus_changed = is_focused != decs.for_window_state.focused;
const bool size_changed = (
decs.for_window_state.width != window->wl.width ||
decs.for_window_state.height != window->wl.height ||
decs.for_window_state.scale != window->wl.scale ||
!decs.mapping.data
);
const bool needs_update = focus_changed || size_changed || !decs.left.surface;
if (!needs_update) return false;
if (size_changed) {
free_csd_buffers(window);
if (!create_shm_buffers(window)) return false;
}
int32_t x, y, scale = window->wl.scale < 1 ? 1 : window->wl.scale;
x = 0; y = -decs.metrics.top;
if (!decs.top.surface) create_csd_surfaces(window, &decs.top);
position_csd_surface(&decs.top, x, y, scale);
x = -decs.metrics.width; y = -decs.metrics.top;
if (!decs.left.surface) create_csd_surfaces(window, &decs.left);
position_csd_surface(&decs.left, x, y, scale);
x = -decs.metrics.width; y = window->wl.height;
if (!decs.bottom.surface) create_csd_surfaces(window, &decs.bottom);
position_csd_surface(&decs.bottom, x, y, scale);
x = window->wl.width; y = -decs.metrics.top;
if (!decs.right.surface) create_csd_surfaces(window, &decs.right);
position_csd_surface(&decs.right, x, y, scale);
if (focus_changed) update_title_bar(window);
damage_csd(top, decs.top.buffer.front);
damage_csd(left, is_focused ? decs.left.buffer.front : decs.left.buffer.back);
damage_csd(bottom, is_focused ? decs.bottom.buffer.front : decs.bottom.buffer.back);
damage_csd(right, is_focused ? decs.right.buffer.front : decs.right.buffer.back);
decs.for_window_state.width = window->wl.width;
decs.for_window_state.height = window->wl.height;
decs.for_window_state.scale = window->wl.scale;
decs.for_window_state.focused = is_focused;
return true;
}
void
free_all_csd_resources(_GLFWwindow *window) {
free_csd_surfaces(window);
free_csd_buffers(window);
if (decs.shadow_tile.data) free(decs.shadow_tile.data);
decs.shadow_tile.data = NULL;
}
void
change_csd_title(_GLFWwindow *window) {
if (!window->decorated || window->wl.decorations.serverSide) return;
if (ensure_csd_resources(window)) return; // CSD were re-rendered for other reasons
if (decs.top.surface) {
update_title_bar(window);
damage_csd(top, decs.top.buffer.front);
}
}
void
set_csd_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) {
bool has_csd = window->decorated && !window->wl.decorations.serverSide && window->wl.decorations.left.surface && !(window->wl.toplevel_states & TOPLEVEL_STATE_FULLSCREEN);
bool size_specified_by_compositor = *width > 0 && *height > 0;
if (!size_specified_by_compositor) {
*width = window->wl.user_requested_content_size.width;
*height = window->wl.user_requested_content_size.height;
if (has_csd) *height += decs.metrics.visible_titlebar_height;
}
decs.geometry.x = 0; decs.geometry.y = 0;
decs.geometry.width = *width; decs.geometry.height = *height;
if (has_csd) {
decs.geometry.y = -decs.metrics.visible_titlebar_height;
*height -= decs.metrics.visible_titlebar_height;
}
}

16
glfw/wl_client_side_decorations.h vendored Normal file
View File

@@ -0,0 +1,16 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "internal.h"
void initialize_csd_metrics(_GLFWwindow *window);
void free_all_csd_resources(_GLFWwindow *window);
void free_csd_surfaces(_GLFWwindow *window);
void change_csd_title(_GLFWwindow *window);
bool ensure_csd_resources(_GLFWwindow *window);
void set_csd_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height);

46
glfw/wl_cursors.c vendored
View File

@@ -1,6 +1,7 @@
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
#include "internal.h"
#include "linux_desktop_settings.h"
#include <assert.h>
#include <errno.h>
@@ -8,38 +9,35 @@
#include <stdio.h>
#include <stdlib.h>
static GLFWWLCursorThemes cursor_themes;
static int
pixels_from_scale(int scale) {
static bool queried_env = false;
static int factor = 32;
if (!queried_env) {
const char *env = getenv("XCURSOR_SIZE");
if (env) {
const int retval = atoi(env);
if (retval > 0 && retval < 2048) factor = retval;
}
queried_env = true;
}
int factor;
const char* name;
glfw_current_cursor_theme(&name, &factor);
return factor * scale;
}
struct wl_cursor_theme*
glfw_wlc_theme_for_scale(int scale) {
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
for (size_t i = 0; i < t->count; i++) {
if (t->themes[i].scale == scale) return t->themes[i].theme;
for (size_t i = 0; i < cursor_themes.count; i++) {
if (cursor_themes.themes[i].scale == scale) return cursor_themes.themes[i].theme;
}
if (t->count >= t->capacity) {
t->themes = realloc(t->themes, sizeof(GLFWWLCursorTheme) * (t->count + 16));
if (!t->themes) {
if (cursor_themes.count >= cursor_themes.capacity) {
cursor_themes.themes = realloc(cursor_themes.themes, sizeof(GLFWWLCursorTheme) * (cursor_themes.count + 16));
if (!cursor_themes.themes) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes");
return NULL;
}
t->capacity = t->count + 16;
cursor_themes.capacity = cursor_themes.count + 16;
}
struct wl_cursor_theme *ans = wl_cursor_theme_load(getenv("XCURSOR_THEME"), pixels_from_scale(scale), _glfw.wl.shm);
int factor;
const char* name;
glfw_current_cursor_theme(&name, &factor);
struct wl_cursor_theme *ans = wl_cursor_theme_load(name, pixels_from_scale(scale), _glfw.wl.shm);
if (!ans) {
_glfwInputError(
GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d",
@@ -47,7 +45,7 @@ glfw_wlc_theme_for_scale(int scale) {
);
return NULL;
}
GLFWWLCursorTheme *theme = t->themes + t->count++;
GLFWWLCursorTheme *theme = cursor_themes.themes + cursor_themes.count++;
theme->scale = scale;
theme->theme = ans;
return ans;
@@ -55,11 +53,9 @@ glfw_wlc_theme_for_scale(int scale) {
void
glfw_wlc_destroy(void) {
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
for (size_t i = 0; i < t->count; i++) {
wl_cursor_theme_destroy(t->themes[i].theme);
for (size_t i = 0; i < cursor_themes.count; i++) {
wl_cursor_theme_destroy(cursor_themes.themes[i].theme);
}
free(t->themes);
t->themes = NULL; t->capacity = 0; t->count = 0;
free(cursor_themes.themes);
cursor_themes.themes = NULL; cursor_themes.capacity = 0; cursor_themes.count = 0;
}

144
glfw/wl_init.c vendored
View File

@@ -29,6 +29,8 @@
#define _GNU_SOURCE
#include "internal.h"
#include "backend_utils.h"
#include "wl_client_side_decorations.h"
#include "linux_desktop_settings.h"
#include "../kitty/monotonic.h"
#include <assert.h>
@@ -58,37 +60,21 @@ static inline int min(int n1, int n2)
return n1 < n2 ? n1 : n2;
}
static _GLFWwindow* findWindowFromDecorationSurface(struct wl_surface* surface,
int* which)
static _GLFWwindow*
findWindowFromDecorationSurface(struct wl_surface* surface, _GLFWdecorationSideWayland* which)
{
int focus;
_GLFWdecorationSideWayland focus;
if (!which) which = &focus;
_GLFWwindow* window = _glfw.windowListHead;
if (!which)
which = &focus;
while (window)
{
if (surface == window->wl.decorations.top.surface)
{
*which = topDecoration;
break;
}
if (surface == window->wl.decorations.left.surface)
{
*which = leftDecoration;
break;
}
if (surface == window->wl.decorations.right.surface)
{
*which = rightDecoration;
break;
}
if (surface == window->wl.decorations.bottom.surface)
{
*which = bottomDecoration;
break;
}
#define q(edge, result) if (surface == window->wl.decorations.edge.surface) { *which = result; break; }
while (window) {
q(top, TOP_DECORATION);
q(left, LEFT_DECORATION);
q(right, RIGHT_DECORATION);
q(bottom, BOTTOM_DECORATION);
window = window->next;
}
#undef q
return window;
}
@@ -103,7 +89,7 @@ static void pointerHandleEnter(void* data UNUSED,
if (!surface)
return;
int focus = 0;
_GLFWdecorationSideWayland focus = CENTRAL_WINDOW;
_GLFWwindow* window = wl_surface_get_user_data(surface);
if (!window)
{
@@ -173,6 +159,8 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
_glfw.wl.cursorPreviousShape = shape;
}
#define x window->wl.allCursorPosX
#define y window->wl.allCursorPosY
static void pointerHandleMotion(void* data UNUSED,
struct wl_pointer* pointer UNUSED,
uint32_t time UNUSED,
@@ -181,46 +169,45 @@ static void pointerHandleMotion(void* data UNUSED,
{
_GLFWwindow* window = _glfw.wl.pointerFocus;
GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR;
double x, y;
if (!window)
return;
if (window->cursorMode == GLFW_CURSOR_DISABLED)
return;
x = wl_fixed_to_double(sx);
y = wl_fixed_to_double(sy);
window->wl.allCursorPosX = wl_fixed_to_double(sx);
window->wl.allCursorPosY = wl_fixed_to_double(sy);
switch (window->wl.decorations.focus)
{
case mainWindow:
case CENTRAL_WINDOW:
window->wl.cursorPosX = x;
window->wl.cursorPosY = y;
_glfwInputCursorPos(window, x, y);
_glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR;
return;
case topDecoration:
if (y < _GLFW_DECORATION_WIDTH)
case TOP_DECORATION:
if (y < window->wl.decorations.metrics.width)
cursorShape = GLFW_VRESIZE_CURSOR;
else
cursorShape = GLFW_ARROW_CURSOR;
break;
case leftDecoration:
if (y < _GLFW_DECORATION_WIDTH)
case LEFT_DECORATION:
if (y < window->wl.decorations.metrics.width)
cursorShape = GLFW_NW_RESIZE_CURSOR;
else
cursorShape = GLFW_HRESIZE_CURSOR;
break;
case rightDecoration:
if (y < _GLFW_DECORATION_WIDTH)
case RIGHT_DECORATION:
if (y < window->wl.decorations.metrics.width)
cursorShape = GLFW_NE_RESIZE_CURSOR;
else
cursorShape = GLFW_HRESIZE_CURSOR;
break;
case bottomDecoration:
if (x < _GLFW_DECORATION_WIDTH)
case BOTTOM_DECORATION:
if (x < window->wl.decorations.metrics.width)
cursorShape = GLFW_SW_RESIZE_CURSOR;
else if (x > window->wl.width + _GLFW_DECORATION_WIDTH)
else if (x > window->wl.width + window->wl.decorations.metrics.width)
cursorShape = GLFW_SE_RESIZE_CURSOR;
else
cursorShape = GLFW_VRESIZE_CURSOR;
@@ -249,33 +236,46 @@ static void pointerHandleButton(void* data UNUSED,
{
switch (window->wl.decorations.focus)
{
case mainWindow:
case CENTRAL_WINDOW:
break;
case topDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
case TOP_DECORATION:
if (state == WL_POINTER_BUTTON_STATE_PRESSED) {
monotonic_t last_click_at = window->wl.decorations.last_click_on_top_decoration_at;
window->wl.decorations.last_click_on_top_decoration_at = monotonic();
if (window->wl.decorations.last_click_on_top_decoration_at - last_click_at <= _glfwPlatformGetDoubleClickInterval(window)) {
window->wl.decorations.last_click_on_top_decoration_at = 0;
if (window->wl.toplevel_states & TOPLEVEL_STATE_MAXIMIZED)
xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
else
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
return;
}
}
if (y < window->wl.decorations.metrics.width)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP;
else
{
if (window->wl.xdg.toplevel)
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
}
break;
case leftDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
case LEFT_DECORATION:
if (y < window->wl.decorations.metrics.width)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
break;
case rightDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
case RIGHT_DECORATION:
if (y < window->wl.decorations.metrics.width)
edges = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
break;
case bottomDecoration:
if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH)
case BOTTOM_DECORATION:
if (x < window->wl.decorations.metrics.width)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH)
else if (x > window->wl.width + window->wl.decorations.metrics.width)
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
else
edges = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
@@ -291,18 +291,15 @@ static void pointerHandleButton(void* data UNUSED,
}
else if (button == BTN_RIGHT)
{
if (window->wl.decorations.focus != mainWindow && window->wl.xdg.toplevel)
if (window->wl.decorations.focus != CENTRAL_WINDOW && window->wl.xdg.toplevel)
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
(int32_t)window->wl.cursorPosX,
(int32_t)window->wl.cursorPosY);
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel, _glfw.wl.seat, serial, (int32_t)x, (int32_t)y - window->wl.decorations.metrics.top);
return;
}
}
// Dont pass the button to the user if it was related to a decoration.
if (window->wl.decorations.focus != mainWindow)
if (window->wl.decorations.focus != CENTRAL_WINDOW)
return;
_glfw.wl.serial = serial;
@@ -318,6 +315,8 @@ static void pointerHandleButton(void* data UNUSED,
: GLFW_RELEASE,
_glfw.wl.xkb.states.modifiers);
}
#undef x
#undef y
static void pointerHandleAxis(void* data UNUSED,
struct wl_pointer* pointer UNUSED,
@@ -393,7 +392,7 @@ static void keyboardHandleEnter(void* data UNUSED,
}
_glfw.wl.serial = serial;
_glfw.wl.keyboardFocus = window;
_glfw.wl.keyboardFocusId = window->id;
_glfwInputWindowFocus(window, true);
uint32_t* key;
if (keys && _glfw.wl.keyRepeatInfo.key) {
@@ -411,21 +410,23 @@ static void keyboardHandleLeave(void* data UNUSED,
uint32_t serial,
struct wl_surface* surface UNUSED)
{
_GLFWwindow* window = _glfw.wl.keyboardFocus;
_GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId);
if (!window)
return;
_glfw.wl.serial = serial;
_glfw.wl.keyboardFocus = NULL;
_glfw.wl.keyboardFocusId = 0;
_glfwInputWindowFocus(window, false);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
}
static void
dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) {
if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
if (_glfw.wl.keyRepeatInfo.keyboardFocusId != _glfw.wl.keyboardFocusId || _glfw.wl.keyboardRepeatRate == 0) return;
_GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId);
if (!window) return;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
}
@@ -438,7 +439,7 @@ static void keyboardHandleKey(void* data UNUSED,
uint32_t key,
uint32_t state)
{
_GLFWwindow* window = _glfw.wl.keyboardFocus;
_GLFWwindow* window = _glfwWindowForId(_glfw.wl.keyboardFocusId);
if (!window)
return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
@@ -449,7 +450,7 @@ static void keyboardHandleKey(void* data UNUSED,
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
{
_glfw.wl.keyRepeatInfo.key = key;
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
_glfw.wl.keyRepeatInfo.keyboardFocusId = window->id;
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
} else if (action == GLFW_RELEASE && key == _glfw.wl.keyRepeatInfo.key) {
@@ -582,6 +583,7 @@ static void registryHandleGlobal(void* data UNUSED,
if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
_glfwSetupWaylandPrimarySelectionDevice();
}
_glfwWaylandInitTextInput();
}
}
else if (strcmp(interface, "xdg_wm_base") == 0)
@@ -596,11 +598,6 @@ static void registryHandleGlobal(void* data UNUSED,
wl_registry_bind(registry, name,
&zxdg_decoration_manager_v1_interface, 1);
}
else if (strcmp(interface, "wp_viewporter") == 0)
{
_glfw.wl.viewporter =
wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
}
else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0)
{
_glfw.wl.relativePointerManager =
@@ -615,6 +612,11 @@ static void registryHandleGlobal(void* data UNUSED,
&zwp_pointer_constraints_v1_interface,
1);
}
else if (strcmp(interface, GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME) == 0)
{
_glfwWaylandBindTextInput(registry, name);
_glfwWaylandInitTextInput();
}
else if (strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0)
{
_glfw.wl.idleInhibitManager =
@@ -755,6 +757,7 @@ int _glfwPlatformInit(void)
"Wayland: Failed to initialize event loop data");
}
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
glfw_initialize_desktop_settings();
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, dispatchPendingKeyRepeats, NULL, NULL);
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL);
@@ -829,8 +832,6 @@ void _glfwPlatformTerminate(void)
wl_compositor_destroy(_glfw.wl.compositor);
if (_glfw.wl.shm)
wl_shm_destroy(_glfw.wl.shm);
if (_glfw.wl.viewporter)
wp_viewporter_destroy(_glfw.wl.viewporter);
if (_glfw.wl.decorationManager)
zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager);
if (_glfw.wl.wmBase)
@@ -845,6 +846,7 @@ void _glfwPlatformTerminate(void)
zwp_relative_pointer_manager_v1_destroy(_glfw.wl.relativePointerManager);
if (_glfw.wl.pointerConstraints)
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
_glfwWaylandDestroyTextInput();
if (_glfw.wl.idleInhibitManager)
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
if (_glfw.wl.dataSourceForClipboard)

101
glfw/wl_platform.h vendored
View File

@@ -53,12 +53,12 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wl_cursors.h"
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#include "wl_text_input.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@@ -88,28 +88,45 @@ 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
#define _GLFW_DECORATION_WIDTH 4
#define _GLFW_DECORATION_TOP 24
#define _GLFW_DECORATION_VERTICAL (_GLFW_DECORATION_TOP + _GLFW_DECORATION_WIDTH)
#define _GLFW_DECORATION_HORIZONTAL (2 * _GLFW_DECORATION_WIDTH)
typedef enum _GLFWdecorationSideWayland
{
mainWindow,
topDecoration,
leftDecoration,
rightDecoration,
bottomDecoration,
CENTRAL_WINDOW,
TOP_DECORATION,
LEFT_DECORATION,
RIGHT_DECORATION,
BOTTOM_DECORATION,
} _GLFWdecorationSideWayland;
typedef struct _GLFWdecorationWayland
{
struct wl_surface* surface;
struct wl_subsurface* subsurface;
struct wp_viewport* viewport;
typedef struct _GLFWWaylandBufferPair {
struct wl_buffer *a, *b, *front, *back;
struct { uint8_t *a, *b, *front, *back; } data;
bool has_pending_update;
size_t size_in_bytes, width, height, stride;
} _GLFWWaylandBufferPair;
typedef struct _GLFWWaylandCSDEdge {
struct wl_surface *surface;
struct wl_subsurface *subsurface;
_GLFWWaylandBufferPair buffer;
int x, y;
} _GLFWWaylandCSDEdge;
typedef enum WaylandWindowState {
TOPLEVEL_STATE_NONE = 0,
TOPLEVEL_STATE_MAXIMIZED = 1,
TOPLEVEL_STATE_FULLSCREEN = 2,
TOPLEVEL_STATE_RESIZING = 4,
TOPLEVEL_STATE_ACTIVATED = 8,
TOPLEVEL_STATE_TILED_LEFT = 16,
TOPLEVEL_STATE_TILED_RIGHT = 32,
TOPLEVEL_STATE_TILED_TOP = 64,
TOPLEVEL_STATE_TILED_BOTTOM = 128,
} WaylandWindowState;
static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM;
} _GLFWdecorationWayland;
// Wayland-specific per-window data
//
@@ -117,7 +134,6 @@ typedef struct _GLFWwindowWayland
{
int width, height;
bool visible;
bool maximized;
bool hovered;
bool transparent;
struct wl_surface* surface;
@@ -131,7 +147,7 @@ typedef struct _GLFWwindowWayland
} xdg;
_GLFWcursor* currentCursor;
double cursorPosX, cursorPosY;
double cursorPosX, cursorPosY, allCursorPosX, allCursorPosY;
char* title;
char appId[256];
@@ -151,13 +167,34 @@ typedef struct _GLFWwindowWayland
struct zwp_idle_inhibitor_v1* idleInhibitor;
bool fullscreened;
struct {
bool serverSide;
struct wl_buffer* buffer;
_GLFWdecorationWayland top, left, right, bottom;
int focus;
bool serverSide;
_GLFWdecorationSideWayland focus;
_GLFWWaylandCSDEdge top, left, right, bottom;
struct {
uint8_t *data;
size_t size;
} mapping;
struct {
int width, height, scale;
bool focused;
} for_window_state;
struct {
unsigned int width, top, horizontal, vertical, visible_titlebar_height;
} metrics;
struct {
int32_t x, y, width, height;
} geometry;
struct {
uint32_t *data;
size_t for_decoration_size, stride, segments, corner_size;
} shadow_tile;
monotonic_t last_click_on_top_decoration_at;
} decorations;
struct {
@@ -166,6 +203,12 @@ typedef struct _GLFWwindowWayland
struct wl_callback *current_wl_callback;
} frameCallbackData;
struct {
int32_t width, height;
} user_requested_content_size;
uint32_t toplevel_states;
bool maximize_on_first_show;
} _GLFWwindowWayland;
@@ -208,7 +251,6 @@ typedef struct _GLFWlibraryWayland
struct wl_data_device* dataDevice;
struct xdg_wm_base* wmBase;
struct zxdg_decoration_manager_v1* decorationManager;
struct wp_viewporter* viewporter;
struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints;
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
@@ -230,14 +272,14 @@ typedef struct _GLFWlibraryWayland
struct {
uint32_t key;
id_type keyRepeatTimer;
_GLFWwindow* keyboardFocus;
GLFWid keyboardFocusId;
} keyRepeatInfo;
id_type cursorAnimationTimer;
_GLFWXKBData xkb;
_GLFWDBUSData dbus;
_GLFWwindow* pointerFocus;
_GLFWwindow* keyboardFocus;
GLFWid keyboardFocusId;
struct {
void* handle;
@@ -262,7 +304,6 @@ typedef struct _GLFWlibraryWayland
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
GLFWWLCursorThemes cursor_themes;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data

154
glfw/wl_text_input.c vendored Normal file
View File

@@ -0,0 +1,154 @@
/*
* wl_text_input.c
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "wl_text_input.h"
#include "internal.h"
#include "wayland-text-input-unstable-v3-client-protocol.h"
#include <stdlib.h>
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
static struct zwp_text_input_v3* text_input;
static struct zwp_text_input_manager_v3* text_input_manager;
static char *pending_pre_edit = NULL;
static char *pending_commit = NULL;
uint32_t commit_serial = 0;
static void commit(void) {
if (text_input) {
zwp_text_input_v3_commit (text_input);
commit_serial++;
}
}
static void
text_input_enter(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED, struct wl_surface *surface UNUSED) {
debug("text-input: enter event\n");
if (text_input) {
zwp_text_input_v3_enable(text_input);
zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
commit();
}
}
static void
text_input_leave(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED, struct wl_surface *surface UNUSED) {
debug("text-input: leave event\n");
if (text_input) {
zwp_text_input_v3_disable(text_input);
commit();
}
}
static inline void
send_text(const char *text, GLFWIMEState ime_state) {
_GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) {
GLFWkeyevent fake_ev = {.action = GLFW_PRESS};
fake_ev.text = text;
fake_ev.ime_state = ime_state;
w->callbacks.keyboard((GLFWwindow*) w, &fake_ev);
}
}
static void
text_input_preedit_string(
void *data UNUSED,
struct zwp_text_input_v3 *text_input UNUSED,
const char *text,
int32_t cursor_begin,
int32_t cursor_end
) {
debug("text-input: preedit_string event: text: %s cursor_begin: %d cursor_end: %d\n", text, cursor_begin, cursor_end);
free(pending_pre_edit);
pending_pre_edit = text ? _glfw_strdup(text) : NULL;
}
static void
text_input_commit_string(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED, const char *text) {
debug("text-input: commit_string event: text: %s\n", text);
free(pending_commit);
pending_commit = text ? _glfw_strdup(text) : NULL;
}
static void
text_input_delete_surrounding_text(
void *data UNUSED,
struct zwp_text_input_v3 *zwp_text_input_v3 UNUSED,
uint32_t before_length,
uint32_t after_length) {
debug("text-input: delete_surrounding_text event: before_length: %u after_length: %u\n", before_length, after_length);
}
static void
text_input_done(void *data UNUSED, struct zwp_text_input_v3 *zwp_text_input_v3 UNUSED, uint32_t serial UNUSED) {
debug("text-input: done event: serial: %u current_commit_serial: %u\n", serial, commit_serial);
if (serial != commit_serial) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
return;
}
if (pending_pre_edit) {
send_text(pending_pre_edit, GLFW_IME_PREEDIT_CHANGED);
free(pending_pre_edit); pending_pre_edit = NULL;
}
if (pending_commit) {
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
free(pending_commit); pending_commit = NULL;
}
}
void
_glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name) {
if (!text_input_manager) text_input_manager = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1);
}
void
_glfwWaylandInitTextInput(void) {
static const struct zwp_text_input_v3_listener text_input_listener = {
.enter = text_input_enter,
.leave = text_input_leave,
.preedit_string = text_input_preedit_string,
.commit_string = text_input_commit_string,
.delete_surrounding_text = text_input_delete_surrounding_text,
.done = text_input_done,
};
if (!text_input) {
if (text_input_manager && _glfw.wl.seat) {
text_input = zwp_text_input_manager_v3_get_text_input(
text_input_manager, _glfw.wl.seat);
if (text_input) zwp_text_input_v3_add_listener(text_input, &text_input_listener, NULL);
}
}
}
void
_glfwWaylandDestroyTextInput(void) {
if (text_input) zwp_text_input_v3_destroy(text_input);
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
text_input = NULL; text_input_manager = NULL;
free(pending_pre_edit); pending_pre_edit = NULL;
free(pending_commit); pending_commit = NULL;
}
void
_glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
if (!text_input) return;
switch(ev->type) {
case GLFW_IME_UPDATE_FOCUS:
debug("\ntext-input: updating IME focus state, focused: %d\n", ev->focused);
if (ev->focused) zwp_text_input_v3_enable(text_input); else zwp_text_input_v3_disable(text_input);
commit();
break;
case GLFW_IME_UPDATE_CURSOR_POSITION: {
const int scale = w->wl.scale;
const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale;
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
commit();
}
break;
}
}

14
glfw/wl_text_input.h vendored Normal file
View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <wayland-client.h>
#define GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME "zwp_text_input_manager_v3"
void _glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name);
void _glfwWaylandInitTextInput(void);
void _glfwWaylandDestroyTextInput(void);

487
glfw/wl_window.c vendored
View File

@@ -32,6 +32,7 @@
#include "backend_utils.h"
#include "memfd.h"
#include "linux_notify.h"
#include "wl_client_side_decorations.h"
#include "../kitty/monotonic.h"
#include <stdio.h>
@@ -41,9 +42,65 @@
#include <fcntl.h>
#include <sys/mman.h>
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
static void setCursorImage(_GLFWwindow* window)
static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data)
{
struct wl_shm_pool* pool;
struct wl_buffer* buffer;
int stride = image->width * 4;
int length = image->width * image->height * 4;
void* data;
int fd, i;
fd = createAnonymousFile(length);
if (fd < 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %s",
length, strerror(errno));
return NULL;
}
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: mmap failed: %s", strerror(errno));
close(fd);
return NULL;
}
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
close(fd);
if (init_data) {
unsigned char* source = (unsigned char*) image->pixels;
unsigned char* target = data;
for (i = 0; i < image->width * image->height; i++, source += 4)
{
unsigned int alpha = source[3];
*target++ = (unsigned char) ((source[2] * alpha) / 255);
*target++ = (unsigned char) ((source[1] * alpha) / 255);
*target++ = (unsigned char) ((source[0] * alpha) / 255);
*target++ = (unsigned char) alpha;
}
}
buffer =
wl_shm_pool_create_buffer(pool, 0,
image->width,
image->height,
stride, is_opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888);
munmap(data, length);
wl_shm_pool_destroy(pool);
return buffer;
}
static void
setCursorImage(_GLFWwindow* window, bool on_theme_change) {
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
struct wl_cursor_image* image = NULL;
@@ -54,9 +111,8 @@ static void setCursorImage(_GLFWwindow* window)
if (cursorWayland->scale < 0) {
buffer = cursorWayland->buffer;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
} else
{
if (cursorWayland->scale != scale) {
} else {
if (on_theme_change || cursorWayland->scale != scale) {
struct wl_cursor *newCursor = NULL;
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme);
@@ -131,7 +187,7 @@ static bool checkScaleChange(_GLFWwindow* window)
{
window->wl.scale = scale;
wl_surface_set_buffer_scale(window->wl.surface, scale);
setCursorImage(window);
setCursorImage(window, false);
return true;
}
if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) {
@@ -157,42 +213,15 @@ static void setOpaqueRegion(_GLFWwindow* window)
}
static void resizeFramebuffer(_GLFWwindow* window)
{
static void
resizeFramebuffer(_GLFWwindow* window) {
int scale = window->wl.scale;
int scaledWidth = window->wl.width * scale;
int scaledHeight = window->wl.height * scale;
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent)
setOpaqueRegion(window);
if (!window->wl.transparent) setOpaqueRegion(window);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
if (!window->wl.decorations.top.surface)
return;
// Top decoration.
wp_viewport_set_destination(window->wl.decorations.top.viewport,
window->wl.width, _GLFW_DECORATION_TOP);
wl_surface_commit(window->wl.decorations.top.surface);
// Left decoration.
wp_viewport_set_destination(window->wl.decorations.left.viewport,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
wl_surface_commit(window->wl.decorations.left.surface);
// Right decoration.
wl_subsurface_set_position(window->wl.decorations.right.subsurface,
window->wl.width, -_GLFW_DECORATION_TOP);
wp_viewport_set_destination(window->wl.decorations.right.viewport,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
wl_surface_commit(window->wl.decorations.right.surface);
// Bottom decoration.
wl_subsurface_set_position(window->wl.decorations.bottom.subsurface,
-_GLFW_DECORATION_WIDTH, window->wl.height);
wp_viewport_set_destination(window->wl.decorations.bottom.viewport,
window->wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
wl_surface_commit(window->wl.decorations.bottom.surface);
}
@@ -205,15 +234,15 @@ clipboard_mime(void) {
return buf;
}
static void dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) {
if (width <= 0) width = window->wl.width;
if (height <= 0) height = window->wl.height;
static void
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) {
bool size_changed = width != window->wl.width || height != window->wl.height;
bool scale_changed = checkScaleChange(window);
if (size_changed) {
_glfwInputWindowSize(window, width, height);
_glfwPlatformSetWindowSize(window, width, height);
window->wl.width = width; window->wl.height = height;
resizeFramebuffer(window);
}
if (scale_changed) {
@@ -226,212 +255,6 @@ static void dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, in
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
* CLOEXEC. The file is immediately suitable for mmap()'ing
* the given size at offset zero.
*
* The file should not have a permanent backing store like a disk,
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
*
* The file name is deleted from the file system.
*
* The file is suitable for buffer sharing between processes by
* transmitting the file descriptor over Unix sockets using the
* SCM_RIGHTS methods.
*
* posix_fallocate() is used to guarantee that disk space is available
* for the file at the given size. If disk space is insufficient, errno
* is set to ENOSPC. If posix_fallocate() is not supported, program may
* receive SIGBUS on accessing mmap()'ed file contents instead.
*/
static int createAnonymousFile(off_t size)
{
int ret, fd = -1, shm_anon = 0;
#ifdef HAS_MEMFD_CREATE
fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd < 0) return -1;
// We can add this seal before calling posix_fallocate(), as the file
// is currently zero-sized anyway.
//
// There is also no need to check for the return value, we couldnt do
// anything with it anyway.
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
#elif defined(SHM_ANON)
fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600);
if (fd < 0) return -1;
shm_anon = 1;
#else
static const char template[] = "/glfw-shared-XXXXXX";
const char* path;
char* name;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
{
errno = ENOENT;
return -1;
}
name = calloc(strlen(path) + sizeof(template), 1);
strcpy(name, path);
strcat(name, template);
fd = createTmpfileCloexec(name);
free(name);
if (fd < 0)
return -1;
#endif
// posix_fallocate does not work on SHM descriptors
ret = shm_anon ? ftruncate(fd, size) : posix_fallocate(fd, 0, size);
if (ret != 0)
{
close(fd);
errno = ret;
return -1;
}
return fd;
}
static struct wl_buffer* createShmBuffer(const GLFWimage* image)
{
struct wl_shm_pool* pool;
struct wl_buffer* buffer;
int stride = image->width * 4;
int length = image->width * image->height * 4;
void* data;
int fd, i;
fd = createAnonymousFile(length);
if (fd < 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %s",
length, strerror(errno));
return NULL;
}
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: mmap failed: %s", strerror(errno));
close(fd);
return NULL;
}
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
close(fd);
unsigned char* source = (unsigned char*) image->pixels;
unsigned char* target = data;
for (i = 0; i < image->width * image->height; i++, source += 4)
{
unsigned int alpha = source[3];
*target++ = (unsigned char) ((source[2] * alpha) / 255);
*target++ = (unsigned char) ((source[1] * alpha) / 255);
*target++ = (unsigned char) ((source[0] * alpha) / 255);
*target++ = (unsigned char) alpha;
}
buffer =
wl_shm_pool_create_buffer(pool, 0,
image->width,
image->height,
stride, WL_SHM_FORMAT_ARGB8888);
munmap(data, length);
wl_shm_pool_destroy(pool);
return buffer;
}
static void createDecoration(_GLFWdecorationWayland* decoration,
struct wl_surface* parent,
struct wl_buffer* buffer, bool opaque,
int x, int y,
int width, int height)
{
struct wl_region* region;
decoration->surface = wl_compositor_create_surface(_glfw.wl.compositor);
decoration->subsurface =
wl_subcompositor_get_subsurface(_glfw.wl.subcompositor,
decoration->surface, parent);
wl_subsurface_set_position(decoration->subsurface, x, y);
decoration->viewport = wp_viewporter_get_viewport(_glfw.wl.viewporter,
decoration->surface);
wp_viewport_set_destination(decoration->viewport, width, height);
wl_surface_attach(decoration->surface, buffer, 0, 0);
if (opaque)
{
region = wl_compositor_create_region(_glfw.wl.compositor);
wl_region_add(region, 0, 0, width, height);
wl_surface_set_opaque_region(decoration->surface, region);
wl_surface_commit(decoration->surface);
wl_region_destroy(region);
}
else
wl_surface_commit(decoration->surface);
}
static void createDecorations(_GLFWwindow* window)
{
unsigned char data[] = { 224, 224, 224, 255 };
const GLFWimage image = { 1, 1, data };
bool opaque = (data[3] == 255);
if (!_glfw.wl.viewporter || !window->decorated || window->wl.decorations.serverSide)
return;
if (!window->wl.decorations.buffer)
window->wl.decorations.buffer = createShmBuffer(&image);
if (!window->wl.decorations.buffer)
return;
createDecoration(&window->wl.decorations.top, window->wl.surface,
window->wl.decorations.buffer, opaque,
0, -_GLFW_DECORATION_TOP,
window->wl.width, _GLFW_DECORATION_TOP);
createDecoration(&window->wl.decorations.left, window->wl.surface,
window->wl.decorations.buffer, opaque,
-_GLFW_DECORATION_WIDTH, -_GLFW_DECORATION_TOP,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
createDecoration(&window->wl.decorations.right, window->wl.surface,
window->wl.decorations.buffer, opaque,
window->wl.width, -_GLFW_DECORATION_TOP,
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
createDecoration(&window->wl.decorations.bottom, window->wl.surface,
window->wl.decorations.buffer, opaque,
-_GLFW_DECORATION_WIDTH, window->wl.height,
window->wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
}
static void destroyDecoration(_GLFWdecorationWayland* decoration)
{
if (decoration->subsurface)
wl_subsurface_destroy(decoration->subsurface);
if (decoration->surface)
wl_surface_destroy(decoration->surface);
if (decoration->viewport)
wp_viewport_destroy(decoration->viewport);
decoration->surface = NULL;
decoration->subsurface = NULL;
decoration->viewport = NULL;
}
static void destroyDecorations(_GLFWwindow* window)
{
destroyDecoration(&window->wl.decorations.top);
destroyDecoration(&window->wl.decorations.left);
destroyDecoration(&window->wl.decorations.right);
destroyDecoration(&window->wl.decorations.bottom);
}
static void xdgDecorationHandleConfigure(void* data,
struct zxdg_toplevel_decoration_v1* decoration UNUSED,
uint32_t mode)
@@ -439,9 +262,7 @@ static void xdgDecorationHandleConfigure(void* data,
_GLFWwindow* window = data;
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
if (!window->wl.decorations.serverSide)
createDecorations(window);
ensure_csd_resources(window);
}
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
@@ -468,6 +289,7 @@ static void surfaceHandleEnter(void *data,
if (checkScaleChange(window)) {
resizeFramebuffer(window);
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
ensure_csd_resources(window);
}
}
@@ -492,6 +314,7 @@ static void surfaceHandleLeave(void *data,
if (checkScaleChange(window)) {
resizeFramebuffer(window);
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
ensure_csd_resources(window);
}
}
@@ -539,6 +362,8 @@ static bool createSurface(_GLFWwindow* window,
window->wl.width = wndconfig->width;
window->wl.height = wndconfig->height;
window->wl.user_requested_content_size.width = wndconfig->width;
window->wl.user_requested_content_size.height = wndconfig->height;
window->wl.scale = 1;
if (!window->wl.transparent)
@@ -555,12 +380,10 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
xdg_toplevel_set_fullscreen(
window->wl.xdg.toplevel,
monitor ? monitor->wl.output : NULL);
if (!window->wl.decorations.serverSide)
destroyDecorations(window);
if (!window->wl.decorations.serverSide) free_csd_surfaces(window);
} else {
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
if (!_glfw.wl.decorationManager)
createDecorations(window);
ensure_csd_resources(window);
}
}
setIdleInhibitor(window, on);
@@ -568,7 +391,7 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
bool
_glfwPlatformToggleFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) {
bool already_fullscreen = window->wl.fullscreened;
bool already_fullscreen = window->wl.toplevel_states & TOPLEVEL_STATE_FULLSCREEN;
setFullscreen(window, NULL, !already_fullscreen);
return !already_fullscreen;
}
@@ -582,32 +405,33 @@ static void xdgToplevelHandleConfigure(void* data,
_GLFWwindow* window = data;
float aspectRatio;
float targetRatio;
uint32_t* state;
bool maximized = false;
bool fullscreen = false;
bool activated = false;
enum xdg_toplevel_state* state;
uint32_t new_states = 0;
debug("top-level configure event: size: %dx%d states: ", width, height);
wl_array_for_each(state, states)
{
switch (*state)
{
case XDG_TOPLEVEL_STATE_MAXIMIZED:
maximized = true;
break;
case XDG_TOPLEVEL_STATE_FULLSCREEN:
fullscreen = true;
break;
case XDG_TOPLEVEL_STATE_RESIZING:
break;
case XDG_TOPLEVEL_STATE_ACTIVATED:
activated = true;
break;
wl_array_for_each(state, states) {
switch (*state) {
#define C(x) case XDG_##x: new_states |= x; debug("%s ", #x); break
C(TOPLEVEL_STATE_RESIZING);
C(TOPLEVEL_STATE_MAXIMIZED);
C(TOPLEVEL_STATE_FULLSCREEN);
C(TOPLEVEL_STATE_ACTIVATED);
C(TOPLEVEL_STATE_TILED_LEFT);
C(TOPLEVEL_STATE_TILED_RIGHT);
C(TOPLEVEL_STATE_TILED_TOP);
C(TOPLEVEL_STATE_TILED_BOTTOM);
#undef C
}
}
debug("\n");
if (new_states & TOPLEVEL_STATE_RESIZING) {
if (width) window->wl.user_requested_content_size.width = width;
if (height) window->wl.user_requested_content_size.height = height;
if (!(window->wl.toplevel_states & TOPLEVEL_STATE_RESIZING)) _glfwInputLiveResize(window, true);
}
if (width != 0 && height != 0)
{
if (!maximized && !fullscreen)
if (!(new_states & TOPLEVEL_STATE_DOCKED))
{
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
{
@@ -620,15 +444,19 @@ static void xdgToplevelHandleConfigure(void* data,
}
}
}
window->wl.fullscreened = fullscreen;
if (!fullscreen) {
if (window->decorated && !window->wl.decorations.serverSide && window->wl.decorations.buffer) {
width -= _GLFW_DECORATION_HORIZONTAL;
height -= _GLFW_DECORATION_VERTICAL;
}
}
bool live_resize_done = !(new_states & TOPLEVEL_STATE_RESIZING) && (window->wl.toplevel_states & TOPLEVEL_STATE_RESIZING);
window->wl.toplevel_states = new_states;
set_csd_window_geometry(window, &width, &height);
dispatchChangesAfterConfigure(window, width, height);
_glfwInputWindowFocus(window, activated);
debug("final window content size: %dx%d\n", window->wl.width, window->wl.height);
_glfwInputWindowFocus(window, window->wl.toplevel_states & TOPLEVEL_STATE_ACTIVATED);
ensure_csd_resources(window);
#define geometry window->wl.decorations.geometry
debug("Setting window geometry: x=%d y=%d %dx%d\n", geometry.x, geometry.y, geometry.width, geometry.height);
xdg_surface_set_window_geometry(window->wl.xdg.surface, geometry.x, geometry.y, geometry.width, geometry.height);
#undef geometry
wl_surface_commit(window->wl.surface);
if (live_resize_done) _glfwInputLiveResize(window, false);
}
static void xdgToplevelHandleClose(void* data,
@@ -671,7 +499,7 @@ static void setXdgDecorations(_GLFWwindow* window)
else
{
window->wl.decorations.serverSide = false;
createDecorations(window);
ensure_csd_resources(window);
}
}
@@ -718,8 +546,9 @@ static bool createXdgSurface(_GLFWwindow* window)
window->monitor->wl.output);
setIdleInhibitor(window, true);
}
else if (window->wl.maximized)
else if (window->wl.maximize_on_first_show)
{
window->wl.maximize_on_first_show = false;
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
setIdleInhibitor(window, false);
setXdgDecorations(window);
@@ -740,13 +569,13 @@ static bool createXdgSurface(_GLFWwindow* window)
static void incrementCursorImage(_GLFWwindow* window)
{
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
if (window && window->wl.decorations.focus == CENTRAL_WINDOW && window->cursorMode != GLFW_CURSOR_HIDDEN) {
_GLFWcursor* cursor = window->wl.currentCursor;
if (cursor && cursor->wl.cursor)
{
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
setCursorImage(window);
setCursorImage(window, false);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
return;
}
@@ -883,6 +712,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
initialize_csd_metrics(window);
window->wl.transparent = fbconfig->transparent;
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
@@ -910,6 +740,8 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
if (wndconfig->title)
window->wl.title = _glfw_strdup(wndconfig->title);
if (wndconfig->maximized)
window->wl.maximize_on_first_show = true;
if (wndconfig->visible)
{
@@ -942,11 +774,14 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
_glfw.wl.pointerFocus = NULL;
_glfwInputCursorEnter(window, false);
}
if (window == _glfw.wl.keyboardFocus)
if (window->id == _glfw.wl.keyboardFocusId)
{
_glfw.wl.keyboardFocus = NULL;
_glfw.wl.keyboardFocusId = 0;
_glfwInputWindowFocus(window, false);
}
if (window->id == _glfw.wl.keyRepeatInfo.keyboardFocusId) {
_glfw.wl.keyRepeatInfo.keyboardFocusId = 0;
}
if (window->wl.idleInhibitor)
zwp_idle_inhibitor_v1_destroy(window->wl.idleInhibitor);
@@ -954,13 +789,10 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->context.destroy)
window->context.destroy(window);
destroyDecorations(window);
free_all_csd_resources(window);
if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
if (window->wl.decorations.buffer)
wl_buffer_destroy(window->wl.decorations.buffer);
if (window->wl.native)
wl_egl_window_destroy(window->wl.native);
@@ -981,14 +813,16 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{
if (window->wl.title)
if (window->wl.title) {
if (title && strcmp(title, window->wl.title) == 0) return;
free(window->wl.title);
} else if (!title) return;
// Wayland cannot handle requests larger than ~8200 bytes. Sending
// one causes an abort(). Since titles this large are meaningless anyway
// ensure they do not happen.
window->wl.title = utf_8_strndup(title, 2048);
if (window->wl.xdg.toplevel)
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
if (window->wl.xdg.toplevel) xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
change_csd_title(window);
}
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
@@ -1002,9 +836,12 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int
{
// A Wayland client is not aware of its position, so just warn and leave it
// as (0, 0)
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not provide the window position");
static bool warned_once = false;
if (!warned_once) {
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not provide the window position");
warned_once = true;
}
}
void _glfwPlatformSetWindowPos(_GLFWwindow* window UNUSED, int xpos UNUSED, int ypos UNUSED)
@@ -1026,9 +863,14 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
{
if (width != window->wl.width || height != window->wl.height) {
window->wl.width = width;
window->wl.height = height;
window->wl.user_requested_content_size.width = width;
window->wl.user_requested_content_size.height = height;
int32_t width = 0, height = 0;
set_csd_window_geometry(window, &width, &height);
window->wl.width = width; window->wl.height = height;
resizeFramebuffer(window);
ensure_csd_resources(window);
wl_surface_commit(window->wl.surface);
}
}
@@ -1081,13 +923,13 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
{
if (top)
*top = _GLFW_DECORATION_TOP;
*top = window->wl.decorations.metrics.top;
if (left)
*left = _GLFW_DECORATION_WIDTH;
*left = window->wl.decorations.metrics.width;
if (right)
*right = _GLFW_DECORATION_WIDTH;
*right = window->wl.decorations.metrics.width;
if (bottom)
*bottom = _GLFW_DECORATION_WIDTH;
*bottom = window->wl.decorations.metrics.width;
}
}
@@ -1117,13 +959,12 @@ void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{
if (window->monitor)
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
if (window->wl.maximized)
if (window->wl.toplevel_states & TOPLEVEL_STATE_MAXIMIZED)
xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
// There is no way to unset minimized, or even to know if we are
// minimized, so there is nothing to do in this case.
}
_glfwInputWindowMonitor(window, NULL);
window->wl.maximized = false;
}
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
@@ -1132,7 +973,6 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
{
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
}
window->wl.maximized = true;
}
void _glfwPlatformShowWindow(_GLFWwindow* window)
@@ -1198,7 +1038,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
int _glfwPlatformWindowFocused(_GLFWwindow* window)
{
return _glfw.wl.keyboardFocus == window;
return _glfw.wl.keyboardFocusId = window ? window->id : 0;
}
int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED)
@@ -1220,7 +1060,7 @@ int _glfwPlatformWindowVisible(_GLFWwindow* window)
int _glfwPlatformWindowMaximized(_GLFWwindow* window)
{
return window->wl.maximized;
return window->wl.toplevel_states & TOPLEVEL_STATE_MAXIMIZED;
}
int _glfwPlatformWindowHovered(_GLFWwindow* window)
@@ -1245,9 +1085,9 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled)
if (!window->monitor)
{
if (enabled)
createDecorations(window);
ensure_csd_resources(window);
else
destroyDecorations(window);
free_csd_surfaces(window);
}
}
@@ -1346,7 +1186,7 @@ const char* _glfwPlatformGetNativeKeyName(int native_key)
return glfw_xkb_keysym_name(native_key);
}
int _glfwPlatformGetNativeKeyForKey(int key)
int _glfwPlatformGetNativeKeyForKey(uint32_t key)
{
return glfw_xkb_sym_for_key(key);
}
@@ -1355,7 +1195,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot, int count UNUSED)
{
cursor->wl.buffer = createShmBuffer(image);
cursor->wl.buffer = createShmBuffer(image, false, true);
if (!cursor->wl.buffer)
return false;
@@ -1506,7 +1346,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
// If we're not in the correct window just save the cursor
// the next time the pointer enters the window the cursor will change
if (window != _glfw.wl.pointerFocus || window->wl.decorations.focus != mainWindow)
if (window != _glfw.wl.pointerFocus || window->wl.decorations.focus != CENTRAL_WINDOW)
return;
// Unlock possible pointer lock if no longer disabled.
@@ -1515,7 +1355,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
if (window->cursorMode == GLFW_CURSOR_NORMAL)
{
setCursorImage(window);
setCursorImage(window, false);
}
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
{
@@ -2098,11 +1938,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return err;
}
void
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
}
static void
frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time UNUSED) {
_GLFWwindow* window = (_GLFWwindow*) data;
@@ -2113,6 +1948,16 @@ frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time UNUS
wl_callback_destroy(callback);
}
void
_glfwPlatformChangeCursorTheme(void) {
glfw_wlc_destroy();
_GLFWwindow *w = _glfw.windowListHead;
while (w) {
setCursorImage(w, true);
w = w->next;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////

26
glfw/x11_window.c vendored
View File

@@ -185,6 +185,7 @@ static int translateState(int state)
{
int mods = 0;
/* Need some way to expose hyper and meta without xkbcommon-x11 */
if (state & ShiftMask)
mods |= GLFW_MOD_SHIFT;
if (state & ControlMask)
@@ -224,7 +225,8 @@ static void sendEventToWM(_GLFWwindow* window, Atom type,
// Updates the normal hints according to the window settings
//
static void updateNormalHints(_GLFWwindow* window, int width, int height)
static void
updateNormalHints(_GLFWwindow* window, int width, int height)
{
XSizeHints* hints = XAllocSizeHints();
@@ -257,7 +259,7 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
}
if (window->widthincr != GLFW_DONT_CARE &&
window->heightincr != GLFW_DONT_CARE)
window->heightincr != GLFW_DONT_CARE && !window->x11.maximized)
{
hints->flags |= PResizeInc;
hints->width_inc = window->widthincr;
@@ -1743,6 +1745,9 @@ static void processEvent(XEvent *event)
if (window->x11.maximized != maximized)
{
window->x11.maximized = maximized;
int width, height;
_glfwPlatformGetWindowSize(window, &width, &height);
updateNormalHints(window, width, height);
_glfwInputWindowMaximize(window, maximized);
}
}
@@ -1990,8 +1995,8 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
for (i = 0; i < count; i++)
longCount += 2 + images[i].width * images[i].height;
long* icon = calloc(longCount, sizeof(long));
long* target = icon;
unsigned long* icon = calloc(longCount, sizeof(unsigned long));
unsigned long* target = icon;
for (i = 0; i < count; i++)
{
@@ -2000,10 +2005,9 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
for (j = 0; j < images[i].width * images[i].height; j++)
{
*target++ = (images[i].pixels[j * 4 + 0] << 16) |
(images[i].pixels[j * 4 + 1] << 8) |
(images[i].pixels[j * 4 + 2] << 0) |
(images[i].pixels[j * 4 + 3] << 24);
unsigned char *p = images->pixels + j * 4;
const unsigned char r = *p++, g = *p++, b = *p++, a = *p++;
*target++ = a << 24 | (r << 16) | (g << 8) | b;
}
}
@@ -2778,7 +2782,7 @@ const char* _glfwPlatformGetNativeKeyName(int native_key)
return glfw_xkb_keysym_name(native_key);
}
int _glfwPlatformGetNativeKeyForKey(int key)
int _glfwPlatformGetNativeKeyForKey(uint32_t key)
{
return glfw_xkb_sym_for_key(key);
}
@@ -3086,8 +3090,8 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
}
void
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, which, a, b, c, d);
_glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, ev);
}
//////////////////////////////////////////////////////////////////////////

838
glfw/xkb-compat-shim.h vendored Normal file
View File

@@ -0,0 +1,838 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <stdint.h>
#include <xkbcommon/xkbcommon.h>
// needed for xkbcommon < 1.0
/* We don't use the uint32_t types here, to save some space. */
struct codepair {
uint16_t keysym;
uint16_t ucs;
};
static const struct codepair keysymtab[] = {
{ 0x01a1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */
{ 0x01a2, 0x02d8 }, /* breve ˘ BREVE */
{ 0x01a3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */
{ 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */
{ 0x01a6, 0x015a }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */
{ 0x01a9, 0x0160 }, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */
{ 0x01aa, 0x015e }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */
{ 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */
{ 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */
{ 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */
{ 0x01af, 0x017b }, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */
{ 0x01b1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */
{ 0x01b2, 0x02db }, /* ogonek ˛ OGONEK */
{ 0x01b3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */
{ 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */
{ 0x01b6, 0x015b }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */
{ 0x01b7, 0x02c7 }, /* caron ˇ CARON */
{ 0x01b9, 0x0161 }, /* scaron š LATIN SMALL LETTER S WITH CARON */
{ 0x01ba, 0x015f }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */
{ 0x01bb, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */
{ 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */
{ 0x01bd, 0x02dd }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */
{ 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */
{ 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */
{ 0x01c0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */
{ 0x01c3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */
{ 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */
{ 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */
{ 0x01c8, 0x010c }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */
{ 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */
{ 0x01cc, 0x011a }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */
{ 0x01cf, 0x010e }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */
{ 0x01d0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */
{ 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */
{ 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */
{ 0x01d5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
{ 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */
{ 0x01d9, 0x016e }, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */
{ 0x01db, 0x0170 }, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
{ 0x01de, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */
{ 0x01e0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */
{ 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */
{ 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */
{ 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */
{ 0x01e8, 0x010d }, /* ccaron č LATIN SMALL LETTER C WITH CARON */
{ 0x01ea, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */
{ 0x01ec, 0x011b }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */
{ 0x01ef, 0x010f }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */
{ 0x01f0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */
{ 0x01f1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */
{ 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */
{ 0x01f5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */
{ 0x01f8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */
{ 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */
{ 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */
{ 0x01fe, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */
{ 0x01ff, 0x02d9 }, /* abovedot ˙ DOT ABOVE */
{ 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */
{ 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
{ 0x02a9, 0x0130 }, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */
{ 0x02ab, 0x011e }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */
{ 0x02ac, 0x0134 }, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
{ 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */
{ 0x02b6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */
{ 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */
{ 0x02bb, 0x011f }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */
{ 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */
{ 0x02c5, 0x010a }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */
{ 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
{ 0x02d5, 0x0120 }, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */
{ 0x02d8, 0x011c }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
{ 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */
{ 0x02de, 0x015c }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
{ 0x02e5, 0x010b }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */
{ 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */
{ 0x02f5, 0x0121 }, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */
{ 0x02f8, 0x011d }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */
{ 0x02fd, 0x016d }, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */
{ 0x02fe, 0x015d }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */
{ 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */
{ 0x03a3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */
{ 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */
{ 0x03a6, 0x013b }, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */
{ 0x03aa, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */
{ 0x03ab, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */
{ 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */
{ 0x03b3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */
{ 0x03b5, 0x0129 }, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */
{ 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */
{ 0x03ba, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */
{ 0x03bb, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */
{ 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */
{ 0x03bd, 0x014a }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */
{ 0x03bf, 0x014b }, /* eng ŋ LATIN SMALL LETTER ENG */
{ 0x03c0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */
{ 0x03c7, 0x012e }, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */
{ 0x03cc, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */
{ 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */
{ 0x03d1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */
{ 0x03d2, 0x014c }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */
{ 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */
{ 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */
{ 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */
{ 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */
{ 0x03e0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */
{ 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */
{ 0x03ec, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */
{ 0x03ef, 0x012b }, /* imacron ī LATIN SMALL LETTER I WITH MACRON */
{ 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */
{ 0x03f2, 0x014d }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */
{ 0x03f3, 0x0137 }, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */
{ 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */
{ 0x03fd, 0x0169 }, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */
{ 0x03fe, 0x016b }, /* umacron ū LATIN SMALL LETTER U WITH MACRON */
{ 0x047e, 0x203e }, /* overline ‾ OVERLINE */
{ 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */
{ 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */
{ 0x04a3, 0x300d }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */
{ 0x04a4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */
{ 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */
{ 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */
{ 0x04a7, 0x30a1 }, /* kana_a ァ KATAKANA LETTER SMALL A */
{ 0x04a8, 0x30a3 }, /* kana_i ィ KATAKANA LETTER SMALL I */
{ 0x04a9, 0x30a5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */
{ 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */
{ 0x04ab, 0x30a9 }, /* kana_o ォ KATAKANA LETTER SMALL O */
{ 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */
{ 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */
{ 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */
{ 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */
{ 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */
{ 0x04b1, 0x30a2 }, /* kana_A ア KATAKANA LETTER A */
{ 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */
{ 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */
{ 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */
{ 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */
{ 0x04b6, 0x30ab }, /* kana_KA カ KATAKANA LETTER KA */
{ 0x04b7, 0x30ad }, /* kana_KI キ KATAKANA LETTER KI */
{ 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */
{ 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */
{ 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */
{ 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */
{ 0x04bc, 0x30b7 }, /* kana_SHI シ KATAKANA LETTER SI */
{ 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */
{ 0x04be, 0x30bb }, /* kana_SE セ KATAKANA LETTER SE */
{ 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */
{ 0x04c0, 0x30bf }, /* kana_TA タ KATAKANA LETTER TA */
{ 0x04c1, 0x30c1 }, /* kana_CHI チ KATAKANA LETTER TI */
{ 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */
{ 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */
{ 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */
{ 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */
{ 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */
{ 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */
{ 0x04c8, 0x30cd }, /* kana_NE ネ KATAKANA LETTER NE */
{ 0x04c9, 0x30ce }, /* kana_NO KATAKANA LETTER NO */
{ 0x04ca, 0x30cf }, /* kana_HA ハ KATAKANA LETTER HA */
{ 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */
{ 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */
{ 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */
{ 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */
{ 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */
{ 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */
{ 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */
{ 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */
{ 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */
{ 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */
{ 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */
{ 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */
{ 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */
{ 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */
{ 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */
{ 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */
{ 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */
{ 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */
{ 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */
{ 0x04de, 0x309b }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */
{ 0x04df, 0x309c }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
{ 0x05ac, 0x060c }, /* Arabic_comma ، ARABIC COMMA */
{ 0x05bb, 0x061b }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */
{ 0x05bf, 0x061f }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */
{ 0x05c1, 0x0621 }, /* Arabic_hamza ء ARABIC LETTER HAMZA */
{ 0x05c2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */
{ 0x05c3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */
{ 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */
{ 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */
{ 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */
{ 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */
{ 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */
{ 0x05c9, 0x0629 }, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */
{ 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */
{ 0x05cb, 0x062b }, /* Arabic_theh ث ARABIC LETTER THEH */
{ 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */
{ 0x05cd, 0x062d }, /* Arabic_hah ح ARABIC LETTER HAH */
{ 0x05ce, 0x062e }, /* Arabic_khah خ ARABIC LETTER KHAH */
{ 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */
{ 0x05d0, 0x0630 }, /* Arabic_thal ذ ARABIC LETTER THAL */
{ 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */
{ 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */
{ 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */
{ 0x05d4, 0x0634 }, /* Arabic_sheen ش ARABIC LETTER SHEEN */
{ 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */
{ 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */
{ 0x05d7, 0x0637 }, /* Arabic_tah ط ARABIC LETTER TAH */
{ 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */
{ 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */
{ 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */
{ 0x05e0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */
{ 0x05e1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */
{ 0x05e2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */
{ 0x05e3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */
{ 0x05e4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */
{ 0x05e5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */
{ 0x05e6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */
{ 0x05e7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */
{ 0x05e8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */
{ 0x05e9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */
{ 0x05ea, 0x064a }, /* Arabic_yeh ي ARABIC LETTER YEH */
{ 0x05eb, 0x064b }, /* Arabic_fathatan ً ARABIC FATHATAN */
{ 0x05ec, 0x064c }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */
{ 0x05ed, 0x064d }, /* Arabic_kasratan ٍ ARABIC KASRATAN */
{ 0x05ee, 0x064e }, /* Arabic_fatha َ ARABIC FATHA */
{ 0x05ef, 0x064f }, /* Arabic_damma ُ ARABIC DAMMA */
{ 0x05f0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */
{ 0x05f1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */
{ 0x05f2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */
{ 0x06a1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */
{ 0x06a2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */
{ 0x06a3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */
{ 0x06a4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */
{ 0x06a5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */
{ 0x06a6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
{ 0x06a7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */
{ 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */
{ 0x06a9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */
{ 0x06aa, 0x045a }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */
{ 0x06ab, 0x045b }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */
{ 0x06ac, 0x045c }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */
{ 0x06ad, 0x0491 }, /* Ukrainian_ghe_with_upturn ґ CYRILLIC SMALL LETTER GHE WITH UPTURN */
{ 0x06ae, 0x045e }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */
{ 0x06af, 0x045f }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */
{ 0x06b0, 0x2116 }, /* numerosign № NUMERO SIGN */
{ 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */
{ 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */
{ 0x06b3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */
{ 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */
{ 0x06b5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */
{ 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
{ 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */
{ 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */
{ 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */
{ 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */
{ 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */
{ 0x06bc, 0x040c }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */
{ 0x06bd, 0x0490 }, /* Ukrainian_GHE_WITH_UPTURN Ґ CYRILLIC CAPITAL LETTER GHE WITH UPTURN */
{ 0x06be, 0x040e }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */
{ 0x06bf, 0x040f }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */
{ 0x06c0, 0x044e }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */
{ 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */
{ 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */
{ 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */
{ 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */
{ 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */
{ 0x06c6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */
{ 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */
{ 0x06c8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */
{ 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */
{ 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */
{ 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */
{ 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */
{ 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */
{ 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */
{ 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */
{ 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */
{ 0x06d1, 0x044f }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */
{ 0x06d2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */
{ 0x06d3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */
{ 0x06d4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */
{ 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */
{ 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */
{ 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */
{ 0x06d8, 0x044c }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */
{ 0x06d9, 0x044b }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */
{ 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */
{ 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */
{ 0x06dc, 0x044d }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */
{ 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */
{ 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */
{ 0x06df, 0x044a }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */
{ 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */
{ 0x06e1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */
{ 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */
{ 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */
{ 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */
{ 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */
{ 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */
{ 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */
{ 0x06e8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */
{ 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */
{ 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */
{ 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */
{ 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */
{ 0x06ed, 0x041c }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */
{ 0x06ee, 0x041d }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */
{ 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */
{ 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */
{ 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */
{ 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */
{ 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */
{ 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */
{ 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */
{ 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */
{ 0x06f7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */
{ 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */
{ 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */
{ 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */
{ 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */
{ 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */
{ 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */
{ 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */
{ 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */
{ 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */
{ 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */
{ 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */
{ 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */
{ 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
{ 0x07a7, 0x038c }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */
{ 0x07a8, 0x038e }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */
{ 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
{ 0x07ab, 0x038f }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */
{ 0x07ae, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */
{ 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */
{ 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */
{ 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */
{ 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */
{ 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */
{ 0x07b5, 0x03ca }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */
{ 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */
{ 0x07b7, 0x03cc }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */
{ 0x07b8, 0x03cd }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */
{ 0x07b9, 0x03cb }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
{ 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */
{ 0x07bb, 0x03ce }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */
{ 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */
{ 0x07c2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */
{ 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */
{ 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */
{ 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */
{ 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */
{ 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */
{ 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */
{ 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */
{ 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */
{ 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */
{ 0x07cc, 0x039c }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */
{ 0x07cd, 0x039d }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */
{ 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */
{ 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */
{ 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */
{ 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */
{ 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */
{ 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */
{ 0x07d5, 0x03a5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */
{ 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */
{ 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */
{ 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */
{ 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */
{ 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */
{ 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */
{ 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */
{ 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */
{ 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */
{ 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */
{ 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */
{ 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */
{ 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */
{ 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */
{ 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */
{ 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */
{ 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */
{ 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */
{ 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */
{ 0x07f0, 0x03c0 }, /* Greek_pi π GREEK SMALL LETTER PI */
{ 0x07f1, 0x03c1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */
{ 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */
{ 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */
{ 0x07f4, 0x03c4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */
{ 0x07f5, 0x03c5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */
{ 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */
{ 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */
{ 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */
{ 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */
{ 0x08a1, 0x23b7 }, /* leftradical ⎷ ??? */
{ 0x08a2, 0x250c }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
{ 0x08a3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */
{ 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */
{ 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */
{ 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */
{ 0x08a7, 0x23a1 }, /* topleftsqbracket ⎡ ??? */
{ 0x08a8, 0x23a3 }, /* botleftsqbracket ⎣ ??? */
{ 0x08a9, 0x23a4 }, /* toprightsqbracket ⎤ ??? */
{ 0x08aa, 0x23a6 }, /* botrightsqbracket ⎦ ??? */
{ 0x08ab, 0x239b }, /* topleftparens ⎛ ??? */
{ 0x08ac, 0x239d }, /* botleftparens ⎝ ??? */
{ 0x08ad, 0x239e }, /* toprightparens ⎞ ??? */
{ 0x08ae, 0x23a0 }, /* botrightparens ⎠ ??? */
{ 0x08af, 0x23a8 }, /* leftmiddlecurlybrace ⎨ ??? */
{ 0x08b0, 0x23ac }, /* rightmiddlecurlybrace ⎬ ??? */
/* 0x08b1 topleftsummation ? ??? */
/* 0x08b2 botleftsummation ? ??? */
/* 0x08b3 topvertsummationconnector ? ??? */
/* 0x08b4 botvertsummationconnector ? ??? */
/* 0x08b5 toprightsummation ? ??? */
/* 0x08b6 botrightsummation ? ??? */
/* 0x08b7 rightmiddlesummation ? ??? */
{ 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */
{ 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */
{ 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */
{ 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */
{ 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */
{ 0x08c1, 0x221d }, /* variation ∝ PROPORTIONAL TO */
{ 0x08c2, 0x221e }, /* infinity ∞ INFINITY */
{ 0x08c5, 0x2207 }, /* nabla ∇ NABLA */
{ 0x08c8, 0x223c }, /* approximate TILDE OPERATOR */
{ 0x08c9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */
{ 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */
{ 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */
{ 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */
{ 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */
{ 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */
{ 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */
{ 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */
{ 0x08dd, 0x222a }, /* union UNION */
{ 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */
{ 0x08df, 0x2228 }, /* logicalor LOGICAL OR */
{ 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */
{ 0x08f6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */
{ 0x08fb, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */
{ 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */
{ 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */
{ 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */
/* 0x09df blank ? ??? */
{ 0x09e0, 0x25c6 }, /* soliddiamond ◆ BLACK DIAMOND */
{ 0x09e1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */
{ 0x09e2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */
{ 0x09e3, 0x240c }, /* ff ␌ SYMBOL FOR FORM FEED */
{ 0x09e4, 0x240d }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */
{ 0x09e5, 0x240a }, /* lf ␊ SYMBOL FOR LINE FEED */
{ 0x09e8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */
{ 0x09e9, 0x240b }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */
{ 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */
{ 0x09eb, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */
{ 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */
{ 0x09ed, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */
{ 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
{ 0x09ef, 0x23ba }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */
{ 0x09f0, 0x23bb }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */
{ 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */
{ 0x09f2, 0x23bc }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */
{ 0x09f3, 0x23bd }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */
{ 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
{ 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */
{ 0x09f6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */
{ 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
{ 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */
{ 0x0aa1, 0x2003 }, /* emspace EM SPACE */
{ 0x0aa2, 0x2002 }, /* enspace EN SPACE */
{ 0x0aa3, 0x2004 }, /* em3space THREE-PER-EM SPACE */
{ 0x0aa4, 0x2005 }, /* em4space FOUR-PER-EM SPACE */
{ 0x0aa5, 0x2007 }, /* digitspace FIGURE SPACE */
{ 0x0aa6, 0x2008 }, /* punctspace PUNCTUATION SPACE */
{ 0x0aa7, 0x2009 }, /* thinspace THIN SPACE */
{ 0x0aa8, 0x200a }, /* hairspace HAIR SPACE */
{ 0x0aa9, 0x2014 }, /* emdash — EM DASH */
{ 0x0aaa, 0x2013 }, /* endash EN DASH */
{ 0x0aac, 0x2423 }, /* signifblank ␣ OPEN BOX */
{ 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */
{ 0x0aaf, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */
{ 0x0ab0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */
{ 0x0ab1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */
{ 0x0ab2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */
{ 0x0ab3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */
{ 0x0ab4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */
{ 0x0ab5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */
{ 0x0ab6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */
{ 0x0ab7, 0x215a }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */
{ 0x0ab8, 0x2105 }, /* careof ℅ CARE OF */
{ 0x0abb, 0x2012 }, /* figdash FIGURE DASH */
{ 0x0abc, 0x27e8 }, /* leftanglebracket ⟨ MATHEMATICAL LEFT ANGLE BRACKET */
{ 0x0abd, 0x002e }, /* decimalpoint . FULL STOP */
{ 0x0abe, 0x27e9 }, /* rightanglebracket ⟩ MATHEMATICAL RIGHT ANGLE BRACKET */
/* 0x0abf marker ? ??? */
{ 0x0ac3, 0x215b }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */
{ 0x0ac4, 0x215c }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */
{ 0x0ac5, 0x215d }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */
{ 0x0ac6, 0x215e }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */
{ 0x0ac9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */
{ 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */
/* 0x0acb trademarkincircle ? ??? */
{ 0x0acc, 0x25c1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */
{ 0x0acd, 0x25b7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */
{ 0x0ace, 0x25cb }, /* emopencircle ○ WHITE CIRCLE */
{ 0x0acf, 0x25af }, /* emopenrectangle ▯ WHITE VERTICAL RECTANGLE */
{ 0x0ad0, 0x2018 }, /* leftsinglequotemark LEFT SINGLE QUOTATION MARK */
{ 0x0ad1, 0x2019 }, /* rightsinglequotemark RIGHT SINGLE QUOTATION MARK */
{ 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */
{ 0x0ad3, 0x201d }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */
{ 0x0ad4, 0x211e }, /* prescription ℞ PRESCRIPTION TAKE */
{ 0x0ad5, 0x2030 }, /* permille ‰ PER MILLE SIGN */
{ 0x0ad6, 0x2032 }, /* minutes PRIME */
{ 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */
{ 0x0ad9, 0x271d }, /* latincross ✝ LATIN CROSS */
/* 0x0ada hexagram ? ??? */
{ 0x0adb, 0x25ac }, /* filledrectbullet ▬ BLACK RECTANGLE */
{ 0x0adc, 0x25c0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */
{ 0x0add, 0x25b6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */
{ 0x0ade, 0x25cf }, /* emfilledcircle ● BLACK CIRCLE */
{ 0x0adf, 0x25ae }, /* emfilledrect ▮ BLACK VERTICAL RECTANGLE */
{ 0x0ae0, 0x25e6 }, /* enopencircbullet ◦ WHITE BULLET */
{ 0x0ae1, 0x25ab }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */
{ 0x0ae2, 0x25ad }, /* openrectbullet ▭ WHITE RECTANGLE */
{ 0x0ae3, 0x25b3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */
{ 0x0ae4, 0x25bd }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */
{ 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */
{ 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */
{ 0x0ae7, 0x25aa }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */
{ 0x0ae8, 0x25b2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */
{ 0x0ae9, 0x25bc }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */
{ 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */
{ 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */
{ 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */
{ 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */
{ 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */
{ 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */
{ 0x0af1, 0x2020 }, /* dagger † DAGGER */
{ 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */
{ 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */
{ 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */
{ 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */
{ 0x0af6, 0x266d }, /* musicalflat ♭ MUSIC FLAT SIGN */
{ 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */
{ 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */
{ 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */
{ 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */
{ 0x0afb, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */
{ 0x0afc, 0x2038 }, /* caret ‸ CARET */
{ 0x0afd, 0x201a }, /* singlelowquotemark SINGLE LOW-9 QUOTATION MARK */
{ 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */
/* 0x0aff cursor ? ??? */
{ 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */
{ 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */
{ 0x0ba8, 0x2228 }, /* downcaret LOGICAL OR */
{ 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */
{ 0x0bc0, 0x00af }, /* overbar ¯ MACRON */
{ 0x0bc2, 0x22a4 }, /* downtack DOWN TACK */
{ 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */
{ 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */
{ 0x0bc6, 0x005f }, /* underbar _ LOW LINE */
{ 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */
{ 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD (Unicode 3.0) */
{ 0x0bce, 0x22a5 }, /* uptack ⊥ UP TACK */
{ 0x0bcf, 0x25cb }, /* circle ○ WHITE CIRCLE */
{ 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */
{ 0x0bd6, 0x222a }, /* downshoe UNION */
{ 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */
{ 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */
{ 0x0bdc, 0x22a2 }, /* lefttack ⊢ RIGHT TACK */
{ 0x0bfc, 0x22a3 }, /* righttack ⊣ LEFT TACK */
{ 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */
{ 0x0ce0, 0x05d0 }, /* hebrew_aleph א HEBREW LETTER ALEF */
{ 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */
{ 0x0ce2, 0x05d2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */
{ 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */
{ 0x0ce4, 0x05d4 }, /* hebrew_he ה HEBREW LETTER HE */
{ 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */
{ 0x0ce6, 0x05d6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */
{ 0x0ce7, 0x05d7 }, /* hebrew_chet ח HEBREW LETTER HET */
{ 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */
{ 0x0ce9, 0x05d9 }, /* hebrew_yod י HEBREW LETTER YOD */
{ 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */
{ 0x0ceb, 0x05db }, /* hebrew_kaph כ HEBREW LETTER KAF */
{ 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */
{ 0x0ced, 0x05dd }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */
{ 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */
{ 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */
{ 0x0cf0, 0x05e0 }, /* hebrew_nun נ HEBREW LETTER NUN */
{ 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */
{ 0x0cf2, 0x05e2 }, /* hebrew_ayin ע HEBREW LETTER AYIN */
{ 0x0cf3, 0x05e3 }, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */
{ 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */
{ 0x0cf5, 0x05e5 }, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */
{ 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */
{ 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */
{ 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */
{ 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */
{ 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */
{ 0x0da1, 0x0e01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */
{ 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */
{ 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */
{ 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */
{ 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */
{ 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */
{ 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */
{ 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */
{ 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */
{ 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */
{ 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */
{ 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */
{ 0x0dad, 0x0e0d }, /* Thai_yoying ญ THAI CHARACTER YO YING */
{ 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */
{ 0x0daf, 0x0e0f }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */
{ 0x0db0, 0x0e10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */
{ 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */
{ 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */
{ 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */
{ 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */
{ 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */
{ 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */
{ 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */
{ 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */
{ 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */
{ 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */
{ 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */
{ 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */
{ 0x0dbd, 0x0e1d }, /* Thai_fofa ฝ THAI CHARACTER FO FA */
{ 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */
{ 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */
{ 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */
{ 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */
{ 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */
{ 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */
{ 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */
{ 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */
{ 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */
{ 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */
{ 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */
{ 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */
{ 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */
{ 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */
{ 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */
{ 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */
{ 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */
{ 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */
{ 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */
{ 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */
{ 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */
{ 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */
{ 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */
{ 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */
{ 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */
{ 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */
{ 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */
{ 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */
{ 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */
{ 0x0dde, 0x0e3e }, /* Thai_maihanakat_maitho ฾ ??? */
{ 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */
{ 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */
{ 0x0de1, 0x0e41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */
{ 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */
{ 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */
{ 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */
{ 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */
{ 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */
{ 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */
{ 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */
{ 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */
{ 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */
{ 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */
{ 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */
{ 0x0ded, 0x0e4d }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */
{ 0x0df0, 0x0e50 }, /* Thai_leksun THAI DIGIT ZERO */
{ 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */
{ 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */
{ 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */
{ 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */
{ 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */
{ 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */
{ 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */
{ 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */
{ 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */
{ 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */
{ 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */
{ 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */
{ 0x0ea4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */
{ 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */
{ 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */
{ 0x0ea7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */
{ 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */
{ 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */
{ 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */
{ 0x0eab, 0x313b }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */
{ 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */
{ 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */
{ 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */
{ 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */
{ 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */
{ 0x0eb1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */
{ 0x0eb2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */
{ 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */
{ 0x0eb4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */
{ 0x0eb5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */
{ 0x0eb6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */
{ 0x0eb7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */
{ 0x0eb8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */
{ 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */
{ 0x0eba, 0x314a }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */
{ 0x0ebb, 0x314b }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */
{ 0x0ebc, 0x314c }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */
{ 0x0ebd, 0x314d }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */
{ 0x0ebe, 0x314e }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */
{ 0x0ebf, 0x314f }, /* Hangul_A ㅏ HANGUL LETTER A */
{ 0x0ec0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */
{ 0x0ec1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */
{ 0x0ec2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */
{ 0x0ec3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */
{ 0x0ec4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */
{ 0x0ec5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */
{ 0x0ec6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */
{ 0x0ec7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */
{ 0x0ec8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */
{ 0x0ec9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */
{ 0x0eca, 0x315a }, /* Hangul_OE ㅚ HANGUL LETTER OE */
{ 0x0ecb, 0x315b }, /* Hangul_YO ㅛ HANGUL LETTER YO */
{ 0x0ecc, 0x315c }, /* Hangul_U ㅜ HANGUL LETTER U */
{ 0x0ecd, 0x315d }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */
{ 0x0ece, 0x315e }, /* Hangul_WE ㅞ HANGUL LETTER WE */
{ 0x0ecf, 0x315f }, /* Hangul_WI ㅟ HANGUL LETTER WI */
{ 0x0ed0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */
{ 0x0ed1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */
{ 0x0ed2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */
{ 0x0ed3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */
{ 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */
{ 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */
{ 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */
{ 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */
{ 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */
{ 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */
{ 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */
{ 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */
{ 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */
{ 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */
{ 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */
{ 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */
{ 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */
{ 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */
{ 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */
{ 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */
{ 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */
{ 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */
{ 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */
{ 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */
{ 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */
{ 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */
{ 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */
{ 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */
{ 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */
{ 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */
{ 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */
{ 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */
{ 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */
{ 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */
{ 0x0ef2, 0x317f }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */
/* 0x0ef3 Hangul_KkogjiDalrinIeung ? ??? */
{ 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */
{ 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */
{ 0x0ef6, 0x318d }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */
{ 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */
{ 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */
{ 0x0ef9, 0x11f0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */
{ 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */
{ 0x0eff, 0x20a9 }, /* Korean_Won ₩ WON SIGN */
{ 0x13a4, 0x20ac }, /* Euro € EURO SIGN */
{ 0x13bc, 0x0152 }, /* OE Œ LATIN CAPITAL LIGATURE OE */
{ 0x13bd, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */
{ 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */
{ 0x20a0, 0x20a0 }, /* EcuSign ₠ EURO-CURRENCY SIGN */
{ 0x20a1, 0x20a1 }, /* ColonSign ₡ COLON SIGN */
{ 0x20a2, 0x20a2 }, /* CruzeiroSign ₢ CRUZEIRO SIGN */
{ 0x20a3, 0x20a3 }, /* FFrancSign ₣ FRENCH FRANC SIGN */
{ 0x20a4, 0x20a4 }, /* LiraSign ₤ LIRA SIGN */
{ 0x20a5, 0x20a5 }, /* MillSign ₥ MILL SIGN */
{ 0x20a6, 0x20a6 }, /* NairaSign ₦ NAIRA SIGN */
{ 0x20a7, 0x20a7 }, /* PesetaSign ₧ PESETA SIGN */
{ 0x20a8, 0x20a8 }, /* RupeeSign ₨ RUPEE SIGN */
{ 0x20a9, 0x20a9 }, /* WonSign ₩ WON SIGN */
{ 0x20aa, 0x20aa }, /* NewSheqelSign ₪ NEW SHEQEL SIGN */
{ 0x20ab, 0x20ab }, /* DongSign ₫ DONG SIGN */
{ 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */
};
xkb_keysym_t
utf32_to_keysym(uint32_t ucs)
{
/* first check for Latin-1 characters (1:1 mapping) */
if ((ucs >= 0x0020 && ucs <= 0x007e) ||
(ucs >= 0x00a0 && ucs <= 0x00ff))
return ucs;
/* special keysyms */
if ((ucs >= (XKB_KEY_BackSpace & 0x7f) && ucs <= (XKB_KEY_Clear & 0x7f)) ||
ucs == (XKB_KEY_Return & 0x7f) || ucs == (XKB_KEY_Escape & 0x7f))
return ucs | 0xff00;
if (ucs == (XKB_KEY_Delete & 0x7f))
return XKB_KEY_Delete;
/* Unicode non-symbols and code points outside Unicode planes */
if ((ucs >= 0xfdd0 && ucs <= 0xfdef) ||
ucs > 0x10ffff || (ucs & 0xfffe) == 0xfffe)
return XKB_KEY_NoSymbol;
/* search main table */
for (size_t i = 0; i < sizeof(keysymtab)/sizeof(keysymtab[0]); i++)
if (keysymtab[i].ucs == ucs)
return keysymtab[i].keysym;
/* Use direct encoding if everything else fails */
return ucs | 0x01000000;
}

615
glfw/xkb_glfw.c vendored
View File

@@ -29,221 +29,255 @@
#include <stdlib.h>
#include "internal.h"
#include "xkb_glfw.h"
START_ALLOW_CASE_RANGE
#ifdef _GLFW_X11
#include <X11/XKBlib.h>
#endif
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
#define map_key(key) \
switch(key) { \
S(space, SPACE); \
S(exclam, EXCLAM); \
S(quotedbl, DOUBLE_QUOTE); \
S(numbersign, NUMBER_SIGN); \
S(dollar, DOLLAR); \
S(ampersand, AMPERSAND); \
S(apostrophe, APOSTROPHE); \
S(parenleft, PARENTHESIS_LEFT); \
S(parenright, PARENTHESIS_RIGHT); \
S(plus, PLUS); \
S(comma, COMMA); \
S(minus, MINUS); \
S(period, PERIOD); \
S(slash, SLASH); \
R(0, 9, 0, 9); \
S(colon, COLON); \
S(semicolon, SEMICOLON); \
S(less, LESS); \
S(equal, EQUAL); \
S(greater, GREATER); \
S(at, AT); \
D(A, Z, A, Z); \
S(bracketleft, LEFT_BRACKET); \
S(backslash, BACKSLASH); \
S(bracketright, RIGHT_BRACKET); \
S(asciicircum, CIRCUMFLEX); \
S(underscore, UNDERSCORE); \
S(grave, GRAVE_ACCENT); \
R(a, z, A, Z); \
S(paragraph, PARAGRAPH); \
S(masculine, MASCULINE); \
S(agrave, A_GRAVE); \
F(Agrave, A_GRAVE); \
S(adiaeresis, A_DIAERESIS); \
F(Adiaeresis, A_DIAERESIS); \
S(aring, A_RING); \
F(Aring, A_RING); \
S(ae, AE); \
F(AE, AE); \
S(ccedilla, C_CEDILLA); \
F(Ccedilla, C_CEDILLA); \
S(egrave, E_GRAVE); \
F(Egrave, E_GRAVE); \
S(aacute, E_ACUTE); \
F(Eacute, E_ACUTE); \
S(igrave, I_GRAVE); \
F(Igrave, I_GRAVE); \
S(ntilde, N_TILDE); \
F(Ntilde, N_TILDE); \
S(ograve, O_GRAVE); \
F(Ograve, O_GRAVE); \
S(odiaeresis, O_DIAERESIS); \
F(Odiaeresis, O_DIAERESIS); \
S(oslash, O_SLASH); \
F(Oslash, O_SLASH); \
S(ugrave, U_GRAVE); \
F(Ugrave, U_GRAVE); \
S(udiaeresis, U_DIAERESIS); \
F(Udiaeresis, U_DIAERESIS); \
S(ssharp, S_SHARP); \
S(Cyrillic_a, CYRILLIC_A); \
F(Cyrillic_A, CYRILLIC_A); \
S(Cyrillic_be, CYRILLIC_BE); \
F(Cyrillic_BE, CYRILLIC_BE); \
S(Cyrillic_ve, CYRILLIC_VE); \
F(Cyrillic_VE, CYRILLIC_VE); \
S(Cyrillic_ghe, CYRILLIC_GHE); \
F(Cyrillic_GHE, CYRILLIC_GHE); \
S(Cyrillic_de, CYRILLIC_DE); \
F(Cyrillic_DE, CYRILLIC_DE); \
S(Cyrillic_ie, CYRILLIC_IE); \
F(Cyrillic_IE, CYRILLIC_IE); \
S(Cyrillic_zhe, CYRILLIC_ZHE); \
F(Cyrillic_ZHE, CYRILLIC_ZHE); \
S(Cyrillic_ze, CYRILLIC_ZE); \
F(Cyrillic_ZE, CYRILLIC_ZE); \
S(Cyrillic_i, CYRILLIC_I); \
F(Cyrillic_I, CYRILLIC_I); \
S(Cyrillic_shorti, CYRILLIC_SHORT_I); \
F(Cyrillic_SHORTI, CYRILLIC_SHORT_I); \
S(Cyrillic_ka, CYRILLIC_KA); \
F(Cyrillic_KA, CYRILLIC_KA); \
S(Cyrillic_el, CYRILLIC_EL); \
F(Cyrillic_EL, CYRILLIC_EL); \
S(Cyrillic_em, CYRILLIC_EM); \
F(Cyrillic_EM, CYRILLIC_EM); \
S(Cyrillic_en, CYRILLIC_EN); \
F(Cyrillic_EN, CYRILLIC_EN); \
S(Cyrillic_o, CYRILLIC_O); \
F(Cyrillic_O, CYRILLIC_O); \
S(Cyrillic_pe, CYRILLIC_PE); \
F(Cyrillic_PE, CYRILLIC_PE); \
S(Cyrillic_er, CYRILLIC_ER); \
F(Cyrillic_ER, CYRILLIC_ER); \
S(Cyrillic_es, CYRILLIC_ES); \
F(Cyrillic_ES, CYRILLIC_ES); \
S(Cyrillic_te, CYRILLIC_TE); \
F(Cyrillic_TE, CYRILLIC_TE); \
S(Cyrillic_u, CYRILLIC_U); \
F(Cyrillic_U, CYRILLIC_U); \
S(Cyrillic_ef, CYRILLIC_EF); \
F(Cyrillic_EF, CYRILLIC_EF); \
S(Cyrillic_ha, CYRILLIC_HA); \
F(Cyrillic_HA, CYRILLIC_HA); \
S(Cyrillic_tse, CYRILLIC_TSE); \
F(Cyrillic_TSE, CYRILLIC_TSE); \
S(Cyrillic_che, CYRILLIC_CHE); \
F(Cyrillic_CHE, CYRILLIC_CHE); \
S(Cyrillic_sha, CYRILLIC_SHA); \
F(Cyrillic_SHA, CYRILLIC_SHA); \
S(Cyrillic_shcha, CYRILLIC_SHCHA); \
F(Cyrillic_SHCHA, CYRILLIC_SHCHA); \
S(Cyrillic_hardsign, CYRILLIC_HARD_SIGN); \
F(Cyrillic_HARDSIGN, CYRILLIC_HARD_SIGN); \
S(Cyrillic_yeru, CYRILLIC_YERU); \
F(Cyrillic_YERU, CYRILLIC_YERU); \
S(Cyrillic_softsign, CYRILLIC_SOFT_SIGN); \
F(Cyrillic_SOFTSIGN, CYRILLIC_SOFT_SIGN); \
S(Cyrillic_e, CYRILLIC_E); \
F(Cyrillic_E, CYRILLIC_E); \
S(Cyrillic_yu, CYRILLIC_YU); \
F(Cyrillic_YU, CYRILLIC_YU); \
S(Cyrillic_ya, CYRILLIC_YA); \
F(Cyrillic_YA, CYRILLIC_YA); \
S(Cyrillic_io, CYRILLIC_IO); \
F(Cyrillic_IO, CYRILLIC_IO); \
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(F1, F25, F1, F25); \
R(KP_0, KP_9, KP_0, KP_9); \
#ifdef XKB_HAS_NO_UTF32
#include "xkb-compat-shim.h"
#else
#define utf32_to_keysym xkb_utf32_to_keysym
#endif
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, __VA_ARGS__)
map_key(key)
S(KP_Up, UP);
S(KP_Down, DOWN);
S(KP_Left, LEFT);
S(KP_Right, RIGHT);
switch(key) {
/* start xkb to glfw (auto generated by gen-key-constants.py do not edit) */
case XKB_KEY_Escape: return GLFW_FKEY_ESCAPE;
case XKB_KEY_Return: return GLFW_FKEY_ENTER;
case XKB_KEY_Tab: return GLFW_FKEY_TAB;
case XKB_KEY_BackSpace: return GLFW_FKEY_BACKSPACE;
case XKB_KEY_Insert: return GLFW_FKEY_INSERT;
case XKB_KEY_Delete: return GLFW_FKEY_DELETE;
case XKB_KEY_Left: return GLFW_FKEY_LEFT;
case XKB_KEY_Right: return GLFW_FKEY_RIGHT;
case XKB_KEY_Up: return GLFW_FKEY_UP;
case XKB_KEY_Down: return GLFW_FKEY_DOWN;
case XKB_KEY_Page_Up: return GLFW_FKEY_PAGE_UP;
case XKB_KEY_Page_Down: return GLFW_FKEY_PAGE_DOWN;
case XKB_KEY_Home: return GLFW_FKEY_HOME;
case XKB_KEY_End: return GLFW_FKEY_END;
case XKB_KEY_Caps_Lock: return GLFW_FKEY_CAPS_LOCK;
case XKB_KEY_Scroll_Lock: return GLFW_FKEY_SCROLL_LOCK;
case XKB_KEY_Num_Lock: return GLFW_FKEY_NUM_LOCK;
case XKB_KEY_Print: return GLFW_FKEY_PRINT_SCREEN;
case XKB_KEY_Pause: return GLFW_FKEY_PAUSE;
case XKB_KEY_Menu: return GLFW_FKEY_MENU;
case XKB_KEY_F1: return GLFW_FKEY_F1;
case XKB_KEY_F2: return GLFW_FKEY_F2;
case XKB_KEY_F3: return GLFW_FKEY_F3;
case XKB_KEY_F4: return GLFW_FKEY_F4;
case XKB_KEY_F5: return GLFW_FKEY_F5;
case XKB_KEY_F6: return GLFW_FKEY_F6;
case XKB_KEY_F7: return GLFW_FKEY_F7;
case XKB_KEY_F8: return GLFW_FKEY_F8;
case XKB_KEY_F9: return GLFW_FKEY_F9;
case XKB_KEY_F10: return GLFW_FKEY_F10;
case XKB_KEY_F11: return GLFW_FKEY_F11;
case XKB_KEY_F12: return GLFW_FKEY_F12;
case XKB_KEY_F13: return GLFW_FKEY_F13;
case XKB_KEY_F14: return GLFW_FKEY_F14;
case XKB_KEY_F15: return GLFW_FKEY_F15;
case XKB_KEY_F16: return GLFW_FKEY_F16;
case XKB_KEY_F17: return GLFW_FKEY_F17;
case XKB_KEY_F18: return GLFW_FKEY_F18;
case XKB_KEY_F19: return GLFW_FKEY_F19;
case XKB_KEY_F20: return GLFW_FKEY_F20;
case XKB_KEY_F21: return GLFW_FKEY_F21;
case XKB_KEY_F22: return GLFW_FKEY_F22;
case XKB_KEY_F23: return GLFW_FKEY_F23;
case XKB_KEY_F24: return GLFW_FKEY_F24;
case XKB_KEY_F25: return GLFW_FKEY_F25;
case XKB_KEY_F26: return GLFW_FKEY_F26;
case XKB_KEY_F27: return GLFW_FKEY_F27;
case XKB_KEY_F28: return GLFW_FKEY_F28;
case XKB_KEY_F29: return GLFW_FKEY_F29;
case XKB_KEY_F30: return GLFW_FKEY_F30;
case XKB_KEY_F31: return GLFW_FKEY_F31;
case XKB_KEY_F32: return GLFW_FKEY_F32;
case XKB_KEY_F33: return GLFW_FKEY_F33;
case XKB_KEY_F34: return GLFW_FKEY_F34;
case XKB_KEY_F35: return GLFW_FKEY_F35;
case XKB_KEY_KP_0: return GLFW_FKEY_KP_0;
case XKB_KEY_KP_1: return GLFW_FKEY_KP_1;
case XKB_KEY_KP_2: return GLFW_FKEY_KP_2;
case XKB_KEY_KP_3: return GLFW_FKEY_KP_3;
case XKB_KEY_KP_4: return GLFW_FKEY_KP_4;
case XKB_KEY_KP_5: return GLFW_FKEY_KP_5;
case XKB_KEY_KP_6: return GLFW_FKEY_KP_6;
case XKB_KEY_KP_7: return GLFW_FKEY_KP_7;
case XKB_KEY_KP_8: return GLFW_FKEY_KP_8;
case XKB_KEY_KP_9: return GLFW_FKEY_KP_9;
case XKB_KEY_KP_Decimal: return GLFW_FKEY_KP_DECIMAL;
case XKB_KEY_KP_Divide: return GLFW_FKEY_KP_DIVIDE;
case XKB_KEY_KP_Multiply: return GLFW_FKEY_KP_MULTIPLY;
case XKB_KEY_KP_Subtract: return GLFW_FKEY_KP_SUBTRACT;
case XKB_KEY_KP_Add: return GLFW_FKEY_KP_ADD;
case XKB_KEY_KP_Enter: return GLFW_FKEY_KP_ENTER;
case XKB_KEY_KP_Equal: return GLFW_FKEY_KP_EQUAL;
case XKB_KEY_KP_Separator: return GLFW_FKEY_KP_SEPARATOR;
case XKB_KEY_KP_Left: return GLFW_FKEY_KP_LEFT;
case XKB_KEY_KP_Right: return GLFW_FKEY_KP_RIGHT;
case XKB_KEY_KP_Up: return GLFW_FKEY_KP_UP;
case XKB_KEY_KP_Down: return GLFW_FKEY_KP_DOWN;
case XKB_KEY_KP_Page_Up: return GLFW_FKEY_KP_PAGE_UP;
case XKB_KEY_KP_Page_Down: return GLFW_FKEY_KP_PAGE_DOWN;
case XKB_KEY_KP_Home: return GLFW_FKEY_KP_HOME;
case XKB_KEY_KP_End: return GLFW_FKEY_KP_END;
case XKB_KEY_KP_Insert: return GLFW_FKEY_KP_INSERT;
case XKB_KEY_KP_Delete: return GLFW_FKEY_KP_DELETE;
case XKB_KEY_KP_Begin: return GLFW_FKEY_KP_BEGIN;
case XKB_KEY_XF86AudioPlay: return GLFW_FKEY_MEDIA_PLAY;
case XKB_KEY_XF86AudioPause: return GLFW_FKEY_MEDIA_PAUSE;
case XKB_KEY_XF86AudioStop: return GLFW_FKEY_MEDIA_STOP;
case XKB_KEY_XF86AudioForward: return GLFW_FKEY_MEDIA_FAST_FORWARD;
case XKB_KEY_XF86AudioRewind: return GLFW_FKEY_MEDIA_REWIND;
case XKB_KEY_XF86AudioNext: return GLFW_FKEY_MEDIA_TRACK_NEXT;
case XKB_KEY_XF86AudioPrev: return GLFW_FKEY_MEDIA_TRACK_PREVIOUS;
case XKB_KEY_XF86AudioRecord: return GLFW_FKEY_MEDIA_RECORD;
case XKB_KEY_XF86AudioLowerVolume: return GLFW_FKEY_LOWER_VOLUME;
case XKB_KEY_XF86AudioRaiseVolume: return GLFW_FKEY_RAISE_VOLUME;
case XKB_KEY_XF86AudioMute: return GLFW_FKEY_MUTE_VOLUME;
case XKB_KEY_Shift_L: return GLFW_FKEY_LEFT_SHIFT;
case XKB_KEY_Control_L: return GLFW_FKEY_LEFT_CONTROL;
case XKB_KEY_Alt_L: return GLFW_FKEY_LEFT_ALT;
case XKB_KEY_Super_L: return GLFW_FKEY_LEFT_SUPER;
case XKB_KEY_Hyper_L: return GLFW_FKEY_LEFT_HYPER;
case XKB_KEY_Meta_L: return GLFW_FKEY_LEFT_META;
case XKB_KEY_Shift_R: return GLFW_FKEY_RIGHT_SHIFT;
case XKB_KEY_Control_R: return GLFW_FKEY_RIGHT_CONTROL;
case XKB_KEY_Alt_R: return GLFW_FKEY_RIGHT_ALT;
case XKB_KEY_Super_R: return GLFW_FKEY_RIGHT_SUPER;
case XKB_KEY_Hyper_R: return GLFW_FKEY_RIGHT_HYPER;
case XKB_KEY_Meta_R: return GLFW_FKEY_RIGHT_META;
case XKB_KEY_ISO_Level3_Shift: return GLFW_FKEY_ISO_LEVEL3_SHIFT;
case XKB_KEY_ISO_Level5_Shift: return GLFW_FKEY_ISO_LEVEL5_SHIFT;
/* end xkb to glfw */
default:
break;
return xkb_keysym_to_utf32(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)
default:
break;
glfw_xkb_sym_for_key(uint32_t key) {
switch(key) {
/* start glfw to xkb (auto generated by gen-key-constants.py do not edit) */
case GLFW_FKEY_ESCAPE: return XKB_KEY_Escape;
case GLFW_FKEY_ENTER: return XKB_KEY_Return;
case GLFW_FKEY_TAB: return XKB_KEY_Tab;
case GLFW_FKEY_BACKSPACE: return XKB_KEY_BackSpace;
case GLFW_FKEY_INSERT: return XKB_KEY_Insert;
case GLFW_FKEY_DELETE: return XKB_KEY_Delete;
case GLFW_FKEY_LEFT: return XKB_KEY_Left;
case GLFW_FKEY_RIGHT: return XKB_KEY_Right;
case GLFW_FKEY_UP: return XKB_KEY_Up;
case GLFW_FKEY_DOWN: return XKB_KEY_Down;
case GLFW_FKEY_PAGE_UP: return XKB_KEY_Page_Up;
case GLFW_FKEY_PAGE_DOWN: return XKB_KEY_Page_Down;
case GLFW_FKEY_HOME: return XKB_KEY_Home;
case GLFW_FKEY_END: return XKB_KEY_End;
case GLFW_FKEY_CAPS_LOCK: return XKB_KEY_Caps_Lock;
case GLFW_FKEY_SCROLL_LOCK: return XKB_KEY_Scroll_Lock;
case GLFW_FKEY_NUM_LOCK: return XKB_KEY_Num_Lock;
case GLFW_FKEY_PRINT_SCREEN: return XKB_KEY_Print;
case GLFW_FKEY_PAUSE: return XKB_KEY_Pause;
case GLFW_FKEY_MENU: return XKB_KEY_Menu;
case GLFW_FKEY_F1: return XKB_KEY_F1;
case GLFW_FKEY_F2: return XKB_KEY_F2;
case GLFW_FKEY_F3: return XKB_KEY_F3;
case GLFW_FKEY_F4: return XKB_KEY_F4;
case GLFW_FKEY_F5: return XKB_KEY_F5;
case GLFW_FKEY_F6: return XKB_KEY_F6;
case GLFW_FKEY_F7: return XKB_KEY_F7;
case GLFW_FKEY_F8: return XKB_KEY_F8;
case GLFW_FKEY_F9: return XKB_KEY_F9;
case GLFW_FKEY_F10: return XKB_KEY_F10;
case GLFW_FKEY_F11: return XKB_KEY_F11;
case GLFW_FKEY_F12: return XKB_KEY_F12;
case GLFW_FKEY_F13: return XKB_KEY_F13;
case GLFW_FKEY_F14: return XKB_KEY_F14;
case GLFW_FKEY_F15: return XKB_KEY_F15;
case GLFW_FKEY_F16: return XKB_KEY_F16;
case GLFW_FKEY_F17: return XKB_KEY_F17;
case GLFW_FKEY_F18: return XKB_KEY_F18;
case GLFW_FKEY_F19: return XKB_KEY_F19;
case GLFW_FKEY_F20: return XKB_KEY_F20;
case GLFW_FKEY_F21: return XKB_KEY_F21;
case GLFW_FKEY_F22: return XKB_KEY_F22;
case GLFW_FKEY_F23: return XKB_KEY_F23;
case GLFW_FKEY_F24: return XKB_KEY_F24;
case GLFW_FKEY_F25: return XKB_KEY_F25;
case GLFW_FKEY_F26: return XKB_KEY_F26;
case GLFW_FKEY_F27: return XKB_KEY_F27;
case GLFW_FKEY_F28: return XKB_KEY_F28;
case GLFW_FKEY_F29: return XKB_KEY_F29;
case GLFW_FKEY_F30: return XKB_KEY_F30;
case GLFW_FKEY_F31: return XKB_KEY_F31;
case GLFW_FKEY_F32: return XKB_KEY_F32;
case GLFW_FKEY_F33: return XKB_KEY_F33;
case GLFW_FKEY_F34: return XKB_KEY_F34;
case GLFW_FKEY_F35: return XKB_KEY_F35;
case GLFW_FKEY_KP_0: return XKB_KEY_KP_0;
case GLFW_FKEY_KP_1: return XKB_KEY_KP_1;
case GLFW_FKEY_KP_2: return XKB_KEY_KP_2;
case GLFW_FKEY_KP_3: return XKB_KEY_KP_3;
case GLFW_FKEY_KP_4: return XKB_KEY_KP_4;
case GLFW_FKEY_KP_5: return XKB_KEY_KP_5;
case GLFW_FKEY_KP_6: return XKB_KEY_KP_6;
case GLFW_FKEY_KP_7: return XKB_KEY_KP_7;
case GLFW_FKEY_KP_8: return XKB_KEY_KP_8;
case GLFW_FKEY_KP_9: return XKB_KEY_KP_9;
case GLFW_FKEY_KP_DECIMAL: return XKB_KEY_KP_Decimal;
case GLFW_FKEY_KP_DIVIDE: return XKB_KEY_KP_Divide;
case GLFW_FKEY_KP_MULTIPLY: return XKB_KEY_KP_Multiply;
case GLFW_FKEY_KP_SUBTRACT: return XKB_KEY_KP_Subtract;
case GLFW_FKEY_KP_ADD: return XKB_KEY_KP_Add;
case GLFW_FKEY_KP_ENTER: return XKB_KEY_KP_Enter;
case GLFW_FKEY_KP_EQUAL: return XKB_KEY_KP_Equal;
case GLFW_FKEY_KP_SEPARATOR: return XKB_KEY_KP_Separator;
case GLFW_FKEY_KP_LEFT: return XKB_KEY_KP_Left;
case GLFW_FKEY_KP_RIGHT: return XKB_KEY_KP_Right;
case GLFW_FKEY_KP_UP: return XKB_KEY_KP_Up;
case GLFW_FKEY_KP_DOWN: return XKB_KEY_KP_Down;
case GLFW_FKEY_KP_PAGE_UP: return XKB_KEY_KP_Page_Up;
case GLFW_FKEY_KP_PAGE_DOWN: return XKB_KEY_KP_Page_Down;
case GLFW_FKEY_KP_HOME: return XKB_KEY_KP_Home;
case GLFW_FKEY_KP_END: return XKB_KEY_KP_End;
case GLFW_FKEY_KP_INSERT: return XKB_KEY_KP_Insert;
case GLFW_FKEY_KP_DELETE: return XKB_KEY_KP_Delete;
case GLFW_FKEY_KP_BEGIN: return XKB_KEY_KP_Begin;
case GLFW_FKEY_MEDIA_PLAY: return XKB_KEY_XF86AudioPlay;
case GLFW_FKEY_MEDIA_PAUSE: return XKB_KEY_XF86AudioPause;
case GLFW_FKEY_MEDIA_STOP: return XKB_KEY_XF86AudioStop;
case GLFW_FKEY_MEDIA_FAST_FORWARD: return XKB_KEY_XF86AudioForward;
case GLFW_FKEY_MEDIA_REWIND: return XKB_KEY_XF86AudioRewind;
case GLFW_FKEY_MEDIA_TRACK_NEXT: return XKB_KEY_XF86AudioNext;
case GLFW_FKEY_MEDIA_TRACK_PREVIOUS: return XKB_KEY_XF86AudioPrev;
case GLFW_FKEY_MEDIA_RECORD: return XKB_KEY_XF86AudioRecord;
case GLFW_FKEY_LOWER_VOLUME: return XKB_KEY_XF86AudioLowerVolume;
case GLFW_FKEY_RAISE_VOLUME: return XKB_KEY_XF86AudioRaiseVolume;
case GLFW_FKEY_MUTE_VOLUME: return XKB_KEY_XF86AudioMute;
case GLFW_FKEY_LEFT_SHIFT: return XKB_KEY_Shift_L;
case GLFW_FKEY_LEFT_CONTROL: return XKB_KEY_Control_L;
case GLFW_FKEY_LEFT_ALT: return XKB_KEY_Alt_L;
case GLFW_FKEY_LEFT_SUPER: return XKB_KEY_Super_L;
case GLFW_FKEY_LEFT_HYPER: return XKB_KEY_Hyper_L;
case GLFW_FKEY_LEFT_META: return XKB_KEY_Meta_L;
case GLFW_FKEY_RIGHT_SHIFT: return XKB_KEY_Shift_R;
case GLFW_FKEY_RIGHT_CONTROL: return XKB_KEY_Control_R;
case GLFW_FKEY_RIGHT_ALT: return XKB_KEY_Alt_R;
case GLFW_FKEY_RIGHT_SUPER: return XKB_KEY_Super_R;
case GLFW_FKEY_RIGHT_HYPER: return XKB_KEY_Hyper_R;
case GLFW_FKEY_RIGHT_META: return XKB_KEY_Meta_R;
case GLFW_FKEY_ISO_LEVEL3_SHIFT: return XKB_KEY_ISO_Level3_Shift;
case GLFW_FKEY_ISO_LEVEL5_SHIFT: return XKB_KEY_ISO_Level5_Shift;
/* end glfw to xkb */
default:
return utf32_to_keysym(key);
}
return GLFW_KEY_UNKNOWN;
#undef F
#undef D
#undef R
#undef S
}
END_ALLOW_CASE_RANGE
#ifdef _GLFW_X11
@@ -283,11 +317,87 @@ glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
if (conn) state = xkb_x11_state_new_from_device(keymap, conn, xkb->keyboard_device_id); \
}
static void
glfw_xkb_update_masks(_GLFWXKBData *xkb) {
// See https://github.com/kovidgoyal/kitty/pull/3430 for discussion
bool succeeded = false;
unsigned used_bits = 0; /* To avoid using the same bit twice */
XkbDescPtr xkb_ptr = XkbGetMap( _glfw.x11.display, XkbVirtualModsMask | XkbVirtualModMapMask, XkbUseCoreKbd );
#define S( a ) xkb->a##Idx = XKB_MOD_INVALID; xkb->a##Mask = 0
S(control); S(alt); S(shift); S(super); S(hyper); S(meta); S(capsLock); S(numLock);
#undef S
if (xkb_ptr) {
Status status = XkbGetNames(_glfw.x11.display, XkbVirtualModNamesMask, xkb_ptr);
if (status == Success) {
for (int indx = 0; indx < XkbNumVirtualMods; ++indx) {
Atom atom = xkb_ptr->names->vmods[indx];
if (atom) {
unsigned mask_rtn = 0;
if (XkbVirtualModsToReal( xkb_ptr, 1<<indx, &mask_rtn) ) {
const char *name = XGetAtomName(_glfw.x11.display, atom);
#define S( a, s ) if (!(used_bits & mask_rtn) && strcmp(name, #s) == 0) xkb->a##Mask = mask_rtn, used_bits |= mask_rtn
/* Note that the order matters here; earlier is higher priority. */
S(alt, Alt);
S(super, Super);
S(numLock, NumLock);
S(meta, Meta);
S(hyper, Hyper);
#undef S
}
}
}
succeeded = true;
}
XkbFreeNames(xkb_ptr, XkbVirtualModNamesMask, True);
XkbFreeKeyboard(xkb_ptr, 0, True);
}
if (succeeded) {
unsigned indx, shifted;
for (indx = 0, shifted = 1; used_bits; ++indx, shifted <<= 1, used_bits >>= 1) {
#define S( a ) if ( ( xkb->a##Mask & shifted ) == shifted ) xkb->a##Idx = indx
S(alt); S(super); S(hyper); S(meta); S(numLock);
#undef S
}
}
#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(shift, XKB_MOD_NAME_SHIFT);
S(capsLock, XKB_MOD_NAME_CAPS);
if (!succeeded) {
S(numLock, XKB_MOD_NAME_NUM);
S(alt, XKB_MOD_NAME_ALT);
S(super, XKB_MOD_NAME_LOGO);
}
#undef S
debug("Modifier indices alt: 0x%x super: 0x%x hyper: 0x%x meta: 0x%x numlock: 0x%x shift: 0x%x capslock: 0x%x\n",
xkb->altIdx, xkb->superIdx, xkb->hyperIdx, xkb->metaIdx, xkb->numLockIdx, xkb->shiftIdx, xkb->capsLockIdx);
}
#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);
static void
glfw_xkb_update_masks(_GLFWXKBData *xkb) {
// Should find better solution under Wayland
// See https://github.com/kovidgoyal/kitty/pull/3430 for discussion
#define S( a ) xkb->a##Idx = XKB_MOD_INVALID; xkb->a##Mask = 0
S(hyper); S(meta);
#undef S
#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(shift, XKB_MOD_NAME_SHIFT);
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
S(alt, XKB_MOD_NAME_ALT);
S(super, XKB_MOD_NAME_LOGO);
#undef S
}
#endif
static void
@@ -324,7 +434,9 @@ glfw_xkb_create_context(_GLFWXKBData *xkb) {
"Failed to initialize XKB context");
return false;
}
#ifndef _GLFW_WAYLAND
glfw_connect_to_ibus(&xkb->ibus);
#endif
return true;
}
@@ -359,6 +471,10 @@ load_compose_tables(_GLFWXKBData *xkb) {
if (!locale) locale = getenv("LC_CTYPE");
if (!locale) locale = getenv("LANG");
if (!locale) locale = "C";
// See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=903373
if (strcmp(locale, "en_IN") == 0) locale = "en_IN.UTF-8";
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
if (!compose_table) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table for locale %s", locale);
@@ -382,11 +498,26 @@ active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
return ans;
}
static unsigned int
update_one_modifier(XKBStateGroup *group, xkb_mod_mask_t mask,
xkb_mod_index_t idx, unsigned int mod) {
if ( idx == XKB_MOD_INVALID )
return 0;
/* Optimization in the case of a single real modifier */
if ( mask && ( ( mask & ( mask-1 ) ) == 0 ) )
return (xkb_state_mod_index_is_active(group->state, idx, XKB_STATE_MODS_EFFECTIVE) == 1) ? mod : 0;
/* Multiple real mods map to the same virtual mod */
for ( unsigned indx = 0; indx < 32 && mask; ++indx, mask >>= 1 )
if ( ( mask & 1 ) && xkb_state_mod_index_is_active(group->state, indx, XKB_STATE_MODS_EFFECTIVE) == 1)
return mod;
return 0;
}
static void
update_modifiers(_GLFWXKBData *xkb) {
XKBStateGroup *group = &xkb->states;
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#define S(attr, name) group->modifiers |= update_one_modifier( group, xkb->attr##Mask, xkb->attr##Idx, GLFW_MOD_##name )
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(hyper, HYPER); S(meta, META); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
@@ -410,18 +541,12 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
return false;
}
load_compose_tables(xkb);
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
S(control, XKB_MOD_NAME_CTRL);
S(alt, XKB_MOD_NAME_ALT);
S(shift, XKB_MOD_NAME_SHIFT);
S(super, XKB_MOD_NAME_LOGO);
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
#undef S
glfw_xkb_update_masks(xkb);
size_t capacity = arraysz(xkb->unknownModifiers), j = 0;
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->hyperIdx && i != xkb->metaIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
}
xkb->states.modifiers = 0;
xkb->states.activeUnknownModifiers = 0;
@@ -493,6 +618,8 @@ format_mods(unsigned int mods) {
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_META) pr("meta+");
if (mods & GLFW_MOD_HYPER) pr("hyper+");
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
if (p == s) pr("none");
@@ -526,16 +653,16 @@ format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
}
void
glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d) {
glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, const GLFWIMEUpdateEvent *ev) {
int x = 0, y = 0;
switch(which) {
case 1:
glfw_ibus_set_focused(&xkb->ibus, a ? true : false);
switch(ev->type) {
case GLFW_IME_UPDATE_FOCUS:
glfw_ibus_set_focused(&xkb->ibus, ev->focused);
break;
case 2:
case GLFW_IME_UPDATE_CURSOR_POSITION:
_glfwPlatformGetWindowPos(w, &x, &y);
x += a; y += b;
glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, c, d);
x += ev->cursor.left; y += ev->cursor.top;
glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, ev->cursor.width, ev->cursor.height);
break;
}
}
@@ -545,9 +672,8 @@ glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) {
_GLFWwindow *window = _glfwWindowForId(ev->window_id);
if (failed && window && window->callbacks.keyboard) {
// notify application to remove any existing pre-edit text
GLFWkeyevent fake_ev;
_glfwInitializeKeyEvent(&fake_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0);
fake_ev.ime_state = 1;
GLFWkeyevent fake_ev = {.action = GLFW_PRESS};
fake_ev.ime_state = GLFW_IME_PREEDIT_CHANGED;
window->callbacks.keyboard((GLFWwindow*) window, &fake_ev);
}
static xkb_keycode_t last_handled_press_keycode = 0;
@@ -566,7 +692,7 @@ glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) {
format_mods(ev->glfw_ev.mods), ev->glfw_ev.text
);
ev->glfw_ev.ime_state = 0;
ev->glfw_ev.ime_state = GLFW_IME_NONE;
_glfwInputKeyboard(window, &ev->glfw_ev);
} else debug("↳ discarded\n");
if (!is_release && handled_by_ime)
@@ -577,16 +703,15 @@ void
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t xkb_keycode, int action) {
static char key_text[64] = {0};
const xkb_keysym_t *syms, *clean_syms, *default_syms;
xkb_keysym_t xkb_sym;
xkb_keysym_t xkb_sym, shifted_xkb_sym = XKB_KEY_NoSymbol, alternate_xkb_sym = XKB_KEY_NoSymbol;
xkb_keycode_t code_for_sym = xkb_keycode, ibus_keycode = xkb_keycode;
GLFWkeyevent glfw_ev;
_glfwInitializeKeyEvent(&glfw_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0); // init with default values
GLFWkeyevent glfw_ev = {.action = GLFW_PRESS};
#ifdef _GLFW_WAYLAND
code_for_sym += 8;
#else
ibus_keycode -= 8;
#endif
debug("%s xkb_keycode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", xkb_keycode);
debug("%s xkb_keycode: 0x%x ", action == GLFW_RELEASE ? "\x1b[32mRelease\x1b[m" : "\x1b[31mPress\x1b[m", xkb_keycode);
XKBStateGroup *sg = &xkb->states;
int num_syms = xkb_state_key_get_syms(sg->state, code_for_sym, &syms);
int num_clean_syms = xkb_state_key_get_syms(sg->clean_state, code_for_sym, &clean_syms);
@@ -598,6 +723,7 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
return;
}
xkb_sym = clean_syms[0];
shifted_xkb_sym = 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";
@@ -618,7 +744,7 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
else xkb_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) & sg->modifiers) == 0) {
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER | GLFW_MOD_HYPER | GLFW_MOD_META) & sg->modifiers) == 0) {
xkb_state_key_get_utf8(sg->state, code_for_sym, key_text, sizeof(key_text));
}
text_type = "text";
@@ -631,22 +757,30 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
if (xkb_sym == XKB_KEY_ISO_First_Group || xkb_sym == XKB_KEY_ISO_Last_Group || xkb_sym == XKB_KEY_ISO_Next_Group || xkb_sym == XKB_KEY_ISO_Prev_Group || xkb_sym == XKB_KEY_Mode_switch) {
return;
}
int glfw_sym = glfw_key_for_sym(xkb_sym);
bool is_fallback = false;
if (glfw_sym == GLFW_KEY_UNKNOWN && !key_text[0]) {
int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms);
if (num_default_syms > 0) {
xkb_sym = default_syms[0];
glfw_sym = glfw_key_for_sym(xkb_sym);
is_fallback = true;
}
if (sg->modifiers & GLFW_MOD_NUM_LOCK && XKB_KEY_KP_Space <= xkb_sym && xkb_sym <= XKB_KEY_KP_9) {
xkb_sym = xkb_state_key_get_one_sym(sg->state, code_for_sym);
}
int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms);
if (num_default_syms > 0) alternate_xkb_sym = default_syms[0];
int glfw_sym = glfw_key_for_sym(xkb_sym);
debug(
"%s%s: %d (%s) xkb_key: %d (%s)\n",
"%s%s: %d (%s) xkb_key: %d (%s)",
format_mods(sg->modifiers),
is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_sym, _glfwGetKeyName(glfw_sym),
"glfw_key", glfw_sym, _glfwGetKeyName(glfw_sym),
xkb_sym, glfw_xkb_keysym_name(xkb_sym)
);
bool has_shifted_key = shifted_xkb_sym != xkb_sym && shifted_xkb_sym != XKB_KEY_NoSymbol;
bool has_alternate_key = alternate_xkb_sym != xkb_sym && alternate_xkb_sym != XKB_KEY_NoSymbol;
if (has_shifted_key) {
glfw_ev.shifted_key = glfw_key_for_sym(shifted_xkb_sym);
if (glfw_ev.shifted_key) debug(" shifted_key: %d (%s)", glfw_ev.shifted_key, _glfwGetKeyName(glfw_ev.shifted_key))
}
if (has_alternate_key) {
glfw_ev.alternate_key = glfw_key_for_sym(alternate_xkb_sym);
if (glfw_ev.alternate_key) debug(" alternate_key: %d (%s)", glfw_ev.alternate_key, _glfwGetKeyName(glfw_ev.alternate_key))
}
debug("%s", "\n");
// NOTE: On linux, the reported native key identifier is the XKB keysym value.
// Do not confuse `native_key` with `xkb_keycode` (the native keycode reported for the
@@ -662,7 +796,6 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
glfw_ev.key = glfw_sym;
glfw_ev.mods = sg->modifiers;
glfw_ev.text = key_text;
_GLFWIBUSKeyEvent ibus_ev;
ibus_ev.glfw_ev = glfw_ev;
ibus_ev.ibus_keycode = ibus_keycode;

8
glfw/xkb_glfw.h vendored
View File

@@ -54,12 +54,16 @@ typedef struct {
xkb_mod_index_t altIdx;
xkb_mod_index_t shiftIdx;
xkb_mod_index_t superIdx;
xkb_mod_index_t hyperIdx;
xkb_mod_index_t metaIdx;
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 hyperMask;
xkb_mod_mask_t metaMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
xkb_mod_index_t unknownModifiers[256];
@@ -89,8 +93,8 @@ bool 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);
bool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode);
const char* glfw_xkb_keysym_name(xkb_keysym_t sym);
xkb_keysym_t glfw_xkb_sym_for_key(int key);
xkb_keysym_t glfw_xkb_sym_for_key(uint32_t key);
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t keycode, int action);
int glfw_xkb_keysym_from_name(const char *name, bool case_sensitive);
void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d);
void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, const GLFWIMEUpdateEvent *ev);
void glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed);

View File

@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
from kitty.cli import parse_args
from kitty.cli_stub import BroadcastCLIOptions
from kitty.key_encoding import RELEASE, encode_key_event, key_defs as K
from kitty.key_encoding import encode_key_event
from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
from kitty.remote_control import create_basic_command, encode_send
from kitty.typing import KeyEventType, ScreenSize
@@ -59,15 +59,15 @@ class Broadcast(Handler):
def on_key(self, key_event: KeyEventType) -> None:
if self.line_edit.on_key(key_event):
self.commit_line()
if key_event.type is not RELEASE and not key_event.mods:
if key_event.key is K['ENTER']:
self.write_broadcast_text('\r')
self.print('')
self.line_edit.clear()
self.write(SAVE_CURSOR)
return
if key_event.matches('enter'):
self.write_broadcast_text('\r')
self.print('')
self.line_edit.clear()
self.write(SAVE_CURSOR)
return
ek = encode_key_event(key_event)
ek = standard_b64encode(ek.encode('utf-8')).decode('ascii')
self.write_broadcast_data('kitty-key:' + ek)
def write_broadcast_text(self, text: str) -> None:

View File

@@ -30,8 +30,6 @@ typedef uint8_t len_t;
typedef uint32_t text_t;
#define LEN_MAX UINT8_MAX
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
#define IS_LOWERCASE(x) (x) >= 'a' && (x) <= 'z'
#define IS_UPPERCASE(x) (x) >= 'A' && (x) <= 'Z'
#define LOWERCASE(x) ((IS_UPPERCASE(x)) ? (x) + 32 : (x))

View File

@@ -6,6 +6,7 @@
*/
#include "choose-data-types.h"
#include "../../kitty/iqsort.h"
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
@@ -45,13 +46,6 @@ output_text(GlobalData *global, const text_t *data, size_t sz) {
}
}
static int
cmpscore(const void *a, const void *b) {
double sa = FIELD(a, score), sb = FIELD(b, score);
// Sort descending
return (sa > sb) ? -1 : ((sa == sb) ? ((int)FIELD(a, idx) - (int)FIELD(b, idx)) : 1);
}
static void
output_with_marks(GlobalData *global, Options *opts, text_t *src, size_t src_sz, len_t *positions, len_t poslen) {
size_t pos, i = 0;
@@ -96,7 +90,9 @@ output_result(GlobalData *global, Candidate *c, Options *opts, len_t needle_len)
void
output_results(GlobalData *global, Candidate *haystack, size_t count, Options *opts, len_t needle_len) {
Candidate *c;
qsort(haystack, count, sizeof(*haystack), cmpscore);
#define lt(b, a) ( (a)->score < (b)->score || ((a)->score == (b)->score && (a->idx < b->idx)) )
QSORT(Candidate, haystack, count, lt);
#undef lt
size_t left = opts->limit > 0 ? opts->limit : count;
for (size_t i = 0; i < left; i++) {
c = haystack + i;

View File

@@ -15,6 +15,11 @@ if TYPE_CHECKING:
path_name_map: Dict[str, str] = {}
remote_dirs: Dict[str, str] = {}
def add_remote_dir(val: str) -> None:
remote_dirs[val] = os.path.basename(val).rpartition('-')[-1]
class Segment:
@@ -88,6 +93,20 @@ class Collection:
return len(self.all_paths)
def remote_hostname(path: str) -> Tuple[Optional[str], Optional[str]]:
for q in remote_dirs:
if path.startswith(q):
return q, remote_dirs[q]
return None, None
def resolve_remote_name(path: str, default: str) -> str:
remote_dir, rh = remote_hostname(path)
if remote_dir and rh:
return rh + ':' + os.path.relpath(path, remote_dir)
return default
def collect_files(collection: Collection, left: str, right: str) -> None:
left_names: Set[str] = set()
right_names: Set[str] = set()
@@ -184,8 +203,8 @@ def create_collection(left: str, right: str) -> Collection:
collect_files(collection, left, right)
else:
pl, pr = os.path.abspath(left), os.path.abspath(right)
path_name_map[pl] = left
path_name_map[pr] = right
path_name_map[pl] = resolve_remote_name(pl, left)
path_name_map[pr] = resolve_remote_name(pr, right)
collection.add_change(pl, pr)
collection.finalize()
return collection

View File

@@ -9,9 +9,8 @@ from gettext import gettext as _
from typing import Any, Dict, Sequence, Union
from kitty.conf.definition import Option, Shortcut, option_func
from kitty.conf.utils import (
positive_int, python_string, to_color, to_color_or_none
)
from kitty.conf.utils import python_string, to_color, to_color_or_none
from kitty.utils import positive_int
# }}}
@@ -89,7 +88,7 @@ c('hunk_bg', '#f1f8ff')
c('search_bg', '#444', long_text=_('Highlighting'))
c('search_fg', 'white')
c('select_bg', '#b4d5fe')
c('select_fg', 'black')
o('select_fg', 'black', option_type=to_color_or_none)
g('shortcuts')
k('quit', 'q', 'quit', _('Quit'))

View File

@@ -22,7 +22,7 @@ from kitty.cli_stub import DiffCLIOptions
from kitty.conf.utils import KittensKeyAction
from kitty.constants import appname
from kitty.fast_data_types import wcswidth
from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
from kitty.key_encoding import EventType, KeyEvent
from kitty.options_stub import DiffOptions
from kitty.utils import ScreenSize
@@ -34,7 +34,7 @@ from ..tui.operations import styled
from . import global_data
from .collect import (
Collection, create_collection, data_for_path, lines_for_path, sanitize,
set_highlight_data
set_highlight_data, add_remote_dir
)
from .config import init_config
from .patch import Differ, Patch, set_diff_command, worker_processes
@@ -57,7 +57,6 @@ except ImportError:
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
ESCAPE = K['ESCAPE']
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
@@ -94,7 +93,7 @@ class DiffHandler(Handler):
self.highlighting_done = False
self.restore_position: Optional[Reference] = None
for key_def, action in self.opts.key_definitions.items():
self.add_shortcut(action, *key_def)
self.add_shortcut(action, key_def)
def perform_action(self, action: KittensKeyAction) -> None:
func, args = action
@@ -483,48 +482,44 @@ class DiffHandler(Handler):
self.message = sanitize(_('No matches found'))
self.cmd.bell()
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
if self.state is COMMAND:
self.line_edit.on_text(text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
self.draw_status_line()
return
action = self.shortcut_action(text)
if action is not None:
return self.perform_action(action)
def on_key(self, key_event: KeyEvent) -> None:
if self.state is MESSAGE:
if key_event.type is not RELEASE:
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
if key_event.text:
if self.state is COMMAND:
self.line_edit.on_text(key_event.text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
self.draw_status_line()
return
if self.state is COMMAND:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
return
else:
if self.state is MESSAGE:
if key_event.type is not EventType.RELEASE:
self.state = DIFFED
self.draw_status_line()
self.draw_status_line()
return
if key_event.type is RELEASE:
return
if self.state is COMMAND:
if key_event.key is ESCAPE:
self.state = DIFFED
self.draw_status_line()
return
if key_event is enter_key:
self.state = DIFFED
self.do_search()
self.line_edit.clear()
if self.state is COMMAND:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
self.state = DIFFED
self.draw_status_line()
return
if key_event.matches('enter'):
self.state = DIFFED
self.do_search()
self.line_edit.clear()
self.draw_screen()
return
if key_event.matches('esc'):
self.state = DIFFED
self.draw_status_line()
return
if self.state >= DIFFED and self.current_search is not None and key_event.matches('esc'):
self.current_search = None
self.draw_screen()
return
if self.state >= DIFFED and self.current_search is not None and key_event.key is ESCAPE:
self.current_search = None
self.draw_screen()
return
if key_event.type is EventType.RELEASE:
return
action = self.shortcut_action(key_event)
if action is not None:
return self.perform_action(action)
@@ -586,17 +581,39 @@ def terminate_processes(processes: Iterable[int]) -> None:
os.kill(pid, signal.SIGKILL)
def get_ssh_file(hostname: str, rpath: str) -> str:
import io
import shutil
import tarfile
tdir = tempfile.mkdtemp(suffix=f'-{hostname}')
add_remote_dir(tdir)
atexit.register(shutil.rmtree, tdir)
is_abs = rpath.startswith('/')
rpath = rpath.lstrip('/')
cmd = ['ssh', hostname, 'tar', '-c', '-f', '-']
if is_abs:
cmd.extend(('-C', '/'))
cmd.append(rpath)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
assert p.stdout is not None
raw = p.stdout.read()
if p.wait() != 0:
raise SystemExit(p.returncode)
with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as tf:
members = tf.getmembers()
tf.extractall(tdir)
if len(members) == 1:
for root, dirs, files in os.walk(tdir):
if files:
return os.path.join(root, files[0])
return os.path.abspath(os.path.join(tdir, rpath))
def get_remote_file(path: str) -> str:
if path.startswith('ssh:'):
parts = path.split(':', 2)
if len(parts) == 3:
hostname, rpath = parts[1:]
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(rpath), prefix='remote:', delete=False) as tf:
atexit.register(os.remove, tf.name)
p = subprocess.Popen(['ssh', hostname, 'cat', rpath], stdout=tf)
if p.wait() != 0:
raise SystemExit(p.returncode)
return tf.name
return get_ssh_file(parts[1], parts[2])
return path
@@ -607,12 +624,12 @@ def main(args: List[str]) -> None:
raise SystemExit('You must specify exactly two files/directories to compare')
left, right = items
global_data.title = _('{} vs. {}').format(left, right)
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(cli_opts)
set_diff_command(opts.diff_cmd)
lines_for_path.replace_tab_by = opts.replace_tab_by
left, right = map(get_remote_file, (left, right))
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.')
for f in left, right:
if not os.path.exists(f):
raise SystemExit('{} does not exist'.format(f))

View File

@@ -3,16 +3,17 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import warnings
from functools import lru_cache
from gettext import gettext as _
from itertools import repeat, zip_longest
from math import ceil
from typing import Callable, Dict, Generator, Iterable, List, Optional, Tuple
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.cli_stub import DiffCLIOptions
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.types import run_once
from kitty.utils import ScreenSize
from ..tui.images import ImageManager, can_display_images
from .collect import (
Collection, Segment, data_for_path, highlights_for_path, is_image,
lines_for_path, path_name_map, sanitize
@@ -20,14 +21,13 @@ from .collect import (
from .config import formats
from .diff_speedup import split_with_highlights as _split_with_highlights
from .patch import Chunk, Hunk, Patch
from ..tui.images import ImageManager, can_display_images
class ImageSupportWarning(Warning):
pass
@lru_cache(maxsize=2)
@run_once
def images_supported() -> bool:
ans = can_display_images()
if not ans:

View File

@@ -17,9 +17,7 @@ from typing import (
from kitty.cli import parse_args
from kitty.cli_stub import HintsCLIOptions
from kitty.fast_data_types import set_clipboard_string
from kitty.key_encoding import (
KeyEvent, backspace_key, enter_key, key_defs as K
)
from kitty.key_encoding import KeyEvent
from kitty.typing import BossType, KittyCommonOpts
from kitty.utils import ScreenSize, screen_size_function, set_primary_selection
@@ -40,7 +38,6 @@ def kitty_common_opts() -> KittyCommonOpts:
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
ESCAPE = K['ESCAPE']
class Mark:
@@ -177,11 +174,11 @@ class Hints(Handler):
self.draw_screen()
def on_key(self, key_event: KeyEvent) -> None:
if key_event is backspace_key:
if key_event.matches('backspace'):
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event is enter_key and self.current_input:
elif key_event.matches('enter') and self.current_input:
try:
idx = decode_hint(self.current_input, self.alphabet)
self.chosen.append(self.index_map[idx])
@@ -196,7 +193,7 @@ class Hints(Handler):
self.draw_screen()
else:
self.quit_loop(0)
elif key_event.key is ESCAPE:
elif key_event.matches('esc'):
self.quit_loop(0 if self.multiple else 1)
def on_interrupt(self) -> None:
@@ -288,6 +285,7 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
@postprocessor
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
from ipaddress import ip_address
# Check validity of IPs (or raise InvalidMatch)
ip = text[s:e]
@@ -320,7 +318,9 @@ def run_loop(args: HintsCLIOptions, text: str, all_marks: Sequence[Mark], index_
return {
'match': handler.text_matches, 'programs': args.program,
'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing,
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args, 'linenum_action': args.linenum_action
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args,
'linenum_action': args.linenum_action,
'cwd': os.getcwd(),
}
raise SystemExit(loop.return_code)
@@ -698,7 +698,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
w.paste_bytes(text + '\r')
elif action == 'background':
import subprocess
subprocess.Popen(cmd)
subprocess.Popen(cmd, cwd=data['cwd'])
else:
getattr(boss, {
'window': 'new_window_with_cwd', 'tab': 'new_tab_with_cwd', 'os_window': 'new_os_window_with_cwd'
@@ -753,12 +753,7 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
elif program == '*':
set_primary_selection(joined_text())
else:
cwd = None
w = boss.window_id_map.get(target_window_id)
if w is not None:
cwd = w.cwd_of_child
if w is None:
w = boss.active_window
cwd = data['cwd']
program = None if program == 'default' else program
if text_type == 'hyperlink':
for m in matches:

View File

@@ -5,30 +5,31 @@
import contextlib
import os
import re
import socket
import signal
import socket
import sys
import zlib
from base64 import standard_b64encode
from functools import lru_cache
from math import ceil
from tempfile import NamedTemporaryFile
from typing import (
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
)
from kitty.guess_mime_type import guess_type
from kitty.cli import parse_args
from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname
from kitty.guess_mime_type import guess_type
from kitty.types import run_once
from kitty.typing import GRT_f, GRT_t
from kitty.utils import (
TTYIO, ScreenSize, ScreenSizeGetter, fit_image, screen_size_function
)
from ..tui.images import (
ConvertFailed, GraphicsCommand, NoImageMagick, OpenFailed, convert, fsenc,
identify
ConvertFailed, Dispose, GraphicsCommand, NoImageMagick, OpenFailed,
OutdatedImageMagick, RenderedImage, fsenc, identify,
render_as_single_image, render_image
)
from ..tui.operations import clear_images_on_screen, raw_mode
@@ -102,7 +103,7 @@ but you can turn it off or on explicitly, if needed.
--silent
type=bool-set
Do not print out anything to stdout during operation.
Do not print out anything to STDOUT during operation.
--z-index -z
@@ -112,9 +113,17 @@ a double minus for values under the threshold for drawing images under cell back
colors. For example, --1 evaluates as -1,073,741,825.
--loop -l
default=-1
type=int
Number of times to loop animations. Negative values loop forever. Zero means
only the first frame of the animation is displayed. Otherwise, the animation
is looped the specified number of times.
--hold
type=bool-set
Wait for keypress before exiting after displaying the images.
Wait for a key press before exiting after displaying the images.
'''
@@ -134,7 +143,7 @@ def get_screen_size() -> ScreenSize:
return screen_size()
@lru_cache(maxsize=2)
@run_once
def options_spec() -> str:
return OPTIONS.format(appname='{}-icat'.format(appname))
@@ -190,15 +199,20 @@ def set_cursor_for_place(place: 'Place', cmd: GraphicsCommand, width: int, heigh
def write_chunked(cmd: GraphicsCommand, data: bytes) -> None:
cmd = cmd.clone()
if cmd.f != 100:
data = zlib.compress(data)
cmd.o = 'z'
data = standard_b64encode(data)
ac = cmd.a
quiet = cmd.q
while data:
chunk, data = data[:4096], data[4096:]
cmd.m = 1 if data else 0
write_gr_cmd(cmd, chunk)
cmd.clear()
cmd.a = ac
cmd.q = quiet
def show(
@@ -207,7 +221,8 @@ def show(
fmt: 'GRT_f',
transmit_mode: 'GRT_t' = 't',
align: str = 'center',
place: Optional['Place'] = None
place: Optional['Place'] = None,
use_number: int = 0
) -> None:
cmd = GraphicsCommand()
cmd.a = 'T'
@@ -215,6 +230,9 @@ def show(
cmd.s = width
cmd.v = height
cmd.z = zindex
if use_number:
cmd.I = use_number # noqa
cmd.q = 2
if place:
set_cursor_for_place(place, cmd, width, height, align)
else:
@@ -232,6 +250,58 @@ def show(
write_chunked(cmd, data)
def show_frames(frame_data: RenderedImage, use_number: int, loops: int) -> None:
transmit_cmd = GraphicsCommand()
transmit_cmd.a = 'f'
transmit_cmd.I = use_number # noqa
transmit_cmd.q = 2
if can_transfer_with_files:
transmit_cmd.t = 't'
transmit_cmd.f = 24 if frame_data.mode == 'rgb' else 32
def control(frame_number: int = 0, loops: Optional[int] = None, gap: Optional[int] = 0, animation_control: int = 0) -> None:
cmd = GraphicsCommand()
cmd.a = 'a'
cmd.I = use_number # noqa
cmd.r = frame_number
if loops is not None:
cmd.v = loops + 1
if gap is not None:
cmd.z = gap if gap > 0 else -1
if animation_control:
cmd.s = animation_control
write_gr_cmd(cmd)
anchor_frame = 0
for frame in frame_data.frames:
frame_number = frame.index + 1
if frame.dispose < Dispose.previous:
anchor_frame = frame_number
if frame_number == 1:
control(frame_number, gap=frame.gap, loops=None if loops < 1 else loops)
continue
if frame.dispose is Dispose.previous:
if anchor_frame != frame_number:
transmit_cmd.c = anchor_frame
else:
transmit_cmd.c = (frame_number - 1) if frame.needs_blend else 0
transmit_cmd.s = frame.width
transmit_cmd.v = frame.height
transmit_cmd.x = frame.canvas_x
transmit_cmd.y = frame.canvas_y
transmit_cmd.z = frame.gap if frame.gap > 0 else -1
if can_transfer_with_files:
write_gr_cmd(transmit_cmd, standard_b64encode(os.path.abspath(frame.path).encode(fsenc)))
else:
with open(frame.path, 'rb') as f:
data = f.read()
write_chunked(transmit_cmd, data)
if frame_number == 2:
control(animation_control=2)
control(animation_control=3)
def parse_z_index(val: str) -> int:
origin = 0
if val.startswith('--'):
@@ -254,6 +324,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
needs_scaling = m.width > available_width or m.height > available_height
needs_scaling = needs_scaling or args.scale_up
file_removed = False
use_number = 0
if m.fmt == 'png' and not needs_scaling:
outfile = path
transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f'
@@ -263,8 +334,25 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
else:
fmt = 24 if m.mode == 'rgb' else 32
transmit_mode = 't'
outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
show(outfile, width, height, parsed_opts.z_index, fmt, transmit_mode, align=args.align, place=parsed_opts.place)
if len(m) == 1 or args.loop == 0:
outfile, width, height = render_as_single_image(path, m, available_width, available_height, args.scale_up)
else:
import struct
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
with NamedTemporaryFile() as f:
prefix = f.name
frame_data = render_image(path, prefix, m, available_width, available_height, args.scale_up)
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
show(
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
align=args.align, place=parsed_opts.place, use_number=use_number
)
if use_number:
show_frames(frame_data, use_number, args.loop)
if not can_transfer_with_files:
for fr in frame_data.frames:
with contextlib.suppress(FileNotFoundError):
os.unlink(fr.path)
if not args.place:
print() # ensure cursor is on a new line
return file_removed
@@ -472,6 +560,9 @@ def main(args: List[str] = sys.argv) -> None:
process_single_item(item, cli_opts, parsed_opts, url_pat)
except NoImageMagick as e:
raise SystemExit(str(e))
except OutdatedImageMagick as e:
print(e.detailed_error, file=sys.stderr)
raise SystemExit(str(e))
except ConvertFailed as e:
raise SystemExit(str(e))
except OpenFailed as e:

View File

@@ -6,8 +6,8 @@ import sys
from typing import List
from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, SUPER, KeyEvent,
encode_key_event
ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT,
SUPER, KeyEvent, encode_key_event
)
from ..tui.handler import Handler
@@ -21,10 +21,7 @@ class KeysHandler(Handler):
self.cmd.set_cursor_visible(False)
self.print('Press any keys - Ctrl+C or Ctrl+D will terminate')
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
self.print('Text input: ' + text)
def on_key(self, key_event: KeyEvent) -> None:
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
etype = {
PRESS: 'PRESS',
REPEAT: 'REPEAT',
@@ -35,13 +32,35 @@ class KeysHandler(Handler):
SHIFT: 'Shift',
ALT: 'Alt',
CTRL: 'Ctrl',
SUPER: 'Super'}.items():
SUPER: 'Super',
HYPER: 'Hyper',
META: 'Meta',
NUM_LOCK: 'NumLock',
CAPS_LOCK: 'CapsLock',
}.items():
if key_event.mods & m:
lmods.append(name)
mods = '+'.join(lmods)
if mods:
mods += '+'
self.print('Key {}: {}{} [{}]'.format(etype, mods, key_event.key, encode_key_event(key_event)))
key = f'{mods}{key_event.key} '
self.cmd.colored(key, 'green')
self.cmd.colored(etype + ' ', 'yellow')
self.cmd.styled(key_event.text, italic=True)
self.print()
rep = 'CSI ' + encode_key_event(key_event)[2:]
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
self.cmd.styled(rep, fg='magenta')
if (key_event.shifted_key or key_event.alternate_key):
self.print()
if key_event.shifted_key:
self.cmd.colored('Shifted key: ', 'gray')
self.print(key_event.shifted_key + ' ', end='')
if key_event.alternate_key:
self.cmd.colored('Alternate key: ', 'gray')
self.print(key_event.alternate_key + ' ', end='')
self.print()
self.print()
def on_interrupt(self) -> None:
self.quit_loop(0)

View File

@@ -10,7 +10,7 @@ from typing import Callable, Dict, List, Tuple
from kitty.cli import parse_args
from kitty.cli_stub import PanelCLIOptions
from kitty.constants import is_macos
from kitty.constants import is_macos, appname
from kitty.os_window_size import WindowSizeData
OPTIONS = r'''
@@ -42,7 +42,19 @@ Path to config file to use for kitty when drawing the panel.
type=list
Override individual kitty configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font_size=20
'''.format
--class
dest=cls
default={appname}-panel
condition=not is_macos
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
--name
condition=not is_macos
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
'''.format(appname=appname).format
args = PanelCLIOptions()
@@ -109,19 +121,17 @@ def setup_x11_window(win_id: int) -> None:
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
from kitty.fast_data_types import glfw_primary_monitor_size, set_smallest_allowed_resize
from kitty.fast_data_types import glfw_primary_monitor_size
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
global window_width, window_height
monitor_width, monitor_height = glfw_primary_monitor_size()
if args.edge in {'top', 'bottom'}:
h = window_height = cell_height * args.lines + 1
window_height = cell_height * args.lines + 1
window_width = monitor_width
set_smallest_allowed_resize(100, h)
else:
w = window_width = cell_width * args.columns + 1
window_width = cell_width * args.columns + 1
window_height = monitor_height
set_smallest_allowed_resize(w, 100)
return window_width, window_height
return initial_window_size
@@ -138,9 +148,12 @@ def main(sys_args: List[str]) -> None:
raise SystemExit('You must specify the program to run')
sys.argv = ['kitty']
for config in args.config:
sys.argv.append('--config={}'.format(config))
sys.argv.extend(('--config', config))
sys.argv.extend(('--class', args.cls))
if args.name:
sys.argv.extend(('--name', args.name))
for override in args.override:
sys.argv.append('--override={}'.format(override))
sys.argv.extend(('--override', override))
sys.argv.extend(items)
from kitty.main import run_app, main as real_main
run_app.cached_values_name = 'panel'

View File

@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional
from kitty.cli import parse_args
from kitty.cli_stub import RCOptions, ResizeCLIOptions
from kitty.constants import version
from kitty.key_encoding import CTRL, RELEASE, KeyEvent, key_defs as K
from kitty.key_encoding import CTRL, EventType, KeyEvent
from kitty.rc.base import command_for_name, parse_subcommand_cli
from kitty.remote_control import encode_send, parse_rc_args
from kitty.utils import ScreenSize
@@ -19,11 +19,6 @@ from ..tui.loop import Loop
from ..tui.operations import styled
global_opts = RCOptions()
ESCAPE = K['ESCAPE']
N = K['N']
S = K['S']
T = K['T']
W = K['W']
class Resize(Handler):
@@ -74,12 +69,13 @@ class Resize(Handler):
self.quit_loop(0)
def on_key(self, key_event: KeyEvent) -> None:
if key_event.type is RELEASE:
if key_event.type is EventType.RELEASE:
return
if key_event.key is ESCAPE:
if key_event.matches('esc'):
self.quit_loop(0)
elif key_event.key in (W, N, T, S) and key_event.mods & CTRL:
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
return
if key_event.key in ('w', 'n', 't', 's') and key_event.mods == CTRL:
self.do_window_resize(is_decrease=key_event.key in 'ns', is_horizontal=key_event.key in 'wn', multiplier=2)
def on_resize(self, new_size: ScreenSize) -> None:
self.draw_screen()

View File

@@ -6,9 +6,11 @@
import importlib
import os
import sys
from functools import lru_cache, partial
from functools import partial
from typing import Any, Dict, FrozenSet, List
from kitty.types import run_once
aliases = {'url_hints': 'hints'}
@@ -57,8 +59,9 @@ def create_kitten_handler(kitten: str, orig_args: List[str]) -> Any:
def set_debug(kitten: str) -> None:
from kittens.tui.loop import debug
import builtins
from kittens.tui.loop import debug
setattr(builtins, 'debug', debug)
@@ -114,20 +117,21 @@ def run_kitten(kitten: str, run_name: str = '__main__') -> None:
for kitten in all_kitten_names():
print(kitten, file=sys.stderr)
raise SystemExit('No kitten named {}'.format(original_kitten_name))
m = runpy.run_path(path, init_globals={'sys': sys, 'os': os}, run_name='__run_kitten__')
m = runpy.run_path(path, init_globals={'sys': sys, 'os': os}, run_name='__run_kitten__') # type: ignore
m['main'](sys.argv)
@lru_cache(maxsize=2)
@run_once
def all_kitten_names() -> FrozenSet[str]:
n = []
import glob
base = os.path.dirname(os.path.abspath(__file__))
for x in glob.glob(os.path.join(base, '*', '__init__.py')):
q = os.path.basename(os.path.dirname(x))
if q != 'tui':
n.append(q)
return frozenset(n)
try:
from importlib.resources import contents
except ImportError:
from importlib_resources import contents # type: ignore
ans = []
for name in contents('kittens'):
if '__' not in name and '.' not in name and name != 'tui':
ans.append(name)
return frozenset(ans)
def list_kittens() -> None:

View File

@@ -46,6 +46,33 @@ exec -a "-$shell_name" "$0"
'''
PYTHON_SCRIPT = '''\
#!/usr/bin/env python
from __future__ import print_function
from tempfile import NamedTemporaryFile
import subprocess, os, sys, pwd, binascii, json
# macOS ships with an ancient version of tic that cannot read from stdin, so we
# create a temp file for it
with NamedTemporaryFile() as tmp:
tmp.write(binascii.unhexlify('{terminfo}'))
p = subprocess.Popen(['tic', '-x', '-o', os.path.expanduser('~/.terminfo'), tmp.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if p.wait() != 0:
getattr(sys.stderr, 'buffer', sys.stderr).write(stdout + stderr)
raise SystemExit('Failed to compile terminfo using tic')
command_to_execute = json.loads(binascii.unhexlify('{command_to_execute}'))
if command_to_execute:
os.execlp(command_to_execute[0], *command_to_execute)
try:
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
except KeyError:
shell_path = '/bin/sh'
shell_name = '-' + os.path.basename(shell_path)
os.execlp(shell_path, shell_name)
'''
def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
other_ssh_args: List[str] = []
boolean_ssh_args: List[str] = []
@@ -150,20 +177,42 @@ def quote(x: str) -> str:
return x
def main(args: List[str]) -> NoReturn:
ssh_args, server_args, passthrough = parse_ssh_args(args[1:])
if passthrough:
cmd = ['ssh'] + ssh_args + server_args
def get_posix_cmd(terminfo: str, remote_args: List[str]) -> List[str]:
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
if remote_args:
command_to_executeg = (quote(c) for c in remote_args)
command_to_execute = 'exec ' + ' '.join(command_to_executeg)
else:
command_to_execute = ''
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)
return [sh_script] + remote_args
def get_python_cmd(terminfo: str, command_to_execute: List[str]) -> List[str]:
import json
script = PYTHON_SCRIPT.format(
terminfo=terminfo.encode('utf-8').hex(),
command_to_execute=json.dumps(command_to_execute).encode('utf-8').hex()
)
return [f'python -c "{script}"']
def main(args: List[str]) -> NoReturn:
args = args[1:]
use_posix = True
if args and args[0] == 'use-python':
args = args[1:]
use_posix = False
ssh_args, server_args, passthrough = parse_ssh_args(args)
cmd = ['ssh'] + ssh_args
if passthrough:
cmd += server_args
else:
hostname, remote_args = server_args[0], server_args[1:]
cmd += ['-t', hostname]
terminfo = subprocess.check_output(['infocmp']).decode('utf-8')
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
if len(server_args) > 1:
command_to_executeg = (quote(c) for c in server_args[1:])
command_to_execute = 'exec ' + ' '.join(command_to_executeg)
else:
command_to_execute = ''
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)
cmd = ['ssh'] + ssh_args + ['-t', server_args[0], sh_script] + server_args[1:]
f = get_posix_cmd if use_posix else get_python_cmd
cmd += f(terminfo, remote_args)
os.execvp('ssh', cmd)

View File

@@ -8,6 +8,7 @@ from typing import (
Any, Callable, ContextManager, Dict, Optional, Sequence, Type, Union
)
from kitty.types import ParsedShortcut
from kitty.typing import (
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType,
KittensKeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
@@ -45,18 +46,18 @@ class Handler:
def asyncio_loop(self) -> AbstractEventLoop:
return self._tui_loop.asycio_loop
def add_shortcut(self, action: KittensKeyActionType, key: str, mods: Optional[int] = None, is_text: Optional[bool] = False) -> None:
if not hasattr(self, '_text_shortcuts'):
self._text_shortcuts, self._key_shortcuts = {}, {}
if is_text:
self._text_shortcuts[key] = action
else:
self._key_shortcuts[(key, mods or 0)] = action
def add_shortcut(self, action: KittensKeyActionType, spec: Union[str, ParsedShortcut]) -> None:
if not hasattr(self, '_key_shortcuts'):
self._key_shortcuts: Dict[ParsedShortcut, KittensKeyActionType] = {}
if isinstance(spec, str):
from kitty.key_encoding import parse_shortcut
spec = parse_shortcut(spec)
self._key_shortcuts[spec] = action
def shortcut_action(self, key_event_or_text: Union[str, KeyEventType]) -> Optional[KittensKeyActionType]:
if isinstance(key_event_or_text, str):
return self._text_shortcuts.get(key_event_or_text)
return self._key_shortcuts.get((key_event_or_text.key, key_event_or_text.mods))
def shortcut_action(self, key_event: KeyEventType) -> Optional[KittensKeyActionType]:
for sc, action in self._key_shortcuts.items():
if key_event.matches(sc):
return action
def __enter__(self) -> None:
if self._image_manager is not None:
@@ -85,6 +86,12 @@ class Handler:
def on_term(self) -> None:
self._tui_loop.quit(1)
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.text:
self.on_text(key_event.text, in_bracketed_paste)
else:
self.on_key(key_event)
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
pass

View File

@@ -8,16 +8,20 @@ import sys
from base64 import standard_b64encode
from collections import defaultdict, deque
from contextlib import suppress
from enum import IntEnum
from itertools import count
from typing import (
Any, Callable, DefaultDict, Deque, Dict, List, Optional, Sequence, Tuple,
Union
Any, Callable, DefaultDict, Deque, Dict, Iterator, List, Optional,
Sequence, Tuple, Union
)
from kitty.fast_data_types import create_canvas
from kitty.typing import (
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType
)
from kitty.utils import ScreenSize, fit_image
from kitty.utils import (
ScreenSize, find_exe, fit_image, positive_float, positive_int
)
from .operations import cursor
@@ -28,11 +32,68 @@ except Exception:
fsenc = 'utf-8'
class Dispose(IntEnum):
undefined = 0
none = 1
background = 2
previous = 3
class Frame:
gap: int # milliseconds
canvas_width: int
canvas_height: int
width: int
height: int
index: int
xdpi: float
ydpi: float
canvas_x: int
canvas_y: int
mode: str
needs_blend: bool
dispose: Dispose
path: str = ''
def __init__(self, identify_data: Union['Frame', Dict[str, str]]):
if isinstance(identify_data, Frame):
for k in Frame.__annotations__:
setattr(self, k, getattr(identify_data, k))
else:
self.gap = max(0, int(identify_data['gap']) * 10)
sz, pos = identify_data['canvas'].split('+', 1)
self.canvas_width, self.canvas_height = map(positive_int, sz.split('x', 1))
self.canvas_x, self.canvas_y = map(int, pos.split('+', 1))
self.width, self.height = map(positive_int, identify_data['size'].split('x', 1))
self.xdpi, self.ydpi = map(positive_float, identify_data['dpi'].split('x', 1))
self.index = positive_int(identify_data['index'])
q = identify_data['transparency'].lower()
self.mode = 'rgba' if q in ('blend', 'true') else 'rgb'
self.needs_blend = q == 'blend'
self.dispose = getattr(Dispose, identify_data['dispose'].lower())
def __repr__(self) -> str:
canvas = f'{self.canvas_width}x{self.canvas_height}:{self.canvas_x}+{self.canvas_y}'
geom = f'{self.width}x{self.height}'
return f'Frame(index={self.index}, gap={self.gap}, geom={geom}, canvas={canvas})'
class ImageData:
def __init__(self, fmt: str, width: int, height: int, mode: str):
def __init__(self, fmt: str, width: int, height: int, mode: str, frames: List[Frame]):
self.width, self.height, self.fmt, self.mode = width, height, fmt, mode
self.transmit_fmt: GRT_f = (24 if self.mode == 'rgb' else 32)
self.frames = frames
def __len__(self) -> int:
return len(self.frames)
def __iter__(self) -> Iterator[Frame]:
yield from self.frames
def __repr__(self) -> str:
frames = '\n '.join(map(repr, self.frames))
return f'Image(fmt={self.fmt}, mode={self.mode},\n {frames}\n)'
class OpenFailed(ValueError):
@@ -57,8 +118,20 @@ class NoImageMagick(Exception):
pass
class OutdatedImageMagick(ValueError):
def __init__(self, detailed_error: str):
super().__init__('ImageMagick on this system is too old ImageMagick 7+ required which was first released in 2016')
self.detailed_error = detailed_error
last_imagemagick_cmd: Sequence[str] = ()
def run_imagemagick(path: str, cmd: Sequence[str], keep_stdout: bool = True) -> CompletedProcess:
global last_imagemagick_cmd
import subprocess
last_imagemagick_cmd = cmd
try:
p = subprocess.run(cmd, stdout=subprocess.PIPE if keep_stdout else subprocess.DEVNULL, stderr=subprocess.PIPE)
except FileNotFoundError:
@@ -69,22 +142,54 @@ def run_imagemagick(path: str, cmd: Sequence[str], keep_stdout: bool = True) ->
def identify(path: str) -> ImageData:
p = run_imagemagick(path, ['identify', '-format', '%m %w %h %A', '--', path])
parts: Tuple[str, ...] = tuple(filter(None, p.stdout.decode('utf-8').split()))
mode = 'rgb' if parts[3].lower() == 'false' else 'rgba'
return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode)
import json
q = '{"fmt":"%m","canvas":"%g","transparency":"%A","gap":"%T","index":"%p","size":"%wx%h","dpi":"%xx%y","dispose":"%D"},'
exe = find_exe('magick')
if exe:
cmd = [exe, 'identify']
else:
cmd = ['identify']
p = run_imagemagick(path, cmd + ['-format', q, '--', path])
data = json.loads(b'[' + p.stdout.rstrip(b',') + b']')
first = data[0]
frames = list(map(Frame, data))
mode = 'rgb'
for f in frames:
if f.mode == 'rgba':
mode = 'rgba'
break
return ImageData(first['fmt'].lower(), frames[0].canvas_width, frames[0].canvas_height, mode, frames)
def convert(
path: str, m: ImageData,
class RenderedImage(ImageData):
def __init__(self, fmt: str, width: int, height: int, mode: str):
super().__init__(fmt, width, height, mode, [])
def render_image(
path: str, output_prefix: str,
m: ImageData,
available_width: int, available_height: int,
scale_up: bool,
tdir: Optional[str] = None
) -> Tuple[str, int, int]:
from tempfile import NamedTemporaryFile
width, height = m.width, m.height
cmd = ['convert', '-background', 'none', '--', path]
only_first_frame: bool = False
) -> RenderedImage:
import tempfile
has_multiple_frames = len(m) > 1
get_multiple_frames = has_multiple_frames and not only_first_frame
exe = find_exe('magick')
if exe:
cmd = [exe, 'convert']
else:
exe = find_exe('convert')
if exe is None:
raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH')
cmd = [exe]
cmd += ['-background', 'none', '--', path]
if only_first_frame and has_multiple_frames:
cmd[-1] += '[0]'
scaled = False
width, height = m.width, m.height
if scale_up:
if width < available_width:
r = available_width / width
@@ -92,25 +197,86 @@ def convert(
scaled = True
if scaled or width > available_width or height > available_height:
width, height = fit_image(width, height, available_width, available_height)
cmd += ['-resize', '{}x{}!'.format(width, height)]
cmd += ['-depth', '8']
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
run_imagemagick(path, cmd + [outfile.name])
# ImageMagick sometimes generated rgba images smaller than the specified
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
sz = os.path.getsize(outfile.name)
resize_cmd = ['-resize', '{}x{}!'.format(width, height)]
if get_multiple_frames:
# we have to coalesce, resize and de-coalesce all frames
resize_cmd = ['-coalesce'] + resize_cmd + ['-deconstruct']
cmd += resize_cmd
cmd += ['-depth', '8', '-set', 'filename:f', '%w-%h-%g-%p']
ans = RenderedImage(m.fmt, width, height, m.mode)
if only_first_frame:
ans.frames = [Frame(m.frames[0])]
else:
ans.frames = list(map(Frame, m.frames))
bytes_per_pixel = 3 if m.mode == 'rgb' else 4
expected_size = bytes_per_pixel * width * height
if sz < expected_size:
missing = expected_size - sz
if missing % (bytes_per_pixel * width) != 0:
raise ConvertFailed(
path, 'ImageMagick failed to convert {} correctly,'
' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
path, sz, expected_size, width, height, bytes_per_pixel))
height -= missing // (bytes_per_pixel * width)
return outfile.name, width, height
def check_resize(frame: Frame) -> None:
# ImageMagick sometimes generates RGBA images smaller than the specified
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
sz = os.path.getsize(frame.path)
expected_size = bytes_per_pixel * frame.width * frame.height
if sz < expected_size:
missing = expected_size - sz
if missing % (bytes_per_pixel * width) != 0:
raise ConvertFailed(
path, 'ImageMagick failed to convert {} correctly,'
' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
path, sz, expected_size, frame.width, frame.height, bytes_per_pixel))
frame.height -= missing // (bytes_per_pixel * frame.width)
if frame.index == 0:
ans.height = frame.height
ans.width = frame.width
with tempfile.TemporaryDirectory(dir=os.path.dirname(output_prefix)) as tdir:
output_template = os.path.join(tdir, f'im-%[filename:f].{m.mode}')
if get_multiple_frames:
cmd.append('+adjoin')
run_imagemagick(path, cmd + [output_template])
unseen = {x.index for x in m}
for x in os.listdir(tdir):
try:
parts = x.split('.', 1)[0].split('-')
index = int(parts[-1])
unseen.discard(index)
f = ans.frames[index]
f.width, f.height = map(positive_int, parts[1:3])
sz, pos = parts[3].split('+', 1)
f.canvas_width, f.canvas_height = map(positive_int, sz.split('x', 1))
f.canvas_x, f.canvas_y = map(int, pos.split('+', 1))
except Exception:
raise OutdatedImageMagick(f'Unexpected output filename: {x!r} produced by ImageMagick command: {last_imagemagick_cmd}')
f.path = output_prefix + f'-{index}.{m.mode}'
os.rename(os.path.join(tdir, x), f.path)
check_resize(f)
f = ans.frames[0]
if f.width != ans.width or f.height != ans.height:
with open(f.path, 'r+b') as ff:
data = ff.read()
ff.seek(0)
ff.truncate()
cd = create_canvas(data, f.width, f.canvas_x, f.canvas_y, ans.width, ans.height, 3 if ans.mode == 'rgb' else 4)
ff.write(cd)
if get_multiple_frames:
if unseen:
raise ConvertFailed(path, f'Failed to render {len(unseen)} out of {len(m)} frames of animation')
elif not ans.frames[0].path:
raise ConvertFailed(path, 'Failed to render image')
return ans
def render_as_single_image(
path: str, m: ImageData,
available_width: int, available_height: int,
scale_up: bool,
tdir: Optional[str] = None
) -> Tuple[str, int, int]:
import tempfile
fd, output = tempfile.mkstemp(prefix='icat-', suffix=f'.{m.mode}', dir=tdir)
os.close(fd)
result = render_image(path, output, m, available_width, available_height, scale_up, only_first_frame=True)
os.rename(result.frames[0].path, output)
return output, result.width, result.height
def can_display_images() -> bool:
@@ -128,6 +294,7 @@ SentImageKey = Tuple[int, int, int]
class GraphicsCommand:
a: GRT_a = 't' # action
q: int = 0 # suppress responses
f: GRT_f = 32 # image data format
t: GRT_t = 'd' # transmission medium
s: int = 0 # sent image width
@@ -135,6 +302,7 @@ class GraphicsCommand:
S: int = 0 # size of data to read from file
O: int = 0 # offset of data to read from file
i: int = 0 # image id
I: int = 0 # image number
p: int = 0 # placement id
o: Optional[GRT_o] = None # type of compression
m: GRT_m = 0 # 0 or 1 whether there is more chunked data
@@ -149,7 +317,16 @@ class GraphicsCommand:
z: int = 0 # z-index
d: GRT_d = 'a' # what to delete
def serialize(self, payload: bytes = b'') -> bytes:
def __repr__(self) -> str:
return self.serialize().decode('ascii').replace('\033', '^]')
def clone(self) -> 'GraphicsCommand':
ans = GraphicsCommand()
for k in GraphicsCommand.__annotations__:
setattr(ans, k, getattr(self, k))
return ans
def serialize(self, payload: Union[bytes, str] = b'') -> bytes:
items = []
for k in GraphicsCommand.__annotations__:
val: Union[str, None, int] = getattr(self, k)
@@ -163,6 +340,8 @@ class GraphicsCommand:
w(','.join(items).encode('ascii'))
if payload:
w(b';')
if isinstance(payload, str):
payload = standard_b64encode(payload.encode('utf-8'))
w(payload)
w(b'\033\\')
return b''.join(ans)
@@ -327,7 +506,7 @@ class ImageManager:
self.handler.cmd.gr_command(gc)
def convert_image(self, path: str, available_width: int, available_height: int, image_data: ImageData, scale_up: bool = False) -> ImageKey:
rgba_path, width, height = convert(path, image_data, available_width, available_height, scale_up, tdir=self.tdir)
rgba_path, width, height = render_as_single_image(path, image_data, available_width, available_height, scale_up, tdir=self.tdir)
return rgba_path, width, height
def transmit_image(self, image_data: ImageData, image_id: int, rgba_path: str, width: int, height: int) -> int:

View File

@@ -5,17 +5,10 @@
from typing import Callable, Tuple
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.key_encoding import RELEASE, KeyEvent, key_defs as K
from kitty.key_encoding import EventType, KeyEvent
from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by
HOME = K['HOME']
END = K['END']
BACKSPACE = K['BACKSPACE']
DELETE = K['DELETE']
LEFT = K['LEFT']
RIGHT = K['RIGHT']
class LineEdit:
@@ -137,22 +130,22 @@ class LineEdit:
return self.cursor_pos != orig
def on_key(self, key_event: KeyEvent) -> bool:
if key_event.type is RELEASE:
if key_event.type is EventType.RELEASE:
return False
elif key_event.key is HOME:
if key_event.matches('home'):
return self.home()
elif key_event.key is END:
if key_event.matches('end'):
return self.end()
elif key_event.key is BACKSPACE:
if key_event.matches('backspace'):
self.backspace()
return True
elif key_event.key is DELETE:
if key_event.matches('delete'):
self.delete()
return True
elif key_event.key is LEFT:
if key_event.matches('left'):
self.left()
return True
elif key_event.key is RIGHT:
if key_event.matches('right'):
self.right()
return True
return False

View File

@@ -21,7 +21,7 @@ from kitty.fast_data_types import (
)
from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, backspace_key, decode_key_event,
enter_key, key_defs as K
enter_key
)
from kitty.typing import ImageManagerType, KeyEventType, Protocol
from kitty.utils import ScreenSizeGetter, screen_size_function, write_all
@@ -29,8 +29,6 @@ from kitty.utils import ScreenSizeGetter, screen_size_function, write_all
from .handler import Handler
from .operations import init_state, reset_state
C, D = K['C'], K['D']
class BinaryWrite(Protocol):
@@ -149,7 +147,7 @@ class UnhandledException(Handler):
self.write('Press the Enter key to quit')
def on_key(self, key_event: KeyEventType) -> None:
if key_event is enter_key:
if key_event.key == 'ENTER':
self.quit_loop(1)
def on_interrupt(self) -> None:
@@ -279,11 +277,25 @@ class Loop:
pass
else:
self.handler.on_mouse(ev)
elif q == '~':
elif q in 'u~ABCDEHFPQRS':
if csi == '200~':
self.in_bracketed_paste = True
return
elif csi == '201~':
self.in_bracketed_paste = False
return
try:
k = decode_key_event(csi[:-1], q)
except Exception:
pass
else:
if k.matches('ctrl+c'):
self.handler.on_interrupt()
return
if k.matches('ctrl+d'):
self.handler.on_eot()
return
self.handler.on_key_event(k)
def _on_pm(self, pm: str) -> None:
pass
@@ -300,21 +312,7 @@ class Loop:
self.handler.on_clipboard_response(standard_b64decode(rest).decode('utf-8'), from_primary)
def _on_apc(self, apc: str) -> None:
if apc.startswith('K'):
try:
k = decode_key_event(apc[1:])
except Exception:
pass
else:
if k.mods is CTRL and k.type is not RELEASE:
if k.key is C:
self.handler.on_interrupt()
return
if k.key is D:
self.handler.on_eot()
return
self.handler.on_key(k)
elif apc.startswith('G'):
if apc.startswith('G'):
if self.handler.image_manager is not None:
self.handler.image_manager.handle_response(apc)
# }}}

View File

@@ -20,7 +20,8 @@ SAVE_CURSOR = '\0337'
RESTORE_CURSOR = '\0338'
SAVE_PRIVATE_MODE_VALUES = '\033[?s'
RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
SAVE_COLORS = '\033[#P'
RESTORE_COLORS = '\033[#Q'
MODES = dict(
LNM=(20, ''),
IRM=(4, ''),
@@ -39,7 +40,6 @@ MODES = dict(
MOUSE_URXVT_MODE=(1015, '?'),
ALTERNATE_SCREEN=(1049, '?'),
BRACKETED_PASTE=(2004, '?'),
EXTENDED_KEYBOARD=(2017, '?'),
)
F = TypeVar('F')
@@ -269,23 +269,24 @@ def init_state(alternate_screen: bool = True) -> str:
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
'\033]30001\033\\' +
set_mode('BRACKETED_PASTE') + SAVE_COLORS +
'\033[*x' # reset DECSACE to default region select
)
if alternate_screen:
ans += set_mode('ALTERNATE_SCREEN') + reset_mode('DECOM')
ans += clear_screen()
ans += '\033[>31u' # extended keyboard mode
return ans
def reset_state(normal_screen: bool = True) -> str:
ans = ''
ans += '\033[<u' # restore keyboard mode
if normal_screen:
ans += reset_mode('ALTERNATE_SCREEN')
ans += RESTORE_PRIVATE_MODE_VALUES
ans += RESTORE_CURSOR
ans += '\033]30101\033\\'
ans += RESTORE_COLORS
return ans

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import string
import subprocess
@@ -19,9 +18,7 @@ from kitty.cli_stub import UnicodeCLIOptions
from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
from kitty.key_encoding import (
CTRL, PRESS, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
)
from kitty.key_encoding import EventType, KeyEvent
from kitty.typing import BossType
from kitty.utils import ScreenSize, get_editor
@@ -34,21 +31,9 @@ from ..tui.operations import (
)
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
UP = K['UP']
DOWN = K['DOWN']
LEFT = K['LEFT']
RIGHT = K['RIGHT']
RIGHT_BRACKET = K['RIGHT_BRACKET']
LEFT_BRACKET = K['LEFT_BRACKET']
TAB = K['TAB']
ESCAPE = K['ESCAPE']
F1 = K['F1']
F2 = K['F2']
F3 = K['F3']
F4 = K['F4']
F12 = K['F12']
favorites_path = os.path.join(config_dir, 'unicode-input-favorites.conf')
INDEX_CHAR = '.'
INDEX_BASE = 36
DEFAULT_SET = tuple(map(
ord,
'‘’“”‹›«»‚„' '😀😛😇😈😉😍😎😮👍👎' '—–§¶†‡©®™' '→⇒•·°±−×÷¼½½¾'
@@ -152,7 +137,7 @@ def encode_hint(num: int, digits: str = string.digits + string.ascii_lowercase)
def decode_hint(x: str) -> int:
return int(x, 36)
return int(x, INDEX_BASE)
class Table:
@@ -172,11 +157,11 @@ class Table:
if self.codepoints:
return self.codepoints[self.current_idx]
def set_codepoints(self, codepoints: List[int], mode: str = HEX) -> None:
def set_codepoints(self, codepoints: List[int], mode: str = HEX, current_idx: int = 0) -> None:
self.codepoints = codepoints
self.mode = mode
self.layout_dirty = True
self.current_idx = 0
self.current_idx = current_idx if current_idx < len(codepoints) else 0
def codepoint_at_hint(self, hint: str) -> int:
return self.codepoints[decode_hint(hint)]
@@ -270,8 +255,10 @@ class Table:
def is_index(w: str) -> bool:
if w[0] != INDEX_CHAR:
return False
try:
int(w.lstrip(INDEX_CHAR), 16)
int(w.lstrip(INDEX_CHAR), INDEX_BASE)
return True
except Exception:
return False
@@ -306,6 +293,7 @@ class UnicodeInput(Handler):
def update_codepoints(self) -> None:
codepoints = None
iindex_word = 0
if self.mode is HEX:
q: Tuple[str, Optional[Union[str, Sequence[int]]]] = (self.mode, None)
codepoints = self.recent
@@ -324,14 +312,11 @@ class UnicodeInput(Handler):
if index_words:
index_word = words[index_words[0]]
words = words[:index_words[0]]
iindex_word = int(index_word.lstrip(INDEX_CHAR), INDEX_BASE)
codepoints = codepoints_matching_search(tuple(words))
if index_words:
iindex_word = int(index_word.lstrip(INDEX_CHAR), 16)
if codepoints and iindex_word < len(codepoints):
codepoints = [codepoints[iindex_word]]
if q != self.last_updated_code_point_at:
self.last_updated_code_point_at = q
self.table.set_codepoints(codepoints or [], self.mode)
self.table.set_codepoints(codepoints or [], self.mode, iindex_word)
def update_current_char(self) -> None:
self.update_codepoints()
@@ -431,46 +416,46 @@ class UnicodeInput(Handler):
self.refresh()
def on_key(self, key_event: KeyEvent) -> None:
if self.mode is HEX and key_event.type is not RELEASE and not key_event.mods:
if self.mode is HEX and key_event.type is not EventType.RELEASE and not key_event.mods:
try:
val = int(self.line_edit.current_input, 16)
except Exception:
pass
else:
if key_event.key is TAB:
if key_event.matches('tab'):
self.line_edit.current_input = hex(val + 0x10)[2:]
self.refresh()
return
if key_event.key is UP:
if key_event.matches('up'):
self.line_edit.current_input = hex(val + 1)[2:]
self.refresh()
return
if key_event.key is DOWN:
if key_event.matches('down'):
self.line_edit.current_input = hex(val - 1)[2:]
self.refresh()
return
if self.mode is NAME and key_event.type is not RELEASE and not key_event.mods:
if key_event.key is TAB:
if key_event.mods == SHIFT:
self.table.move_current(cols=-1)
self.refresh()
elif not key_event.mods:
self.table.move_current(cols=1)
self.refresh()
return
elif key_event.key is LEFT and not key_event.mods:
if self.mode is NAME and key_event.type is not EventType.RELEASE and not key_event.mods:
if key_event.matches('shift+tab'):
self.table.move_current(cols=-1)
self.refresh()
return
elif key_event.key is RIGHT and not key_event.mods:
if key_event.matches('tab'):
self.table.move_current(cols=1)
self.refresh()
return
elif key_event.key is UP and not key_event.mods:
if key_event.matches('left'):
self.table.move_current(cols=-1)
self.refresh()
return
if key_event.matches('right'):
self.table.move_current(cols=1)
self.refresh()
return
if key_event.matches('up'):
self.table.move_current(rows=-1)
self.refresh()
return
elif key_event.key is DOWN and not key_event.mods:
if key_event.matches('down'):
self.table.move_current(rows=1)
self.refresh()
return
@@ -478,26 +463,34 @@ class UnicodeInput(Handler):
if self.line_edit.on_key(key_event):
self.refresh()
return
if key_event is enter_key:
if key_event.matches('enter'):
self.quit_loop(0)
elif key_event.type is PRESS:
if not key_event.mods:
if key_event.key is ESCAPE:
self.quit_loop(1)
elif key_event.key is F1:
self.switch_mode(HEX)
elif key_event.key is F2:
self.switch_mode(NAME)
elif key_event.key is F3:
self.switch_mode(EMOTICONS)
elif key_event.key is F4:
self.switch_mode(FAVORITES)
elif key_event.key is F12 and self.mode is FAVORITES:
self.edit_favorites()
elif key_event.mods == CTRL and key_event.key in (TAB, RIGHT_BRACKET, LEFT_BRACKET):
self.next_mode(-1 if key_event.key is LEFT_BRACKET else 1)
elif key_event.mods == CTRL | SHIFT and key_event.key is TAB:
self.next_mode(-1)
return
if key_event.matches('esc'):
self.quit_loop(1)
return
if key_event.matches('f1'):
self.switch_mode(HEX)
return
if key_event.matches('f2'):
self.switch_mode(NAME)
return
if key_event.matches('f3'):
self.switch_mode(EMOTICONS)
return
if key_event.matches('f4'):
self.switch_mode(FAVORITES)
return
if key_event.matches('f12') and self.mode is FAVORITES:
self.edit_favorites()
return
if key_event.matches('ctrl+shift+tab'):
self.next_mode(-1)
return
for key in ('tab', '[', ']'):
if key_event.matches(f'ctrl+{key}'):
self.next_mode(-1 if key == '[' else 1)
return
def edit_favorites(self) -> None:
if not os.path.exists(favorites_path):

File diff suppressed because one or more lines are too long

42
kitty/banned.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#define BANNED(func) sorry_##func##_is_a_banned_function
#undef strcpy
#define strcpy(x,y) BANNED(strcpy)
#undef strcat
#define strcat(x,y) BANNED(strcat)
#undef strncpy
#define strncpy(x,y,n) BANNED(strncpy)
#undef strncat
#define strncat(x,y,n) BANNED(strncat)
#undef sprintf
#undef vsprintf
#ifdef HAVE_VARIADIC_MACROS
#define sprintf(...) BANNED(sprintf)
#define vsprintf(...) BANNED(vsprintf)
#else
#define sprintf(buf,fmt,arg) BANNED(sprintf)
#define vsprintf(buf,fmt,arg) BANNED(vsprintf)
#endif
#undef gmtime
#define gmtime(t) BANNED(gmtime)
#undef localtime
#define localtime(t) BANNED(localtime)
#undef ctime
#define ctime(t) BANNED(ctime)
#undef ctime_r
#define ctime_r(t, buf) BANNED(ctime_r)
#undef asctime
#define asctime(t) BANNED(asctime)
#undef asctime_r
#define asctime_r(t, buf) BANNED(asctime_r)

103
kitty/boss.py Normal file → Executable file
View File

@@ -29,14 +29,14 @@ from .constants import (
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
ChildMonitor, add_timer, background_opacity_of, change_background_opacity,
change_os_window_state, cocoa_set_menubar_title, create_os_window,
current_application_quit_request, current_os_window, destroy_global_data,
focus_os_window, get_clipboard_string, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
safe_pipe, set_application_quit_request, set_background_image, set_boss,
set_clipboard_string, set_in_sequence_mode, thread_write,
toggle_fullscreen, toggle_maximized
ChildMonitor, KeyEvent, add_timer, background_opacity_of,
change_background_opacity, change_os_window_state, cocoa_set_menubar_title,
create_os_window, current_application_quit_request, current_os_window,
destroy_global_data, focus_os_window, get_clipboard_string,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, safe_pipe, set_application_quit_request,
set_background_image, set_boss, set_clipboard_string, set_in_sequence_mode,
thread_write, toggle_fullscreen, toggle_maximized
)
from .keys import get_shortcut, shortcut_matches
from .layout.base import set_layout_options
@@ -48,6 +48,7 @@ from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
)
from .types import SingleKey
from .typing import PopenType, TypedDict
from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
@@ -63,6 +64,8 @@ class OSWindowDict(TypedDict):
platform_window_id: Optional[int]
is_focused: bool
tabs: List[TabDict]
wm_class: str
wm_name: str
def listen_on(spec: str) -> int:
@@ -138,9 +141,10 @@ class Boss:
opts: Options,
args: CLIOptions,
cached_values: Dict[str, Any],
new_os_window_trigger: Optional[Tuple[int, bool, int]]
global_shortcuts: Dict[str, SingleKey]
):
set_layout_options(opts)
self.cocoa_application_launched = False
self.clipboard_buffers: Dict[str, str] = {}
self.update_check_process: Optional[PopenType] = None
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
@@ -165,8 +169,9 @@ class Boss:
set_boss(self)
self.opts, self.args = opts, args
self.keymap = self.opts.keymap.copy()
if new_os_window_trigger is not None:
self.keymap.pop(new_os_window_trigger, None)
self.global_shortcuts_map = {v: KeyAction(k) for k, v in global_shortcuts.items()}
for sc in global_shortcuts.values():
self.keymap.pop(sc, None)
if is_macos:
from .fast_data_types import (
cocoa_set_notification_activated_callback
@@ -174,12 +179,8 @@ class Boss:
cocoa_set_notification_activated_callback(notification_activated)
def startup_first_child(self, os_window_id: Optional[int]) -> None:
from kitty.launch import load_watch_modules
startup_sessions = create_sessions(self.opts, self.args, default_session=self.opts.startup_session)
watchers = load_watch_modules(self.args.watcher)
for startup_session in startup_sessions:
if watchers is not None and watchers.has_watchers:
startup_session.add_watchers_to_all_windows(watchers)
self.add_os_window(startup_session, os_window_id=os_window_id)
os_window_id = None
if self.args.start_as != 'normal':
@@ -200,16 +201,20 @@ class Boss:
if os_window_id is None:
size_data = get_os_window_sizing_data(opts_for_size or self.opts, startup_session)
wclass = wclass or getattr(startup_session, 'os_window_class', None) or self.args.cls or appname
wname = wname or self.args.name or wclass
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
os_window_id = create_os_window(
initial_window_size_func(size_data, self.cached_values),
pre_show_callback,
self.args.title or appname, wname or self.args.name or wclass, wclass)
tm = TabManager(os_window_id, self.opts, self.args, startup_session)
self.args.title or appname, wname, wclass)
else:
wname = self.args.name or self.args.cls or appname
wclass = self.args.cls or appname
tm = TabManager(os_window_id, self.opts, self.args, wclass, wname, startup_session)
self.os_window_map[os_window_id] = tm
return os_window_id
def list_os_windows(self) -> Generator[OSWindowDict, None, None]:
def list_os_windows(self, self_window: Optional[Window] = None) -> Generator[OSWindowDict, None, None]:
with cached_process_data():
active_tab, active_window = self.active_tab, self.active_window
active_tab_manager = self.active_tab_manager
@@ -218,7 +223,9 @@ class Boss:
'id': os_window_id,
'platform_window_id': platform_window_id(os_window_id),
'is_focused': tm is active_tab_manager,
'tabs': list(tm.list_tabs(active_tab, active_window)),
'tabs': list(tm.list_tabs(active_tab, active_window, self_window)),
'wm_class': tm.wm_class,
'wm_name': tm.wm_name
}
@property
@@ -280,6 +287,13 @@ class Boss:
if tab.matches(field, pat):
yield tab
found = True
elif field in ('window_id', 'window_title'):
wf = field.split('_')[1]
tabs = {self.tab_for_window(w) for w in self.match_windows(f'{wf}:{exp}')}
for q in tabs:
if q:
found = True
yield q
if not found:
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
for q in tabs:
@@ -505,10 +519,10 @@ class Boss:
run_update_check(self.opts.update_check_interval * 60 * 60)
self.update_check_started = True
def activate_tab_at(self, os_window_id: int, x: int) -> int:
def activate_tab_at(self, os_window_id: int, x: int, is_double: bool = False) -> int:
tm = self.os_window_map.get(os_window_id)
if tm is not None:
tm.activate_tab_at(x)
tm.activate_tab_at(x, is_double)
def on_window_resize(self, os_window_id: int, w: int, h: int, dpi_changed: bool) -> None:
if dpi_changed:
@@ -647,20 +661,21 @@ class Boss:
if t is not None:
return t.active_window
def dispatch_special_key(self, key: int, native_key: int, action: int, mods: int) -> bool:
def dispatch_possible_special_key(self, ev: KeyEvent) -> bool:
# Handles shortcuts, return True if the key was consumed
key_action = get_shortcut(self.keymap, mods, key, native_key)
key_action = get_shortcut(self.keymap, ev)
if key_action is None:
sequences = get_shortcut(self.opts.sequence_map, mods, key, native_key)
sequences = get_shortcut(self.opts.sequence_map, ev)
if sequences and not isinstance(sequences, KeyAction):
self.pending_sequences = sequences
set_in_sequence_mode(True)
return True
if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev):
return True
elif isinstance(key_action, KeyAction):
self.current_key_press_info = key, native_key, action, mods
return self.dispatch_action(key_action)
def process_sequence(self, key: int, native_key: int, action: Any, mods: int) -> None:
def process_sequence(self, ev: KeyEvent) -> None:
if not self.pending_sequences:
set_in_sequence_mode(False)
return
@@ -668,7 +683,7 @@ class Boss:
remaining = {}
matched_action = None
for seq, key_action in self.pending_sequences.items():
if shortcut_matches(seq[0], mods, key, native_key):
if shortcut_matches(seq[0], ev):
seq = seq[1:]
if seq:
remaining[seq] = key_action
@@ -839,7 +854,7 @@ class Boss:
s.shutdown(socket.SHUT_RDWR)
s.close()
def display_scrollback(self, window: Window, data: Optional[bytes], cmd: Optional[List[str]]) -> None:
def display_scrollback(self, window: Window, data: Optional[bytes], cmd: List[str]) -> None:
tab = self.active_tab
if tab is not None:
tab.new_special_window(
@@ -851,7 +866,7 @@ class Boss:
confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay.
cmd = [kitty_exe(), '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + get_editor() + [confpath]
cmd = [kitty_exe(), '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + get_editor(self.opts) + [confpath]
self.new_os_window(*cmd)
def get_output(self, source_window: Window, num_lines: Optional[int] = 1) -> str:
@@ -1511,6 +1526,7 @@ class Boss:
tab_id_map: Dict[int, Optional[Union[str, int]]] = {}
current_tab = self.active_tab
done_tab_id: Optional[Union[str, int]] = None
done_called = False
for i, tab in enumerate(self.all_tabs):
if tab is not current_tab:
@@ -1524,10 +1540,13 @@ class Boss:
lines.append(fmt.format(new_idx, 'New OS Window'))
def done(data: Dict[str, Any], target_window_id: int, self: Boss) -> None:
nonlocal done_tab_id
nonlocal done_tab_id, done_called
done_tab_id = tab_id_map[int(data['groupdicts'][0]['index'])]
done_called = True
def done2(target_window_id: int, self: Boss) -> None:
if not done_called:
return
tab_id = done_tab_id
target_window = None
for w in self.all_windows:
@@ -1617,3 +1636,27 @@ class Boss:
self.set_active_window(w, switch_os_window_if_needed=True)
if report:
w.report_notification_activated(identifier)
def show_kitty_env_vars(self) -> None:
w = self.active_window
if w:
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items()).encode('utf-8')
self.display_scrollback(w, output, ['less'])
def open_file(self, path: str) -> None:
if path == ":cocoa::application launched::":
self.cocoa_application_launched = True
return
def new_os_window() -> None:
self.new_os_window(path)
if self.cocoa_application_launched or not self.os_window_map:
return new_os_window()
tab = self.active_tab
if tab is None:
return new_os_window()
w = tab.active_window
self.new_window(path)
if w is not None:
tab.remove_window(w)

11
kitty/charsets.c generated
View File

@@ -7,10 +7,8 @@
// Taken from consolemap.c in the linux vt driver sourcecode
#include <stddef.h>
#include <stdint.h>
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
#include "data-types.h"
static uint32_t charset_translations[5][256] = {
/* 8-bit Latin-1 mapped to Unicode -- trivial mapping */
@@ -231,7 +229,7 @@ static const uint8_t utf8_data[] = {
};
uint32_t
decode_utf8(uint32_t* state, uint32_t* codep, uint8_t byte) {
decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) {
uint32_t type = utf8_data[byte];
*codep = (*state != UTF8_ACCEPT) ?
@@ -245,7 +243,8 @@ decode_utf8(uint32_t* state, uint32_t* codep, uint8_t byte) {
size_t
decode_utf8_string(const char *src, size_t sz, uint32_t *dest) {
// dest must be a zeroed array of size at least sz
uint32_t codep = 0, state = 0, prev = UTF8_ACCEPT;
uint32_t codep = 0;
UTF8State state = 0, prev = UTF8_ACCEPT;
size_t i, d;
for (i = 0, d = 0; i < sz; i++) {
switch(decode_utf8(&state, &codep, src[i])) {

View File

@@ -6,6 +6,7 @@
*/
#include "loop-utils.h"
#include "safe-wrappers.h"
#include "state.h"
#include "threading.h"
#include "screen.h"
@@ -84,8 +85,6 @@ static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
static pthread_mutex_t children_lock, talk_lock;
static bool kill_signal_received = false;
static ChildMonitor *the_monitor = NULL;
static uint8_t drain_buf[1024];
typedef struct {
pid_t pid;
@@ -164,7 +163,6 @@ dealloc(ChildMonitor* self) {
pthread_mutex_destroy(&talk_lock);
Py_CLEAR(self->dump_callback);
Py_CLEAR(self->death_notify);
Py_TYPE(self)->tp_free((PyObject*)self);
while (remove_queue_count) {
remove_queue_count--;
FREE_CHILD(remove_queue[remove_queue_count]);
@@ -174,6 +172,7 @@ dealloc(ChildMonitor* self) {
FREE_CHILD(add_queue[add_queue_count]);
}
free_loop_data(&self->io_loop_data);
Py_TYPE(self)->tp_free((PyObject*)self);
}
static void
@@ -191,12 +190,15 @@ static PyObject *
start(PyObject *s, PyObject *a UNUSED) {
#define start_doc "start() -> Start the I/O thread"
ChildMonitor *self = (ChildMonitor*)s;
int ret;
if (self->talk_fd > -1 || self->listen_fd > -1) {
if (pthread_create(&self->talk_thread, NULL, talk_loop, self) != 0) return PyErr_SetFromErrno(PyExc_OSError);
if ((ret = pthread_create(&self->talk_thread, NULL, talk_loop, self)) != 0) {
return PyErr_Format(PyExc_OSError, "Failed to start talk thread with error: %s", strerror(ret));
}
talk_thread_started = true;
}
int ret = pthread_create(&self->io_thread, NULL, io_loop, self);
if (ret != 0) return PyErr_SetFromErrno(PyExc_OSError);
ret = pthread_create(&self->io_thread, NULL, io_loop, self);
if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to start I/O thread with error: %s", strerror(ret));
Py_RETURN_NONE;
}
@@ -543,7 +545,7 @@ change_menubar_title(PyObject *title UNUSED) {
}
static inline bool
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg) {
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) {
#define TD os_window->tab_bar_render_data
bool needs_render = os_window->needs_render;
os_window->needs_render = false;
@@ -588,6 +590,14 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
set_os_window_title_from_window(w, os_window);
*active_window_bg = window_bg;
} else WD.screen->cursor_render_info.is_visible = false;
if (scan_for_animated_images) {
monotonic_t min_gap;
if (scan_active_animations(WD.screen->grman, now, &min_gap, true)) needs_render = true;
if (min_gap < MONOTONIC_T_MAX) {
global_state.check_for_active_animated_images = true;
set_maximum_wait(min_gap);
}
}
if (send_cell_data_to_gpu(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
if (WD.screen->start_visual_bell_at != 0) needs_render = true;
}
@@ -660,7 +670,7 @@ no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_
static inline void
render(monotonic_t now, bool input_read) {
EVDBG("input_read: %d", input_read);
EVDBG("input_read: %d, check_for_active_animated_images: %d", input_read, global_state.check_for_active_animated_images);
static monotonic_t last_render_at = MONOTONIC_T_MIN;
monotonic_t time_since_last_render = last_render_at == MONOTONIC_T_MIN ? OPT(repaint_delay) : now - last_render_at;
if (!input_read && time_since_last_render < OPT(repaint_delay)) {
@@ -668,6 +678,9 @@ render(monotonic_t now, bool input_read) {
return;
}
const bool scan_for_animated_images = global_state.check_for_active_animated_images;
global_state.check_for_active_animated_images = false;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
w->render_calls++;
@@ -701,7 +714,7 @@ render(monotonic_t now, bool input_read) {
bool all_windows_have_same_bg;
color_type active_window_bg = 0;
if (!w->fonts_data) { log_error("No fonts data found for window id: %llu", w->id); continue; }
if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg)) needs_render = true;
if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg, scan_for_animated_images)) needs_render = true;
if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true;
if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true;
if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg);
@@ -799,7 +812,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
data->fd = fd;
memcpy(data->buf, buf, data->sz);
int ret = pthread_create(&thread, NULL, thread_write, data);
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_Format(PyExc_OSError, "Failed to start write thread with error: %s", strerror(ret)); }
pthread_detach(thread);
Py_RETURN_NONE;
}
@@ -922,14 +935,25 @@ process_pending_closes(ChildMonitor *self) {
// If we create new OS windows during wait_events(), using global menu actions
// via the mouse causes a crash because of the way autorelease pools work in
// glfw/cocoa. So we use a flag instead.
static unsigned int cocoa_pending_actions = 0;
static char *cocoa_pending_actions_wd = NULL;
static CocoaPendingAction cocoa_pending_actions = NO_COCOA_PENDING_ACTION;
typedef struct {
char* wd;
char **open_files;
size_t open_files_count;
size_t open_files_capacity;
} CocoaPendingActionsData;
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
void
set_cocoa_pending_action(CocoaPendingAction action, const char *wd) {
if (wd) {
if (cocoa_pending_actions_wd) free(cocoa_pending_actions_wd);
cocoa_pending_actions_wd = strdup(wd);
if (action == OPEN_FILE) {
ensure_space_for(&cocoa_pending_actions_data, open_files, char*, cocoa_pending_actions_data.open_files_count + 8, open_files_capacity, 8, true);
cocoa_pending_actions_data.open_files[cocoa_pending_actions_data.open_files_count++] = strdup(wd);
} else {
if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd);
cocoa_pending_actions_data.wd = strdup(wd);
}
}
cocoa_pending_actions |= action;
// The main loop may be blocking on the event queue, if e.g. unfocused.
@@ -967,11 +991,27 @@ process_global_state(void *data) {
if (cocoa_pending_actions) {
if (cocoa_pending_actions & PREFERENCES_WINDOW) { call_boss(edit_config_file, NULL); }
if (cocoa_pending_actions & NEW_OS_WINDOW) { call_boss(new_os_window, NULL); }
if (cocoa_pending_actions_wd) {
if (cocoa_pending_actions & NEW_OS_WINDOW_WITH_WD) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_wd); }
if (cocoa_pending_actions & NEW_TAB_WITH_WD) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_wd); }
free(cocoa_pending_actions_wd);
cocoa_pending_actions_wd = NULL;
if (cocoa_pending_actions & CLOSE_OS_WINDOW) { call_boss(close_os_window, NULL); }
if (cocoa_pending_actions & CLOSE_TAB) { call_boss(close_tab, NULL); }
if (cocoa_pending_actions & NEW_TAB) { call_boss(new_tab, NULL); }
if (cocoa_pending_actions & NEXT_TAB) { call_boss(next_tab, NULL); }
if (cocoa_pending_actions & PREVIOUS_TAB) { call_boss(previous_tab, NULL); }
if (cocoa_pending_actions & DETACH_TAB) { call_boss(detach_tab, NULL); }
if (cocoa_pending_actions_data.wd) {
if (cocoa_pending_actions & NEW_OS_WINDOW_WITH_WD) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_data.wd); }
if (cocoa_pending_actions & NEW_TAB_WITH_WD) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_data.wd); }
free(cocoa_pending_actions_data.wd);
cocoa_pending_actions_data.wd = NULL;
}
if (cocoa_pending_actions_data.open_files_count) {
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_files_count; cpa++) {
if (cocoa_pending_actions_data.open_files[cpa]) {
call_boss(open_file, "s", cocoa_pending_actions_data.open_files[cpa]);
free(cocoa_pending_actions_data.open_files[cpa]);
cocoa_pending_actions_data.open_files[cpa] = NULL;
}
}
cocoa_pending_actions_data.open_files_count = 0;
}
cocoa_pending_actions = 0;
}
@@ -996,7 +1036,13 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
state_check_timer = add_main_loop_timer(1000, true, do_state_check, self, NULL);
run_main_loop(process_global_state, self);
#ifdef __APPLE__
if (cocoa_pending_actions_wd) { free(cocoa_pending_actions_wd); cocoa_pending_actions_wd = NULL; }
if (cocoa_pending_actions_data.wd) { free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; }
if (cocoa_pending_actions_data.open_files) {
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_files_count; cpa++) {
if (cocoa_pending_actions_data.open_files[cpa]) free(cocoa_pending_actions_data.open_files[cpa]);
}
free(cocoa_pending_actions_data.open_files); cocoa_pending_actions_data.open_files = NULL;
}
#endif
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
@@ -1096,19 +1142,6 @@ read_bytes(int fd, Screen *screen) {
}
static inline void
drain_fd(int fd) {
while(true) {
ssize_t len = read(fd, drain_buf, sizeof(drain_buf));
if (len < 0) {
if (errno == EINTR) continue;
break;
}
if (len > 0) continue;
break;
}
}
typedef struct { bool kill_signal, child_died; } SignalSet;
static void

View File

@@ -6,6 +6,7 @@
*/
#include "data-types.h"
#include "safe-wrappers.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -78,10 +79,16 @@ spawn(PyObject *self UNUSED, PyObject *args) {
char **argv = serialize_string_tuple(argv_p);
char **env = serialize_string_tuple(env_p);
#if PY_VERSION_HEX >= 0x03070000
PyOS_BeforeFork();
#endif
pid_t pid = fork();
switch(pid) {
case 0: {
// child
#if PY_VERSION_HEX >= 0x03070000
PyOS_AfterFork_Child();
#endif
sigset_t signals = {0};
struct sigaction act = {.sa_handler=SIG_DFL};
#define SA(which) { if (sigaction(which, &act, NULL) != 0) exit_on_err("sigaction() in child process failed"); }
@@ -93,7 +100,7 @@ spawn(PyObject *self UNUSED, PyObject *args) {
if (setsid() == -1) exit_on_err("setsid() in child process failed");
// Establish the controlling terminal (see man 7 credentials)
int tfd = open(name, O_RDWR);
int tfd = safe_open(name, O_RDWR, 0);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
#ifdef TIOCSCTTY
// On BSD open() does not establish the controlling terminal
@@ -138,10 +145,19 @@ spawn(PyObject *self UNUSED, PyObject *args) {
exit(EXIT_FAILURE);
break;
}
case -1:
case -1: {
#if PY_VERSION_HEX >= 0x03070000
int saved_errno = errno;
PyOS_AfterFork_Parent();
errno = saved_errno;
#endif
PyErr_SetFromErrno(PyExc_OSError);
break;
}
default:
#if PY_VERSION_HEX >= 0x03070000
PyOS_AfterFork_Parent();
#endif
break;
}
#undef exit_on_err

23
kitty/cleanup.c Normal file
View File

@@ -0,0 +1,23 @@
/*
* cleanup.c
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "cleanup.h"
kitty_cleanup_at_exit_func exit_funcs[NUM_CLEANUP_FUNCS] = {0};
void
register_at_exit_cleanup_func(AtExitCleanupFunc which, kitty_cleanup_at_exit_func func) {
if (which < NUM_CLEANUP_FUNCS) exit_funcs[which] = func;
}
void
run_at_exit_cleanup_functions(void) {
for (unsigned i = 0; i < NUM_CLEANUP_FUNCS; i++) {
if (exit_funcs[i]) exit_funcs[i]();
}
}

25
kitty/cleanup.h Normal file
View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
typedef void (*kitty_cleanup_at_exit_func)(void);
typedef enum {
STATE_CLEANUP_FUNC,
GLFW_CLEANUP_FUNC,
DESKTOP_CLEANUP_FUNC,
FREETYPE_CLEANUP_FUNC,
CORE_TEXT_CLEANUP_FUNC,
COCOA_CLEANUP_FUNC,
PNG_READER_CLEANUP_FUNC,
FONTCONFIG_CLEANUP_FUNC,
NUM_CLEANUP_FUNCS
} AtExitCleanupFunc;
void register_at_exit_cleanup_func(AtExitCleanupFunc which, kitty_cleanup_at_exit_func func);
void run_at_exit_cleanup_functions(void);

View File

@@ -16,7 +16,8 @@ from .conf.utils import resolve_config
from .config import KeyAction
from .constants import appname, defconf, is_macos, is_wayland, str_version
from .options_stub import Options as OptionsStub
from .typing import BadLineType, KeySpec, SequenceMap, TypedDict
from .types import SingleKey
from .typing import BadLineType, SequenceMap, TypedDict
class OptionDict(TypedDict):
@@ -610,7 +611,7 @@ created after startup.
--hold
type=bool-set
Remain open after child process exits. Note that this only affects the first
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
window. You can quit by either using the close window shortcut or pressing any key.
--single-instance -1
@@ -755,38 +756,33 @@ def parse_args(
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
ShortcutMap = Dict[Tuple[KeySpec, ...], KeyAction]
ShortcutMap = Dict[Tuple[SingleKey, ...], KeyAction]
def print_shortcut(key_sequence: Iterable[KeySpec], action: KeyAction) -> None:
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()}
setattr(print_shortcut, 'maps', (mmap, krmap))
mmap, krmap = getattr(print_shortcut, 'maps')
def print_shortcut(key_sequence: Iterable[SingleKey], action: KeyAction) -> None:
from .fast_data_types import (
GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER,
glfw_get_key_name
)
modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER,
'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK}
keys = []
for key_spec in key_sequence:
names = []
mods, is_native, key = key_spec
for name, val in mmap.items():
for name, val in modmap.items():
if mods & val:
names.append(name)
if key:
if is_native:
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
kn = glfw_get_key_name(GLFW_KEY_UNKNOWN, key) or 'Unknown key'
names.append(kn)
else:
names.append(krmap[key])
kname = glfw_get_key_name(0, key) if is_native else glfw_get_key_name(key, 0)
names.append(kname or f'{key}')
keys.append('+'.join(names))
print('\t', ' > '.join(keys), action)
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[KeySpec, ...]]) -> None:
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[SingleKey, ...]]) -> None:
if changes:
print(title(text))
@@ -804,7 +800,7 @@ def compare_keymaps(final: ShortcutMap, initial: ShortcutMap) -> None:
def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
ans: Dict[Tuple[KeySpec, ...], KeyAction] = {}
ans: Dict[Tuple[SingleKey, ...], KeyAction] = {}
for key_spec, rest_map in m.items():
for r, action in rest_map.items():
ans[(key_spec,) + (r)] = action
@@ -858,3 +854,10 @@ def create_opts(args: CLIOptions, debug_config: bool = False, accumulate_bad_lin
print('Running under:', green('Wayland' if is_wayland(opts) else 'X11'))
compare_opts(opts)
return opts
def create_default_opts() -> OptionsStub:
from .config import load_config
config = tuple(resolve_config(SYSTEM_CONF, defconf, ()))
opts = load_config(*config)
return opts

View File

@@ -11,7 +11,6 @@
import sys
from contextlib import suppress
from typing import Any
from functools import partial
CSI = '\033['
@@ -109,6 +108,22 @@ def screen_delete_characters(count: int) -> None:
write(CSI + '%dP' % count)
def screen_push_colors(which: int) -> None:
write(CSI + '%d#P' % which)
def screen_pop_colors(which: int) -> None:
write(CSI + '%d#Q' % which)
def screen_report_colors() -> None:
write(CSI + '#R')
def screen_repeat_character(num: int) -> None:
write(CSI + '%db' % num)
def screen_insert_characters(count: int) -> None:
write(CSI + '%d@' % count)
@@ -192,16 +207,15 @@ def write_osc(code: int, string: str = '') -> None:
write(OSC + str(code) + string + '\x07')
set_dynamic_color = set_color_table_color = write_osc
screen_push_dynamic_colors = partial(write_osc, 30001)
screen_pop_dynamic_colors = partial(write_osc, 30101)
set_dynamic_color = set_color_table_color = process_cwd_notification = write_osc
def replay(raw: str) -> None:
specials = {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color', 'process_cwd_notification'}
for line in raw.splitlines():
if line.strip() and not line.startswith('#'):
cmd, rest = line.partition(' ')[::2]
if cmd in {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color'}:
if cmd in specials:
globals()[cmd](rest)
else:
r = map(int, rest.split()) if rest else ()

View File

@@ -7,6 +7,7 @@
#include "state.h"
#include "cleanup.h"
#include "monotonic.h"
#include <Cocoa/Cocoa.h>
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
@@ -75,23 +76,25 @@ find_app_name(void) {
+ (GlobalMenuTarget *) shared_instance;
@end
#define PENDING(selector, which) - (void)selector:(id)sender { (void)sender; set_cocoa_pending_action(which, NULL); }
@implementation GlobalMenuTarget
- (void)show_preferences:(id)sender {
(void)sender;
set_cocoa_pending_action(PREFERENCES_WINDOW, NULL);
}
- (void)new_os_window:(id)sender {
(void)sender;
set_cocoa_pending_action(NEW_OS_WINDOW, NULL);
}
PENDING(edit_config_file, PREFERENCES_WINDOW)
PENDING(new_os_window, NEW_OS_WINDOW)
PENDING(detach_tab, DETACH_TAB)
PENDING(close_os_window, CLOSE_OS_WINDOW)
PENDING(close_tab, CLOSE_TAB)
PENDING(new_tab, NEW_TAB)
PENDING(next_tab, NEXT_TAB)
PENDING(previous_tab, PREVIOUS_TAB)
- (void)open_kitty_website_url:(id)sender {
(void)sender;
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]];
}
#undef PENDING
+ (GlobalMenuTarget *) shared_instance
{
@@ -106,17 +109,32 @@ find_app_name(void) {
@end
static char new_window_key[32] = {0};
static NSEventModifierFlags new_window_mods = 0;
typedef struct {
char key[32];
NSEventModifierFlags mods;
} GlobalShortcut;
typedef struct {
GlobalShortcut new_os_window, close_os_window, close_tab, edit_config_file;
GlobalShortcut previous_tab, next_tab, new_tab;
} GlobalShortcuts;
static GlobalShortcuts global_shortcuts;
static PyObject*
cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
int mods, key;
if (!PyArg_ParseTuple(args, "ii", &mods, &key)) return NULL;
int nwm;
get_cocoa_key_equivalent(key, mods, new_window_key, sizeof(new_window_key), &nwm);
new_window_mods = nwm;
if (new_window_key[0]) Py_RETURN_TRUE;
cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) {
int mods;
unsigned int key;
const char *name;
if (!PyArg_ParseTuple(args, "siI", &name, &mods, &key)) return NULL;
GlobalShortcut *gs = NULL;
#define Q(x) if (strcmp(name, #x) == 0) gs = &global_shortcuts.x
Q(new_os_window); else Q(close_os_window); else Q(close_tab); else Q(edit_config_file);
else Q(new_tab); else Q(next_tab); else Q(previous_tab);
#undef Q
if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; }
int cocoa_mods;
get_cocoa_key_equivalent(key, mods, gs->key, 32, &cocoa_mods);
gs->mods = cocoa_mods;
if (gs->key[0]) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
@@ -338,6 +356,12 @@ cocoa_create_global_menu(void) {
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
[NSApp setMainMenu:bar];
#define MENU_ITEM(menu, title, name) { \
NSMenuItem *__mi = [menu addItemWithTitle:title action:@selector(name:) keyEquivalent:@(global_shortcuts.name.key)]; \
[__mi setKeyEquivalentModifierMask:global_shortcuts.name.mods]; \
[__mi setTarget:global_menu_target]; \
}
NSMenuItem* appMenuItem =
[bar addItemWithTitle:@""
action:NULL
@@ -349,18 +373,8 @@ cocoa_create_global_menu(void) {
action:@selector(orderFrontStandardAboutPanel:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[[appMenu addItemWithTitle:@"Preferences..."
action:@selector(show_preferences:)
keyEquivalent:@","]
setTarget:global_menu_target];
NSMenuItem* new_os_window_menu_item =
[appMenu addItemWithTitle:@"New OS window"
action:@selector(new_os_window:)
keyEquivalent:@(new_window_key)];
[new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods];
[new_os_window_menu_item setTarget:global_menu_target];
MENU_ITEM(appMenu, @"Preferences…", edit_config_file);
MENU_ITEM(appMenu, @"New OS window", new_os_window);
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
action:@selector(hide:)
@@ -383,6 +397,12 @@ cocoa_create_global_menu(void) {
[appMenu addItem:[NSMenuItem separatorItem]];
[[appMenu addItemWithTitle:@"Secure Keyboard Entry"
action:@selector(toggleSecureInput:)
keyEquivalent:@"s"]
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
action:@selector(terminate:)
keyEquivalent:@"q"];
@@ -406,6 +426,16 @@ cocoa_create_global_menu(void) {
action:@selector(arrangeInFront:)
keyEquivalent:@""];
[windowMenu addItem:[NSMenuItem separatorItem]];
MENU_ITEM(windowMenu, @"New Tab", new_tab);
MENU_ITEM(windowMenu, @"Show Previous Tab", previous_tab);
MENU_ITEM(windowMenu, @"Show Next Tab", next_tab);
MENU_ITEM(windowMenu, @"Close Tab", close_tab);
MENU_ITEM(windowMenu, @"Close OS Window", close_os_window);
[[windowMenu addItemWithTitle:@"Move Tab to New Window"
action:@selector(detach_tab:)
keyEquivalent:@""] setTarget:global_menu_target];
[windowMenu addItem:[NSMenuItem separatorItem]];
[[windowMenu addItemWithTitle:@"Enter Full Screen"
action:@selector(toggleFullScreen:)
@@ -436,6 +466,7 @@ cocoa_create_global_menu(void) {
[NSApp setServicesProvider:[[[ServiceProvider alloc] init] autorelease]];
#undef MENU_ITEM
}
void
@@ -622,7 +653,7 @@ cocoa_system_beep(void) {
static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
@@ -630,10 +661,8 @@ static PyMethodDef module_methods[] = {
bool
init_cocoa(PyObject *module) {
memset(&global_shortcuts, 0, sizeof(global_shortcuts));
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
if (Py_AtExit(cleanup) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the cocoa_window at exit handler");
return false;
}
register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup);
return true;
}

View File

@@ -79,6 +79,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
static void
dealloc(ColorProfile* self) {
if (self->color_stack) free(self->color_stack);
Py_TYPE(self)->tp_free((PyObject*)self);
}
@@ -318,19 +319,65 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
self->dirty = false;
}
void
colorprofile_push_dynamic_colors(ColorProfile *self) {
if (self->dynamic_color_stack_idx >= arraysz(self->dynamic_color_stack)) {
memmove(self->dynamic_color_stack, self->dynamic_color_stack + 1, sizeof(self->dynamic_color_stack) - sizeof(self->dynamic_color_stack[0]));
self->dynamic_color_stack_idx = arraysz(self->dynamic_color_stack) - 1;
static void
push_onto_color_stack_at(ColorProfile *self, unsigned int i) {
self->color_stack[i].dynamic_colors = self->overridden;
memcpy(self->color_stack[i].color_table, self->color_table, sizeof(self->color_stack->color_table));
}
static void
copy_from_color_stack_at(ColorProfile *self, unsigned int i) {
self->overridden = self->color_stack[i].dynamic_colors;
memcpy(self->color_table, self->color_stack[i].color_table, sizeof(self->color_table));
}
bool
colorprofile_push_colors(ColorProfile *self, unsigned int idx) {
if (idx > 10) return false;
size_t sz = idx ? idx : self->color_stack_idx + 1;
sz = MIN(10u, sz);
if (self->color_stack_sz < sz) {
self->color_stack = realloc(self->color_stack, sz * sizeof(self->color_stack[0]));
if (self->color_stack == NULL) fatal("Out of memory while ensuring space for %zu elements in color stack", sz);
memset(self->color_stack + self->color_stack_sz, 0, (sz - self->color_stack_sz) * sizeof(self->color_stack[0]));
self->color_stack_sz = sz;
}
self->dynamic_color_stack[self->dynamic_color_stack_idx++] = self->overridden;
if (idx == 0) {
if (self->color_stack_idx >= self->color_stack_sz) {
memmove(self->color_stack, self->color_stack + 1, (self->color_stack_sz - 1) * sizeof(self->color_stack[0]));
idx = self->color_stack_sz - 1;
} else idx = self->color_stack_idx++;
push_onto_color_stack_at(self, idx);
return true;
}
idx -= 1;
if (idx < self->color_stack_sz) {
push_onto_color_stack_at(self, idx);
return true;
}
return false;
}
bool
colorprofile_pop_colors(ColorProfile *self, unsigned int idx) {
if (idx == 0) {
if (!self->color_stack_idx) return false;
copy_from_color_stack_at(self, --self->color_stack_idx);
memset(self->color_stack + self->color_stack_idx, 0, sizeof(self->color_stack[0]));
return true;
}
idx -= 1;
if (idx < self->color_stack_sz) {
copy_from_color_stack_at(self, idx);
return true;
}
return false;
}
void
colorprofile_pop_dynamic_colors(ColorProfile *self) {
if (!self->dynamic_color_stack_idx) return;
self->overridden = self->dynamic_color_stack[--(self->dynamic_color_stack_idx)];
colorprofile_report_stack(ColorProfile *self, unsigned int *idx, unsigned int *count) {
*count = self->color_stack_idx;
*idx = self->color_stack_idx ? self->color_stack_idx - 1 : 0;
}
static PyObject*

View File

@@ -5,7 +5,6 @@
import os
import shlex
import sys
from functools import lru_cache
from typing import (
Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple
)
@@ -17,6 +16,7 @@ from .cli import (
)
from .rc.base import all_command_names, command_for_name
from .shell import options_for_cmd
from .types import run_once
'''
To add completion for a new shell, you need to:
@@ -75,7 +75,7 @@ class Completions:
self.delegate: Delegate = Delegate()
@lru_cache(maxsize=2)
@run_once
def remote_control_command_names() -> Tuple[str, ...]:
return tuple(sorted(x.replace('_', '-') for x in all_command_names()))

View File

@@ -157,6 +157,7 @@ def remove_markup(text: str) -> str:
return {
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
'functional': 'https://sw.kovidgoyal.net/kitty/keyboard-protocol.html#functional-key-definitions',
}[m.group(2)]
return str(m.group(2))

View File

@@ -11,7 +11,8 @@ from typing import (
)
from ..rgb import Color, to_color as as_color
from ..utils import log_error, expandvars
from ..types import ParsedShortcut, ConvertibleToNumbers
from ..utils import expandvars, log_error
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
T = TypeVar('T')
@@ -34,17 +35,6 @@ def to_color_or_none(x: str) -> Optional[Color]:
return None if x.lower() == 'none' else to_color(x)
ConvertibleToNumbers = Union[str, bytes, int, float]
def positive_int(x: ConvertibleToNumbers) -> int:
return max(0, int(x))
def positive_float(x: ConvertibleToNumbers) -> float:
return max(0, float(x))
def unit_float(x: ConvertibleToNumbers) -> float:
return max(0, min(float(x), 1))
@@ -267,7 +257,7 @@ def load_config(
try:
with open(path, encoding='utf-8', errors='replace') as f:
vals = parse_config(f)
except FileNotFoundError:
except (FileNotFoundError, PermissionError):
continue
ans = merge_configs(ans, vals)
if overrides is not None:
@@ -301,38 +291,6 @@ def key_func() -> Tuple[Callable[..., Callable], Dict[str, Callable]]:
return func_with_args, ans
def parse_kittens_shortcut(sc: str) -> Tuple[Optional[int], str, bool]:
from ..key_encoding import config_key_map, config_mod_map, text_match
if sc.endswith('+'):
parts = list(filter(None, sc.rstrip('+').split('+') + ['+']))
else:
parts = sc.split('+')
qmods = parts[:-1]
if qmods:
resolved_mods = 0
for mod in qmods:
m = config_mod_map.get(mod.upper())
if m is None:
raise ValueError('Unknown shortcut modifiers: {}'.format(sc))
resolved_mods |= m
mods: Optional[int] = resolved_mods
else:
mods = None
is_text = False
rkey = parts[-1]
tkey = text_match(rkey)
if tkey is None:
rkey = rkey.upper()
q = config_key_map.get(rkey)
if q is None:
raise ValueError('Unknown shortcut key: {}'.format(sc))
rkey = q
else:
is_text = True
rkey = tkey
return mods, rkey, is_text
KittensKeyAction = Tuple[str, Tuple[str, ...]]
@@ -362,15 +320,12 @@ def parse_kittens_func_args(action: str, args_funcs: Dict[str, Callable]) -> Kit
return func, tuple(args)
KittensKey = Tuple[str, Optional[int], bool]
def parse_kittens_key(
val: str, funcs_with_args: Dict[str, Callable]
) -> Optional[Tuple[KittensKeyAction, KittensKey]]:
) -> Optional[Tuple[KittensKeyAction, ParsedShortcut]]:
from ..key_encoding import parse_shortcut
sc, action = val.partition(' ')[::2]
if not sc or not action:
return None
mods, key, is_text = parse_kittens_shortcut(sc)
ans = parse_kittens_func_args(action, funcs_with_args)
return ans, (key, mods, is_text)
return ans, parse_shortcut(sc)

View File

@@ -19,49 +19,23 @@ from .conf.utils import (
BadLine, init_config, key_func, load_config as _load_config, merge_dicts,
parse_config_base, python_string, to_bool, to_cmdline
)
from .config_data import all_options, parse_mods, type_convert
from .config_data import InvalidMods, all_options, parse_shortcut, type_convert
from .constants import cache_dir, defconf, is_macos
from .fonts import FontFeature
from .key_names import get_key_name_lookup, key_name_aliases
from .options_stub import Options as OptionsStub
from .types import SingleKey
from .typing import TypedDict
from .utils import expandvars, log_error
KeySpec = Tuple[int, bool, int]
KeyMap = Dict[KeySpec, 'KeyAction']
KeySequence = Tuple[KeySpec, ...]
KeyMap = Dict[SingleKey, 'KeyAction']
KeySequence = Tuple[SingleKey, ...]
SubSequenceMap = Dict[KeySequence, 'KeyAction']
SequenceMap = Dict[KeySpec, SubSequenceMap]
class InvalidMods(ValueError):
pass
def parse_shortcut(sc: str) -> Tuple[int, bool, Optional[int]]:
parts = sc.split('+')
mods = 0
if len(parts) > 1:
mods = parse_mods(parts[:-1], sc) or 0
if not mods:
raise InvalidMods('Invalid shortcut')
q = parts[-1].upper()
key: Optional[int] = getattr(defines, 'GLFW_KEY_' + key_name_aliases.get(q, q), None)
is_native = False
if key is None:
q = parts[-1]
if q.startswith('0x'):
with suppress(Exception):
key = int(q, 16)
else:
key = get_key_name_lookup()(q, False)
is_native = key is not None
return mods, is_native, key
SequenceMap = Dict[SingleKey, SubSequenceMap]
class KeyAction(NamedTuple):
func: str
args: Sequence[str]
args: Sequence[str] = ()
func_with_args, args_funcs = key_func()
@@ -324,11 +298,12 @@ def parse_marker_spec(ftype: str, parts: Sequence[str]) -> Tuple[str, Union[str,
@func_with_args('toggle_marker')
def toggle_marker(func: str, rest: str) -> FuncArgsType:
import shlex
parts = rest.split(maxsplit=1)
if len(parts) != 2:
raise ValueError('{} is not a valid marker specification'.format(rest))
ftype, spec = parts
parts = spec.split()
parts = shlex.split(spec)
return func, list(parse_marker_spec(ftype, parts))
@@ -371,15 +346,22 @@ sequence_sep = '>'
class KeyDefinition:
def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[KeySpec, ...] = ()):
def __init__(self, is_sequence: bool, action: KeyAction, mods: int, is_native: bool, key: int, rest: Tuple[SingleKey, ...] = ()):
self.is_sequence = is_sequence
self.action = action
self.trigger = mods, is_native, key
self.trigger = SingleKey(mods, is_native, key)
self.rest = rest
def resolve(self, kitty_mod: int) -> None:
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1], self.trigger[2]
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), is_native, key) for mods, is_native, key in self.rest)
def r(k: SingleKey) -> SingleKey:
mods = defines.resolve_key_mods(kitty_mod, k.mods)
key = k.key
is_native = k.is_native
return SingleKey(mods, is_native, key)
self.trigger = r(self.trigger)
self.rest = tuple(map(r, self.rest))
def resolve_kitten_aliases(self, aliases: Dict[str, Sequence[str]]) -> None:
if not self.action.args:
@@ -407,28 +389,28 @@ def parse_key(val: str, key_definitions: List[KeyDefinition]) -> None:
return
is_sequence = sequence_sep in sc
if is_sequence:
trigger: Optional[Tuple[int, bool, int]] = None
restl: List[Tuple[int, bool, int]] = []
trigger: Optional[SingleKey] = None
restl: List[SingleKey] = []
for part in sc.split(sequence_sep):
try:
mods, is_native, key = parse_shortcut(part)
except InvalidMods:
return
if key is None:
if key == 0:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return
if trigger is None:
trigger = mods, is_native, key
trigger = SingleKey(mods, is_native, key)
else:
restl.append((mods, is_native, key))
restl.append(SingleKey(mods, is_native, key))
rest = tuple(restl)
else:
try:
mods, is_native, key = parse_shortcut(sc)
except InvalidMods:
return
if key is None:
if key == 0:
if mods is not None:
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
return

View File

@@ -13,20 +13,30 @@ from typing import (
from . import fast_data_types as defines
from .conf.definition import Option, Shortcut, option_func
from .conf.utils import (
choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color,
to_color_or_none, unit_float
choices, to_bool, to_cmdline as tc, to_color, to_color_or_none, unit_float
)
from .constants import FloatEdges, config_dir, is_macos
from .constants import config_dir, is_macos
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .key_names import (
character_key_name_aliases, functional_key_name_aliases,
get_key_name_lookup
)
from .layout.interface import all_layouts
from .rgb import Color, color_as_int, color_as_sharp, color_from_int
from .utils import log_error
from .types import FloatEdges, SingleKey
from .utils import log_error, positive_float, positive_int
class InvalidMods(ValueError):
pass
MINIMUM_FONT_SIZE = 4
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER',
'': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
character_key_name_aliases_with_ascii_lowercase = character_key_name_aliases.copy()
for x in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
character_key_name_aliases_with_ascii_lowercase[x] = x.lower()
def to_cmdline(x: str) -> List[str]:
@@ -43,7 +53,8 @@ def parse_mods(parts: Iterable[str], sc: str) -> Optional[int]:
try:
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
except AttributeError:
log_error('Shortcut: {} has unknown modifier, ignoring'.format(sc))
if m.upper() != 'NONE':
log_error('Shortcut: {} has unknown modifier, ignoring'.format(sc))
return None
return mods
@@ -53,6 +64,42 @@ def to_modifiers(val: str) -> int:
return parse_mods(val.split('+'), val) or 0
def parse_shortcut(sc: str) -> SingleKey:
if sc.endswith('+') and len(sc) > 1:
sc = sc[:-1] + 'plus'
parts = sc.split('+')
mods = 0
if len(parts) > 1:
mods = parse_mods(parts[:-1], sc) or 0
if not mods:
raise InvalidMods('Invalid shortcut')
q = parts[-1]
q = character_key_name_aliases_with_ascii_lowercase.get(q.upper(), q)
is_native = False
if q.startswith('0x'):
try:
key = int(q, 16)
except Exception:
key = 0
else:
is_native = True
else:
try:
key = ord(q)
except Exception:
uq = q.upper()
uq = functional_key_name_aliases.get(uq, uq)
x: Optional[int] = getattr(defines, f'GLFW_FKEY_{uq}', None)
if x is None:
lf = get_key_name_lookup()
key = lf(q, False) or 0
is_native = key > 0
else:
key = x
return SingleKey(mods, is_native, key or 0)
T = TypeVar('T')
@@ -86,8 +133,8 @@ o, k, g, all_groups = option_func(all_options, {
'colors.table': [
_('The color table'),
_('''\
The 16 terminal colors. There are 8 basic colors, each color has a dull and
bright version. You can also set the remaining colors from the 256 color table
The 256 terminal colors. There are 8 basic colors, each color has a dull and
bright version, for the first 16 colors. You can set the remaining 240 colors
as color16 to color255.''')
],
'advanced': [_('Advanced')],
@@ -95,9 +142,9 @@ as color16 to color255.''')
'shortcuts': [
_('Keyboard shortcuts'),
_('''\
For a list of key names, see: :link:`the GLFW key macros
<https://github.com/kovidgoyal/kitty/blob/master/glfw/glfw3.h#L349>`.
The name to use is the part after the :code:`GLFW_KEY_` prefix.
Keys are identified simply by their lowercase unicode characters. For example:
``a`` for the A key, ``[`` for the left square bracket key, etc. For functional
keys, such as ``Enter or Escape`` the names are present at :ref:`functional`.
For a list of modifier names, see:
:link:`GLFW mods <https://www.glfw.org/docs/latest/group__mods.html>`
@@ -468,6 +515,10 @@ The current implementation stores the data in UTF-8, so approximatively
10000 lines per megabyte at 100 chars per line, for pure ASCII text, unformatted text.
A value of zero or less disables this feature. The maximum allowed size is 4GB.'''))
o('scrollback_fill_enlarged_window', False, long_text=_('''
Fill new space with lines from the scrollback buffer after enlarging a window.
'''))
o('wheel_scroll_multiplier', 5.0, long_text=_('''
Modify the amount scrolled by the mouse wheel. Note this is only used for low
precision scrolling devices, not for high precision scrolling on platforms such
@@ -520,7 +571,7 @@ def url_prefixes(x: str) -> Tuple[str, ...]:
return tuple(a.lower() for a in x.replace(',', ' ').split())
o('url_prefixes', 'http https file ftp', option_type=url_prefixes, long_text=_('''
o('url_prefixes', 'http https file ftp gemini irc gopher mailto news git', option_type=url_prefixes, long_text=_('''
The set of URL prefixes to look for when detecting a URL under the mouse cursor.'''))
o('detect_urls', True, long_text=_('''
@@ -907,6 +958,11 @@ entries to this list.
o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_('''
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
o('tab_powerline_style', 'angled', option_type=choices('angled', 'slanted', 'round'), long_text=_('''
The powerline separator style between tabs in the tab bar when using :code:`powerline`
as the :opt:`tab_bar_style`, can be one of: :code:`angled`, :code:`slanted`, or :code:`round`.
'''))
def tab_activity_symbol(x: str) -> Optional[str]:
if x == 'none':
@@ -1025,14 +1081,7 @@ o('dim_opacity', 0.75, option_type=unit_float, long_text=_('''
How much to dim text that has the DIM/FAINT attribute set. One means no dimming and
zero means fully dimmed (i.e. invisible).'''))
def selection_foreground(x: str) -> Optional[Color]:
if x.lower() != 'none':
return to_color(x)
return None
o('selection_foreground', '#000000', option_type=selection_foreground, long_text=_('''
o('selection_foreground', '#000000', option_type=to_color_or_none, long_text=_('''
The foreground for text selected with the mouse. A value of none means to leave the color unchanged.'''))
o('selection_background', '#fffacd', option_type=to_color, long_text=_('''
The background for text selected with the mouse.'''))
@@ -1448,12 +1497,12 @@ if is_macos:
g('shortcuts.tab') # {{{
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
if is_macos:
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'), add_to_docs=False)
k('next_tab', 'shift+cmd+]', 'next_tab', _('Next tab'), add_to_docs=False)
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'), add_to_docs=False)
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
if is_macos:
k('previous_tab', 'shift+ctrl+tab', 'previous_tab', _('Previous tab'), add_to_docs=False)
k('previous_tab', 'shift+cmd+[', 'previous_tab', _('Previous tab'), add_to_docs=False)
k('previous_tab', 'shift+ctrl+tab', 'previous_tab', _('Previous tab'), add_to_docs=False)
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
if is_macos:
k('new_tab', 'cmd+t', 'new_tab', _('New tab'), add_to_docs=False)
@@ -1474,12 +1523,17 @@ k('next_layout', 'kitty_mod+l', 'next_layout', _('Next layout'))
g('shortcuts.fonts') # {{{
k('increase_font_size', 'kitty_mod+equal', 'change_font_size all +2.0', _('Increase font size'))
k('increase_font_size', 'kitty_mod+plus', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
k('increase_font_size', 'kitty_mod+kp_add', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
if is_macos:
k('increase_font_size', 'cmd+plus', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
k('increase_font_size', 'cmd+equal', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
k('increase_font_size', 'cmd+shift+equal', 'change_font_size all +2.0', _('Increase font size'), add_to_docs=False)
k('decrease_font_size', 'kitty_mod+minus', 'change_font_size all -2.0', _('Decrease font size'))
k('decrease_font_size', 'kitty_mod+kp_subtract', 'change_font_size all -2.0', _('Decrease font size'))
if is_macos:
k('decrease_font_size', 'cmd+minus', 'change_font_size all -2.0', _('Decrease font size'), add_to_docs=False)
k('decrease_font_size', 'cmd+shift+minus', 'change_font_size all -2.0', _('Decrease font size'), add_to_docs=False)
k('reset_font_size', 'kitty_mod+backspace', 'change_font_size all 0', _('Reset font size'))
if is_macos:
k('reset_font_size', 'cmd+0', 'change_font_size all 0', _('Reset font size'), add_to_docs=False)
@@ -1522,7 +1576,11 @@ g('shortcuts.misc') # {{{
k('toggle_fullscreen', 'kitty_mod+f11', 'toggle_fullscreen', _('Toggle fullscreen'))
k('toggle_maximized', 'kitty_mod+f10', 'toggle_maximized', _('Toggle maximized'))
k('input_unicode_character', 'kitty_mod+u', 'kitten unicode_input', _('Unicode input'))
if is_macos:
k('input_unicode_character', 'cmd+ctrl+space', 'kitten unicode_input', _('Unicode input'), add_to_docs=False)
k('edit_config_file', 'kitty_mod+f2', 'edit_config_file', _('Edit config file'))
if is_macos:
k('edit_config_file', 'cmd+,', 'edit_config_file', _('Edit config file'), add_to_docs=False)
k('kitty_shell', 'kitty_mod+escape', 'kitty_shell window', _('Open the kitty command shell'), long_text=_('''
Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.'''))
k('increase_background_opacity', 'kitty_mod+a>m', 'set_background_opacity +0.1', _('Increase background opacity'))

View File

@@ -7,10 +7,10 @@ import os
import pwd
import sys
from contextlib import suppress
from functools import lru_cache
from typing import NamedTuple, Optional, Set
from .options_stub import Options
from .types import run_once
class Version(NamedTuple):
@@ -20,51 +20,27 @@ class Version(NamedTuple):
appname: str = 'kitty'
version: Version = Version(0, 19, 3)
kitty_face = '🐱'
version: Version = Version(0, 20, 0)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat
base = os.path.dirname(os.path.abspath(__file__))
if getattr(sys, 'frozen', False):
extensions_dir: str = getattr(sys, 'kitty_extensions_dir')
kitty_base_dir = os.path.dirname(extensions_dir)
if is_macos:
kitty_base_dir = os.path.dirname(os.path.dirname(kitty_base_dir))
kitty_base_dir = os.path.join(kitty_base_dir, 'kitty')
else:
kitty_base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
extensions_dir = os.path.join(kitty_base_dir, 'kitty')
class Edges(NamedTuple):
left: int = 0
top: int = 0
right: int = 0
bottom: int = 0
class FloatEdges(NamedTuple):
left: float = 0
top: float = 0
right: float = 0
bottom: float = 0
class ScreenGeometry(NamedTuple):
xstart: float
ystart: float
xnum: int
ynum: int
dx: float
dy: float
class WindowGeometry(NamedTuple):
left: int
top: int
right: int
bottom: int
xnum: int
ynum: int
spaces: Edges = Edges()
@lru_cache(maxsize=2)
@run_once
def kitty_exe() -> str:
rpath = sys._xoptions.get('bundle_exe_dir')
if not rpath:
items = os.environ.get('PATH', '').split(os.pathsep) + [os.path.join(base, 'launcher')]
items = os.environ.get('PATH', '').split(os.pathsep) + [os.path.join(kitty_base_dir, 'kitty', 'launcher')]
seen: Set[str] = set()
for candidate in filter(None, items):
if candidate not in seen:
@@ -126,7 +102,7 @@ del _get_config_dir
defconf = os.path.join(config_dir, 'kitty.conf')
@lru_cache(maxsize=2)
@run_once
def cache_dir() -> str:
if 'KITTY_CACHE_DIRECTORY' in os.environ:
candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY'])
@@ -146,11 +122,9 @@ def wakeup() -> None:
b.child_monitor.wakeup()
base_dir = os.path.dirname(base)
terminfo_dir = os.path.join(base_dir, 'terminfo')
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
logo_png_file = os.path.join(base_dir, 'logo', 'kitty.png')
beam_cursor_data_file = os.path.join(base_dir, 'logo', 'beam-cursor.png')
terminfo_dir = os.path.join(kitty_base_dir, 'terminfo')
logo_png_file = os.path.join(kitty_base_dir, 'logo', 'kitty.png')
beam_cursor_data_file = os.path.join(kitty_base_dir, 'logo', 'beam-cursor.png')
try:
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
except KeyError:
@@ -160,7 +134,8 @@ except KeyError:
def glfw_path(module: str) -> str:
return os.path.join(base, 'glfw-{}.so'.format(module))
prefix = 'kitty.' if getattr(sys, 'frozen', False) else ''
return os.path.join(extensions_dir, f'{prefix}glfw-{module}.so')
def detect_if_wayland_ok() -> bool:
@@ -171,26 +146,7 @@ def detect_if_wayland_ok() -> bool:
wayland = glfw_path('wayland')
if not os.path.exists(wayland):
return False
# GNOME does not support xdg-decorations
# https://gitlab.gnome.org/GNOME/mutter/issues/217
import ctypes
lib = ctypes.CDLL(wayland)
check = lib.glfwWaylandCheckForServerSideDecorations
check.restype = ctypes.c_char_p
check.argtypes = ()
try:
ans = bytes(check())
except Exception:
return False
if ans == b'NO':
print(
'Your Wayland compositor does not support server side window decorations,'
' disabling Wayland. You can force Wayland support using the'
' linux_display_server option in kitty.conf'
' See https://drewdevault.com/2018/01/27/Sway-and-client-side-decorations.html'
' for more information.',
file=sys.stderr)
return ans == b'YES'
return True
def is_wayland(opts: Optional[Options] = None) -> bool:
@@ -220,3 +176,11 @@ def resolve_custom_file(path: str) -> str:
if not os.path.isabs(path):
path = os.path.join(config_dir, path)
return path
def read_kitty_resource(name: str) -> bytes:
try:
from importlib.resources import read_binary
except ImportError:
from importlib_resources import read_binary # type: ignore
return read_binary('kitty', name)

View File

@@ -6,6 +6,7 @@
*/
#include "state.h"
#include "cleanup.h"
#include "fonts.h"
#include "unicode-data.h"
#include <structmember.h>
@@ -390,6 +391,7 @@ static size_t render_buf_sz = 0;
static CGGlyph glyphs[128];
static CGRect boxes[128];
static CGPoint positions[128];
static CGSize advances[128];
static void
finalize(void) {
@@ -459,15 +461,15 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
CTFontRef font = self->ct_font;
size_t num_chars = strnlen(text, 32);
unichar chars[num_chars];
CGSize advances[num_chars];
CGSize local_advances[num_chars];
for (size_t i = 0; i < num_chars; i++) chars[i] = text[i];
CTFontGetGlyphsForCharacters(font, chars, glyphs, num_chars);
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, advances, num_chars);
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, local_advances, num_chars);
CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, glyphs, boxes, num_chars);
CGFloat x = 0, y = 0;
for (size_t i = 0; i < num_chars; i++) {
positions[i] = CGPointMake(x, y);
x += advances[i].width; y += advances[i].height;
x += local_advances[i].width; y += local_advances[i].height;
}
StringCanvas ans = { .width = (size_t)ceil(x), .height = (size_t)(2 * bounding_box.size.height) };
ensure_render_space(ans.width, ans.height);
@@ -482,11 +484,13 @@ static inline bool
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
unsigned int canvas_width = cell_width * num_cells;
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs);
const bool debug_rendering = false;
if (allow_resize) {
// Resize glyphs that would bleed into neighboring cells, by scaling the font size
float right = 0;
for (unsigned i=0; i < num_glyphs; i++) right = MAX(right, boxes[i].origin.x + boxes[i].size.width);
if (!bold && !italic && right > canvas_width + 1) {
if (debug_rendering) printf("resizing glyphs, right: %f canvas_width: %u\n", right, canvas_width);
CGFloat sz = CTFontGetSize(ct_font);
sz *= canvas_width / right;
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
@@ -495,9 +499,12 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
return ret;
}
}
CGFloat x = 0, y = 0;
CTFontGetAdvancesForGlyphs(ct_font, kCTFontOrientationDefault, glyphs, advances, num_glyphs);
for (unsigned i=0; i < num_glyphs; i++) {
positions[i].x = MAX(0, -boxes[i].origin.x) + hb_positions[i].x_offset / 64.f;
positions[i].y = hb_positions[i].y_offset / 64.f;
positions[i].x = x; positions[i].y = y;
if (debug_rendering) printf("x=%f origin=%f width=%f advance=%f\n", x, boxes[i].origin.x, boxes[i].size.width, advances[i].width);
x += advances[i].width; y += advances[i].height;
}
if (*was_colored) {
render_color_glyph(ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline);
@@ -508,6 +515,7 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
render_alpha_mask(render_buf, canvas, &src, &dest, canvas_width, canvas_width);
}
if (num_cells && (center_glyph || (num_cells == 2 && *was_colored))) {
if (debug_rendering) printf("centering glyphs: center_glyph: %d\n", center_glyph);
// center glyphs (two cell emoji, PUA glyphs, ligatures, etc)
CGFloat delta = (((CGFloat)canvas_width - br.size.width) / 2.f);
// FiraCode ligatures result in negative origins
@@ -601,10 +609,7 @@ init_CoreText(PyObject *module) {
if (PyType_Ready(&CTFace_Type) < 0) return 0;
if (PyModule_AddObject(module, "CTFace", (PyObject *)&CTFace_Type) != 0) return 0;
if (PyModule_AddFunctions(module, module_methods) != 0) return 0;
if (Py_AtExit(finalize) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the CoreText at exit handler");
return false;
}
register_at_exit_cleanup_func(CORE_TEXT_CLEANUP_FUNC, finalize);
return 1;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2020 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <stdlib.h>
#include <stdbool.h>
#if __linux__
#include <errno.h>
#if __has_include(<sys/random.h>)
#include <sys/random.h>
static inline bool
secure_random_bytes(void *buf, size_t nbytes) {
unsigned char* p = buf;
ssize_t left = nbytes;
while(1) {
ssize_t n = getrandom(p, left, 0);
if (n >= left) return true;
if (n < 0) {
if (errno != EINTR) return false; // should never happen but if it does, we fail without any feedback
continue;
}
left -= n; p += n;
}
}
#else
#include "safe-wrappers.h"
static inline bool
secure_random_bytes(void *buf, size_t nbytes) {
int fd = safe_open("/dev/urandom", O_RDONLY, 0);
if (fd < 0) return false;
size_t bytes_read = 0;
while (bytes_read < nbytes) {
ssize_t n = read(fd, (uint8_t*)buf + bytes_read, nbytes - bytes_read);
if (n < 0) {
if (errno == EINTR) continue;
break;
}
bytes_read += n;
}
safe_close(fd, __FILE__, __LINE__);
return bytes_read == nbytes;
}
#endif
#else
static inline bool
secure_random_bytes(void *buf, size_t nbytes) {
arc4random_buf(buf, nbytes);
return true;
}
#endif

View File

@@ -47,7 +47,7 @@ cursor_reset_display_attrs(Cursor *self) {
static inline void
parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) {
parse_color(int *params, unsigned int *i, unsigned int count, uint32_t *result) {
unsigned int attr;
uint8_t r, g, b;
if (*i < count) {
@@ -73,7 +73,7 @@ parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t
void
cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count) {
cursor_from_sgr(Cursor *self, int *params, unsigned int count) {
#define SET_COLOR(which) { parse_color(params, &i, count, &self->which); } break;
START_ALLOW_CASE_RANGE
unsigned int i = 0, attr;
@@ -90,7 +90,7 @@ START_ALLOW_CASE_RANGE
case 3:
self->italic = true; break;
case 4:
if (i < count) { self->decoration = MIN(3u, params[i]); i++; }
if (i < count) { self->decoration = MIN(3, params[i]); i++; }
else self->decoration = 1;
break;
case 7:
@@ -136,7 +136,7 @@ END_ALLOW_CASE_RANGE
}
void
apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count) {
apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count) {
#define RANGE for(unsigned c = 0; c < cell_count; c++, cell++)
#define SET(shift) RANGE { cell->attrs |= (1 << shift); } break;
#define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break;
@@ -161,7 +161,7 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *p
case 3:
SET(ITALIC_SHIFT);
case 4:
if (i < count) { uint8_t val = MIN(3u, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); }
if (i < count) { uint8_t val = MIN(3, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); }
else { SETM(1, DECORATION_MASK, DECORATION_SHIFT); }
case 7:
SET(REVERSE_SHIFT);

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