Compare commits

...

116 Commits

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

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

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

Also follow the full XDG spec for finding config files, including
XDG_CONFIG_DIRS.
2018-03-25 11:47:11 +05:30
Kovid Goyal
be8f2b8106 Create a shortcut to set the tab title
No longer need to use remote control just to change tab titles.
2018-03-25 09:55:02 +05:30
Kovid Goyal
71db024a3b Add history based completion to the ask kitten 2018-03-24 11:49:34 +05:30
Kovid Goyal
3790af6897 Basic ask kitten 2018-03-23 18:03:43 +05:30
Kovid Goyal
dc6ef63ddd Merge branch 'oS' of https://github.com/jubalh/kitty 2018-03-23 15:33:40 +05:30
Michael Vetter
ef762f7a2b Add link to openSUSE package 2018-03-23 10:45:42 +01:00
Kovid Goyal
4ed646e062 Merge branch 'tic2' of https://github.com/jubalh/kitty 2018-03-21 21:17:46 +05:30
Michael Vetter
afbd843388 Mention tic as dependency 2018-03-21 16:42:44 +01:00
Kovid Goyal
18c3598ef2 Merge branch 'desktop' of https://github.com/jubalh/kitty 2018-03-21 20:43:50 +05:30
Michael Vetter
1c2623e609 Have more detailed desktop file category
Let's use the 'TerminalEmulator' category according to the spec:
https://specifications.freedesktop.org/menu-spec/menu-spec-1.0.html
2018-03-21 11:28:57 +01:00
Kovid Goyal
e232863028 Also recognize unicode aliases for macOS modifiers 2018-03-20 15:10:29 +05:30
Kovid Goyal
a89309153d Merge branch 'patch-1' of https://github.com/sle-odoo/kitty 2018-03-18 17:59:27 +05:30
Simon Lejeune
fee7c8e573 update dependencies to run make app
while imagemagick is defined as an optional dependency in the installation section, its presence is indeed mandatory to successfully run `make app` as seen at https://github.com/kovidgoyal/kitty/blob/master/logo/make.py#L27
2018-03-18 10:56:40 +01:00
70 changed files with 3143 additions and 3349 deletions

View File

@@ -20,8 +20,6 @@ matrix:
- libxcursor-dev - libxcursor-dev
- libunistring-dev - libunistring-dev
- libxcb-xkb-dev - libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev - libpng-dev
- python3-pil - python3-pil
@@ -44,8 +42,6 @@ matrix:
- libxcursor-dev - libxcursor-dev
- libunistring-dev - libunistring-dev
- libxcb-xkb-dev - libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev - libpng-dev
- python3-pil - python3-pil
@@ -68,8 +64,6 @@ matrix:
- libxcursor-dev - libxcursor-dev
- libunistring-dev - libunistring-dev
- libxcb-xkb-dev - libxcb-xkb-dev
- libxkbcommon-dev
- libxkbcommon-x11-dev
- libpng-dev - libpng-dev
- python3-pil - python3-pil
- clang - clang
@@ -102,6 +96,12 @@ install: |
fi fi
else else
pushd /tmp pushd /tmp
wget http://xkbcommon.org/download/libxkbcommon-0.6.1.tar.xz
tar xf libxkbcommon-0.6.1.tar.xz
cd libxkbcommon-0.6.1
./configure --prefix=$HOME/harfbuzz --disable-dependency-tracking --disable-static
make -j2
make install
wget https://github.com/behdad/harfbuzz/releases/download/1.6.3/harfbuzz-1.6.3.tar.bz2 wget https://github.com/behdad/harfbuzz/releases/download/1.6.3/harfbuzz-1.6.3.tar.bz2
tar xf harfbuzz-1.6.3.tar.bz2 tar xf harfbuzz-1.6.3.tar.bz2
cd harfbuzz-1.6.3 cd harfbuzz-1.6.3

View File

@@ -3,6 +3,85 @@ Changelog
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator. kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
version 0.9.0 [2018-04-15]
------------------------------
- A new kitty command shell to allow controlling kitty via commands. Press
`ctrl+shift+escape` to run the shell.
- The hints kitten has become much more powerful. Now in addition to URLs you
can use it to select word, paths, filenames, lines, etc. from the screen.
These can be inserted into the terminal, copied to clipboard or sent to
external programs.
- Linux: Switch to libxkbcommon for keyboard handling. It allows kitty to
support XCompose and dead keys and also react to keyboard remapping/layout
change without needing a restart.
- Add support for multiple-key-sequence shortcuts
- A new remote control command `set-colors` to change the current and/or
configured colors.
- When double-clicking to select a word, select words that continue onto the
next/prev line as well.
- Add an `include` directive for the config files to read multiple config files
- Improve mouse selection for windows with padding. Moving the mouse into the
padding area now acts as if the mouse is over the nearest cell.
- Allow setting all 256 terminal colors in the config file
- Fix using `kitty --single-instance` to open a new window in a running kitty
instance, not respecting the `--directory` flag
- URL hints: Exclude trailing punctuation from URLs
- URL hints: Launch the browser from the kitty parent process rather than the
hints kitten. Fixes launching on some systems where xdg-open doesn't like
being run from a kitten.
- Allow using rectangle select mode by pressing shift in addition to the
rectangle select modifiers even when the terminal program has grabbed the
mouse.
version 0.8.4 [2018-03-31]
-----------------------------
- Fix presence of XDG_CONFIG_DIRS and absence of XDG_CONFIG_HOME preventing
kitty from starting
- Revert change in last release to cell width calculation. Instead just clip
the right edges of characters that overflow the cell by at most two pixels
version 0.8.3 [2018-03-29]
-----------------------------
- Fix a regression that broke the visual bell and invert screen colors escape
code
- Allow double-click and triple-click + drag to extend selections word at a
time or line at a time
- Add a keyboard shortcut to set the tab title
- Fix setting window title to empty via OSC escape code not working correctly
- Linux: Fix cell width calculation incorrect for some fonts (cell widths are
now calculated by actually rendering bitmaps, which is slower but more
accurate)
- Allow specifying a system wide kitty config file, for all users
- Add a --debug-config command line flag to output data about the system and
kitty configuration.
- Wayland: Fix auto-repeat of keys not working
version 0.8.2 [2018-03-17] version 0.8.2 [2018-03-17]
----------------------------- -----------------------------

View File

@@ -13,6 +13,7 @@
:sc_fourth_window: pass:quotes[`ctrl+shift+4`] :sc_fourth_window: pass:quotes[`ctrl+shift+4`]
:sc_increase_font_size: pass:quotes[`ctrl+shift+equal`] :sc_increase_font_size: pass:quotes[`ctrl+shift+equal`]
:sc_input_unicode_character: pass:quotes[`ctrl+shift+u`] :sc_input_unicode_character: pass:quotes[`ctrl+shift+u`]
:sc_kitty_shell_window: pass:quotes[`ctrl+shift+escape`]
:sc_move_tab_backward: pass:quotes[`ctrl+shift+,`] :sc_move_tab_backward: pass:quotes[`ctrl+shift+,`]
:sc_move_tab_forward: pass:quotes[`ctrl+shift+.`] :sc_move_tab_forward: pass:quotes[`ctrl+shift+.`]
:sc_move_window_backward: pass:quotes[`ctrl+shift+b`] :sc_move_window_backward: pass:quotes[`ctrl+shift+b`]
@@ -31,7 +32,11 @@
:sc_previous_tab: pass:quotes[`ctrl+shift+left`] :sc_previous_tab: pass:quotes[`ctrl+shift+left`]
:sc_previous_window: pass:quotes[`ctrl+shift+[`] :sc_previous_window: pass:quotes[`ctrl+shift+[`]
:sc_restore_font_size: pass:quotes[`ctrl+shift+backspace`] :sc_restore_font_size: pass:quotes[`ctrl+shift+backspace`]
:sc_run_simple_kitten_text_url_hints: pass:quotes[`ctrl+shift+e`] :sc_run_kitten_text_hints: pass:quotes[`ctrl+shift+e`]
:sc_run_kitten_text_hints_type_line_program: pass:quotes[`ctrl+shift+p → l`]
:sc_run_kitten_text_hints_type_path: pass:quotes[`ctrl+shift+p → shift+f`]
:sc_run_kitten_text_hints_type_path_program: pass:quotes[`ctrl+shift+p → f`]
:sc_run_kitten_text_hints_type_word_program: pass:quotes[`ctrl+shift+p → w`]
:sc_scroll_end: pass:quotes[`ctrl+shift+end`] :sc_scroll_end: pass:quotes[`ctrl+shift+end`]
:sc_scroll_home: pass:quotes[`ctrl+shift+home`] :sc_scroll_home: pass:quotes[`ctrl+shift+home`]
:sc_scroll_line_down: pass:quotes[`ctrl+shift+down` or `ctrl+shift+j`] :sc_scroll_line_down: pass:quotes[`ctrl+shift+down` or `ctrl+shift+j`]
@@ -39,6 +44,7 @@
:sc_scroll_page_down: pass:quotes[`ctrl+shift+page_down`] :sc_scroll_page_down: pass:quotes[`ctrl+shift+page_down`]
:sc_scroll_page_up: pass:quotes[`ctrl+shift+page_up`] :sc_scroll_page_up: pass:quotes[`ctrl+shift+page_up`]
:sc_second_window: pass:quotes[`ctrl+shift+2`] :sc_second_window: pass:quotes[`ctrl+shift+2`]
:sc_set_tab_title: pass:quotes[`ctrl+shift+alt+t`]
:sc_seventh_window: pass:quotes[`ctrl+shift+7`] :sc_seventh_window: pass:quotes[`ctrl+shift+7`]
:sc_show_scrollback: pass:quotes[`ctrl+shift+h`] :sc_show_scrollback: pass:quotes[`ctrl+shift+h`]
:sc_sixth_window: pass:quotes[`ctrl+shift+6`] :sc_sixth_window: pass:quotes[`ctrl+shift+6`]
@@ -67,7 +73,7 @@ link:#layouts[layouts] without needing to use an extra program like tmux
* Can be link:remote-control.asciidoc[controlled from scripts or the shell prompt], even over SSH. * Can be link:remote-control.asciidoc[controlled from scripts or the shell prompt], even over SSH.
* Has a framework for _kittens_, small terminal programs that can be used to extend kitty's functionality. * Has a framework for _kittens_, small terminal programs that can be used to extend kitty's functionality.
For example, they are used for link:#unicode-input[Unicode input] and link:#url-hints[URL hints]. For example, they are used for link:#unicode-input[Unicode input] and link:#url-hints[Hints].
* Supports link:#startup-sessions[startup sessions] which allow you to specify the window/tab layout, * Supports link:#startup-sessions[startup sessions] which allow you to specify the window/tab layout,
working directories and programs to run on startup. working directories and programs to run on startup.
@@ -132,6 +138,8 @@ python3 .
* Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty * Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty
* openSUSE: https://build.opensuse.org/package/show/X11:terminals/kitty
=== macOS packages === macOS packages
kitty is available as a macOS `dmg` file for easy installation from the kitty is available as a macOS `dmg` file for easy installation from the
@@ -200,6 +208,7 @@ windows are:
|Next layout | {sc_next_layout} |Next layout | {sc_next_layout}
|Move tab forward | {sc_move_tab_forward} |Move tab forward | {sc_move_tab_forward}
|Move tab backward | {sc_move_tab_backward} |Move tab backward | {sc_move_tab_backward}
|Set tab title | {sc_set_tab_title}
|=== |===
@@ -235,7 +244,8 @@ windows are:
|Input unicode character | {sc_input_unicode_character} |Input unicode character | {sc_input_unicode_character}
|Click URL using the keyboard | {sc_run_simple_kitten_text_url_hints} |Click URL using the keyboard | {sc_run_simple_kitten_text_url_hints}
|Pass current selection to program | {sc_pass_selection_to_program} |Pass current selection to program | {sc_pass_selection_to_program}
|Edit kitty config file| {sc_edit_config_file} |Edit kitty config file | {sc_edit_config_file}
|Open a kitty shell | {sc_kitty_shell}
|=== |===
@@ -280,13 +290,23 @@ keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you dont like to use arrow a leading period and the index for the match if you dont like to use arrow
keys. keys.
== URL hints == Hints
kitty has a _hints mode_ to click URLs visible on the screen by using only the keyboard. kitty has a _hints mode_ to select and act on arbitrary text snippets currently
Press {sc_run_simple_kitten_text_url_hints} to activate hints mode, as shown below. visible on the screen. For example, you can press {sc_run_kitten_text_hints}
to choose any URL visible on the screen and then open it using your system
browser.
image::hints_mode.png?raw=true[URL hints mode] image::hints_mode.png?raw=true[URL hints mode]
Similarly, you can press {sc_run_kitten_text_hints_type_path_program} to
select anything that looks like a path or filename and then insert it into the
terminal, very useful for picking files from the output of a git or ls command and
adding them to the command line for the next command.
The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, use: `kitty +kitten hints --help`.
== Miscellaneous features == Miscellaneous features
* You can also hold down `ctrl+shift` and click on a URL to open it in a browser. * You can also hold down `ctrl+shift` and click on a URL to open it in a browser.
@@ -317,11 +337,16 @@ a pull request!
kitty is highly customizable, everything from keyboard shortcuts, to painting kitty is highly customizable, everything from keyboard shortcuts, to painting
frames-per-second. See the heavily commented link:kitty/kitty.conf[default frames-per-second. See the heavily commented link:kitty/kitty.conf[default
config file]. By default kitty looks for a config file in the OS config config file] for an overview of all customization possibilities.
directory (usually `~/.config/kitty/kitty.conf` on Linux and
By default kitty looks for a config file in the OS config directories (usually
`~/.config/kitty/kitty.conf` and additionally
`~/Library/Preferences/kitty/kitty.conf` on macOS) but you can pass a specific `~/Library/Preferences/kitty/kitty.conf` on macOS) but you can pass a specific
path via the `--config` option or use the `KITTY_CONFIG_DIRECTORY` environment path via the `--config` option or use the `KITTY_CONFIG_DIRECTORY` environment
variable. variable. See the help for the `--config` option in `kitty --help` for full
details. You can also dump the current configuration using the `--debug-config`
option.
== Startup Sessions == Startup Sessions
@@ -438,6 +463,8 @@ installs the terminfo file and `kitty` that installs the main program.
This allows users to install the terminfo file on servers into which they ssh, This allows users to install the terminfo file on servers into which they ssh,
without needing to install all of kitty. without needing to install all of kitty.
You also need `tic` to compile the terminfo files, it is usually found in the development package of `ncurses`
This applies to creating packages for kitty for macOS package managers such as This applies to creating packages for kitty for macOS package managers such as
brew or MacPorts as well. brew or MacPorts as well.
@@ -464,7 +491,7 @@ makes use of the extra cell to render them in two cells.
=== How do I build kitty.app on macOS? === How do I build kitty.app on macOS?
Install `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step). Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
And run: And run:
``` ```
@@ -502,9 +529,7 @@ Either install kitty on the server, or simply copy over the terminfo files,
using: using:
.... ....
ssh myserver mkdir -p \~/.terminfo infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
infocmp xterm-kitty | ssh myserver "cat > /tmp/kitty.terminfo"
ssh myserver tic -x -o \~/.terminfo /tmp/kitty.terminfo
.... ....
Really, the correct solution for this is to convince the OpenSSH maintainers to Really, the correct solution for this is to convince the OpenSSH maintainers to
@@ -512,11 +537,19 @@ have ssh do this automatically when connecting to a server, so that all
terminals work transparently. terminals work transparently.
=== How do I change the colors in a running kitty instance?
You can either use the
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands[OSC
terminal escape codes] to set colors or you can enable link:remote-control.asciidoc[remote control]
for kitty and use `kitty @ set-colors --help`.
=== How do I specify command line options for kitty on macOS? === How do I specify command line options for kitty on macOS?
Apple does not want you to use command line options with GUI applications. To Apple does not want you to use command line options with GUI applications. To
workaround that limitation, kitty will read command line options from the file workaround that limitation, kitty will read command line options from the file
`~/Library/Preferences/kitty/macos-launch-services-cmdline` when it is launched `<kitty config dir>/macos-launch-services-cmdline` when it is launched
from the GUI, i.e. by clicking the kitty application icon or using `open -a kitty`. from the GUI, i.e. by clicking the kitty application icon or using `open -a kitty`.
Note that this file is *only read* when running via the GUI. Note that this file is *only read* when running via the GUI.

View File

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

View File

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

View File

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

View File

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

View File

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

189
glfw/glfw3.h vendored
View File

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

189
glfw/input.c vendored
View File

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

15
glfw/internal.h vendored
View File

@@ -128,7 +128,7 @@ typedef enum VkStructureType
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000, VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000, VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000, VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000053000, VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkStructureType; } VkStructureType;
@@ -407,9 +407,7 @@ struct _GLFWwindow
GLFWcursorposfun cursorPos; GLFWcursorposfun cursorPos;
GLFWcursorenterfun cursorEnter; GLFWcursorenterfun cursorEnter;
GLFWscrollfun scroll; GLFWscrollfun scroll;
GLFWkeyfun key; GLFWkeyboardfun keyboard;
GLFWcharfun character;
GLFWcharmodsfun charmods;
GLFWdropfun drop; GLFWdropfun drop;
} callbacks; } callbacks;
@@ -653,7 +651,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
void _glfwPlatformShowWindow(_GLFWwindow* window); void _glfwPlatformShowWindow(_GLFWwindow* window);
void _glfwPlatformHideWindow(_GLFWwindow* window); void _glfwPlatformHideWindow(_GLFWwindow* window);
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window); void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t); int _glfwPlatformWindowBell(_GLFWwindow* window);
void _glfwPlatformFocusWindow(_GLFWwindow* window); void _glfwPlatformFocusWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor,
int xpos, int ypos, int width, int height, int xpos, int ypos, int width, int height,
@@ -711,10 +709,7 @@ void _glfwInputWindowDamage(_GLFWwindow* window);
void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKey(_GLFWwindow* window, void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
int key, int scancode, int action, int mods);
void _glfwInputChar(_GLFWwindow* window,
unsigned int codepoint, int mods, GLFWbool plain);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
@@ -763,10 +758,10 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name,
int buttonCount, int buttonCount,
int hatCount); int hatCount);
void _glfwFreeJoystick(_GLFWjoystick* js); void _glfwFreeJoystick(_GLFWjoystick* js);
const char* _glfwGetKeyName(int key);
GLFWbool _glfwInitVulkan(int mode); GLFWbool _glfwInitVulkan(int mode);
void _glfwTerminateVulkan(void); void _glfwTerminateVulkan(void);
const char* _glfwGetVulkanResultString(VkResult result); const char* _glfwGetVulkanResultString(VkResult result);
char* _glfw_strdup(const char* source); char* _glfw_strdup(const char* source);

View File

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

3
glfw/null_window.c vendored
View File

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

View File

@@ -55,12 +55,12 @@
"wayland": { "wayland": {
"headers": [ "headers": [
"wl_platform.h", "wl_platform.h",
"linux_joystick.h",
"posix_time.h", "posix_time.h",
"posix_thread.h", "posix_thread.h",
"xkb_unicode.h", "xkb_glfw.h",
"egl_context.h", "egl_context.h",
"osmesa_context.h" "osmesa_context.h",
"linux_joystick.h"
], ],
"protocols": [ "protocols": [
"stable/xdg-shell/xdg-shell.xml", "stable/xdg-shell/xdg-shell.xml",
@@ -73,12 +73,12 @@
"wl_init.c", "wl_init.c",
"wl_monitor.c", "wl_monitor.c",
"wl_window.c", "wl_window.c",
"linux_joystick.c",
"posix_time.c", "posix_time.c",
"posix_thread.c", "posix_thread.c",
"xkb_unicode.c", "xkb_glfw.c",
"egl_context.c", "egl_context.c",
"osmesa_context.c" "osmesa_context.c",
"linux_joystick.c"
] ]
}, },
"wayland_protocols": [ "wayland_protocols": [
@@ -108,7 +108,7 @@
"x11": { "x11": {
"headers": [ "headers": [
"x11_platform.h", "x11_platform.h",
"xkb_unicode.h", "xkb_glfw.h",
"posix_time.h", "posix_time.h",
"posix_thread.h", "posix_thread.h",
"glx_context.h", "glx_context.h",
@@ -120,7 +120,7 @@
"x11_init.c", "x11_init.c",
"x11_monitor.c", "x11_monitor.c",
"x11_window.c", "x11_window.c",
"xkb_unicode.c", "xkb_glfw.c",
"posix_time.c", "posix_time.c",
"posix_thread.c", "posix_thread.c",
"glx_context.c", "glx_context.c",

5
glfw/win32_window.c vendored
View File

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

7
glfw/window.c vendored
View File

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

425
glfw/wl_init.c vendored
View File

@@ -32,7 +32,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/timerfd.h>
#include <unistd.h> #include <unistd.h>
#include <wayland-client.h> #include <wayland-client.h>
@@ -350,16 +349,7 @@ static void keyboardHandleKeymap(void* data,
int fd, int fd,
uint32_t size) uint32_t size)
{ {
struct xkb_keymap* keymap;
struct xkb_state* state;
#ifdef HAVE_XKBCOMMON_COMPOSE_H
struct xkb_compose_table* composeTable;
struct xkb_compose_state* composeState;
#endif
char* mapStr; char* mapStr;
const char* locale;
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
{ {
@@ -372,78 +362,10 @@ static void keyboardHandleKeymap(void* data,
close(fd); close(fd);
return; return;
} }
glfw_xkb_compile_keymap(&_glfw.wl.xkb, mapStr);
keymap = xkb_keymap_new_from_string(_glfw.wl.xkb.context,
mapStr,
XKB_KEYMAP_FORMAT_TEXT_V1,
0);
munmap(mapStr, size); munmap(mapStr, size);
close(fd); close(fd);
if (!keymap)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to compile keymap");
return;
}
state = xkb_state_new(keymap);
if (!state)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create XKB state");
xkb_keymap_unref(keymap);
return;
}
// Look up the preferred locale, falling back to "C" as default.
locale = getenv("LC_ALL");
if (!locale)
locale = getenv("LC_CTYPE");
if (!locale)
locale = getenv("LANG");
if (!locale)
locale = "C";
#ifdef HAVE_XKBCOMMON_COMPOSE_H
composeTable =
xkb_compose_table_new_from_locale(_glfw.wl.xkb.context, locale,
XKB_COMPOSE_COMPILE_NO_FLAGS);
if (composeTable)
{
composeState =
xkb_compose_state_new(composeTable, XKB_COMPOSE_STATE_NO_FLAGS);
xkb_compose_table_unref(composeTable);
if (composeState)
_glfw.wl.xkb.composeState = composeState;
else
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create XKB compose state");
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to create XKB compose table");
}
#endif
xkb_keymap_unref(_glfw.wl.xkb.keymap);
xkb_state_unref(_glfw.wl.xkb.state);
_glfw.wl.xkb.keymap = keymap;
_glfw.wl.xkb.state = state;
_glfw.wl.xkb.controlMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Control");
_glfw.wl.xkb.altMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod1");
_glfw.wl.xkb.shiftMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift");
_glfw.wl.xkb.superMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4");
_glfw.wl.xkb.capsLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock");
_glfw.wl.xkb.numLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2");
} }
static void keyboardHandleEnter(void* data, static void keyboardHandleEnter(void* data,
@@ -482,65 +404,6 @@ static void keyboardHandleLeave(void* data,
_glfwInputWindowFocus(window, GLFW_FALSE); _glfwInputWindowFocus(window, GLFW_FALSE);
} }
static int toGLFWKeyCode(uint32_t key)
{
if (key < sizeof(_glfw.wl.keycodes) / sizeof(_glfw.wl.keycodes[0]))
return _glfw.wl.keycodes[key];
return GLFW_KEY_UNKNOWN;
}
#ifdef HAVE_XKBCOMMON_COMPOSE_H
static xkb_keysym_t composeSymbol(xkb_keysym_t sym)
{
if (sym == XKB_KEY_NoSymbol || !_glfw.wl.xkb.composeState)
return sym;
if (xkb_compose_state_feed(_glfw.wl.xkb.composeState, sym)
!= XKB_COMPOSE_FEED_ACCEPTED)
return sym;
switch (xkb_compose_state_get_status(_glfw.wl.xkb.composeState))
{
case XKB_COMPOSE_COMPOSED:
return xkb_compose_state_get_one_sym(_glfw.wl.xkb.composeState);
case XKB_COMPOSE_COMPOSING:
case XKB_COMPOSE_CANCELLED:
return XKB_KEY_NoSymbol;
case XKB_COMPOSE_NOTHING:
default:
return sym;
}
}
#endif
static GLFWbool inputChar(_GLFWwindow* window, uint32_t key)
{
uint32_t code, numSyms;
long cp;
const xkb_keysym_t *syms;
xkb_keysym_t sym;
code = key + 8;
numSyms = xkb_state_key_get_syms(_glfw.wl.xkb.state, code, &syms);
if (numSyms == 1)
{
#ifdef HAVE_XKBCOMMON_COMPOSE_H
sym = composeSymbol(syms[0]);
#else
sym = syms[0];
#endif
cp = _glfwKeySym2Unicode(sym);
if (cp != -1)
{
const int mods = _glfw.wl.xkb.modifiers;
const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT));
_glfwInputChar(window, cp, mods, plain);
}
}
return xkb_keymap_key_repeats(_glfw.wl.xkb.keymap, syms[0]);
}
static void keyboardHandleKey(void* data, static void keyboardHandleKey(void* data,
struct wl_keyboard* keyboard, struct wl_keyboard* keyboard,
uint32_t serial, uint32_t serial,
@@ -548,37 +411,19 @@ static void keyboardHandleKey(void* data,
uint32_t key, uint32_t key,
uint32_t state) uint32_t state)
{ {
int keyCode;
int action;
_GLFWwindow* window = _glfw.wl.keyboardFocus; _GLFWwindow* window = _glfw.wl.keyboardFocus;
GLFWbool shouldRepeat;
struct itimerspec timer = {};
if (!window) if (!window)
return; return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
_glfw.wl.keyRepeatInfo.nextRepeatAt = 0;
keyCode = toGLFWKeyCode(key); if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
action = state == WL_KEYBOARD_KEY_STATE_PRESSED
? GLFW_PRESS : GLFW_RELEASE;
_glfwInputKey(window, keyCode, key, action,
_glfw.wl.xkb.modifiers);
if (action == GLFW_PRESS)
{ {
shouldRepeat = inputChar(window, key); _glfw.wl.keyRepeatInfo.key = key;
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
if (shouldRepeat && _glfw.wl.keyboardRepeatRate > 0) _glfw.wl.keyRepeatInfo.keyboardFocus = window;
{
_glfw.wl.keyboardLastKey = keyCode;
_glfw.wl.keyboardLastScancode = key;
timer.it_interval.tv_sec = _glfw.wl.keyboardRepeatRate / 1000;
timer.it_interval.tv_nsec = (_glfw.wl.keyboardRepeatRate % 1000) * 1000000;
timer.it_value.tv_sec = _glfw.wl.keyboardRepeatDelay / 1000;
timer.it_value.tv_nsec = (_glfw.wl.keyboardRepeatDelay % 1000) * 1000000;
}
} }
timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL);
} }
static void keyboardHandleModifiers(void* data, static void keyboardHandleModifiers(void* data,
@@ -589,41 +434,9 @@ static void keyboardHandleModifiers(void* data,
uint32_t modsLocked, uint32_t modsLocked,
uint32_t group) uint32_t group)
{ {
xkb_mod_mask_t mask; glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
unsigned int modifiers = 0;
if (!_glfw.wl.xkb.keymap)
return;
xkb_state_update_mask(_glfw.wl.xkb.state,
modsDepressed,
modsLatched,
modsLocked,
0,
0,
group);
mask = xkb_state_serialize_mods(_glfw.wl.xkb.state,
XKB_STATE_MODS_DEPRESSED |
XKB_STATE_LAYOUT_DEPRESSED |
XKB_STATE_MODS_LATCHED |
XKB_STATE_LAYOUT_LATCHED);
if (mask & _glfw.wl.xkb.controlMask)
modifiers |= GLFW_MOD_CONTROL;
if (mask & _glfw.wl.xkb.altMask)
modifiers |= GLFW_MOD_ALT;
if (mask & _glfw.wl.xkb.shiftMask)
modifiers |= GLFW_MOD_SHIFT;
if (mask & _glfw.wl.xkb.superMask)
modifiers |= GLFW_MOD_SUPER;
if (mask & _glfw.wl.xkb.capsLockMask)
modifiers |= GLFW_MOD_CAPS_LOCK;
if (mask & _glfw.wl.xkb.numLockMask)
modifiers |= GLFW_MOD_NUM_LOCK;
_glfw.wl.xkb.modifiers = modifiers;
} }
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION
static void keyboardHandleRepeatInfo(void* data, static void keyboardHandleRepeatInfo(void* data,
struct wl_keyboard* keyboard, struct wl_keyboard* keyboard,
int32_t rate, int32_t rate,
@@ -635,7 +448,6 @@ static void keyboardHandleRepeatInfo(void* data,
_glfw.wl.keyboardRepeatRate = rate; _glfw.wl.keyboardRepeatRate = rate;
_glfw.wl.keyboardRepeatDelay = delay; _glfw.wl.keyboardRepeatDelay = delay;
} }
#endif
static const struct wl_keyboard_listener keyboardListener = { static const struct wl_keyboard_listener keyboardListener = {
keyboardHandleKeymap, keyboardHandleKeymap,
@@ -643,9 +455,7 @@ static const struct wl_keyboard_listener keyboardListener = {
keyboardHandleLeave, keyboardHandleLeave,
keyboardHandleKey, keyboardHandleKey,
keyboardHandleModifiers, keyboardHandleModifiers,
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION
keyboardHandleRepeatInfo, keyboardHandleRepeatInfo,
#endif
}; };
static void seatHandleCapabilities(void* data, static void seatHandleCapabilities(void* data,
@@ -798,140 +608,6 @@ static const struct wl_registry_listener registryListener = {
registryHandleGlobalRemove registryHandleGlobalRemove
}; };
// Create key code translation tables
//
static void createKeyTables(void)
{
int scancode;
memset(_glfw.wl.keycodes, -1, sizeof(_glfw.wl.keycodes));
memset(_glfw.wl.scancodes, -1, sizeof(_glfw.wl.scancodes));
_glfw.wl.keycodes[KEY_GRAVE] = GLFW_KEY_GRAVE_ACCENT;
_glfw.wl.keycodes[KEY_1] = GLFW_KEY_1;
_glfw.wl.keycodes[KEY_2] = GLFW_KEY_2;
_glfw.wl.keycodes[KEY_3] = GLFW_KEY_3;
_glfw.wl.keycodes[KEY_4] = GLFW_KEY_4;
_glfw.wl.keycodes[KEY_5] = GLFW_KEY_5;
_glfw.wl.keycodes[KEY_6] = GLFW_KEY_6;
_glfw.wl.keycodes[KEY_7] = GLFW_KEY_7;
_glfw.wl.keycodes[KEY_8] = GLFW_KEY_8;
_glfw.wl.keycodes[KEY_9] = GLFW_KEY_9;
_glfw.wl.keycodes[KEY_0] = GLFW_KEY_0;
_glfw.wl.keycodes[KEY_SPACE] = GLFW_KEY_SPACE;
_glfw.wl.keycodes[KEY_MINUS] = GLFW_KEY_MINUS;
_glfw.wl.keycodes[KEY_EQUAL] = GLFW_KEY_EQUAL;
_glfw.wl.keycodes[KEY_Q] = GLFW_KEY_Q;
_glfw.wl.keycodes[KEY_W] = GLFW_KEY_W;
_glfw.wl.keycodes[KEY_E] = GLFW_KEY_E;
_glfw.wl.keycodes[KEY_R] = GLFW_KEY_R;
_glfw.wl.keycodes[KEY_T] = GLFW_KEY_T;
_glfw.wl.keycodes[KEY_Y] = GLFW_KEY_Y;
_glfw.wl.keycodes[KEY_U] = GLFW_KEY_U;
_glfw.wl.keycodes[KEY_I] = GLFW_KEY_I;
_glfw.wl.keycodes[KEY_O] = GLFW_KEY_O;
_glfw.wl.keycodes[KEY_P] = GLFW_KEY_P;
_glfw.wl.keycodes[KEY_LEFTBRACE] = GLFW_KEY_LEFT_BRACKET;
_glfw.wl.keycodes[KEY_RIGHTBRACE] = GLFW_KEY_RIGHT_BRACKET;
_glfw.wl.keycodes[KEY_A] = GLFW_KEY_A;
_glfw.wl.keycodes[KEY_S] = GLFW_KEY_S;
_glfw.wl.keycodes[KEY_D] = GLFW_KEY_D;
_glfw.wl.keycodes[KEY_F] = GLFW_KEY_F;
_glfw.wl.keycodes[KEY_G] = GLFW_KEY_G;
_glfw.wl.keycodes[KEY_H] = GLFW_KEY_H;
_glfw.wl.keycodes[KEY_J] = GLFW_KEY_J;
_glfw.wl.keycodes[KEY_K] = GLFW_KEY_K;
_glfw.wl.keycodes[KEY_L] = GLFW_KEY_L;
_glfw.wl.keycodes[KEY_SEMICOLON] = GLFW_KEY_SEMICOLON;
_glfw.wl.keycodes[KEY_APOSTROPHE] = GLFW_KEY_APOSTROPHE;
_glfw.wl.keycodes[KEY_Z] = GLFW_KEY_Z;
_glfw.wl.keycodes[KEY_X] = GLFW_KEY_X;
_glfw.wl.keycodes[KEY_C] = GLFW_KEY_C;
_glfw.wl.keycodes[KEY_V] = GLFW_KEY_V;
_glfw.wl.keycodes[KEY_B] = GLFW_KEY_B;
_glfw.wl.keycodes[KEY_N] = GLFW_KEY_N;
_glfw.wl.keycodes[KEY_M] = GLFW_KEY_M;
_glfw.wl.keycodes[KEY_COMMA] = GLFW_KEY_COMMA;
_glfw.wl.keycodes[KEY_DOT] = GLFW_KEY_PERIOD;
_glfw.wl.keycodes[KEY_SLASH] = GLFW_KEY_SLASH;
_glfw.wl.keycodes[KEY_BACKSLASH] = GLFW_KEY_BACKSLASH;
_glfw.wl.keycodes[KEY_ESC] = GLFW_KEY_ESCAPE;
_glfw.wl.keycodes[KEY_TAB] = GLFW_KEY_TAB;
_glfw.wl.keycodes[KEY_LEFTSHIFT] = GLFW_KEY_LEFT_SHIFT;
_glfw.wl.keycodes[KEY_RIGHTSHIFT] = GLFW_KEY_RIGHT_SHIFT;
_glfw.wl.keycodes[KEY_LEFTCTRL] = GLFW_KEY_LEFT_CONTROL;
_glfw.wl.keycodes[KEY_RIGHTCTRL] = GLFW_KEY_RIGHT_CONTROL;
_glfw.wl.keycodes[KEY_LEFTALT] = GLFW_KEY_LEFT_ALT;
_glfw.wl.keycodes[KEY_RIGHTALT] = GLFW_KEY_RIGHT_ALT;
_glfw.wl.keycodes[KEY_LEFTMETA] = GLFW_KEY_LEFT_SUPER;
_glfw.wl.keycodes[KEY_RIGHTMETA] = GLFW_KEY_RIGHT_SUPER;
_glfw.wl.keycodes[KEY_MENU] = GLFW_KEY_MENU;
_glfw.wl.keycodes[KEY_NUMLOCK] = GLFW_KEY_NUM_LOCK;
_glfw.wl.keycodes[KEY_CAPSLOCK] = GLFW_KEY_CAPS_LOCK;
_glfw.wl.keycodes[KEY_PRINT] = GLFW_KEY_PRINT_SCREEN;
_glfw.wl.keycodes[KEY_SCROLLLOCK] = GLFW_KEY_SCROLL_LOCK;
_glfw.wl.keycodes[KEY_PAUSE] = GLFW_KEY_PAUSE;
_glfw.wl.keycodes[KEY_DELETE] = GLFW_KEY_DELETE;
_glfw.wl.keycodes[KEY_BACKSPACE] = GLFW_KEY_BACKSPACE;
_glfw.wl.keycodes[KEY_ENTER] = GLFW_KEY_ENTER;
_glfw.wl.keycodes[KEY_HOME] = GLFW_KEY_HOME;
_glfw.wl.keycodes[KEY_END] = GLFW_KEY_END;
_glfw.wl.keycodes[KEY_PAGEUP] = GLFW_KEY_PAGE_UP;
_glfw.wl.keycodes[KEY_PAGEDOWN] = GLFW_KEY_PAGE_DOWN;
_glfw.wl.keycodes[KEY_INSERT] = GLFW_KEY_INSERT;
_glfw.wl.keycodes[KEY_LEFT] = GLFW_KEY_LEFT;
_glfw.wl.keycodes[KEY_RIGHT] = GLFW_KEY_RIGHT;
_glfw.wl.keycodes[KEY_DOWN] = GLFW_KEY_DOWN;
_glfw.wl.keycodes[KEY_UP] = GLFW_KEY_UP;
_glfw.wl.keycodes[KEY_F1] = GLFW_KEY_F1;
_glfw.wl.keycodes[KEY_F2] = GLFW_KEY_F2;
_glfw.wl.keycodes[KEY_F3] = GLFW_KEY_F3;
_glfw.wl.keycodes[KEY_F4] = GLFW_KEY_F4;
_glfw.wl.keycodes[KEY_F5] = GLFW_KEY_F5;
_glfw.wl.keycodes[KEY_F6] = GLFW_KEY_F6;
_glfw.wl.keycodes[KEY_F7] = GLFW_KEY_F7;
_glfw.wl.keycodes[KEY_F8] = GLFW_KEY_F8;
_glfw.wl.keycodes[KEY_F9] = GLFW_KEY_F9;
_glfw.wl.keycodes[KEY_F10] = GLFW_KEY_F10;
_glfw.wl.keycodes[KEY_F11] = GLFW_KEY_F11;
_glfw.wl.keycodes[KEY_F12] = GLFW_KEY_F12;
_glfw.wl.keycodes[KEY_F13] = GLFW_KEY_F13;
_glfw.wl.keycodes[KEY_F14] = GLFW_KEY_F14;
_glfw.wl.keycodes[KEY_F15] = GLFW_KEY_F15;
_glfw.wl.keycodes[KEY_F16] = GLFW_KEY_F16;
_glfw.wl.keycodes[KEY_F17] = GLFW_KEY_F17;
_glfw.wl.keycodes[KEY_F18] = GLFW_KEY_F18;
_glfw.wl.keycodes[KEY_F19] = GLFW_KEY_F19;
_glfw.wl.keycodes[KEY_F20] = GLFW_KEY_F20;
_glfw.wl.keycodes[KEY_F21] = GLFW_KEY_F21;
_glfw.wl.keycodes[KEY_F22] = GLFW_KEY_F22;
_glfw.wl.keycodes[KEY_F23] = GLFW_KEY_F23;
_glfw.wl.keycodes[KEY_F24] = GLFW_KEY_F24;
_glfw.wl.keycodes[KEY_KPSLASH] = GLFW_KEY_KP_DIVIDE;
_glfw.wl.keycodes[KEY_KPDOT] = GLFW_KEY_KP_MULTIPLY;
_glfw.wl.keycodes[KEY_KPMINUS] = GLFW_KEY_KP_SUBTRACT;
_glfw.wl.keycodes[KEY_KPPLUS] = GLFW_KEY_KP_ADD;
_glfw.wl.keycodes[KEY_KP0] = GLFW_KEY_KP_0;
_glfw.wl.keycodes[KEY_KP1] = GLFW_KEY_KP_1;
_glfw.wl.keycodes[KEY_KP2] = GLFW_KEY_KP_2;
_glfw.wl.keycodes[KEY_KP3] = GLFW_KEY_KP_3;
_glfw.wl.keycodes[KEY_KP4] = GLFW_KEY_KP_4;
_glfw.wl.keycodes[KEY_KP5] = GLFW_KEY_KP_5;
_glfw.wl.keycodes[KEY_KP6] = GLFW_KEY_KP_6;
_glfw.wl.keycodes[KEY_KP7] = GLFW_KEY_KP_7;
_glfw.wl.keycodes[KEY_KP8] = GLFW_KEY_KP_8;
_glfw.wl.keycodes[KEY_KP9] = GLFW_KEY_KP_9;
_glfw.wl.keycodes[KEY_KPCOMMA] = GLFW_KEY_KP_DECIMAL;
_glfw.wl.keycodes[KEY_KPEQUAL] = GLFW_KEY_KP_EQUAL;
_glfw.wl.keycodes[KEY_KPENTER] = GLFW_KEY_KP_ENTER;
for (scancode = 0; scancode < 256; scancode++)
{
if (_glfw.wl.keycodes[scancode] > 0)
_glfw.wl.scancodes[_glfw.wl.keycodes[scancode]] = scancode;
}
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW platform API ////// ////// GLFW platform API //////
@@ -971,54 +647,6 @@ int _glfwPlatformInit(void)
_glfw.wl.egl.window_resize = (PFN_wl_egl_window_resize) _glfw.wl.egl.window_resize = (PFN_wl_egl_window_resize)
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_resize"); _glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_resize");
_glfw.wl.xkb.handle = _glfw_dlopen("libxkbcommon.so.0");
if (!_glfw.wl.xkb.handle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to open libxkbcommon");
return GLFW_FALSE;
}
_glfw.wl.xkb.context_new = (PFN_xkb_context_new)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_new");
_glfw.wl.xkb.context_unref = (PFN_xkb_context_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_unref");
_glfw.wl.xkb.keymap_new_from_string = (PFN_xkb_keymap_new_from_string)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_new_from_string");
_glfw.wl.xkb.keymap_unref = (PFN_xkb_keymap_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_unref");
_glfw.wl.xkb.keymap_mod_get_index = (PFN_xkb_keymap_mod_get_index)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index");
_glfw.wl.xkb.keymap_key_repeats = (PFN_xkb_keymap_key_repeats)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_key_repeats");
_glfw.wl.xkb.state_new = (PFN_xkb_state_new)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_new");
_glfw.wl.xkb.state_unref = (PFN_xkb_state_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_unref");
_glfw.wl.xkb.state_key_get_syms = (PFN_xkb_state_key_get_syms)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_key_get_syms");
_glfw.wl.xkb.state_update_mask = (PFN_xkb_state_update_mask)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask");
_glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_serialize_mods");
#ifdef HAVE_XKBCOMMON_COMPOSE_H
_glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_new_from_locale");
_glfw.wl.xkb.compose_table_unref = (PFN_xkb_compose_table_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_unref");
_glfw.wl.xkb.compose_state_new = (PFN_xkb_compose_state_new)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_new");
_glfw.wl.xkb.compose_state_unref = (PFN_xkb_compose_state_unref)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_unref");
_glfw.wl.xkb.compose_state_feed = (PFN_xkb_compose_state_feed)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_feed");
_glfw.wl.xkb.compose_state_get_status = (PFN_xkb_compose_state_get_status)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_status");
_glfw.wl.xkb.compose_state_get_one_sym = (PFN_xkb_compose_state_get_one_sym)
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_one_sym");
#endif
_glfw.wl.display = wl_display_connect(NULL); _glfw.wl.display = wl_display_connect(NULL);
if (!_glfw.wl.display) if (!_glfw.wl.display)
{ {
@@ -1030,15 +658,7 @@ int _glfwPlatformInit(void)
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display); _glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
wl_registry_add_listener(_glfw.wl.registry, &registryListener, NULL); wl_registry_add_listener(_glfw.wl.registry, &registryListener, NULL);
createKeyTables(); if (!glfw_xkb_create_context(&_glfw.wl.xkb)) return GLFW_FALSE;
_glfw.wl.xkb.context = xkb_context_new(0);
if (!_glfw.wl.xkb.context)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to initialize xkb context");
return GLFW_FALSE;
}
// Sync so we got all registry objects // Sync so we got all registry objects
wl_display_roundtrip(_glfw.wl.display); wl_display_roundtrip(_glfw.wl.display);
@@ -1046,15 +666,13 @@ int _glfwPlatformInit(void)
// Sync so we got all initial output events // Sync so we got all initial output events
wl_display_roundtrip(_glfw.wl.display); wl_display_roundtrip(_glfw.wl.display);
#ifdef __linux__
if (!_glfwInitJoysticksLinux()) if (!_glfwInitJoysticksLinux())
return GLFW_FALSE; return GLFW_FALSE;
#endif
_glfwInitTimerPOSIX(); _glfwInitTimerPOSIX();
_glfw.wl.timerfd = -1;
if (_glfw.wl.seatVersion >= 4)
_glfw.wl.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
if (_glfw.wl.pointer && _glfw.wl.shm) if (_glfw.wl.pointer && _glfw.wl.shm)
{ {
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm); _glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
@@ -1073,7 +691,9 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void) void _glfwPlatformTerminate(void)
{ {
#ifdef __linux__
_glfwTerminateJoysticksLinux(); _glfwTerminateJoysticksLinux();
#endif
_glfwTerminateEGL(); _glfwTerminateEGL();
if (_glfw.wl.egl.handle) if (_glfw.wl.egl.handle)
{ {
@@ -1081,21 +701,7 @@ void _glfwPlatformTerminate(void)
_glfw.wl.egl.handle = NULL; _glfw.wl.egl.handle = NULL;
} }
#ifdef HAVE_XKBCOMMON_COMPOSE_H glfw_xkb_release(&_glfw.wl.xkb);
if (_glfw.wl.xkb.composeState)
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
#endif
if (_glfw.wl.xkb.keymap)
xkb_keymap_unref(_glfw.wl.xkb.keymap);
if (_glfw.wl.xkb.state)
xkb_state_unref(_glfw.wl.xkb.state);
if (_glfw.wl.xkb.context)
xkb_context_unref(_glfw.wl.xkb.context);
if (_glfw.wl.xkb.handle)
{
_glfw_dlclose(_glfw.wl.xkb.handle);
_glfw.wl.xkb.handle = NULL;
}
if (_glfw.wl.cursorTheme) if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme); wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
@@ -1154,4 +760,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif #endif
; ;
} }

101
glfw/wl_platform.h vendored
View File

@@ -25,10 +25,6 @@
//======================================================================== //========================================================================
#include <wayland-client.h> #include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#ifdef HAVE_XKBCOMMON_COMPOSE_H
#include <xkbcommon/xkbcommon-compose.h>
#endif
#include <dlfcn.h> #include <dlfcn.h>
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
@@ -47,8 +43,12 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "posix_thread.h" #include "posix_thread.h"
#include "posix_time.h" #include "posix_time.h"
#ifdef __linux__
#include "linux_joystick.h" #include "linux_joystick.h"
#include "xkb_unicode.h" #else
#include "null_joystick.h"
#endif
#include "xkb_glfw.h"
#include "egl_context.h" #include "egl_context.h"
#include "osmesa_context.h" #include "osmesa_context.h"
@@ -101,46 +101,6 @@ typedef void (* PFN_wl_egl_window_resize)(struct wl_egl_window*, int, int, int,
#define wl_egl_window_destroy _glfw.wl.egl.window_destroy #define wl_egl_window_destroy _glfw.wl.egl.window_destroy
#define wl_egl_window_resize _glfw.wl.egl.window_resize #define wl_egl_window_resize _glfw.wl.egl.window_resize
typedef struct xkb_context* (* PFN_xkb_context_new)(enum xkb_context_flags);
typedef void (* PFN_xkb_context_unref)(struct xkb_context*);
typedef struct xkb_keymap* (* PFN_xkb_keymap_new_from_string)(struct xkb_context*, const char*, enum xkb_keymap_format, enum xkb_keymap_compile_flags);
typedef void (* PFN_xkb_keymap_unref)(struct xkb_keymap*);
typedef xkb_mod_index_t (* PFN_xkb_keymap_mod_get_index)(struct xkb_keymap*, const char*);
typedef int (* PFN_xkb_keymap_key_repeats)(struct xkb_keymap*, xkb_keycode_t);
typedef struct xkb_state* (* PFN_xkb_state_new)(struct xkb_keymap*);
typedef void (* PFN_xkb_state_unref)(struct xkb_state*);
typedef int (* PFN_xkb_state_key_get_syms)(struct xkb_state*, xkb_keycode_t, const xkb_keysym_t**);
typedef enum xkb_state_component (* PFN_xkb_state_update_mask)(struct xkb_state*, xkb_mod_mask_t, xkb_mod_mask_t, xkb_mod_mask_t, xkb_layout_index_t, xkb_layout_index_t, xkb_layout_index_t);
typedef xkb_mod_mask_t (* PFN_xkb_state_serialize_mods)(struct xkb_state*, enum xkb_state_component);
#define xkb_context_new _glfw.wl.xkb.context_new
#define xkb_context_unref _glfw.wl.xkb.context_unref
#define xkb_keymap_new_from_string _glfw.wl.xkb.keymap_new_from_string
#define xkb_keymap_unref _glfw.wl.xkb.keymap_unref
#define xkb_keymap_mod_get_index _glfw.wl.xkb.keymap_mod_get_index
#define xkb_keymap_key_repeats _glfw.wl.xkb.keymap_key_repeats
#define xkb_state_new _glfw.wl.xkb.state_new
#define xkb_state_unref _glfw.wl.xkb.state_unref
#define xkb_state_key_get_syms _glfw.wl.xkb.state_key_get_syms
#define xkb_state_update_mask _glfw.wl.xkb.state_update_mask
#define xkb_state_serialize_mods _glfw.wl.xkb.state_serialize_mods
#ifdef HAVE_XKBCOMMON_COMPOSE_H
typedef struct xkb_compose_table* (* PFN_xkb_compose_table_new_from_locale)(struct xkb_context*, const char*, enum xkb_compose_compile_flags);
typedef void (* PFN_xkb_compose_table_unref)(struct xkb_compose_table*);
typedef struct xkb_compose_state* (* PFN_xkb_compose_state_new)(struct xkb_compose_table*, enum xkb_compose_state_flags);
typedef void (* PFN_xkb_compose_state_unref)(struct xkb_compose_state*);
typedef enum xkb_compose_feed_result (* PFN_xkb_compose_state_feed)(struct xkb_compose_state*, xkb_keysym_t);
typedef enum xkb_compose_status (* PFN_xkb_compose_state_get_status)(struct xkb_compose_state*);
typedef xkb_keysym_t (* PFN_xkb_compose_state_get_one_sym)(struct xkb_compose_state*);
#define xkb_compose_table_new_from_locale _glfw.wl.xkb.compose_table_new_from_locale
#define xkb_compose_table_unref _glfw.wl.xkb.compose_table_unref
#define xkb_compose_state_new _glfw.wl.xkb.compose_state_new
#define xkb_compose_state_unref _glfw.wl.xkb.compose_state_unref
#define xkb_compose_state_feed _glfw.wl.xkb.compose_state_feed
#define xkb_compose_state_get_status _glfw.wl.xkb.compose_state_get_status
#define xkb_compose_state_get_one_sym _glfw.wl.xkb.compose_state_get_one_sym
#endif
#define _GLFW_DECORATION_WIDTH 4 #define _GLFW_DECORATION_WIDTH 4
#define _GLFW_DECORATION_TOP 24 #define _GLFW_DECORATION_TOP 24
#define _GLFW_DECORATION_VERTICAL (_GLFW_DECORATION_TOP + _GLFW_DECORATION_WIDTH) #define _GLFW_DECORATION_VERTICAL (_GLFW_DECORATION_TOP + _GLFW_DECORATION_WIDTH)
@@ -241,52 +201,12 @@ typedef struct _GLFWlibraryWayland
int32_t keyboardRepeatRate; int32_t keyboardRepeatRate;
int32_t keyboardRepeatDelay; int32_t keyboardRepeatDelay;
int keyboardLastKey;
int keyboardLastScancode;
int timerfd;
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
struct { struct {
void* handle; uint32_t key;
struct xkb_context* context; double nextRepeatAt;
struct xkb_keymap* keymap; _GLFWwindow* keyboardFocus;
struct xkb_state* state; } keyRepeatInfo;
_GLFWXKBData xkb;
#ifdef HAVE_XKBCOMMON_COMPOSE_H
struct xkb_compose_state* composeState;
#endif
xkb_mod_mask_t controlMask;
xkb_mod_mask_t altMask;
xkb_mod_mask_t shiftMask;
xkb_mod_mask_t superMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
unsigned int modifiers;
PFN_xkb_context_new context_new;
PFN_xkb_context_unref context_unref;
PFN_xkb_keymap_new_from_string keymap_new_from_string;
PFN_xkb_keymap_unref keymap_unref;
PFN_xkb_keymap_mod_get_index keymap_mod_get_index;
PFN_xkb_keymap_key_repeats keymap_key_repeats;
PFN_xkb_state_new state_new;
PFN_xkb_state_unref state_unref;
PFN_xkb_state_key_get_syms state_key_get_syms;
PFN_xkb_state_update_mask state_update_mask;
PFN_xkb_state_serialize_mods state_serialize_mods;
#ifdef HAVE_XKBCOMMON_COMPOSE_H
PFN_xkb_compose_table_new_from_locale compose_table_new_from_locale;
PFN_xkb_compose_table_unref compose_table_unref;
PFN_xkb_compose_state_new compose_state_new;
PFN_xkb_compose_state_unref compose_state_unref;
PFN_xkb_compose_state_feed compose_state_feed;
PFN_xkb_compose_state_get_status compose_state_get_status;
PFN_xkb_compose_state_get_one_sym compose_state_get_one_sym;
#endif
} xkb;
_GLFWwindow* pointerFocus; _GLFWwindow* pointerFocus;
_GLFWwindow* keyboardFocus; _GLFWwindow* keyboardFocus;
@@ -336,4 +256,3 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version); void _glfwAddOutputWayland(uint32_t name, uint32_t version);

66
glfw/wl_window.c vendored
View File

@@ -35,7 +35,6 @@
#include <string.h> #include <string.h>
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/timerfd.h>
#include <poll.h> #include <poll.h>
@@ -690,16 +689,32 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
return GLFW_TRUE; return GLFW_TRUE;
} }
static void
dispatchPendingKeyRepeats() {
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
double now = glfwGetTime();
while (_glfw.wl.keyRepeatInfo.nextRepeatAt <= now) {
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
_glfw.wl.keyRepeatInfo.nextRepeatAt += 1.0 / _glfw.wl.keyboardRepeatRate;
now = glfwGetTime();
}
}
static int
adjustTimeoutForKeyRepeat(int timeout) {
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return timeout;
double now = glfwGetTime();
if (timeout < 0 || now + timeout / 1000. > _glfw.wl.keyRepeatInfo.nextRepeatAt) {
timeout = _glfw.wl.keyRepeatInfo.nextRepeatAt <= now ? 0 : ( (_glfw.wl.keyRepeatInfo.nextRepeatAt - now) * 1000 + 1 );
}
return timeout;
}
static void static void
handleEvents(int timeout) handleEvents(int timeout)
{ {
struct wl_display* display = _glfw.wl.display; struct wl_display* display = _glfw.wl.display;
struct pollfd fds[] = { struct pollfd pfd = { wl_display_get_fd(display), POLLIN };
{ wl_display_get_fd(display), POLLIN },
{ _glfw.wl.timerfd, POLLIN },
};
ssize_t read_ret;
uint64_t repeats, i;
while (wl_display_prepare_read(display) != 0) while (wl_display_prepare_read(display) != 0)
wl_display_dispatch_pending(display); wl_display_dispatch_pending(display);
@@ -719,9 +734,12 @@ handleEvents(int timeout)
return; return;
} }
if (poll(fds, 2, timeout) > 0) dispatchPendingKeyRepeats();
timeout = adjustTimeoutForKeyRepeat(timeout);
if (poll(&pfd, 1, timeout) > 0)
{ {
if (fds[0].revents & POLLIN) if (pfd.revents & POLLIN)
{ {
wl_display_read_events(display); wl_display_read_events(display);
wl_display_dispatch_pending(display); wl_display_dispatch_pending(display);
@@ -731,22 +749,12 @@ handleEvents(int timeout)
wl_display_cancel_read(display); wl_display_cancel_read(display);
} }
if (fds[1].revents & POLLIN)
{
read_ret = read(_glfw.wl.timerfd, &repeats, sizeof(repeats));
if (read_ret != 8)
return;
for (i = 0; i < repeats; ++i)
_glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey,
_glfw.wl.keyboardLastScancode, GLFW_REPEAT,
_glfw.wl.xkb.modifiers);
}
} }
else else
{ {
wl_display_cancel_read(display); wl_display_cancel_read(display);
} }
dispatchPendingKeyRepeats();
} }
// Translates a GLFW standard cursor to a theme cursor name // Translates a GLFW standard cursor to a theme cursor name
@@ -1079,11 +1087,15 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
"Wayland: Window attention request not implemented yet"); "Wayland: Window attention request not implemented yet");
} }
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param) int _glfwPlatformWindowBell(_GLFWwindow* window)
{ {
// TODO // TODO: Use an actual Wayland API to implement this when one becomes available
_glfwInputError(GLFW_PLATFORM_ERROR, int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC);
"Wayland: Window bell request not implemented yet"); if (fd > -1) {
int ret = write(fd, "\x07", 1) == 1 ? GLFW_TRUE : GLFW_FALSE;
close(fd);
return ret;
}
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -1230,13 +1242,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
const char* _glfwPlatformGetScancodeName(int scancode) const char* _glfwPlatformGetScancodeName(int scancode)
{ {
// TODO return glfw_xkb_keysym_name(scancode);
return NULL;
} }
int _glfwPlatformGetKeyScancode(int key) int _glfwPlatformGetKeyScancode(int key)
{ {
return _glfw.wl.scancodes[key]; return glfw_xkb_sym_for_key(key);
} }
int _glfwPlatformCreateCursor(_GLFWcursor* cursor, int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
@@ -1547,4 +1558,3 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return window->wl.surface; return window->wl.surface;
} }

404
glfw/x11_init.c vendored
View File

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

25
glfw/x11_platform.h vendored
View File

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

216
glfw/x11_window.c vendored
View File

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

422
glfw/xkb_glfw.c vendored Normal file
View File

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

83
glfw/xkb_glfw.h vendored Normal file
View File

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

940
glfw/xkb_unicode.c vendored
View File

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

28
glfw/xkb_unicode.h vendored
View File

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

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

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

View File

View File

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

76
kittens/runner.py Normal file
View File

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

View File

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

View File

@@ -5,7 +5,6 @@
import os import os
import string import string
import subprocess import subprocess
import sys
from functools import lru_cache from functools import lru_cache
from gettext import gettext as _ from gettext import gettext as _
@@ -43,7 +42,7 @@ def codepoint_ok(code):
@lru_cache(maxsize=256) @lru_cache(maxsize=256)
def points_for_word(w): def points_for_word(w):
from .unicode_names import codepoints_for_word from .unicode_names import codepoints_for_word
return codepoints_for_word(w) return codepoints_for_word(w.lower())
@lru_cache(maxsize=4096) @lru_cache(maxsize=4096)
@@ -223,6 +222,8 @@ class Table:
return self.text return self.text
def move_current(self, rows=0, cols=0): def move_current(self, rows=0, cols=0):
if len(self.codepoints) == 0:
return
if cols: if cols:
self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints) self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
self.layout_dirty = True self.layout_dirty = True
@@ -452,26 +453,34 @@ class UnicodeInput(Handler):
self.refresh() self.refresh()
def run_loop(args): def main(args):
loop = Loop() loop = Loop()
with cached_values_for('unicode-input') as cached_values: with cached_values_for('unicode-input') as cached_values:
handler = UnicodeInput(cached_values) handler = UnicodeInput(cached_values)
loop.loop(handler) loop.loop(handler)
if handler.current_char and loop.return_code == 0: if handler.current_char and loop.return_code == 0:
print('OK:', hex(ord(handler.current_char))[2:])
try: try:
handler.recent.remove(ord(handler.current_char)) handler.recent.remove(ord(handler.current_char))
except Exception: except Exception:
pass pass
recent = [ord(handler.current_char)] + handler.recent recent = [ord(handler.current_char)] + handler.recent
cached_values['recent'] = recent[:len(DEFAULT_SET)] cached_values['recent'] = recent[:len(DEFAULT_SET)]
return loop.return_code return handler.current_char
if loop.return_code != 0:
raise SystemExit(loop.return_code)
def main(args=sys.argv): def handle_result(args, current_char, target_window_id, boss):
try: w = boss.window_id_map.get(target_window_id)
raise SystemExit(run_loop(args)) if w is not None:
except Exception: w.paste(current_char)
import traceback
traceback.print_exc()
input(_('Press Enter to quit.')) if __name__ == '__main__':
import sys
if '-h' in sys.argv or '--help' in sys.argv:
print('Choose a unicode character to input into the terminal')
raise SystemExit(0)
ans = main(sys.argv)
if ans:
print(ans)

View File

@@ -3,6 +3,8 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import atexit import atexit
import json
import os
import re import re
import socket import socket
from functools import partial from functools import partial
@@ -13,17 +15,18 @@ from .cli import create_opts, parse_args
from .config import ( from .config import (
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
) )
from .constants import appname, editor, set_boss from .constants import appname, editor, set_boss, config_dir
from .fast_data_types import ( from .fast_data_types import (
ChildMonitor, create_os_window, current_os_window, destroy_global_data, ChildMonitor, create_os_window, current_os_window, destroy_global_data,
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event, destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
layout_sprite_map, mark_os_window_for_close, set_clipboard_string, layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
set_dpi_from_os_window, show_window, toggle_fullscreen, set_dpi_from_os_window, set_in_sequence_mode, show_window,
viewport_for_window toggle_fullscreen, viewport_for_window
) )
from .fonts.render import prerender, resize_fonts, set_font_family from .fonts.render import prerender, resize_fonts, set_font_family
from .keys import get_shortcut from .keys import get_shortcut, shortcut_matches
from .remote_control import handle_cmd from .remote_control import handle_cmd
from .rgb import Color, color_from_int
from .session import create_session from .session import create_session
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
from .utils import ( from .utils import (
@@ -78,6 +81,8 @@ class Boss:
def __init__(self, os_window_id, opts, args, cached_values): def __init__(self, os_window_id, opts, args, cached_values):
self.window_id_map = WeakValueDictionary() self.window_id_map = WeakValueDictionary()
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.pending_sequences = None
self.cached_values = cached_values self.cached_values = cached_values
self.os_window_map = {} self.os_window_map = {}
self.cursor_blinking = True self.cursor_blinking = True
@@ -115,6 +120,7 @@ class Boss:
self.os_window_map[os_window_id] = tm self.os_window_map[os_window_id] = tm
if dpi_changed: if dpi_changed:
self.on_dpi_change(os_window_id) self.on_dpi_change(os_window_id)
return os_window_id
def list_os_windows(self): def list_os_windows(self):
for os_window_id, tm in self.os_window_map.items(): for os_window_id, tm in self.os_window_map.items():
@@ -123,24 +129,35 @@ class Boss:
'tabs': list(tm.list_tabs()), 'tabs': list(tm.list_tabs()),
} }
@property
def all_tab_managers(self):
yield from self.os_window_map.values()
@property
def all_tabs(self):
for tm in self.all_tab_managers:
yield from tm
@property
def all_windows(self):
for tab in self.all_tabs:
yield from tab
def match_windows(self, match): def match_windows(self, match):
try: try:
field, exp = match.split(':', 1) field, exp = match.split(':', 1)
except ValueError: except ValueError:
return return
pat = re.compile(exp) pat = re.compile(exp)
for tm in self.os_window_map.values(): for window in self.all_windows:
for tab in tm: if window.matches(field, pat):
for window in tab: yield window
if window.matches(field, pat):
yield window
def tab_for_window(self, window): def tab_for_window(self, window):
for tm in self.os_window_map.values(): for tab in self.all_tabs:
for tab in tm: for w in tab:
for w in tab: if w.id == window.id:
if w.id == window.id: return tab
return tab
def match_tabs(self, match): def match_tabs(self, match):
try: try:
@@ -148,14 +165,12 @@ class Boss:
except ValueError: except ValueError:
return return
pat = re.compile(exp) pat = re.compile(exp)
tms = tuple(self.os_window_map.values())
found = False found = False
if field in ('title', 'id'): if field in ('title', 'id'):
for tm in tms: for tab in self.all_tabs:
for tab in tm: if tab.matches(field, pat):
if tab.matches(field, pat): yield tab
yield tab found = True
found = True
if not found: if not found:
tabs = {self.tab_for_window(w) for w in self.match_windows(match)} tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
for tab in tabs: for tab in tabs:
@@ -175,7 +190,7 @@ class Boss:
def _new_os_window(self, args, cwd_from=None): def _new_os_window(self, args, cwd_from=None):
sw = self.args_to_special_window(args, cwd_from) if args else None sw = self.args_to_special_window(args, cwd_from) if args else None
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from) startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
self.add_os_window(startup_session) return self.add_os_window(startup_session)
def new_os_window(self, *args): def new_os_window(self, *args):
self._new_os_window(args) self._new_os_window(args)
@@ -191,7 +206,7 @@ class Boss:
def _handle_remote_command(self, cmd, window=None): def _handle_remote_command(self, cmd, window=None):
response = None response = None
if self.opts.allow_remote_control: if self.opts.allow_remote_control or getattr(window, 'allow_remote_control', False):
try: try:
response = handle_cmd(self, window, cmd) response = handle_cmd(self, window, cmd)
except Exception as err: except Exception as err:
@@ -204,7 +219,6 @@ class Boss:
return response return response
def peer_message_received(self, msg): def peer_message_received(self, msg):
import json
msg = msg.decode('utf-8') msg = msg.decode('utf-8')
cmd_prefix = '\x1bP@kitty-cmd' cmd_prefix = '\x1bP@kitty-cmd'
if msg.startswith(cmd_prefix): if msg.startswith(cmd_prefix):
@@ -220,7 +234,9 @@ class Boss:
args, rest = parse_args(msg['args'][1:]) args, rest = parse_args(msg['args'][1:])
args.args = rest args.args = rest
opts = create_opts(args) opts = create_opts(args)
session = create_session(opts, args) if not os.path.isabs(args.directory):
args.directory = os.path.join(msg['cwd'], args.directory)
session = create_session(opts, args, respect_cwd=True)
self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts, self.cached_values), startup_id=startup_id) self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts, self.cached_values), startup_id=startup_id)
else: else:
log_error('Unknown message received from peer, ignoring') log_error('Unknown message received from peer, ignoring')
@@ -352,8 +368,37 @@ class Boss:
def dispatch_special_key(self, key, scancode, action, mods): def dispatch_special_key(self, key, scancode, action, mods):
# Handles shortcuts, return True if the key was consumed # Handles shortcuts, return True if the key was consumed
key_action = get_shortcut(self.opts.keymap, mods, key, scancode) key_action = get_shortcut(self.opts.keymap, mods, key, scancode)
self.current_key_press_info = key, scancode, action, mods if key_action is None:
return self.dispatch_action(key_action) sequences = get_shortcut(self.opts.sequence_map, mods, key, scancode)
if sequences:
self.pending_sequences = sequences
set_in_sequence_mode(True)
return True
else:
self.current_key_press_info = key, scancode, action, mods
return self.dispatch_action(key_action)
def process_sequence(self, key, scancode, action, mods):
if not self.pending_sequences:
set_in_sequence_mode(False)
remaining = {}
matched_action = None
for seq, key_action in self.pending_sequences.items():
if shortcut_matches(seq[0], mods, key, scancode):
seq = seq[1:]
if seq:
remaining[seq] = key_action
else:
matched_action = key_action
if remaining:
self.pending_sequences = remaining
else:
self.pending_sequences = None
set_in_sequence_mode(False)
if matched_action is not None:
self.dispatch_action(matched_action)
def default_bg_changed_for(self, window_id): def default_bg_changed_for(self, window_id):
w = self.window_id_map.get(window_id) w = self.window_id_map.get(window_id)
@@ -425,49 +470,86 @@ class Boss:
cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + editor + [confpath] cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + editor + [confpath]
self.new_os_window(*cmd) self.new_os_window(*cmd)
def input_unicode_character(self): def get_output(self, source_window, num_lines=1):
output = ''
s = source_window.screen
if num_lines is None:
num_lines = s.lines
for i in range(min(num_lines, s.lines)):
output += str(s.linebuf.line(i))
return output
def _run_kitten(self, kitten, args=(), type_of_input='none'):
w = self.active_window w = self.active_window
tab = self.active_tab tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None: if w is not None and tab is not None and w.overlay_for is None:
overlay_window = tab.new_special_window( orig_args, args = list(args), list(args)
SpecialWindow( args[0:0] = [config_dir, kitten]
['kitty', '+runpy', 'from kittens.unicode_input.main import main; main()'],
overlay_for=w.id))
overlay_window.action_on_close = partial(self.send_unicode_character, w.id)
def send_unicode_character(self, target_window_id, source_window):
w = self.window_id_map.get(target_window_id)
if w is not None:
output = str(source_window.screen.linebuf.line(0))
if output.startswith('OK: '):
try:
text = chr(int(output.partition(' ')[2], 16))
except Exception:
import traceback
traceback.print_exc()
else:
w.paste(text)
def run_simple_kitten(self, type_of_input, kitten, *args):
import shlex
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
cmdline = args[0] if args else ''
args = shlex.split(cmdline) if cmdline else []
if '--program' not in cmdline:
args.extend(('--program', self.opts.open_url_with))
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'): if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8') data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
elif type_of_input == 'none': elif type_of_input == 'none':
data = None data = None
else: else:
raise ValueError('Unknown type_of_input: {}'.format(type_of_input)) raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
tab.new_special_window( from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
copts = {k: self.opts[k] for k in ('select_by_word_characters', 'open_url_with')}
overlay_window = tab.new_special_window(
SpecialWindow( SpecialWindow(
['kitty', '+runpy', 'from kittens.{}.main import main; main()'.format(kitten)] + args, ['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
stdin=data, stdin=data,
env={'KITTY_COMMON_OPTS': json.dumps(copts)},
overlay_for=w.id)) overlay_for=w.id))
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
def run_kitten(self, type_of_input, kitten, *args):
import shlex
cmdline = args[0] if args else ''
args = shlex.split(cmdline) if cmdline else []
self._run_kitten(kitten, args, type_of_input)
def on_kitten_finish(self, target_window_id, end_kitten, source_window):
output = self.get_output(source_window, num_lines=None)
if output.startswith('OK: '):
data = json.loads(output.partition(' ')[2].strip())
end_kitten(data, target_window_id, self)
def input_unicode_character(self):
self._run_kitten('unicode_input')
def set_tab_title(self):
tab = self.active_tab
if tab:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
self._run_kitten('ask', args)
def do_set_tab_title(self, title, tab_id):
tm = self.active_tab_manager
if tm is not None and title:
tab_id = int(tab_id)
for tab in tm.tabs:
if tab.id == tab_id:
tab.set_title(title)
break
def kitty_shell(self, window_type):
cmd = ['kitty', '@']
if window_type == 'tab':
window = self._new_tab(cmd).active_window
elif window_type == 'os_window':
os_window_id = self._new_os_window(cmd)
window = self.os_window_map[os_window_id].active_window
elif window_type == 'overlay':
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
else:
window = None
else:
window = self._new_window(cmd)
if window is not None:
window.allow_remote_control = True
def switch_focus_to(self, window_idx): def switch_focus_to(self, window_idx):
tab = self.active_tab tab = self.active_tab
@@ -477,12 +559,12 @@ class Boss:
old_focus.focus_changed(False) old_focus.focus_changed(False)
tab.active_window.focus_changed(True) tab.active_window.focus_changed(True)
def open_url(self, url): def open_url(self, url, program=None):
if url: if url:
open_url(url, self.opts.open_url_with) open_url(url, program or self.opts.open_url_with)
def open_url_lines(self, lines): def open_url_lines(self, lines, program=None):
self.open_url(''.join(lines)) self.open_url(''.join(lines), program)
def destroy(self): def destroy(self):
self.shutting_down = True self.shutting_down = True
@@ -578,7 +660,7 @@ class Boss:
special_window = self.args_to_special_window(args, cwd_from=cwd_from) special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.new_tab(special_window=special_window, cwd_from=cwd_from) return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
def new_tab(self, *args): def new_tab(self, *args):
self._new_tab(args) self._new_tab(args)
@@ -592,9 +674,9 @@ class Boss:
tab = self.active_tab tab = self.active_tab
if tab is not None: if tab is not None:
if args: if args:
tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from)) return tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
else: else:
tab.new_window(cwd_from=cwd_from) return tab.new_window(cwd_from=cwd_from)
def new_window(self, *args): def new_window(self, *args):
self._new_window(args) self._new_window(args)
@@ -615,3 +697,11 @@ class Boss:
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.move_tab(-1) tm.move_tab(-1)
def patch_colors(self, spec, configured=False):
if configured:
for k, v in spec.items():
if hasattr(self.opts, k):
setattr(self.opts, k, color_from_int(v))
for tm in self.all_tab_managers:
tm.tab_bar.patch_colors(spec)

View File

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

View File

@@ -735,6 +735,30 @@ process_pending_resizes(double now) {
} }
} }
static inline void
close_all_windows() {
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
global_state.close_all_windows = false;
}
static inline bool
process_pending_closes(ChildMonitor *self) {
bool has_open_windows = false;
for (size_t w = global_state.num_os_windows; w > 0; w--) {
OSWindow *os_window = global_state.os_windows + w - 1;
if (should_os_window_close(os_window)) {
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id);
} else has_open_windows = true;
}
return has_open_windows;
}
static PyObject* static PyObject*
main_loop(ChildMonitor *self, PyObject *a UNUSED) { main_loop(ChildMonitor *self, PyObject *a UNUSED) {
#define main_loop_doc "The main thread loop" #define main_loop_doc "The main thread loop"
@@ -746,23 +770,8 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
render(now); render(now);
wait_for_events(); wait_for_events();
parse_input(self); parse_input(self);
if (global_state.close_all_windows) { if (global_state.close_all_windows) close_all_windows();
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true); has_open_windows = process_pending_closes(self);
global_state.close_all_windows = false;
}
has_open_windows = false;
for (size_t w = global_state.num_os_windows; w > 0; w--) {
OSWindow *os_window = global_state.os_windows + w - 1;
if (should_os_window_close(os_window)) {
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
}
remove_os_window(os_window->id);
} else has_open_windows = true;
}
} }
if (PyErr_Occurred()) return NULL; if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE; Py_RETURN_NONE;

View File

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

547
kitty/cmds.py Normal file
View File

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

View File

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

View File

@@ -20,6 +20,7 @@ from .config_utils import (
from .constants import cache_dir, defconf from .constants import cache_dir, defconf
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts from .layout import all_layouts
from .rgb import color_from_int
from .utils import log_error from .utils import log_error
MINIMUM_FONT_SIZE = 4 MINIMUM_FONT_SIZE = 4
@@ -50,7 +51,7 @@ def to_cursor_shape(x):
def parse_mods(parts): def parse_mods(parts):
def map_mod(m): def map_mod(m):
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.get(m, m) return {'CTRL': 'CONTROL', 'CMD': 'SUPER', '': 'SUPER', '': 'ALT', 'OPTION': 'ALT'}.get(m, m)
mods = 0 mods = 0
for m in parts: for m in parts:
@@ -107,11 +108,13 @@ def parse_key_action(action):
args = tuple(map(parse_key_action, filter(None, parts))) args = tuple(map(parse_key_action, filter(None, parts)))
elif func == 'send_text': elif func == 'send_text':
args = rest.split(' ', 1) args = rest.split(' ', 1)
elif func == 'run_simple_kitten': elif func in ('run_kitten', 'run_simple_kitten'):
if func == 'run_simple_kitten':
func = 'run_kitten'
args = rest.split(' ', 2) args = rest.split(' ', 2)
elif func == 'goto_tab': elif func == 'goto_tab':
args = (max(0, int(rest)), ) args = (max(0, int(rest)), )
elif func == 'goto_layout': elif func == 'goto_layout' or func == 'kitty_shell':
args = [rest] args = [rest]
elif func == 'set_font_size': elif func == 'set_font_size':
args = (float(rest),) args = (float(rest),)
@@ -120,16 +123,36 @@ def parse_key_action(action):
return KeyAction(func, args) return KeyAction(func, args)
def parse_key(val, keymap): all_key_actions = set()
sequence_sep = '>'
def parse_key(val, keymap, sequence_map):
sc, action = val.partition(' ')[::2] sc, action = val.partition(' ')[::2]
sc, action = sc.strip(), action.strip() sc, action = sc.strip().strip(sequence_sep), action.strip()
if not sc or not action: if not sc or not action:
return return
mods, key = parse_shortcut(sc) is_sequence = sequence_sep in sc
if key is None: if is_sequence:
log_error('Shortcut: {} has unknown key, ignoring'.format( trigger = None
val)) rest = []
return for part in sc.split(sequence_sep):
mods, key = parse_shortcut(part)
if key is None:
log_error('Shortcut: {} has unknown key, ignoring'.format(
sc))
return
if trigger is None:
trigger = mods, key
else:
rest.append((mods, key))
rest = tuple(rest)
else:
mods, key = parse_shortcut(sc)
if key is None:
log_error('Shortcut: {} has unknown key, ignoring'.format(
sc))
return
try: try:
paction = parse_key_action(action) paction = parse_key_action(action)
except Exception: except Exception:
@@ -137,7 +160,12 @@ def parse_key(val, keymap):
action)) action))
else: else:
if paction is not None: if paction is not None:
keymap[(mods, key)] = paction all_key_actions.add(paction.func)
if is_sequence:
s = sequence_map.setdefault(trigger, {})
s[rest] = paction
else:
keymap[(mods, key)] = paction
def parse_symbol_map(val): def parse_symbol_map(val):
@@ -177,7 +205,7 @@ def parse_send_text_bytes(text):
).encode('utf-8') ).encode('utf-8')
def parse_send_text(val, keymap): def parse_send_text(val, keymap, sequence_map):
parts = val.split(' ') parts = val.split(' ')
def abort(msg): def abort(msg):
@@ -190,7 +218,7 @@ def parse_send_text(val, keymap):
mode, sc = parts[:2] mode, sc = parts[:2]
text = ' '.join(parts[2:]) text = ' '.join(parts[2:])
key_str = '{} send_text {} {}'.format(sc, mode, text) key_str = '{} send_text {} {}'.format(sc, mode, text)
return parse_key(key_str, keymap) return parse_key(key_str, keymap, sequence_map)
def to_modifiers(val): def to_modifiers(val):
@@ -293,7 +321,6 @@ type_map = {
'macos_option_as_alt': to_bool, 'macos_option_as_alt': to_bool,
'macos_titlebar_color': macos_titlebar_color, 'macos_titlebar_color': macos_titlebar_color,
'box_drawing_scale': box_drawing_scale, 'box_drawing_scale': box_drawing_scale,
'x11_bell_volume': int,
'background_opacity': unit_float, 'background_opacity': unit_float,
'tab_separator': tab_separator, 'tab_separator': tab_separator,
'active_tab_font_style': tab_font_style, 'active_tab_font_style': tab_font_style,
@@ -309,8 +336,8 @@ for name in (
' selection_foreground selection_background url_color' ' selection_foreground selection_background url_color'
).split(): ).split():
type_map[name] = to_color type_map[name] = to_color
for i in range(16): for i in range(256):
type_map['color%d' % i] = to_color type_map['color{}'.format(i)] = to_color
for a in ('active', 'inactive'): for a in ('active', 'inactive'):
for b in ('foreground', 'background'): for b in ('foreground', 'background'):
type_map['%s_tab_%s' % (a, b)] = to_color type_map['%s_tab_%s' % (a, b)] = to_color
@@ -318,14 +345,14 @@ for a in ('active', 'inactive'):
def special_handling(key, val, ans): def special_handling(key, val, ans):
if key == 'map': if key == 'map':
parse_key(val, ans['keymap']) parse_key(val, ans['keymap'], ans['sequence_map'])
return True return True
if key == 'symbol_map': if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val)) ans['symbol_map'].update(parse_symbol_map(val))
return True return True
if key == 'send_text': if key == 'send_text':
# For legacy compatibility # For legacy compatibility
parse_send_text(val, ans['keymap']) parse_send_text(val, ans['keymap'], ans['sequence_map'])
return True return True
@@ -338,6 +365,7 @@ default_config_path = os.path.join(
def parse_config(lines, check_keys=True): def parse_config(lines, check_keys=True):
ans = { ans = {
'keymap': {}, 'keymap': {},
'sequence_map': {},
'symbol_map': {}, 'symbol_map': {},
} }
parse_config_base( parse_config_base(
@@ -351,24 +379,48 @@ def parse_config(lines, check_keys=True):
return ans return ans
Options, defaults = init_config(default_config_path, parse_config) def parse_defaults(lines, check_keys=False):
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset( ans = parse_config(lines, check_keys)
'combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'. dfctl = defines.default_color_table()
for i in range(16, 256):
k = 'color{}'.format(i)
ans.setdefault(k, color_from_int(dfctl[i]))
return ans
Options, defaults = init_config(default_config_path, parse_defaults)
actions = frozenset(all_key_actions) | frozenset(
'run_simple_kitten combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
split() split()
) )
no_op_actions = frozenset({'noop', 'no-op', 'no_op'}) no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
def merge_keymaps(defaults, newvals): def merge_keys(ans, defaults, newvals):
ans = defaults.copy() ans['keymap'] = defaults['keymap'].copy()
for k, v in newvals.items(): ans['sequence_map'] = {t: r.copy() for t, r in defaults['sequence_map'].items()}
# Merge the keymap
for k, v in newvals['keymap'].items():
ans['sequence_map'].pop(k, None)
f = v.func f = v.func
if f in no_op_actions: if f in no_op_actions:
ans.pop(k, None) ans['keymap'].pop(k, None)
continue elif f in actions:
if f in actions: ans['keymap'][k] = v
ans[k] = v # Merge the sequence map
return ans for trigger, rest_map in newvals['sequence_map'].items():
ans['keymap'].pop(trigger, None)
if trigger in newvals['keymap']:
log_error('The shortcut for {} has conflicting definitions'.format(newvals['keymap'][trigger].func))
s = ans['sequence_map'].setdefault(trigger, {})
for k, v in rest_map.items():
f = v.func
if f in no_op_actions:
s.pop(k, None)
elif f in actions:
s[k] = v
ans['sequence_map'] = {k: v for k, v in ans['sequence_map'].items() if v}
def merge_dicts(defaults, newvals): def merge_dicts(defaults, newvals):
@@ -381,13 +433,16 @@ def merge_configs(defaults, vals):
ans = {} ans = {}
for k, v in defaults.items(): for k, v in defaults.items():
if isinstance(v, dict): if isinstance(v, dict):
newvals = vals.get(k, {}) if k not in ('keymap', 'sequence_map'):
if k == 'keymap': newvals = vals.get(k, {})
ans['keymap'] = merge_keymaps(v, newvals)
else:
ans[k] = merge_dicts(v, newvals) ans[k] = merge_dicts(v, newvals)
else: else:
ans[k] = vals.get(k, v) ans[k] = vals.get(k, v)
merge_keys(
ans,
{'keymap': defaults.get('keymap', {}), 'sequence_map': defaults.get('sequence_map', {})},
{'keymap': vals.get('keymap', {}), 'sequence_map': vals.get('sequence_map', {})}
)
return ans return ans
@@ -406,7 +461,7 @@ def load_config(*paths, overrides=None) -> Options:
if overrides is not None: if overrides is not None:
vals = parse_config(overrides) vals = parse_config(overrides)
ans = merge_configs(ans, vals) ans = merge_configs(ans, vals)
return Options(**ans) return Options(ans)
def build_ansi_color_table(opts: Options = defaults): def build_ansi_color_table(opts: Options = defaults):
@@ -417,7 +472,7 @@ def build_ansi_color_table(opts: Options = defaults):
def col(i): def col(i):
return as_int(getattr(opts, 'color{}'.format(i))) return as_int(getattr(opts, 'color{}'.format(i)))
return list(map(col, range(16))) return list(map(col, range(256)))
def atomic_save(data, path): def atomic_save(data, path):

View File

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

View File

@@ -11,7 +11,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss from .fast_data_types import set_boss as set_c_boss
appname = 'kitty' appname = 'kitty'
version = (0, 8, 2) version = (0, 9, 0)
str_version = '.'.join(map(str, version)) str_version = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos = 'darwin' in _plat is_macos = 'darwin' in _plat
@@ -27,7 +27,22 @@ def _get_config_dir():
if 'KITTY_CONFIG_DIRECTORY' in os.environ: if 'KITTY_CONFIG_DIRECTORY' in os.environ:
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY'])) return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config'))) locations = []
if 'XDG_CONFIG_HOME' in os.environ:
locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME'])))
locations.append(os.path.expanduser('~/.config'))
if is_macos:
locations.append(os.path.expanduser('~/Library/Preferences'))
if 'XDG_CONFIG_DIRS' in os.environ:
for loc in os.environ['XDG_CONFIG_DIRS'].split(os.pathsep):
locations.append(os.path.abspath(os.path.expanduser(loc)))
for loc in locations:
if loc:
q = os.path.join(loc, appname)
if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')):
return q
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config'))
ans = os.path.join(candidate, appname) ans = os.path.join(candidate, appname)
os.makedirs(ans, exist_ok=True) os.makedirs(ans, exist_ok=True)
return ans return ans
@@ -46,10 +61,7 @@ def _get_cache_dir():
else: else:
candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache') candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache')
candidate = os.path.join(os.path.expanduser(candidate), appname) candidate = os.path.join(os.path.expanduser(candidate), appname)
try: os.makedirs(candidate, exist_ok=True)
os.makedirs(candidate)
except FileExistsError:
pass
return candidate return candidate

View File

@@ -203,6 +203,9 @@ PyInit_fast_data_types(void) {
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT); PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT); PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
PyModule_AddStringMacro(m, ERROR_PREFIX); PyModule_AddStringMacro(m, ERROR_PREFIX);
#ifdef KITTY_VCS_REV
PyModule_AddStringMacro(m, KITTY_VCS_REV);
#endif
PyModule_AddIntMacro(m, CURSOR_BLOCK); PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM); PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE); PyModule_AddIntMacro(m, CURSOR_UNDERLINE);

View File

@@ -265,6 +265,5 @@ void wakeup_io_loop(bool);
void scroll_event(double, double); void scroll_event(double, double);
void fake_scroll(int, bool); void fake_scroll(int, bool);
void set_special_key_combo(int glfw_key, int mods); void set_special_key_combo(int glfw_key, int mods);
void on_text_input(unsigned int codepoint, int mods); void on_key_input(int key, int scancode, int action, int mods, const char*, int);
void on_key_input(int key, int scancode, int action, int mods);
void request_window_attention(id_type, bool); void request_window_attention(id_type, bool);

View File

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

10
kitty/glfw-wrapper.c generated
View File

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

80
kitty/glfw-wrapper.h generated
View File

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

View File

@@ -16,6 +16,7 @@ extern void cocoa_set_titlebar_color(void *w);
#endif #endif
static GLFWcursor *standard_cursor = NULL, *click_cursor = NULL, *arrow_cursor = NULL; static GLFWcursor *standard_cursor = NULL, *click_cursor = NULL, *arrow_cursor = NULL;
static bool event_loop_blocking_with_no_timeout = false;
void void
update_os_window_viewport(OSWindow *window, bool notify_boss) { update_os_window_viewport(OSWindow *window, bool notify_boss) {
@@ -74,9 +75,20 @@ is_window_ready_for_callbacks() {
static inline void static inline void
show_mouse_cursor(GLFWwindow *w) { show_mouse_cursor(GLFWwindow *w) {
if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
} }
// On Cocoa, glfwWaitEvents() can block indefinitely because of the way Cocoa
// works. See https://github.com/glfw/glfw/issues/1251. I have noticed this
// happening in particular with window resize events, when waiting with no
// timeout. See https://github.com/kovidgoyal/kitty/issues/458
// So we use an unlovely hack to workaround that case
#ifdef __APPLE__
#define unjam_event_loop() { if (event_loop_blocking_with_no_timeout) wakeup_main_loop(); }
#else
#define unjam_event_loop()
#endif
static void static void
framebuffer_size_callback(GLFWwindow *w, int width, int height) { framebuffer_size_callback(GLFWwindow *w, int width, int height) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
@@ -84,6 +96,7 @@ framebuffer_size_callback(GLFWwindow *w, int width, int height) {
OSWindow *window = global_state.callback_os_window; OSWindow *window = global_state.callback_os_window;
window->has_pending_resizes = true; global_state.has_pending_resizes = true; window->has_pending_resizes = true; global_state.has_pending_resizes = true;
window->last_resize_event_at = monotonic(); window->last_resize_event_at = monotonic();
unjam_event_loop();
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height); } else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
@@ -96,21 +109,13 @@ refresh_callback(GLFWwindow *w) {
} }
static void static void
char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) { key_callback(GLFWwindow *w, int key, int scancode, int action, int mods, const char* text, int state) {
if (!set_callback_window(w)) return;
global_state.callback_os_window->cursor_blink_zero_time = monotonic();
if (is_window_ready_for_callbacks()) on_text_input(codepoint, mods);
global_state.callback_os_window = NULL;
}
static void
key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
global_state.callback_os_window->cursor_blink_zero_time = monotonic(); global_state.callback_os_window->cursor_blink_zero_time = monotonic();
if (key >= 0 && key <= GLFW_KEY_LAST) { if (key >= 0 && key <= GLFW_KEY_LAST) {
global_state.callback_os_window->is_key_pressed[key] = action == GLFW_RELEASE ? false : true; global_state.callback_os_window->is_key_pressed[key] = action == GLFW_RELEASE ? false : true;
if (is_window_ready_for_callbacks()) on_key_input(key, scancode, action, mods);
} }
if (is_window_ready_for_callbacks()) on_key_input(key, scancode, action, mods, text, state);
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
@@ -231,7 +236,11 @@ static GLFWimage logo = {0};
static PyObject* static PyObject*
set_default_window_icon(PyObject UNUSED *self, PyObject *args) { set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
Py_ssize_t sz; Py_ssize_t sz;
if(!PyArg_ParseTuple(args, "s#ii", &(logo.pixels), &sz, &(logo.width), &(logo.height))) return NULL; const char *logo_data;
if(!PyArg_ParseTuple(args, "s#ii", &(logo_data), &sz, &(logo.width), &(logo.height))) return NULL;
sz = (MAX(logo.width * logo.height, sz));
logo.pixels = malloc(sz);
if (logo.pixels) memcpy(logo.pixels, logo_data, sz);
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@@ -338,7 +347,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
PyErr_SetString(PyExc_ValueError, "Too many windows"); PyErr_SetString(PyExc_ValueError, "Too many windows");
return NULL; return NULL;
} }
bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false; bool want_semi_transparent = (1.0 - OPT(background_opacity) >= 0.01) ? true : false;
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent);
GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL); GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL);
if (glfw_window == NULL) { if (glfw_window == NULL) {
@@ -375,11 +384,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
update_os_window_viewport(w, false); update_os_window_viewport(w, false);
glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback); glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback);
glfwSetWindowRefreshCallback(glfw_window, refresh_callback); glfwSetWindowRefreshCallback(glfw_window, refresh_callback);
glfwSetCharModsCallback(glfw_window, char_mods_callback);
glfwSetMouseButtonCallback(glfw_window, mouse_button_callback); glfwSetMouseButtonCallback(glfw_window, mouse_button_callback);
glfwSetScrollCallback(glfw_window, scroll_callback); glfwSetScrollCallback(glfw_window, scroll_callback);
glfwSetCursorPosCallback(glfw_window, cursor_pos_callback); glfwSetCursorPosCallback(glfw_window, cursor_pos_callback);
glfwSetKeyCallback(glfw_window, key_callback); glfwSetKeyboardCallback(glfw_window, key_callback);
glfwSetWindowFocusCallback(glfw_window, window_focus_callback); glfwSetWindowFocusCallback(glfw_window, window_focus_callback);
glfwSetDropCallback(glfw_window, drop_callback); glfwSetDropCallback(glfw_window, drop_callback);
#ifdef __APPLE__ #ifdef __APPLE__
@@ -488,18 +496,6 @@ glfw_terminate(PYNOARG) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
PyObject*
glfw_wait_events(PyObject UNUSED *self, PyObject *args) {
double time = -1;
if (PyTuple_GET_SIZE(args) > 0) {
time = PyFloat_AsDouble(PyTuple_GET_ITEM(args, 0));
if (PyErr_Occurred()) PyErr_Clear();
}
if (time < 0) glfwWaitEvents();
else glfwWaitEventsTimeout(time);
Py_RETURN_NONE;
}
PyObject* PyObject*
glfw_post_empty_event(PYNOARG) { glfw_post_empty_event(PYNOARG) {
glfwPostEmptyEvent(); glfwPostEmptyEvent();
@@ -604,7 +600,7 @@ ring_audio_bell(OSWindow *w) {
if (now - last_bell_at <= 0.1) return; if (now - last_bell_at <= 0.1) return;
last_bell_at = now; last_bell_at = now;
if (w->handle) { if (w->handle) {
glfwWindowBell(w->handle, OPT(x11_bell_volume)); glfwWindowBell(w->handle);
} }
} }
@@ -625,9 +621,7 @@ set_os_window_title(OSWindow *w, const char *title) {
void void
hide_mouse(OSWindow *w) { hide_mouse(OSWindow *w) {
if (glfwGetInputMode(w->handle, GLFW_CURSOR) != GLFW_CURSOR_HIDDEN) { glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
} }
void void
@@ -637,8 +631,8 @@ swap_window_buffers(OSWindow *w) {
void void
event_loop_wait(double timeout) { event_loop_wait(double timeout) {
if (timeout < 0) glfwWaitEvents(); if (timeout < 0) { event_loop_blocking_with_no_timeout = true; glfwWaitEvents(); event_loop_blocking_with_no_timeout = false; }
else if (timeout > 0) glfwWaitEventsTimeout(timeout); else glfwWaitEventsTimeout(timeout);
} }
void void
@@ -771,7 +765,6 @@ static PyMethodDef module_methods[] = {
METHODB(glfw_poll_events, METH_NOARGS), METHODB(glfw_poll_events, METH_NOARGS),
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""}, {"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""}, {"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
{"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""},
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""},
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""},
@@ -780,10 +773,19 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };
void cleanup_glfw(void) {
if (logo.pixels) free(logo.pixels);
logo.pixels = NULL;
}
// constants {{{ // constants {{{
bool bool
init_glfw(PyObject *m) { init_glfw(PyObject *m) {
if (PyModule_AddFunctions(m, module_methods) != 0) return false; if (PyModule_AddFunctions(m, module_methods) != 0) return false;
if (Py_AtExit(cleanup_glfw) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the glfw exit handler");
return false;
}
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; #define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
ADDC(GLFW_RELEASE); ADDC(GLFW_RELEASE);
ADDC(GLFW_PRESS); ADDC(GLFW_PRESS);

View File

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

View File

@@ -251,6 +251,10 @@ def get_shortcut(keymap, mods, key, scancode):
return keymap.get((mods & 0b1111, key)) return keymap.get((mods & 0b1111, key))
def shortcut_matches(s, mods, key, scancode):
return s[0] & 0b1111 == mods & 0b1111 and s[1] == key
def generate_key_table(): def generate_key_table():
# To run this, use: python3 . -c "from kitty.keys import *; generate_key_table()" # To run this, use: python3 . -c "from kitty.keys import *; generate_key_table()"
import os import os

View File

@@ -1,5 +1,10 @@
# vim:fileencoding=utf-8:ft=conf # vim:fileencoding=utf-8:ft=conf
# You can include secondary config files via the "include" directive.
# If you use a relative path for include, it is resolved with respect to the
# location od the current config file. For example:
# include other.conf
# Fonts {{{ # Fonts {{{
# Font family. You can also specify different fonts for the # Font family. You can also specify different fonts for the
# bold/italic/bold-italic variants. By default they are derived automatically, # bold/italic/bold-italic variants. By default they are derived automatically,
@@ -242,7 +247,8 @@ selection_foreground #000000
selection_background #FFFACD selection_background #FFFACD
# The 16 terminal colors. There are 8 basic colors, each color has a dull and # The 16 terminal colors. There are 8 basic colors, each color has a dull and
# bright version. # bright version. You can also set the remaining colors from the 256 color table
# as color16 to color256.
# black # black
color0 #000000 color0 #000000
@@ -323,6 +329,12 @@ term xterm-kitty
# For example: # For example:
# map ctrl+shift+e combine : new_window : next_layout # map ctrl+shift+e combine : new_window : next_layout
# this will create a new window and switch to the next available layout # this will create a new window and switch to the next available layout
#
# You can use multi-key shortcuts using the syntax shown below:
# map key1>key2>key3 action
# For example:
# map ctrl+f>2 set_font_size 20
# this will change the font size to 20 points when you press ctrl+f and then 2
# Clipboard {{{ # Clipboard {{{
map ctrl+shift+v paste_from_clipboard map ctrl+shift+v paste_from_clipboard
@@ -392,6 +404,7 @@ map ctrl+shift+q close_tab
map ctrl+shift+l next_layout map ctrl+shift+l next_layout
map ctrl+shift+. move_tab_forward map ctrl+shift+. move_tab_forward
map ctrl+shift+, move_tab_backward map ctrl+shift+, move_tab_backward
map ctrl+shift+alt+t set_tab_title
# You can also create shortcuts to go to specific tabs, with 1 being the first tab # You can also create shortcuts to go to specific tabs, with 1 being the first tab
# map ctrl+alt+1 goto_tab 1 # map ctrl+alt+1 goto_tab 1
# map ctrl+alt+2 goto_tab 2 # map ctrl+alt+2 goto_tab 2
@@ -415,15 +428,39 @@ map ctrl+shift+backspace restore_font_size
# map ctrl+shift+f7 set_font_size 20.5 # map ctrl+shift+f7 set_font_size 20.5
# }}} # }}}
# Select and act on visible text {{{
# Use the hints kitten to select text and either pass it to an external program or
# insert it into the terminal or copy it to the clipboard.
#
# Open a currently visible URL using the keyboard. The program used to open the
# URL is specified in open_url_with.
map ctrl+shift+e run_kitten text hints
# Select a path/filename and insert it into the terminal. Useful, for instance to
# run git commands on a filename output from a previous git command.
map ctrl+shift+p>f run_kitten text hints --type path --program -
# Select a path/filename and open it with the default open program.
map ctrl+shift+p>shift+f run_kitten text hints --type path
# Select a line of text and insert it into the terminal. Use for the
# output of things like: ls -1
map ctrl+shift+p>l run_kitten text hints --type line --program -
# Select words and insert into terminal.
map ctrl+shift+p>w run_kitten text hints --type word --program -
# The hints kitten has many more modes of operation that you can map to different
# shortcuts. For a full description run: kitty +kitten hints --help
# }}}
# Miscellaneous {{{ # Miscellaneous {{{
map ctrl+shift+f11 toggle_fullscreen map ctrl+shift+f11 toggle_fullscreen
map ctrl+shift+u input_unicode_character map ctrl+shift+u input_unicode_character
map ctrl+shift+f2 edit_config_file map ctrl+shift+f2 edit_config_file
# Open a currently visible URL using the keyboard. The program used to open the URL is specified in open_url_with. You can customize how the URLs are
# You can customize how the URLs are detected and opened by specifying command line options to # Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.
# url_hints. For example: map ctrl+shift+escape kitty_shell window
# map ctrl+shift+e run_simple_kitten text url_hints --program firefox --regex "http://[^ ]+"
map ctrl+shift+e run_simple_kitten text url_hints
# Sending arbitrary text on shortcut key presses # Sending arbitrary text on shortcut key presses
# You can tell kitty to send arbitrary (UTF-8) encoded text to # You can tell kitty to send arbitrary (UTF-8) encoded text to
@@ -462,8 +499,4 @@ macos_hide_titlebar no
# break any Alt+key keyboard shortcuts in your terminal programs, but you # break any Alt+key keyboard shortcuts in your terminal programs, but you
# can use the macOS unicode input technique. # can use the macOS unicode input technique.
macos_option_as_alt yes macos_option_as_alt yes
# The number is a percentage of maximum volume.
# See man XBell for details.
x11_bell_volume 80
# }}} # }}}

View File

@@ -141,10 +141,17 @@ def _main():
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1': if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
os.chdir(os.path.expanduser('~')) os.chdir(os.path.expanduser('~'))
args = macos_cmdline() args = macos_cmdline()
if not os.path.isdir(os.getcwd()): try:
cwd_ok = os.path.isdir(os.getcwd())
except Exception:
cwd_ok = False
if not cwd_ok:
os.chdir(os.path.expanduser('~')) os.chdir(os.path.expanduser('~'))
args, rest = parse_args(args=args) args, rest = parse_args(args=args)
args.args = rest args.args = rest
if args.debug_config:
create_opts(args, debug_config=True)
return
if getattr(args, 'detach', False): if getattr(args, 'detach', False):
detach() detach()
if args.cmd: if args.cmd:
@@ -158,7 +165,9 @@ def _main():
is_first = single_instance(args.instance_group) is_first = single_instance(args.instance_group)
if not is_first: if not is_first:
import json import json
data = {'cmd': 'new_instance', 'args': tuple(sys.argv), 'startup_id': os.environ.get('DESKTOP_STARTUP_ID')} data = {'cmd': 'new_instance', 'args': tuple(sys.argv),
'startup_id': os.environ.get('DESKTOP_STARTUP_ID'),
'cwd': os.getcwd()}
data = json.dumps(data, ensure_ascii=False).encode('utf-8') data = json.dumps(data, ensure_ascii=False).encode('utf-8')
single_instance.socket.sendall(data) single_instance.socket.sendall(data)
return return

View File

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

View File

@@ -310,7 +310,7 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
} }
if (i > 0) { if (i > 0) {
code = utoi(screen->parser_buf, i); code = utoi(screen->parser_buf, i);
if (i < limit - 1 && screen->parser_buf[i] == ';') i++; if (i < limit && screen->parser_buf[i] == ';') i++;
} }
PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + i, limit - i); PyObject *string = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + i, limit - i);
if (string != NULL) { if (string != NULL) {

View File

@@ -10,464 +10,9 @@ import types
from functools import partial from functools import partial
from .cli import emph, parse_args from .cli import emph, parse_args
from .config import parse_send_text_bytes
from .constants import appname, version from .constants import appname, version
from .tabs import SpecialWindow from .utils import parse_address_spec, read_with_timeout
from .utils import non_blocking_read, parse_address_spec, read_with_timeout from .cmds import cmap, parse_subcommand_cli
class MatchError(ValueError):
hide_traceback = True
def __init__(self, expression, target='windows'):
ValueError.__init__(self, 'No matching {} for expression: {}'.format(target, expression))
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...'):
def w(func):
func.short_desc = short_desc
func.argspec = argspec
func.desc = desc or short_desc
func.name = func.__name__[4:].replace('_', '-')
func.options_spec = options_spec
func.is_cmd = True
func.impl = lambda: globals()[func.__name__[4:]]
func.no_response = no_response
return func
return w
def parse_subcommand_cli(func, args):
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name))
return opts, items
MATCH_WINDOW_OPTION = '''\
--match -m
The window to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of windows. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression.
'''
MATCH_TAB_OPTION = '''\
--match -m
The tab to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of tabs. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression. When using title or id, first a matching tab is
looked for and if not found a matching window is looked for, and the tab
for that window is used.
'''
# ls {{{
@cmd(
'List all tabs/windows',
'List all windows. The list is returned as JSON tree. The top-level is a list of'
' operating system {appname} windows. Each OS window has an |_ id| and a list'
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
' |_ command-line| of the process running in the window.\n\n'
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
argspec=''
)
def cmd_ls(global_opts, opts, args):
pass
def ls(boss, window):
data = list(boss.list_os_windows())
data = json.dumps(data, indent=2, sort_keys=True)
return data
# }}}
# set_font_size {{{
@cmd(
'Set the font size in all windows',
'Sets the font size to the specified size, in pts.',
argspec='FONT_SIZE'
)
def cmd_set_font_size(global_opts, opts, args):
try:
return {'size': float(args[0])}
except IndexError:
raise SystemExit('No font size specified')
def set_font_size(boss, window, payload):
boss.set_font_size(payload['size'])
# }}}
# send_text {{{
@cmd(
'Send arbitrary text to specified windows',
'Send arbitrary text to specified windows. The text follows Python'
' escaping rules. So you can use escapes like |_ \\x1b| to send control codes'
' and |_ \\u21fa| to send unicode characters. If you use the |_ --match| option'
' the text will be sent to all matched windows. By default, text is sent to'
' only the currently active window.',
options_spec=MATCH_WINDOW_OPTION + '''\n
--stdin
type=bool-set
Read the text to be sent from |_ stdin|. Note that in this case the text is sent as is,
not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end reading.
--from-file
Path to a file whose contents you wish to send. Note that in this case the file contents
are sent as is, not interpreted for escapes.
''',
no_response=True,
argspec='[TEXT TO SEND]'
)
def cmd_send_text(global_opts, opts, args):
limit = 1024
ret = {'match': opts.match, 'is_binary': False}
def pipe(src=sys.stdin):
ret['is_binary'] = True
import select
with non_blocking_read() as fd:
keep_going = True
while keep_going:
rd = select.select([fd], [], [])
if rd:
data = sys.stdin.buffer.read()
if not data:
break
data = data.decode('utf-8')
if '\x04' in data:
data = data[:data.index('\x04')]
keep_going = False
while data:
ret['text'] = data[:limit]
yield ret
data = data[limit:]
else:
break
def chunks(text):
ret['is_binary'] = False
while text:
ret['text'] = text[:limit]
yield ret
text = text[limit:]
def file_pipe(path):
ret['is_binary'] = True
with open(path, encoding='utf-8') as f:
while True:
data = f.read(limit)
if not data:
break
ret['text'] = data
yield ret
sources = []
if opts.stdin:
sources.append(pipe())
if opts.from_file:
sources.append(file_pipe(opts.from_file))
text = ' '.join(args)
sources.append(chunks(text))
def chain():
for src in sources:
yield from src
return chain()
def send_text(boss, window, payload):
windows = [boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
for window in windows:
if window is not None:
window.write_to_child(data)
# }}}
# set_window_title {{{
@cmd(
'Set the window title',
'Set the title for the specified window(s). If you use the |_ --match| option'
' the title will be set for all matched windows. By default, only the window'
' in which the command is run is affected. If you do not specify a title, the'
' last title set by the child process running in the window will be used.',
options_spec=MATCH_WINDOW_OPTION,
argspec='TITLE ...'
)
def cmd_set_window_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_window_title(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window:
window.set_title(payload['title'])
# }}}
# set_tab_title {{{
@cmd(
'Set the tab title',
'Set the title for the specified tab(s). If you use the |_ --match| option'
' the title will be set for all matched tabs. By default, only the tab'
' in which the command is run is affected. If you do not specify a title, the'
' title of the currently active window in the tab is used.',
options_spec=MATCH_TAB_OPTION,
argspec='TITLE ...'
)
def cmd_set_tab_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_tab_title(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
for tab in tabs:
if tab:
tab.set_title(payload['title'])
# }}}
# close_window {{{
@cmd(
'Close the specified window(s)',
options_spec=MATCH_WINDOW_OPTION + '''\n
--self
type=bool-set
If specified close the window this command is run in, rather than the active window.
''',
argspec=''
)
def cmd_close_window(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_window(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload['self'] else boss.active_window]
for window in windows:
if window:
boss.close_window(window)
# }}}
# close_tab {{{
@cmd(
'Close the specified tab(s)',
options_spec=MATCH_TAB_OPTION + '''\n
--self
type=bool-set
If specified close the tab this command is run in, rather than the active tab.
''',
argspec=''
)
def cmd_close_tab(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_tab(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window and payload['self'] else boss.active_tab]
for tab in tabs:
if window:
if tab:
boss.close_tab(tab)
# }}}
# new_window {{{
@cmd(
'Open new window',
'Open a new window in the specified tab. If you use the |_ --match| option'
' the first matching tab is used. Otherwise the currently active tab is used.'
' Prints out the id of the newly opened window. Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:\n'
'|_ kitty @ new-window --title Email mutt|',
options_spec=MATCH_TAB_OPTION + '''\n
--title
The title for the new window. By default it will use the title set by the
program running in it.
--cwd
The initial working directory for the new window.
--keep-focus
type=bool-set
Keep the current window focused instead of switching to the newly opened window
--new-tab
type=bool-set
Open a new tab
--tab-title
When using --new-tab set the title of the tab.
''',
argspec='[CMD ...]'
)
def cmd_new_window(global_opts, opts, args):
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
'keep_focus': opts.keep_focus, 'args': args or []}
def new_window(boss, window, payload):
w = SpecialWindow(cmd=payload['args'] or None, override_title=payload['title'], cwd=payload['cwd'])
old_window = boss.active_window
if payload['new_tab']:
boss._new_tab(w)
tab = boss.active_tab
if payload['tab_title']:
tab.set_title(payload['tab_title'])
wid = boss.active_window.id
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(wid)
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.active_tab]
tab = tabs[0]
w = tab.new_special_window(w)
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(w.id)
# }}}
# focus_window {{{
@cmd(
'Focus the specified window',
options_spec=MATCH_WINDOW_OPTION,
argspec='',
)
def cmd_focus_window(global_opts, opts, args):
return {'match': opts.match}
def focus_window(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window:
boss.set_active_window(window)
break
# }}}
# focus_tab {{{
@cmd(
'Focus the specified tab',
'The active window in the specified tab will be focused.',
options_spec=MATCH_TAB_OPTION,
argspec='',
)
def cmd_focus_tab(global_opts, opts, args):
return {'match': opts.match}
def focus_tab(boss, window, payload):
match = payload['match']
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
tab = tabs[0]
boss.set_active_tab(tab)
# }}}
# get_text {{{
@cmd(
'Get text from the specified window',
options_spec=MATCH_WINDOW_OPTION + '''\n
--extent
default=screen
choices=screen, all, selection
What text to get. The default of screen means all text currently on the screen. all means
all the screen+scrollback and selection means currently selected text.
--ansi
type=bool-set
By default, only plain text is returned. If you specify this flag, the text will
include the formatting escape codes for colors/bold/italic/etc. Note that when
getting the current selection, the result is always plain text.
--self
type=bool-set
If specified get text from the window this command is run in, rather than the active window.
''',
argspec=''
)
def cmd_get_text(global_opts, opts, args):
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
def get_text(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
else:
windows = [window if window and payload['self'] else boss.active_window]
window = windows[0]
if payload['extent'] == 'selection':
ans = window.text_for_selection()
else:
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True)
return ans
# }}}
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
def handle_cmd(boss, window, cmd): def handle_cmd(boss, window, cmd):
@@ -548,7 +93,9 @@ def main(args):
global_opts, items = parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname)) global_opts, items = parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname))
if not items: if not items:
raise SystemExit('You must specify a command') from kitty.shell import main
main(global_opts)
return
cmd = items[0] cmd = items[0]
try: try:
func = cmap[cmd] func = cmap[cmd]

8
kitty/rgb.py generated
View File

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

View File

@@ -1066,7 +1066,7 @@ bool
screen_invert_colors(Screen *self) { screen_invert_colors(Screen *self) {
bool inverted = false; bool inverted = false;
if (self->start_visual_bell_at > 0) { if (self->start_visual_bell_at > 0) {
if (monotonic() - self->start_visual_bell_at <= global_state.opts.visual_bell_duration) inverted = true; if (monotonic() - self->start_visual_bell_at <= OPT(visual_bell_duration)) inverted = true;
else self->start_visual_bell_at = 0; else self->start_visual_bell_at = 0;
} }
if (self->modes.mDECSCNM) inverted = inverted ? false : true; if (self->modes.mDECSCNM) inverted = inverted ? false : true;
@@ -1076,6 +1076,7 @@ screen_invert_colors(Screen *self) {
void void
screen_bell(Screen *self) { screen_bell(Screen *self) {
request_window_attention(self->window_id, OPT(enable_audio_bell)); request_window_attention(self->window_id, OPT(enable_audio_bell));
if (OPT(visual_bell_duration) > 0.0f) self->start_visual_bell_at = monotonic();
} }
void void
@@ -1410,7 +1411,7 @@ screen_apply_selection(Screen *self, void *address, size_t size) {
self->last_selection_scrolled_by = self->scrolled_by; self->last_selection_scrolled_by = self->scrolled_by;
self->selection_updated_once = true; self->selection_updated_once = true;
selection_limits_(selection, &self->last_rendered_selection_start, &self->last_rendered_selection_end); selection_limits_(selection, &self->last_rendered_selection_start, &self->last_rendered_selection_end);
apply_selection(self, address, &self->last_rendered_selection_start, &self->last_rendered_selection_end, 1, self->rectangle_select); apply_selection(self, address, &self->last_rendered_selection_start, &self->last_rendered_selection_end, 1, self->selection.rectangle_select);
selection_limits_(url_range, &self->last_rendered_url_start, &self->last_rendered_url_end); selection_limits_(url_range, &self->last_rendered_url_start, &self->last_rendered_url_end);
apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2, false); apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2, false);
} }
@@ -1626,9 +1627,17 @@ WRAP0(carriage_return)
WRAP2(resize, 1, 1) WRAP2(resize, 1, 1)
WRAP2(set_margins, 1, 1) WRAP2(set_margins, 1, 1)
WRAP2(rescale_images, 1, 1) WRAP2(rescale_images, 1, 1)
WRAP2B(start_selection)
WRAP2B(update_selection) WRAP2B(update_selection)
static PyObject*
start_selection(Screen *self, PyObject *args) {
unsigned int x, y;
int rectangle_select = 0, extend_mode = EXTEND_CELL;
if (!PyArg_ParseTuple(args, "II|pp", &x, &y, &rectangle_select, &extend_mode)) return NULL;
screen_start_selection(self, x, y, rectangle_select, extend_mode);
Py_RETURN_NONE;
}
static PyObject* static PyObject*
change_scrollback_size(Screen *self, PyObject *args) { change_scrollback_size(Screen *self, PyObject *args) {
unsigned int count = 1; unsigned int count = 1;
@@ -1643,7 +1652,7 @@ text_for_selection(Screen *self, PyObject *a UNUSED) {
full_selection_limits_(selection, &start, &end); full_selection_limits_(selection, &start, &end);
PyObject *ans = NULL; PyObject *ans = NULL;
if (start.y == end.y && start.x == end.x) ans = PyTuple_New(0); if (start.y == end.y && start.x == end.x) ans = PyTuple_New(0);
else text_for_range(ans, start, end, self->rectangle_select, true, range_line_, int); else text_for_range(ans, start, end, self->selection.rectangle_select, true, range_line_, int);
return ans; return ans;
} }
@@ -1667,17 +1676,31 @@ is_opt_word_char(char_type ch) {
} }
bool bool
screen_selection_range_for_word(Screen *self, index_type x, index_type y, index_type *s, index_type *e) { screen_selection_range_for_word(Screen *self, index_type x, index_type *y1, index_type *y2, index_type *s, index_type *e) {
if (y >= self->lines || x >= self->columns) return false; if (*y1 >= self->lines || x >= self->columns) return false;
index_type start, end; index_type start, end;
Line *line = visual_line_(self, y); Line *line = visual_line_(self, *y1);
*y2 = *y1;
#define is_ok(x) (is_word_char((line->cells[x].ch)) || is_opt_word_char(line->cells[x].ch)) #define is_ok(x) (is_word_char((line->cells[x].ch)) || is_opt_word_char(line->cells[x].ch))
if (!is_ok(x)) { if (!is_ok(x)) {
start = x; end = x + 1; start = x; end = x + 1;
} else { } else {
start = x, end = x; start = x, end = x;
while(start > 0 && is_ok(start - 1)) start--; while(true) {
while(end < self->columns - 1 && is_ok(end + 1)) end++; while(start > 0 && is_ok(start - 1)) start--;
if (start > 0 || !line->continued || *y1 == 0) break;
line = visual_line_(self, *y1 - 1);
if (!is_ok(self->columns - 1)) break;
(*y1)--; start = self->columns - 1;
}
line = visual_line_(self, *y2);
while(true) {
while(end < self->columns - 1 && is_ok(end + 1)) end++;
if (end < self->columns - 1 || *y2 >= self->lines - 1) break;
line = visual_line_(self, *y2 + 1);
if (!line->continued || !is_ok(0)) break;
(*y2)++; end = 0;
}
} }
*s = start; *e = end; *s = start; *e = end;
return true; return true;
@@ -1733,10 +1756,10 @@ screen_is_selection_dirty(Screen *self) {
} }
void void
screen_start_selection(Screen *self, index_type x, index_type y, bool rectangle_select) { screen_start_selection(Screen *self, index_type x, index_type y, bool rectangle_select, SelectionExtendMode extend_mode) {
self->rectangle_select = rectangle_select;
#define A(attr, val) self->selection.attr = val; #define A(attr, val) self->selection.attr = val;
A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by); A(in_progress, true); A(start_x, x); A(end_x, x); A(start_y, y); A(end_y, y); A(start_scrolled_by, self->scrolled_by); A(end_scrolled_by, self->scrolled_by);
A(in_progress, true); A(rectangle_select, rectangle_select); A(extend_mode, extend_mode);
#undef A #undef A
} }
@@ -1751,6 +1774,50 @@ void
screen_update_selection(Screen *self, index_type x, index_type y, bool ended) { screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by; self->selection.end_x = x; self->selection.end_y = y; self->selection.end_scrolled_by = self->scrolled_by;
if (ended) self->selection.in_progress = false; if (ended) self->selection.in_progress = false;
index_type start, end;
bool found = false;
bool extending_leftwards = self->selection.end_y < self->selection.start_y || (self->selection.end_y == self->selection.start_y && self->selection.end_x < self->selection.start_x);
switch(self->selection.extend_mode) {
case EXTEND_WORD: {
index_type y1 = y, y2;
found = screen_selection_range_for_word(self, x, &y1, &y2, &start, &end);
if (found) {
if (extending_leftwards) {
self->selection.end_x = start; self->selection.end_y = y1;
y1 = self->selection.start_y;
found = screen_selection_range_for_word(self, self->selection.start_x, &y1, &y2, &start, &end);
if (found) {
self->selection.start_x = end; self->selection.start_y = y2;
}
} else {
self->selection.end_x = end; self->selection.end_y = y2;
y1 = self->selection.start_y;
found = screen_selection_range_for_word(self, self->selection.start_x, &y1, &y2, &start, &end);
if (found) {
self->selection.start_x = start; self->selection.start_y = y1;
}
}
}
break;
}
case EXTEND_LINE:
if (extending_leftwards) {
found = screen_selection_range_for_line(self, self->selection.end_y, &start, &end);
if (found) { self->selection.end_x = start; }
found = screen_selection_range_for_line(self, self->selection.start_y, &start, &end);
if (found) { self->selection.start_x = end; }
} else {
found = screen_selection_range_for_line(self, self->selection.start_y, &start, &end);
if (found) { self->selection.start_x = start; }
found = screen_selection_range_for_line(self, self->selection.end_y, &start, &end);
if (found) { self->selection.end_x = end; }
}
break;
case EXTEND_CELL:
break;
}
call_boss(set_primary_selection, NULL);
} }
static PyObject* static PyObject*

View File

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

View File

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

View File

@@ -214,9 +214,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
struct CellRenderData { struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity; GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style; GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
GLint color1, color2;
GLuint xnum, ynum, cursor_x, cursor_y, cursor_w; GLuint xnum, ynum, cursor_x, cursor_y, cursor_w;
}; };
@@ -241,7 +239,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
unsigned int x, y, z; unsigned int x, y, z;
sprite_tracker_current_layout(&x, &y, &z); sprite_tracker_current_layout(&x, &y, &z);
rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y; rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y;
rd->color1 = inverted & 1; rd->color2 = 1 - (inverted & 1); rd->inverted = inverted ? 1 : 0;
rd->background_opacity = OPT(background_opacity); rd->background_opacity = OPT(background_opacity);
#define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name) #define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name)
@@ -472,7 +470,7 @@ init_cursor_program() {
#define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location #define SET_LOC(which) if (strcmp(p->uniforms[i].name, #which) == 0) cursor_uniform_locations[CURSOR_##which] = p->uniforms[i].location
SET_LOC(color); SET_LOC(color);
else SET_LOC(pos); else SET_LOC(pos);
else { fatal("Unknown uniform in cursor program"); } else { fatal("Unknown uniform in cursor program: %s", p->uniforms[i].name); }
} }
if (left) { fatal("Left over uniforms in cursor program"); } if (left) { fatal("Left over uniforms in cursor program"); }
#undef SET_LOC #undef SET_LOC

206
kitty/shell.py Normal file
View File

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

View File

@@ -284,6 +284,7 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
#define KKI(name) PYWRAP1(name) { id_type a, b; unsigned int c; PA("KKI", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; } #define KKI(name) PYWRAP1(name) { id_type a, b; unsigned int c; PA("KKI", &a, &b, &c); name(a, b, c); Py_RETURN_NONE; }
#define KKII(name) PYWRAP1(name) { id_type a, b; unsigned int c, d; PA("KKII", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; } #define KKII(name) PYWRAP1(name) { id_type a, b; unsigned int c, d; PA("KKII", &a, &b, &c, &d); name(a, b, c, d); Py_RETURN_NONE; }
#define KK5I(name) PYWRAP1(name) { id_type a, b; unsigned int c, d, e, f, g; PA("KKIIIII", &a, &b, &c, &d, &e, &f, &g); name(a, b, c, d, e, f, g); Py_RETURN_NONE; } #define KK5I(name) PYWRAP1(name) { id_type a, b; unsigned int c, d, e, f, g; PA("KKIIIII", &a, &b, &c, &d, &e, &f, &g); name(a, b, c, d, e, f, g); Py_RETURN_NONE; }
#define BOOL_SET(name) PYWRAP1(set_##name) { global_state.name = PyObject_IsTrue(args); Py_RETURN_NONE; }
static inline color_type static inline color_type
color_as_int(PyObject *color) { color_as_int(PyObject *color) {
@@ -338,9 +339,9 @@ PYWRAP1(set_options) {
S(cursor_stop_blinking_after, PyFloat_AsDouble); S(cursor_stop_blinking_after, PyFloat_AsDouble);
S(background_opacity, PyFloat_AsDouble); S(background_opacity, PyFloat_AsDouble);
S(inactive_text_alpha, PyFloat_AsDouble); S(inactive_text_alpha, PyFloat_AsDouble);
S(window_padding_width, PyFloat_AsDouble);
S(cursor_shape, PyLong_AsLong); S(cursor_shape, PyLong_AsLong);
S(url_style, PyLong_AsUnsignedLong); S(url_style, PyLong_AsUnsignedLong);
S(x11_bell_volume, PyLong_AsLong);
S(tab_bar_edge, PyLong_AsLong); S(tab_bar_edge, PyLong_AsLong);
S(mouse_hide_wait, PyFloat_AsDouble); S(mouse_hide_wait, PyFloat_AsDouble);
S(wheel_scroll_multiplier, PyFloat_AsDouble); S(wheel_scroll_multiplier, PyFloat_AsDouble);
@@ -368,6 +369,8 @@ PYWRAP1(set_options) {
GA(keymap); set_special_keys(ret); GA(keymap); set_special_keys(ret);
Py_DECREF(ret); if (PyErr_Occurred()) return NULL; Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
GA(sequence_map); set_special_keys(ret);
Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
#define read_adjust(name) { \ #define read_adjust(name) { \
PyObject *al = PyObject_GetAttrString(opts, #name); \ PyObject *al = PyObject_GetAttrString(opts, #name); \
@@ -387,6 +390,8 @@ PYWRAP1(set_options) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
BOOL_SET(in_sequence_mode)
PYWRAP1(set_tab_bar_render_data) { PYWRAP1(set_tab_bar_render_data) {
ScreenRenderData d = {0}; ScreenRenderData d = {0};
id_type os_window_id; id_type os_window_id;
@@ -556,6 +561,7 @@ KK5I(add_borders_rect)
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
MW(current_os_window, METH_NOARGS), MW(current_os_window, METH_NOARGS),
MW(set_options, METH_VARARGS), MW(set_options, METH_VARARGS),
MW(set_in_sequence_mode, METH_O),
MW(handle_for_window_id, METH_VARARGS), MW(handle_for_window_id, METH_VARARGS),
MW(set_logical_dpi, METH_VARARGS), MW(set_logical_dpi, METH_VARARGS),
MW(pt_to_px, METH_O), MW(pt_to_px, METH_O),

View File

@@ -26,9 +26,9 @@ typedef struct {
bool macos_option_as_alt, macos_hide_titlebar; bool macos_option_as_alt, macos_hide_titlebar;
int adjust_line_height_px, adjust_column_width_px; int adjust_line_height_px, adjust_column_width_px;
float adjust_line_height_frac, adjust_column_width_frac; float adjust_line_height_frac, adjust_column_width_frac;
int x11_bell_volume;
float background_opacity; float background_opacity;
float inactive_text_alpha; float inactive_text_alpha;
float window_padding_width;
Edge tab_bar_edge; Edge tab_bar_edge;
bool sync_to_monitor; bool sync_to_monitor;
bool close_on_child_death; bool close_on_child_death;
@@ -133,11 +133,12 @@ typedef struct {
bool is_wayland; bool is_wayland;
bool debug_gl, debug_font_fallback; bool debug_gl, debug_font_fallback;
bool has_pending_resizes; bool has_pending_resizes;
bool in_sequence_mode;
} GlobalState; } GlobalState;
extern GlobalState global_state; extern GlobalState global_state;
#define call_boss(name, ...) { \ #define call_boss(name, ...) if (global_state.boss) { \
PyObject *cret_ = PyObject_CallMethod(global_state.boss, #name, __VA_ARGS__); \ PyObject *cret_ = PyObject_CallMethod(global_state.boss, #name, __VA_ARGS__); \
if (cret_ == NULL) { PyErr_Print(); } \ if (cret_ == NULL) { PyErr_Print(); } \
else Py_DECREF(cret_); \ else Py_DECREF(cret_); \

View File

@@ -21,11 +21,11 @@ from .utils import color_as_int, log_error
from .window import Window, calculate_gl_geometry from .window import Window, calculate_gl_geometry
TabbarData = namedtuple('TabbarData', 'title is_active is_last') TabbarData = namedtuple('TabbarData', 'title is_active is_last')
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd overlay_for') SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd overlay_for env')
def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None): def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None):
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for) return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for, env)
class Tab: # {{{ class Tab: # {{{
@@ -124,25 +124,27 @@ class Tab: # {{{
self.current_layout = self.create_layout_object(layout_name) self.current_layout = self.create_layout_object(layout_name)
self.relayout() self.relayout()
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None): def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None):
if cmd is None: if cmd is None:
if use_shell: if use_shell:
cmd = resolved_shell(self.opts) cmd = resolved_shell(self.opts)
else: else:
cmd = self.args.args or resolved_shell(self.opts) cmd = self.args.args or resolved_shell(self.opts)
env = {} fenv = {}
if env:
fenv.update(env)
if not is_macos and not is_wayland: if not is_macos and not is_wayland:
try: try:
env['WINDOWID'] = str(x11_window_id(self.os_window_id)) fenv['WINDOWID'] = str(x11_window_id(self.os_window_id))
except Exception: except Exception:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, env, cwd_from) ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from)
ans.fork() ans.fork()
return ans return ans
def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None): def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None, overlay_for=None, env=None):
child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd) child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env)
window = Window(self, child, self.opts, self.args, override_title=override_title) window = Window(self, child, self.opts, self.args, override_title=override_title)
if overlay_for is not None: if overlay_for is not None:
overlaid = next(w for w in self.windows if w.id == overlay_for) overlaid = next(w for w in self.windows if w.id == overlay_for)
@@ -247,6 +249,7 @@ class TabBar: # {{{
def __init__(self, os_window_id, opts): def __init__(self, os_window_id, opts):
self.os_window_id = os_window_id self.os_window_id = os_window_id
self.opts = opts
self.num_tabs = 1 self.num_tabs = 1
self.cell_width = 1 self.cell_width = 1
self.data_buffer_size = 0 self.data_buffer_size = 0
@@ -277,6 +280,16 @@ class TabBar: # {{{
self.active_bg = as_rgb(color_as_int(opts.active_tab_background)) self.active_bg = as_rgb(color_as_int(opts.active_tab_background))
self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground)) self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
def patch_colors(self, spec):
if 'active_tab_foreground' in spec:
self.active_fg = (spec['active_tab_foreground'] << 8) | 2
if 'active_tab_background' in spec:
self.active_bg = (spec['active_tab_background'] << 8) | 2
self.screen.color_profile.set_configured_colors(
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))
)
def layout(self): def layout(self):
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id) central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
if tab_bar.width < 2: if tab_bar.width < 2:
@@ -456,6 +469,7 @@ class TabManager: # {{{
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from)) self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
self._set_active_tab(idx) self._set_active_tab(idx)
self.update_tab_bar() self.update_tab_bar()
return self.tabs[idx]
def remove(self, tab): def remove(self, tab):
self._remove_tab(tab) self._remove_tab(tab)

View File

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

View File

@@ -34,7 +34,7 @@ for sz in (16, 32, 64, 128, 256, 512, 1024):
iname = 'icon_{0}x{0}.png'.format(sz) iname = 'icon_{0}x{0}.png'.format(sz)
iname2x = 'icon_{0}x{0}@2x.png'.format(sz // 2) iname2x = 'icon_{0}x{0}@2x.png'.format(sz // 2)
render(iname, sz) render(iname, sz)
if sz > 16: if sz > 16 and sz != 128:
shutil.copy2(iname, iname2x) shutil.copy2(iname, iname2x)
if sz > 512: if sz in (64, 1024):
os.remove(iname) os.remove(iname)

View File

@@ -16,8 +16,8 @@ defns = defaultdict(list)
for line in open('kitty/kitty.conf'): for line in open('kitty/kitty.conf'):
if line.startswith('map '): if line.startswith('map '):
_, sc, name = line.split(maxsplit=2) _, sc, name = line.split(maxsplit=2)
name = name.rstrip().replace(' ', '_') name = name.rstrip().replace(' ', '_').replace('-', '_').replace('___', '_').replace('__', '_').strip('_')
defns[name].append('`' + sc + '`') defns[name].append('`' + sc.replace('>', '') + '`')
defns = [ defns = [
':sc_{}: pass:quotes[{}]'.format(name, ' or '.join(defns[name])) ':sc_{}: pass:quotes[{}]'.format(name, ' or '.join(defns[name]))

View File

@@ -98,3 +98,13 @@ instance of kitty using the `--to` command line argument to `kitty @`. For examp
Note that is all you want to do is run a single kitty "daemon" and have subsequent Note that is all you want to do is run a single kitty "daemon" and have subsequent
kitty invocations appear as new top-level windows, you can use the simpler `--single-instance` kitty invocations appear as new top-level windows, you can use the simpler `--single-instance`
option, see `kitty --help` for that. option, see `kitty --help` for that.
== The builtin kitty shell
You can explore the kitty command language more easily using the builtin kitty
shell. Run `kitty @` with no arguments and you will be dropped into the kitty
shell with completion for kitty command names and options.
You can even open the kitty shell inside a running kitty using a simple
keyboard shortcut (`ctrl+shift+escape` by default). This has the added
advantage that you dont need to use `allow_remote_control` to make it work.

View File

@@ -182,6 +182,9 @@ def init_env(
cflags = shlex.split(cflags) + shlex.split( cflags = shlex.split(cflags) + shlex.split(
sysconfig.get_config_var('CCSHARED') sysconfig.get_config_var('CCSHARED')
) )
if os.path.exists('.git'):
rev = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8').strip()
cflags.append('-DKITTY_VCS_REV="{}"'.format(rev))
ldflags = os.environ.get( ldflags = os.environ.get(
'OVERRIDE_LDFLAGS', 'OVERRIDE_LDFLAGS',
'-Wall ' + ' '.join(sanitize_args) + ('' if debug else ' -O3') '-Wall ' + ' '.join(sanitize_args) + ('' if debug else ' -O3')
@@ -367,7 +370,12 @@ def compile_c_extension(kenv, module, incremental, compilation_database, all_key
parallel_run(todo) parallel_run(todo)
dest = os.path.join(base, module + '.so') dest = os.path.join(base, module + '.so')
if not incremental or newer(dest, *objects): if not incremental or newer(dest, *objects):
run_tool([kenv.cc] + kenv.ldflags + objects + kenv.ldpaths + ['-o', dest], desc='Linking {} ...'.format(emphasis(module))) # Old versions of clang dont like -pthread being passed to the linker
# Dont treat linker warnings as errors (linker generates spurious
# warnings on some old systems)
unsafe = {'-pthread', '-Werror', '-pedantic-errors'}
linker_cflags = list(filter(lambda x: x not in unsafe, kenv.cflags))
run_tool([kenv.cc] + linker_cflags + kenv.ldflags + objects + kenv.ldpaths + ['-o', dest], desc='Linking {} ...'.format(emphasis(module)))
def find_c_files(): def find_c_files():
@@ -549,7 +557,7 @@ Comment=A modern, hackable, featureful, OpenGL based terminal emulator
TryExec=kitty TryExec=kitty
Exec=kitty Exec=kitty
Icon=kitty Icon=kitty
Categories=System; Categories=System;TerminalEmulator;
''' '''
) )
# }}} # }}}