mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-14 12:37:48 +02:00
Compare commits
320 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb5dd364ae | ||
|
|
4a64f812ad | ||
|
|
c2acc2460b | ||
|
|
bea8fd25a7 | ||
|
|
1593baa9f9 | ||
|
|
0447b17af2 | ||
|
|
0d3c5497ff | ||
|
|
7826fefe30 | ||
|
|
10ca74f502 | ||
|
|
715548b144 | ||
|
|
43df7be977 | ||
|
|
9b5f665218 | ||
|
|
5e934c081e | ||
|
|
22dbc94c5f | ||
|
|
2df1273d53 | ||
|
|
54900183ec | ||
|
|
1c72a94b2f | ||
|
|
46bb027d14 | ||
|
|
37f0c8c0a8 | ||
|
|
cad7047a7a | ||
|
|
e6acc69460 | ||
|
|
40d8111717 | ||
|
|
970cc9ba7a | ||
|
|
6e685d01b0 | ||
|
|
b8c5c62585 | ||
|
|
7427e65f60 | ||
|
|
e656a75d5e | ||
|
|
1e249035c7 | ||
|
|
044f53b35b | ||
|
|
e20eff277b | ||
|
|
5ea1d14617 | ||
|
|
1bea64768e | ||
|
|
ab2af0c141 | ||
|
|
0a2fcf1805 | ||
|
|
858f0c1073 | ||
|
|
fb1124b1b9 | ||
|
|
c76db4bfb4 | ||
|
|
1e5d14c834 | ||
|
|
5583f60289 | ||
|
|
4519b3abee | ||
|
|
cff490f881 | ||
|
|
7d8c017215 | ||
|
|
91cdf4af00 | ||
|
|
79db8b43e0 | ||
|
|
c03d99e744 | ||
|
|
7c0cb5481f | ||
|
|
912c5ce4f9 | ||
|
|
39a3c38037 | ||
|
|
5dfe4427cf | ||
|
|
b2eac37164 | ||
|
|
ec8b7853c5 | ||
|
|
d9903f5283 | ||
|
|
ccfb979218 | ||
|
|
ff8387f8e7 | ||
|
|
017947de7f | ||
|
|
2a5ba519f5 | ||
|
|
b2587c1d54 | ||
|
|
7e5230e6f4 | ||
|
|
64cfe8171f | ||
|
|
a9b424e307 | ||
|
|
d9ccbcd0ce | ||
|
|
7cec82f453 | ||
|
|
18c0a449d2 | ||
|
|
00f8f340bf | ||
|
|
e9e889457d | ||
|
|
d41138c4c6 | ||
|
|
69a5c7e3b2 | ||
|
|
92befa26db | ||
|
|
22ac57c374 | ||
|
|
392a301cd8 | ||
|
|
a1f2a7df4d | ||
|
|
04eafbea9b | ||
|
|
cf6c00cebe | ||
|
|
08da87a622 | ||
|
|
59e3b202b9 | ||
|
|
a7bdbb11f2 | ||
|
|
b9fabc62a5 | ||
|
|
d483c3eb33 | ||
|
|
d191896528 | ||
|
|
ca6c607148 | ||
|
|
cc5424c054 | ||
|
|
77551cdfc1 | ||
|
|
c6eab841f9 | ||
|
|
99995fd9dc | ||
|
|
74388b4183 | ||
|
|
f1fc2126bc | ||
|
|
9a0e84293f | ||
|
|
847ef008e2 | ||
|
|
3c4f2aa1b8 | ||
|
|
6c8f499303 | ||
|
|
b0ba4b4a42 | ||
|
|
293cd40509 | ||
|
|
0d10ee1a8c | ||
|
|
d63b852b90 | ||
|
|
9560968a7d | ||
|
|
53980d00f0 | ||
|
|
5bc2cde454 | ||
|
|
d4ff54e0d8 | ||
|
|
f2075f99fd | ||
|
|
f46425c2f9 | ||
|
|
ad4e9bb42c | ||
|
|
97f5cad335 | ||
|
|
788295e534 | ||
|
|
336035f507 | ||
|
|
1883208cf6 | ||
|
|
8d1fde18f8 | ||
|
|
d7633b95d0 | ||
|
|
165f1ccfd1 | ||
|
|
063a663958 | ||
|
|
66bc86e4f2 | ||
|
|
15eb03c5e2 | ||
|
|
c064a2e559 | ||
|
|
9e815212dc | ||
|
|
cb418a0040 | ||
|
|
716bf714db | ||
|
|
f11f770011 | ||
|
|
8a1571f62c | ||
|
|
3876d518bd | ||
|
|
72e14053f5 | ||
|
|
bd7f38eb7d | ||
|
|
e19335377f | ||
|
|
382d2f1ed0 | ||
|
|
9bb85bd294 | ||
|
|
c10ea63818 | ||
|
|
4b3d29ecc3 | ||
|
|
b297c27e09 | ||
|
|
6a09804910 | ||
|
|
c2b4df0666 | ||
|
|
79f3692f1e | ||
|
|
35b2dcb065 | ||
|
|
2b751f56bd | ||
|
|
882d471c90 | ||
|
|
fc64ef41b3 | ||
|
|
b1e4c06220 | ||
|
|
ef9dada80d | ||
|
|
6d49686aee | ||
|
|
3cbb8d5c8f | ||
|
|
ea28e20915 | ||
|
|
4897c4df00 | ||
|
|
b3c5b1185c | ||
|
|
b961e65f22 | ||
|
|
6d9593944a | ||
|
|
a0d5f7a07b | ||
|
|
69e3e5c727 | ||
|
|
04506975e5 | ||
|
|
b5067c1369 | ||
|
|
0f52b69372 | ||
|
|
24bb28b773 | ||
|
|
553d833fd4 | ||
|
|
2d5ce6191e | ||
|
|
5fe77e38b7 | ||
|
|
9a08489112 | ||
|
|
2047ea8eec | ||
|
|
4d510e3318 | ||
|
|
87f7bd2f9e | ||
|
|
2ffe03ee48 | ||
|
|
4c56e76840 | ||
|
|
a3c8f32c1a | ||
|
|
176ffc7f51 | ||
|
|
70bc4f1033 | ||
|
|
0f2196357c | ||
|
|
2759ec1fe1 | ||
|
|
dee2e83fb4 | ||
|
|
65754a2032 | ||
|
|
77292a16d6 | ||
|
|
2402c6f253 | ||
|
|
77140fc798 | ||
|
|
cda97b5451 | ||
|
|
b247fda672 | ||
|
|
8dfe1fcca9 | ||
|
|
a6f13a64dc | ||
|
|
bd5fcb00e0 | ||
|
|
ac2ec44a7f | ||
|
|
d2444bae80 | ||
|
|
a6571ba8db | ||
|
|
5fab30b36f | ||
|
|
9a179a7e90 | ||
|
|
bc1da5525e | ||
|
|
995112d952 | ||
|
|
169315d33d | ||
|
|
114b8dff51 | ||
|
|
61429c73c7 | ||
|
|
2aa37de6ff | ||
|
|
a54586dfa9 | ||
|
|
b4f88b4f81 | ||
|
|
954b7f87a5 | ||
|
|
d113a6c2cf | ||
|
|
52cebf0150 | ||
|
|
827a7d5094 | ||
|
|
a04d19df4a | ||
|
|
7e1580ef09 | ||
|
|
f3ece8b7c4 | ||
|
|
be3b8fcfb7 | ||
|
|
9d5bb3b2f2 | ||
|
|
492ec3dfbf | ||
|
|
309a6e9319 | ||
|
|
12db4683ac | ||
|
|
30b736eed7 | ||
|
|
4d085a00e1 | ||
|
|
dfd84d85a2 | ||
|
|
cb139692f5 | ||
|
|
47836f5086 | ||
|
|
778adfcd3d | ||
|
|
24d9d502b1 | ||
|
|
8dadcf568c | ||
|
|
0f02725f50 | ||
|
|
3beb1e454a | ||
|
|
c174c3cb38 | ||
|
|
f05a58f363 | ||
|
|
9f42e915c8 | ||
|
|
52d5a4679f | ||
|
|
aee14a49f0 | ||
|
|
acf586867c | ||
|
|
7cb392c7ab | ||
|
|
f8d5e30300 | ||
|
|
be5a0e8559 | ||
|
|
7cffb2a714 | ||
|
|
6111bc8ed6 | ||
|
|
1c1519c6e4 | ||
|
|
b55883591d | ||
|
|
ae4a13b249 | ||
|
|
61c4f80e90 | ||
|
|
0ce996120a | ||
|
|
47b8b442dc | ||
|
|
32d23921df | ||
|
|
19374208e0 | ||
|
|
8c83284d5e | ||
|
|
9c25a183db | ||
|
|
24895f0225 | ||
|
|
eefb865e6e | ||
|
|
b1ec1c2678 | ||
|
|
ce583ea460 | ||
|
|
539a8706dc | ||
|
|
467e7e5041 | ||
|
|
d94a5bd386 | ||
|
|
480737a355 | ||
|
|
d63739a598 | ||
|
|
3526e59d3b | ||
|
|
7292d1c9df | ||
|
|
c9a95cacd9 | ||
|
|
8853f6bae2 | ||
|
|
beb18cc250 | ||
|
|
321f1a6650 | ||
|
|
8d8d89573c | ||
|
|
89ee128a92 | ||
|
|
defa2e29ac | ||
|
|
56963c693e | ||
|
|
f098240ace | ||
|
|
4b997a961c | ||
|
|
512a672398 | ||
|
|
6cfb451ec8 | ||
|
|
4a463f7712 | ||
|
|
5ea9700c82 | ||
|
|
1332cf8ac7 | ||
|
|
f8ab5f3f67 | ||
|
|
822311d523 | ||
|
|
713569fcfa | ||
|
|
7d32a77fe4 | ||
|
|
314fe4fe4a | ||
|
|
a9b412baba | ||
|
|
0300a355d0 | ||
|
|
fa53ac7896 | ||
|
|
cb15b98afd | ||
|
|
6a50af12d3 | ||
|
|
45a98b19c5 | ||
|
|
e9e1a7a7c3 | ||
|
|
6804d16519 | ||
|
|
ee8399ba56 | ||
|
|
c03dff2322 | ||
|
|
dc6edf9191 | ||
|
|
70a588cb36 | ||
|
|
81b032a161 | ||
|
|
ddb121b418 | ||
|
|
d2026574b8 | ||
|
|
187fa996f8 | ||
|
|
870522a792 | ||
|
|
5478f6665a | ||
|
|
5d5bbe9b96 | ||
|
|
3a6c7e78df | ||
|
|
10a2f246bc | ||
|
|
17ce474b79 | ||
|
|
d66074f19f | ||
|
|
1693107608 | ||
|
|
e15b16b072 | ||
|
|
9375e671bc | ||
|
|
0e11174aa5 | ||
|
|
baddc966dc | ||
|
|
119582a9d4 | ||
|
|
792b74503c | ||
|
|
4f1971c480 | ||
|
|
a8a1571ed1 | ||
|
|
a79dd3996a | ||
|
|
e6ef2fceea | ||
|
|
56063b96fd | ||
|
|
cae19bba60 | ||
|
|
1f91250a40 | ||
|
|
ee1e65e619 | ||
|
|
00dc5a8dc5 | ||
|
|
4d230f5035 | ||
|
|
73bc6e4bfc | ||
|
|
b78264183f | ||
|
|
455d0a6048 | ||
|
|
5d0dabe51c | ||
|
|
16e7ca3a95 | ||
|
|
70f3909cdc | ||
|
|
f73d32e15d | ||
|
|
3338e4fb25 | ||
|
|
726d4ca9bc | ||
|
|
32b7be2201 | ||
|
|
dc26db7759 | ||
|
|
8fd5b62560 | ||
|
|
619d4e3dc4 | ||
|
|
f03027c33d | ||
|
|
a95dc0ceb8 | ||
|
|
17b7703dab | ||
|
|
50968c12b1 | ||
|
|
edb9c924fe | ||
|
|
805bc499ee | ||
|
|
baa0270ef3 | ||
|
|
d310acc780 |
@@ -11,4 +11,4 @@ https://www.reddit.com/r/KittyTerminal[Reddit community]
|
||||
|
||||
Packaging status in various repositories:
|
||||
|
||||
image:https://repology.org/badge/vertical-allrepos/kitty.svg["Packaging status", link="https://repology.org/project/kitty/versions"]
|
||||
image:https://repology.org/badge/vertical-allrepos/kitty.svg?columns=3&header=kitty["Packaging status", link="https://repology.org/project/kitty/versions"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
@@ -147,9 +147,9 @@
|
||||
{
|
||||
"name": "python",
|
||||
"unix": {
|
||||
"filename": "Python-3.9.4.tar.xz",
|
||||
"hash": "sha256:4b0e6644a76f8df864ae24ac500a51bbf68bd098f6a173e27d3b61cdca9aa134",
|
||||
"urls": ["https://www.python.org/ftp/python/3.9.4/{filename}"]
|
||||
"filename": "Python-3.11.6.tar.xz",
|
||||
"hash": "sha256:0fab78fa7f133f4f38210c6260d90d7c0d5c7198446419ce057ec7ac2e6f5f38",
|
||||
"urls": ["https://www.python.org/ftp/python/3.11.6/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -244,9 +244,9 @@
|
||||
{
|
||||
"name": "harfbuzz",
|
||||
"unix": {
|
||||
"filename": "harfbuzz-2.7.4.tar.xz",
|
||||
"hash": "sha256:6ad11d653347bd25d8317589df4e431a2de372c0cf9be3543368e07ec23bb8e7",
|
||||
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/2.7.4/{filename}"]
|
||||
"filename": "harfbuzz-8.2.2.tar.xz",
|
||||
"hash": "sha256:e433ad85fbdf57f680be29479b3f964577379aaf319f557eb76569f0ecbc90f3",
|
||||
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/8.2.2/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -12,6 +12,6 @@ for line in cp.stdout.decode().splitlines():
|
||||
fname = line.split(':', 1)[0]
|
||||
all_files.discard(fname)
|
||||
|
||||
all_files -= {'nerd-fonts-glyphs.txt', 'rowcolumn-diacritics.txt'}
|
||||
all_files -= {'gen/nerd-fonts-glyphs.txt', 'gen/rowcolumn-diacritics.txt'}
|
||||
cp = subprocess.run(['cloc', '--list-file', '-'], input='\n'.join(all_files).encode())
|
||||
raise SystemExit(cp.returncode)
|
||||
|
||||
@@ -6,5 +6,6 @@ Mappable actions
|
||||
The actions described below can be mapped to any key press or mouse action
|
||||
using the ``map`` and ``mouse_map`` directives in :file:`kitty.conf`. For
|
||||
configuration examples, see the default shortcut links for each action.
|
||||
To read about keyboard mapping in more detail, see :doc:`mapping`.
|
||||
|
||||
.. include:: /generated/actions.rst
|
||||
|
||||
@@ -124,7 +124,9 @@ Other keyboard shortcuts
|
||||
----------------------------------
|
||||
|
||||
The full list of actions that can be mapped to key presses is available
|
||||
:doc:`here </actions>`.
|
||||
:doc:`here </actions>`. To learn how to do more sophisticated keyboard
|
||||
mappings, such as modal mappings, per application mappings, etc. see
|
||||
:doc:`mapping`.
|
||||
|
||||
================================== =======================
|
||||
Action Shortcut
|
||||
|
||||
@@ -43,6 +43,98 @@ The :doc:`ssh kitten <kittens/ssh>` is redesigned with powerful new features:
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.32.0 [2024-01-19]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- :ref:`conditional_mappings`
|
||||
|
||||
- Support for :ref:`modal_mappings` such as in modal editors like vim
|
||||
|
||||
- A new option :opt:`notify_on_cmd_finish` to show a desktop notification when a long running command finishes (:pull:`6817`)
|
||||
|
||||
- A new action :ac:`send_key` to simplify mapping key presses to other keys without needing :ac:`send_text`
|
||||
|
||||
- Allow focusing previously active OS windows via :ac:`nth_os_window` (:pull:`7009`)
|
||||
|
||||
- Wayland: Fix a regression in the previous release that broke copying to clipboard under wl-roots based compositors in some circumstances
|
||||
(:iss:`6890`)
|
||||
|
||||
- macOS: Fix some combining characters not being rendered (:iss:`6898`)
|
||||
|
||||
- macOS: Fix returning from full screen via the button when the titlebar is hidden not hiding the buttons (:iss:`6883`)
|
||||
|
||||
- macOS: Fix newly created OS windows not always appearing on the "active" monitor (:pull:`6932`)
|
||||
|
||||
- Font fallback: Fix the font used to render a character sometimes dependent on the order in which characters appear on screen (:iss:`6865`)
|
||||
|
||||
- panel kitten: Fix rendering with non-zero margin/padding in kitty.conf (:iss:`6923`)
|
||||
|
||||
- kitty keyboard protocol: Specify the behavior of the modifier bits during modifier key events (:iss:`6913`)
|
||||
|
||||
- Wayland: Enable support for the new cursor-shape protocol so that the mouse cursor is always rendered at the correct size in compositors that support this protocol (:iss:`6914`)
|
||||
|
||||
- GNOME Wayland: Fix remembered window size smaller than actual size (:iss:`6946`)
|
||||
|
||||
- Mouse reporting: Fix incorrect position reported for windows with padding (:iss:`6950`)
|
||||
|
||||
- Fix :ac:`focus_visible_window` not switching to other window in stack layout
|
||||
when only two windows are present (:iss:`6970`)
|
||||
|
||||
|
||||
0.31.0 [2023-11-08]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Allow :ac:`easily running arbitrarily complex remote control scripts <remote_control_script>` without needing to turn on remote control (:iss:`6712`)
|
||||
|
||||
- A new option :opt:`menu_map` that allows adding entries to the global menubar on macOS (:disc:`6680`)
|
||||
|
||||
- A new :doc:`escape code <pointer-shapes>` that can be used by programs running in the terminal to change the shape of the mouse pointer (:iss:`6711`)
|
||||
|
||||
- Graphics protocol: Support for positioning :ref:`images relative to other images <relative_image_placement>` (:iss:`6400`)
|
||||
|
||||
- A new option :opt:`single_window_padding_width` to use a different padding when only a single window is visible (:iss:`6734`)
|
||||
|
||||
- A new mouse action ``mouse_selection word_and_line_from_point`` to select the current word under the mouse cursor and extend to end of line (:pull:`6663`)
|
||||
|
||||
- A new option :opt:`underline_hyperlinks` to control when hyperlinks are underlined (:iss:`6766`)
|
||||
|
||||
- Allow using the full range of standard mouse cursor shapes when customizing the mouse cursor
|
||||
|
||||
- macOS: When running the default shell with the login program fix :file:`~/.hushlogin` not being respected when opening windows not in the home directory (:iss:`6689`)
|
||||
|
||||
- macOS: Fix poor performance when using ligatures with some fonts, caused by slow harfbuzz shaping (:iss:`6743`)
|
||||
|
||||
- :option:`kitten @ set-background-opacity --toggle` - a new flag to easily switch opacity between the specified value and the default (:iss:`6691`)
|
||||
|
||||
- Fix a regression caused by rewrite of kittens to Go that made various kittens reset colors in a terminal when the colors were changed by escape code (:iss:`6708`)
|
||||
|
||||
- Fix trailing bracket not ignored when detecting a multi-line URL with the trailing bracket as the first character on the last line (:iss:`6710`)
|
||||
|
||||
- Fix the :option:`kitten @ launch --copy-env` option not copying current environment variables (:iss:`6724`)
|
||||
|
||||
- Fix a regression that broke :program:`kitten update-self` (:iss:`6729`)
|
||||
|
||||
- Two new event types for :ref:`watchers <watchers>`, :code:`on_title_change` and :code:`on_set_user_var`
|
||||
|
||||
- When pasting, if the text contains terminal control codes ask the user for permission. See :opt:`paste_actions` for details. Thanks to David Leadbeater for discovering this.
|
||||
|
||||
- Render Private Use Unicode symbols using two cells if the second cell contains an en-space as well as a normal space
|
||||
|
||||
- macOS: Fix a regression in the previous release that caused kitten @ ls to not report the environment variables for the default shell (:iss:`6749`)
|
||||
|
||||
- :doc:`Desktop notification protocol </desktop-notifications>`: Allow applications sending notifications to specify that the notification should only be displayed if the window is currently unfocused (:iss:`6755`)
|
||||
|
||||
- :doc:`unicode_input kitten </kittens/unicode_input>`: Fix a regression that broke the "Emoticons" tab (:iss:`6760`)
|
||||
|
||||
- Shell integration: Fix ``sudo --edit`` not working and also fix completions for sudo not working in zsh (:iss:`6754`, :iss:`6771`)
|
||||
|
||||
- A new action :ac:`set_window_title` to interactively change the title of the active window
|
||||
|
||||
- ssh kitten: Fix a regression that broken :kbd:`ctrl+space` mapping in zsh (:iss:`6780`)
|
||||
|
||||
- Wayland: Fix primary selections not working with the river compositor (:iss:`6785`)
|
||||
|
||||
|
||||
0.30.1 [2023-10-05]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1304,10 +1396,10 @@ Detailed list of changes
|
||||
- Fix deleting windows that are not the last window via remote control leaving
|
||||
no window focused (:iss:`3619`)
|
||||
|
||||
- Add an option :option:`kitty @ get-text --add-cursor` to also get the current
|
||||
- Add an option :option:`kitten @ get-text --add-cursor` to also get the current
|
||||
cursor position and state as ANSI escape codes (:iss:`3625`)
|
||||
|
||||
- Add an option :option:`kitty @ get-text --add-wrap-markers` to add line wrap
|
||||
- Add an option :option:`kitten @ get-text --add-wrap-markers` to add line wrap
|
||||
markers to the output (:pull:`3633`)
|
||||
|
||||
- Improve rendering of curly underlines on HiDPI screens (:pull:`3637`)
|
||||
@@ -2911,7 +3003,7 @@ Detailed list of changes
|
||||
- diff kitten: Fix error when right hand side file is binary and left hand side
|
||||
file is text (:pull:`752`)
|
||||
|
||||
- kitty @ new-window: Add a new option :option:`kitty @ new-window --window-type`
|
||||
- kitty @ new-window: Add a new option :option:`kitten @ new-window --window-type`
|
||||
to create top-level OS windows (:iss:`770`)
|
||||
|
||||
- macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows
|
||||
@@ -3028,7 +3120,7 @@ Detailed list of changes
|
||||
- diff kitten: Fix default foreground/background colors not being restored when
|
||||
kitten quits (:iss:`637`)
|
||||
|
||||
- Fix :option:`kitty @ set-colors --all` not working when more than one window
|
||||
- Fix :option:`kitten @ set-colors --all` not working when more than one window
|
||||
present (:iss:`632`)
|
||||
|
||||
- Fix a regression that broke the legacy increase/decrease_font_size actions
|
||||
|
||||
205
docs/conf.py
205
docs/conf.py
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
@@ -7,18 +7,19 @@
|
||||
# full list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/config
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from functools import lru_cache, partial
|
||||
from typing import Any, Callable, Dict, Iterable, List, Tuple
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
from pygments.lexer import RegexLexer, bygroups # type: ignore
|
||||
from pygments.token import Comment, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
|
||||
from pygments.token import Comment, Error, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
|
||||
from sphinx import addnodes, version_info
|
||||
from sphinx.util.logging import getLogger
|
||||
|
||||
@@ -27,7 +28,8 @@ if kitty_src not in sys.path:
|
||||
sys.path.insert(0, kitty_src)
|
||||
|
||||
from kitty.conf.types import Definition, expand_opt_references # noqa
|
||||
from kitty.constants import str_version, website_url # noqa
|
||||
from kitty.constants import str_version, website_url # noqa
|
||||
from kitty.fast_data_types import Shlex # noqa
|
||||
|
||||
# config {{{
|
||||
# -- Project information -----------------------------------------------------
|
||||
@@ -176,8 +178,8 @@ manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.h
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('invocation', 'kitty', 'kitty Documentation', [author], 1),
|
||||
('conf', 'kitty.conf', 'kitty terminal emulator configuration file', [author], 5)
|
||||
('invocation', 'kitty', 'The fast, feature rich terminal emulator', [author], 1),
|
||||
('conf', 'kitty.conf', 'Configuration file for kitty', [author], 5)
|
||||
]
|
||||
|
||||
|
||||
@@ -251,20 +253,20 @@ if you specify a program-to-run you can use the special placeholder
|
||||
as_rst = partial(option_spec_as_rst, heading_char='_')
|
||||
from kitty.rc.base import all_command_names, command_for_name
|
||||
from kitty.remote_control import cli_msg, global_options_spec
|
||||
with open('generated/cli-kitty-at.rst', 'w') as f:
|
||||
with open('generated/cli-kitten-at.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('kitty @')
|
||||
p('kitten @')
|
||||
p('-' * 80)
|
||||
p('.. program::', 'kitty @')
|
||||
p('.. program::', 'kitten @')
|
||||
p('\n\n' + as_rst(
|
||||
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
|
||||
global_options_spec, message=cli_msg, usage='command ...', appname='kitten @'))
|
||||
from kitty.rc.base import cli_params_for
|
||||
for cmd_name in sorted(all_command_names()):
|
||||
func = command_for_name(cmd_name)
|
||||
p(f'.. _at-{func.name}:\n')
|
||||
p('kitty @', func.name)
|
||||
p('kitten @', func.name)
|
||||
p('-' * 120)
|
||||
p('.. program::', 'kitty @', func.name)
|
||||
p('.. program::', 'kitten @', func.name)
|
||||
p('\n\n' + as_rst(*cli_params_for(func)))
|
||||
from kittens.runner import get_kitten_cli_docs
|
||||
|
||||
@@ -344,14 +346,70 @@ class ConfLexer(RegexLexer): # type: ignore
|
||||
aliases = ['conf']
|
||||
filenames = ['*.conf']
|
||||
|
||||
def map_flags(self: RegexLexer, val: str, start_pos: int) -> Iterator[Tuple[int, Any, str]]:
|
||||
expecting_arg = ''
|
||||
s = Shlex(val)
|
||||
from kitty.options.utils import allowed_key_map_options
|
||||
last_pos = 0
|
||||
while (tok := s.next_word())[0] > -1:
|
||||
x = tok[1]
|
||||
if tok[0] > last_pos:
|
||||
yield start_pos + last_pos, Whitespace, ' ' * (tok[0] - last_pos)
|
||||
last_pos = tok[0] + len(x)
|
||||
tok_start = start_pos + tok[0]
|
||||
if expecting_arg:
|
||||
yield tok_start, String, x
|
||||
expecting_arg = ''
|
||||
elif x.startswith('--'):
|
||||
expecting_arg = x[2:]
|
||||
k, sep, v = expecting_arg.partition('=')
|
||||
k = k.replace('-', '_')
|
||||
expecting_arg = k
|
||||
if expecting_arg not in allowed_key_map_options:
|
||||
yield tok_start, Error, x
|
||||
elif sep == '=':
|
||||
expecting_arg = ''
|
||||
yield tok_start, Name, x
|
||||
else:
|
||||
yield tok_start, Name, x
|
||||
else:
|
||||
break
|
||||
|
||||
def mapargs(self: RegexLexer, match: 're.Match[str]') -> Iterator[Tuple[int, Any, str]]:
|
||||
start_pos = match.start()
|
||||
val = match.group()
|
||||
parts = val.split(maxsplit=1)
|
||||
if parts[0].startswith('--'):
|
||||
seen = 0
|
||||
for (pos, token, text) in self.map_flags(val, start_pos):
|
||||
yield pos, token, text
|
||||
seen += len(text)
|
||||
start_pos += seen
|
||||
val = val[seen:]
|
||||
parts = val.split(maxsplit=1)
|
||||
|
||||
if not val:
|
||||
return
|
||||
yield start_pos, Literal, parts[0] # key spec
|
||||
if len(parts) == 1:
|
||||
return
|
||||
start_pos += len(parts[0])
|
||||
val = val[len(parts[0]):]
|
||||
m = re.match(r'(\s+)(\S+)', val)
|
||||
if m is None:
|
||||
return
|
||||
yield start_pos, Whitespace, m.group(1)
|
||||
yield start_pos + m.start(2), Name.Function, m.group(2) # action function
|
||||
yield start_pos + m.end(2), String, val[m.end(2):]
|
||||
|
||||
tokens = {
|
||||
'root': [
|
||||
(r'#.*?$', Comment.Single),
|
||||
(r'\s+$', Whitespace),
|
||||
(r'\s+', Whitespace),
|
||||
(r'(include)(\s+)(.+?)$', bygroups(Comment.Preproc, Whitespace, Name.Namespace)),
|
||||
(r'(map)(\s+)(\S+)(\s+)', bygroups(
|
||||
Keyword.Declaration, Whitespace, String, Whitespace), 'action'),
|
||||
(r'(map)(\s+)', bygroups(
|
||||
Keyword.Declaration, Whitespace), 'mapargs'),
|
||||
(r'(mouse_map)(\s+)(\S+)(\s+)(\S+)(\s+)(\S+)(\s+)', bygroups(
|
||||
Keyword.Declaration, Whitespace, String, Whitespace, Name.Variable, Whitespace, String, Whitespace), 'action'),
|
||||
(r'(symbol_map)(\s+)(\S+)(\s+)(.+?)$', bygroups(
|
||||
@@ -363,6 +421,9 @@ class ConfLexer(RegexLexer): # type: ignore
|
||||
(r'[a-z_0-9]+$', Name.Function, 'root'),
|
||||
(r'[a-z_0-9]+', Name.Function, 'args'),
|
||||
],
|
||||
'mapargs': [
|
||||
(r'.+$', mapargs, 'root'),
|
||||
],
|
||||
'args': [
|
||||
(r'\s+', Whitespace, 'args'),
|
||||
(r'\b(yes|no)\b$', Number.Bin, 'root'),
|
||||
@@ -529,6 +590,18 @@ def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
||||
from kitty.actions import as_rst
|
||||
with open('generated/actions.rst', 'w', encoding='utf-8') as f:
|
||||
f.write(as_rst())
|
||||
|
||||
from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
|
||||
with open('generated/matching.rst', 'w') as f:
|
||||
print('Matching windows', file=f)
|
||||
print('______________________________', file=f)
|
||||
w = 'm' + MATCH_WINDOW_OPTION[MATCH_WINDOW_OPTION.find('Match') + 1:]
|
||||
print('When matching windows,', w, file=f)
|
||||
print('Matching tabs', file=f)
|
||||
print('______________________________', file=f)
|
||||
w = 'm' + MATCH_TAB_OPTION[MATCH_TAB_OPTION.find('Match') + 1:]
|
||||
print('When matching tabs,', w, file=f)
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -549,10 +622,13 @@ def add_html_context(app: Any, pagename: str, templatename: str, context: Any, d
|
||||
@lru_cache
|
||||
def monkeypatch_man_writer() -> None:
|
||||
'''
|
||||
Monkeypatch the docutils man translator to output better tables
|
||||
Monkeypatch the docutils man translator to be nicer
|
||||
'''
|
||||
from docutils.nodes import Element
|
||||
from docutils.writers.manpage import Table, Translator
|
||||
from sphinx.writers.manpage import ManualPageTranslator
|
||||
|
||||
# Generate nicer tables https://sourceforge.net/p/docutils/bugs/475/
|
||||
class PatchedTable(Table): # type: ignore
|
||||
_options: list[str]
|
||||
def __init__(self) -> None:
|
||||
@@ -572,15 +648,103 @@ def monkeypatch_man_writer() -> None:
|
||||
del ans[3] # top border
|
||||
del ans[-2] # bottom border
|
||||
return ans
|
||||
def visit_table(self: Translator, node: object) -> None:
|
||||
def visit_table(self: ManualPageTranslator, node: object) -> None:
|
||||
setattr(self, '_active_table', PatchedTable())
|
||||
setattr(Translator, 'visit_table', visit_table)
|
||||
setattr(ManualPageTranslator, 'visit_table', visit_table)
|
||||
|
||||
# Improve header generation
|
||||
def header(self: ManualPageTranslator) -> str:
|
||||
di = getattr(self, '_docinfo')
|
||||
di['ktitle'] = di['title'].replace('_', '-')
|
||||
th = (".TH \"%(ktitle)s\" %(manual_section)s"
|
||||
" \"%(date)s\" \"%(version)s\"") % di
|
||||
if di["manual_group"]:
|
||||
th += " \"%(manual_group)s\"" % di
|
||||
th += "\n"
|
||||
sh_tmpl: str = (".SH Name\n"
|
||||
"%(ktitle)s \\- %(subtitle)s\n")
|
||||
return th + sh_tmpl % di # type: ignore
|
||||
|
||||
setattr(ManualPageTranslator, 'header', header)
|
||||
|
||||
def visit_image(self: ManualPageTranslator, node: Element) -> None:
|
||||
pass
|
||||
|
||||
def depart_image(self: ManualPageTranslator, node: Element) -> None:
|
||||
pass
|
||||
|
||||
def depart_figure(self: ManualPageTranslator, node: Element) -> None:
|
||||
self.body.append(' (images not supported)\n')
|
||||
Translator.depart_figure(self, node)
|
||||
|
||||
setattr(ManualPageTranslator, 'visit_image', visit_image)
|
||||
setattr(ManualPageTranslator, 'depart_image', depart_image)
|
||||
setattr(ManualPageTranslator, 'depart_figure', depart_figure)
|
||||
|
||||
orig_astext = Translator.astext
|
||||
def astext(self: Translator) -> Any:
|
||||
b = []
|
||||
for line in self.body:
|
||||
if line.startswith('.SH'):
|
||||
x, y = line.split(' ', 1)
|
||||
parts = y.splitlines(keepends=True)
|
||||
parts[0] = parts[0].capitalize()
|
||||
line = x + ' ' + '\n'.join(parts)
|
||||
b.append(line)
|
||||
self.body = b
|
||||
return orig_astext(self)
|
||||
setattr(Translator, 'astext', astext)
|
||||
|
||||
|
||||
def setup_man_pages() -> None:
|
||||
from kittens.runner import get_kitten_cli_docs
|
||||
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')):
|
||||
kn = os.path.basename(x).rpartition('.')[0]
|
||||
if kn == 'custom':
|
||||
continue
|
||||
cd = get_kitten_cli_docs(kn) or {}
|
||||
khn = kn.replace('_', '-')
|
||||
man_pages.append((f'kittens/{kn}', 'kitten-' + khn, cd.get('short_desc', 'kitten Documentation'), [author], 1))
|
||||
monkeypatch_man_writer()
|
||||
|
||||
|
||||
def build_extra_man_pages() -> None:
|
||||
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
kitten = os.environ.get('KITTEN_EXE_FOR_DOCS', os.path.join(base, 'kitty/launcher/kitten'))
|
||||
if not os.path.exists(kitten):
|
||||
kitten = os.path.join(base, 'kitty/launcher/kitty.app/Contents/MacOS/kitten')
|
||||
if not os.path.exists(kitten):
|
||||
subprocess.call(['find', os.path.join(base, 'kitty/launcher')])
|
||||
raise Exception(f'The kitten binary {kitten} is not built cannot generate man pages')
|
||||
raw = subprocess.check_output([kitten, '-h']).decode()
|
||||
started = 0
|
||||
names = set()
|
||||
for line in raw.splitlines():
|
||||
if line.strip() == '@':
|
||||
started = len(line.rstrip()[:-1])
|
||||
q = line.strip()
|
||||
if started and len(q.split()) == 1 and not q.startswith('-') and ':' not in q:
|
||||
if len(line) - len(line.lstrip()) == started:
|
||||
if not os.path.exists(os.path.join(base, f'docs/kittens/{q}.rst')):
|
||||
names.add(q)
|
||||
cwd = os.path.join(base, 'docs/_build/man')
|
||||
subprocess.check_call([kitten, '__generate_man_pages__'], cwd=cwd)
|
||||
subprocess.check_call([kitten, '__generate_man_pages__'] + list(names), cwd=cwd)
|
||||
|
||||
|
||||
if building_man_pages:
|
||||
setup_man_pages()
|
||||
|
||||
|
||||
def build_finished(*a: Any, **kw: Any) -> None:
|
||||
if building_man_pages:
|
||||
build_extra_man_pages()
|
||||
|
||||
|
||||
def setup(app: Any) -> None:
|
||||
os.makedirs('generated/conf', exist_ok=True)
|
||||
from kittens.runner import all_kitten_names
|
||||
monkeypatch_man_writer()
|
||||
kn = all_kitten_names()
|
||||
write_cli_docs(kn)
|
||||
write_remote_control_protocol_docs()
|
||||
@@ -589,10 +753,7 @@ def setup(app: Any) -> None:
|
||||
app.connect('source-read', replace_string)
|
||||
app.add_config_value('analytics_id', '', 'env')
|
||||
app.connect('html-page-context', add_html_context)
|
||||
app.connect('build-finished', build_finished)
|
||||
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
||||
app.add_role('link', link_role)
|
||||
app.add_role('commit', commit_role)
|
||||
# monkey patch sphinx_inline_tabs to avoid a warning about parallel reads
|
||||
# see https://github.com/pradyunsg/sphinx-inline-tabs/issues/26
|
||||
inline_tabs = app.extensions['sphinx_inline_tabs']
|
||||
inline_tabs.parallel_read_safe = inline_tabs.parallel_write_safe = True
|
||||
|
||||
@@ -94,9 +94,9 @@ to display it based on what it does understand.
|
||||
revisions.
|
||||
|
||||
|
||||
======= ==================== ========= =================
|
||||
======= ==================== ========== =================
|
||||
Key Value Default Description
|
||||
======= ==================== ========= =================
|
||||
======= ==================== ========== =================
|
||||
``a`` Comma separated list ``focus`` What action to perform when the
|
||||
of ``report``, notification is clicked
|
||||
``focus``, with
|
||||
@@ -113,7 +113,19 @@ Key Value Default Description
|
||||
|
||||
``p`` One of ``title`` or ``title`` Whether the payload is the notification title or body. If a
|
||||
``body``. notification has no title, the body will be used as title.
|
||||
======= ==================== ========= =================
|
||||
|
||||
``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window
|
||||
``unfocused`` or the notification is sent on does not have keyboard focus. ``invisible``
|
||||
``invisible`` means the window both is unfocused
|
||||
and not visible to the user, for example, because it is in an inactive tab or
|
||||
its OS window is not currently active.
|
||||
``always`` is the default and always honors the request.
|
||||
======= ==================== ========== =================
|
||||
|
||||
|
||||
.. note::
|
||||
Support for the ``o`` key to prevent notifications from focused windows
|
||||
was added in kitty version 0.31.0
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
22
docs/faq.rst
22
docs/faq.rst
@@ -19,9 +19,9 @@ use Unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are wide and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the Unicode
|
||||
standard, |kitty| renders them either smaller or truncated. The exception is if
|
||||
these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells. This behavior can be
|
||||
turned off for specific symbols using :opt:`narrow_symbols`.
|
||||
these characters are followed by a space or en-space (U+2002) in which case
|
||||
kitty makes use of the extra cell to render them in two cells. This behavior
|
||||
can be turned off for specific symbols using :opt:`narrow_symbols`.
|
||||
|
||||
|
||||
Using a color theme with a background color does not work well in vim?
|
||||
@@ -389,20 +389,14 @@ How do I map key presses in kitty to different keys in the terminal program?
|
||||
This is accomplished by using ``map`` with :sc:`send_text <send_text>` in :file:`kitty.conf`.
|
||||
For example::
|
||||
|
||||
map alt+s send_text normal,application \x13
|
||||
map alt+s send_key ctrl+s
|
||||
|
||||
This maps :kbd:`alt+s` to :kbd:`ctrl+s`. To figure out what bytes to use for
|
||||
the :sc:`send_text <send_text>` you can use the ``show_key`` kitten. Run::
|
||||
This causes the program running in kitty to receive the :kbd:`ctrl+s` key when
|
||||
you press the :kbd:`alt+s` key. To see this in action, run::
|
||||
|
||||
kitten show_key
|
||||
kitten show-key -m kitty
|
||||
|
||||
Then press the key you want to emulate. Note that this kitten will only show
|
||||
keys that actually reach the terminal program, in particular, keys mapped to
|
||||
actions in kitty will not be shown. To check those first map them to
|
||||
:ac:`no_op`. You can also start a kitty instance without any shortcuts to
|
||||
interfere::
|
||||
|
||||
kitty -o clear_all_shortcuts=yes kitten show_key
|
||||
Which will print out what key events it receives.
|
||||
|
||||
|
||||
How do I open a new window or tab with the same working directory as the current window?
|
||||
|
||||
@@ -120,7 +120,7 @@ Variables that influence kitty behavior
|
||||
|
||||
.. envvar:: KITTY_RC_PASSWORD
|
||||
|
||||
Set this to a pass phrase to use the ``kitty @`` remote control command with
|
||||
Set this to a pass phrase to use the ``kitten @`` remote control command with
|
||||
:opt:`remote_control_password`.
|
||||
|
||||
|
||||
@@ -179,8 +179,10 @@ Variables that kitty sets when running child programs
|
||||
|
||||
Set when the :doc:`remote control <remote-control>` facility is enabled and
|
||||
the a socket is used for control via :option:`kitty --listen-on` or :opt:`listen_on`.
|
||||
Contains the path to the socket. Avoid the need to use :option:`kitty @ --to` when
|
||||
issuing remote control commands.
|
||||
Contains the path to the socket. Avoid the need to use :option:`kitten @ --to` when
|
||||
issuing remote control commands. Can also be a file descriptor of the form
|
||||
fd:num instead of a socket address, in which case, remote control
|
||||
communication should proceed over the specified file descriptor.
|
||||
|
||||
.. envvar:: KITTY_PIPE_DATA
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ Some programs and libraries that use the kitty graphics protocol:
|
||||
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||
* `twitch-tui <https://github.com/Xithrius/twitch-tui>`_ - Twitch chat in the terminal
|
||||
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
|
||||
* `fzf <https://github.com/junegunn/fzf/commit/d8188fce7b7bea982e7f9050c35e488e49fb8fd0>`_ - A command line fuzzy finder
|
||||
|
||||
Other terminals that have implemented the graphics protocol:
|
||||
|
||||
@@ -478,18 +479,22 @@ Controlling displayed image layout
|
||||
|
||||
The image is rendered at the current cursor position, from the upper left corner of
|
||||
the current cell. You can also specify extra ``X=3`` and ``Y=4`` pixel offsets to display from
|
||||
a different origin within the cell. Note that the offsets must be smaller that the size of the cell.
|
||||
a different origin within the cell. Note that the offsets must be smaller than the size of the cell.
|
||||
|
||||
By default, the entire image will be displayed (images wider than the available
|
||||
width will be truncated on the right edge). You can choose a source rectangle (in pixels)
|
||||
as the part of the image to display. This is done with the keys: ``x, y, w, h`` which specify
|
||||
the top-left corner, width and height of the source rectangle.
|
||||
the top-left corner, width and height of the source rectangle. The displayed
|
||||
area is the intersection of the specified rectangle with the source image
|
||||
rectangle.
|
||||
|
||||
You can also ask the terminal emulator to display the image in a specified rectangle
|
||||
(num of columns / num of lines), using the control codes ``c,r``. ``c`` is the number of columns
|
||||
and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit
|
||||
the specified area. Note that if you specify a start cell offset via the ``X,Y`` keys, it is not
|
||||
added to the number of rows/columns.
|
||||
added to the number of rows/columns. If only one of either ``r`` or ``c`` is
|
||||
specified, the other one is computed based on the source image aspect ratio, so
|
||||
that the image is displayed without distortion.
|
||||
|
||||
Finally, you can specify the image *z-index*, i.e. the vertical stacking order. Images
|
||||
placed in the same location with different z-index values will be blended if
|
||||
@@ -554,7 +559,7 @@ The image will eventually be fit to the specified rectangle, its aspect ratio
|
||||
preserved. Finally, the image can be actually displayed by using the
|
||||
placeholder character, encoding the image ID in its foreground color. The row
|
||||
and column values are specified with diacritics listed in
|
||||
:download:`rowcolumn-diacritics.txt <../rowcolumn-diacritics.txt>`. For
|
||||
:download:`rowcolumn-diacritics.txt <../gen/rowcolumn-diacritics.txt>`. For
|
||||
example, here is how you can print a ``2x2`` placeholder for image ID ``42``:
|
||||
|
||||
.. code-block:: sh
|
||||
@@ -639,6 +644,70 @@ placements from the protocol perspective. They cannot be manipulated using
|
||||
graphics commands, instead they should be moved, deleted, or modified by
|
||||
manipulating the underlying Unicode placeholder as normal text.
|
||||
|
||||
.. _relative_image_placement:
|
||||
|
||||
Relative placements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 0.31.0
|
||||
Support for positioning images relative to other images
|
||||
|
||||
You can specify that a placement is positioned relative to another placement.
|
||||
This is particularly useful in combination with
|
||||
:ref:`graphics_unicode_placeholders` above. It can be used to specify a single
|
||||
transparent pixel image using a Unicode placeholder, which moves around
|
||||
naturally with the text, the real image(s) can base their position relative to
|
||||
the placeholder.
|
||||
|
||||
To specify that a placement should be relative to another, use the
|
||||
``P=<image_id>,Q=<placement_id>`` keys, when creating the relative placement.
|
||||
For example::
|
||||
|
||||
<ESC>_Ga=p,i=<image_id>,p=<placement_id>,P=<parent_img_id>,Q=<parent_placement_id><ESC>\
|
||||
|
||||
This will create a *relative placement* that refers to the *parent placement*
|
||||
specified by the ``P`` and ``Q`` keys. When the parent placement moves, the
|
||||
relative placement moves along with it. The relative placement can be offset
|
||||
from the parent's location by a specified number of cells, using the ``H`` and
|
||||
``V`` keys for horizontal and vertical displacement. Positive values move right
|
||||
and down. Negative values move left and up. The origin is the top left cell of
|
||||
the parent placement.
|
||||
|
||||
The lifetime of a relative placement is tied to the lifetime of its parent. If
|
||||
its parent is deleted, it is deleted as well. If the image that the relative
|
||||
placement is a placement of, has no more placements, the image is deleted as
|
||||
well. Thus, a parent and its relative placements form a *group* that is managed
|
||||
together.
|
||||
|
||||
A relative placement can refer to another relative placement as its parent.
|
||||
Thus the relative placements can form a chain. It is implementation dependent
|
||||
how long a chain of such placements is allowed, but implementation must allow
|
||||
a chain of length at least 8. If the implementation max depth is exceeded, the
|
||||
terminal must respond with the ``ETOODEEP`` error code.
|
||||
|
||||
Virtual placements created for Unicode placeholder based images cannot also be
|
||||
relative placements. However, a relative placement can refer to a virtual
|
||||
placement as its parent. When a virtual placement is the parent, its position
|
||||
is derived from all the actual Unicode placeholder images that refer to it.
|
||||
The x position is the minimum of all the placeholder x positions and the y
|
||||
position is the minimum of all the placeholder y positions. If a client
|
||||
attempts to make a virtual placement relative the terminal must respond with
|
||||
the ``EINVAL`` error code.
|
||||
|
||||
Terminals are required to reject the creation of a relative placement
|
||||
that would create a cycle, such as when A is relative to B and B is relative to
|
||||
C and C is relative to A. In such cases, the terminal must respond with the
|
||||
``ECYCLE`` error code.
|
||||
|
||||
If a client attempts to create a reference to a placement that does not exist
|
||||
the terminal must respond with the ``ENOPARENT`` error code.
|
||||
|
||||
.. note::
|
||||
Since a relative placement gets its position specified based on another
|
||||
placement, instead of the cursor, the cursor must not move after a relative
|
||||
position, regardless of the value of the ``C`` key to control cursor
|
||||
movement.
|
||||
|
||||
|
||||
Deleting images
|
||||
---------------------
|
||||
@@ -959,8 +1028,11 @@ Key Value Default Description
|
||||
``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image.
|
||||
``1`` is to not move the cursor at all when placing the image.
|
||||
``U`` Positive integer ``0`` Set to ``1`` to create a virtual placement for a Unicode placeholder.
|
||||
``1`` is to not move the cursor at all when placing the image.
|
||||
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
|
||||
``P`` Positive integer ``0`` The id of a parent image for relative placement
|
||||
``Q`` Positive integer ``0`` The id of a placement in the parent image for relative placement
|
||||
``H`` 32-bit integer ``0`` The offset in cells in the horizontal direction for relative placement
|
||||
``V`` 32-bit integer ``0`` The offset in cells in the vertical direction for relative placement
|
||||
|
||||
**Keys for animation frame loading**
|
||||
-----------------------------------------------------------
|
||||
|
||||
@@ -47,6 +47,13 @@ graphics protocol.
|
||||
Another terminal file manager, with previews of file contents powered by kitty's
|
||||
graphics protocol.
|
||||
|
||||
.. _tool_presentterm:
|
||||
|
||||
`presenterm <https://github.com/mfontanini/presenterm>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Show markdown based slides with images in your terminal, powered by the
|
||||
kitty graphics protocol.
|
||||
|
||||
.. _tool_term_image:
|
||||
|
||||
`term-image <https://github.com/AnonymouX47/term-image>`__
|
||||
@@ -232,16 +239,23 @@ running around inside nvim <https://github.com/giusgad/pets.nvim>`__.
|
||||
Scrollback manipulation
|
||||
-------------------------
|
||||
|
||||
.. tool_kitty_scrollback_nvim:
|
||||
|
||||
`kitty-scrollback.nvim <https://github.com/mikesmithgh/kitty-scrollback.nvim>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Browse the scrollback buffer with Neovim, with simple key actions for efficient
|
||||
copy/paste and even execution of commands.
|
||||
|
||||
.. tool_kitty_search:
|
||||
|
||||
`kitty-search <https://github.com/trygveaa/kitty-kitten-search>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Live incremental search of the scrollback buffer.
|
||||
|
||||
.. tool_kitty_grab:
|
||||
|
||||
`kitty-grab <https://github.com/yurikhan/kitty_grab>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Keyboard based text selection for the kitty scrollback buffer.
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ In addition to kitty, this protocol is also implemented in:
|
||||
|
||||
* The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__
|
||||
* The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__
|
||||
* The `alacritty terminal <https://github.com/alacritty/alacritty/pull/7125>`__
|
||||
* The `rio terminal <https://github.com/raphamorim/rio/commit/cd463ca37677a0fc48daa8795ea46dadc92b1e95>`__
|
||||
* The `notcurses library
|
||||
<https://github.com/dankamongmen/notcurses/issues/2131>`__
|
||||
* The `crossterm library
|
||||
@@ -184,6 +186,12 @@ In the escape code, the modifier value is encoded as a decimal number which is
|
||||
and so on. If the modifier field is not present in the escape code, its default
|
||||
value is ``1`` which means no modifiers.
|
||||
|
||||
When the key event is related to an actual modifier key, the corresponding
|
||||
modifier's bit must be set to the modifier state including the effect for the
|
||||
current event. For example, when pressing the :kbd:`LEFT_CONTROL` key, the
|
||||
``ctrl`` bit must be set and when releasing it, it must be reset. When both
|
||||
left and right control keys are pressed and one is released, the release event
|
||||
must have the ``ctrl`` bit set. See :iss:`6913` for discussion of this design.
|
||||
|
||||
.. _event_types:
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
broadcast
|
||||
==================================================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
*Type text in all kitty windows simultaneously*
|
||||
|
||||
The ``broadcast`` kitten can be used to type text simultaneously in all
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
clipboard
|
||||
==================================================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
*Copy/paste to the system clipboard from shell scripts*
|
||||
|
||||
.. highlight:: sh
|
||||
|
||||
@@ -56,7 +56,7 @@ Kittens have full access to internal kitty APIs. However these are neither
|
||||
entirely stable nor documented. You can instead use the kitty
|
||||
:doc:`Remote control API </remote-control>`. Simply call
|
||||
:code:`boss.call_remote_control()`, with the same arguments you
|
||||
would pass to ``kitty @``. For example:
|
||||
would pass to ``kitten @``. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -337,9 +337,10 @@ You can parse and read the options in your kitten using the following code:
|
||||
return ans
|
||||
|
||||
overrides = tuple(overrides) if overrides is not None else ()
|
||||
opts_dict, paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
|
||||
opts_dict, found_paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
|
||||
opts = Options(opts_dict)
|
||||
opts.config_paths = paths
|
||||
opts.config_paths = found_paths
|
||||
opts.all_config_paths = paths
|
||||
opts.config_overrides = overrides
|
||||
return opts
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Hints
|
||||
==========
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
|kitty| has a *hints mode* to select and act on arbitrary text snippets
|
||||
currently visible on the screen. For example, you can press :sc:`open_url`
|
||||
to choose any URL visible on the screen and then open it using your default web
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Hyperlinked grep
|
||||
=================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
As of ripgrep versions newer that 13.0 it supports hyperlinks
|
||||
@@ -39,7 +45,8 @@ Now, run a search with::
|
||||
Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to
|
||||
open the file in :program:`vim` at the matching line. If you use some editor
|
||||
other than :program:`vim`, you should adjust the :file:`open-actions.conf` file
|
||||
accordingly.
|
||||
accordingly. TO open links with the keyboard instead, use
|
||||
:sc:`open_selected_hyperlink`.
|
||||
|
||||
Finally, add an alias to your shell's rc files to invoke the kitten as
|
||||
:command:`hg`::
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
icat
|
||||
========================================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
*Display images in the terminal*
|
||||
|
||||
The ``icat`` kitten can be used to display arbitrary images in the |kitty|
|
||||
terminal. Using it is as simple as::
|
||||
|
||||
kitten icat image.jpeg
|
||||
kitten icat image.jpeg
|
||||
|
||||
It supports all image types supported by `ImageMagick
|
||||
<https://www.imagemagick.org>`__. It even works over SSH. For details, see the
|
||||
|
||||
@@ -3,6 +3,11 @@ Draw a GPU accelerated dock panel on your desktop
|
||||
|
||||
.. highlight:: sh
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
You can use this kitten to draw a GPU accelerated panel on the edge of your
|
||||
screen, that shows the output from an arbitrary terminal program.
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Query terminal
|
||||
=================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
This kitten is used to query |kitty| from terminal programs about version, values
|
||||
of various runtime options controlling its features, etc.
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Remote files
|
||||
==============
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
|kitty| has the ability to easily *Edit*, *Open* or *Download* files from a
|
||||
computer into which you are SSHed. In your SSH session run::
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
Truly convenient SSH
|
||||
=========================================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
----------------
|
||||
|
||||
* Automatic :ref:`shell_integration` on remote hosts
|
||||
|
||||
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote hosts
|
||||
|
||||
* Automatic :opt:`re-use of existing connections <kitten-ssh.share_connections>` to avoid connection setup latency
|
||||
|
||||
* Make kitty itself available in the remote host :opt:`on demand <kitten-ssh.remote_kitty>`
|
||||
* Make the kitten binary available in the remote host :opt:`on demand <kitten-ssh.remote_kitty>`
|
||||
|
||||
* Easily :opt:`change terminal colors <kitten-ssh.color_scheme>` when connecting to remote hosts
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Changing kitty colors
|
||||
========================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
The themes kitten allows you to easily change color themes, from a collection of
|
||||
over three hundred pre-built themes available at `kitty-themes
|
||||
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Transfer files
|
||||
================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
.. versionadded:: 0.30.0
|
||||
|
||||
.. _rsync: https://en.wikipedia.org/wiki/Rsync
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
Unicode input
|
||||
================
|
||||
|
||||
.. only:: man
|
||||
|
||||
Overview
|
||||
--------------
|
||||
|
||||
|
||||
You can input Unicode characters by name, hex code, recently used and even an
|
||||
editable favorites list. Press :sc:`input_unicode_character` to start the
|
||||
unicode input kitten, shown below.
|
||||
|
||||
@@ -83,7 +83,7 @@ the command line:
|
||||
data to STDIN.
|
||||
|
||||
``@input-line-number``
|
||||
Replaced the number of lines a pager should scroll to match the current
|
||||
Replaced by the number of lines a pager should scroll to match the current
|
||||
scroll position in kitty. See :opt:`scrollback_pager` for details.
|
||||
|
||||
``@scrolled-by``
|
||||
@@ -133,8 +133,21 @@ functions for the events you are interested in, for example:
|
||||
|
||||
def on_close(boss: Boss, window: Window, data: Dict[str, Any])-> None:
|
||||
# called when window is closed, typically when the program running in
|
||||
# it exits.
|
||||
# it exits
|
||||
|
||||
def on_set_user_var(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
|
||||
# called when a "user variable" is set or deleted on a window. Here
|
||||
# data will contain key and value
|
||||
|
||||
def on_title_change(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
|
||||
# called when the window title is changed on a window. Here
|
||||
# data will contain title and from_child. from_child will be True
|
||||
# when a title change was requested via escape code from the program
|
||||
# running in the terminal
|
||||
|
||||
def on_cmd_startstop(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
|
||||
# called when the shell starts/stops executing a command. Here
|
||||
# data will contain is_start and time.
|
||||
|
||||
Every callback is passed a reference to the global ``Boss`` object as well as
|
||||
the ``Window`` object the action is occurring on. The ``data`` object is a dict
|
||||
|
||||
321
docs/mapping.rst
Normal file
321
docs/mapping.rst
Normal file
@@ -0,0 +1,321 @@
|
||||
:orphan:
|
||||
|
||||
Making your keyboard dance
|
||||
==============================
|
||||
|
||||
.. highlight:: conf
|
||||
|
||||
kitty has extremely powerful facilities for mapping keyboard actions.
|
||||
Things like combining actions, multi-key mappings, modal mappings,
|
||||
mappings that send arbitrary text, and mappings dependent on the program
|
||||
currently running in kitty.
|
||||
|
||||
Let's start with the basics. You can map a key press to an action in kitty using
|
||||
the following syntax::
|
||||
|
||||
map ctrl+a new_window_with_cwd
|
||||
|
||||
This will map the key press :kbd:`Ctrl+a` to open a new :term:`window`
|
||||
with the working directory set to the working directory of the current window.
|
||||
This is the basic operation of the map directive, the tip of the iceberg, for
|
||||
more read the sections below.
|
||||
|
||||
|
||||
Combining multiple actions on a single keypress
|
||||
-----------------------------------------------------
|
||||
|
||||
Multiple actions can be combined on a single keypress, like a macro. To do this
|
||||
map the key press to the :ac:`combine` action::
|
||||
|
||||
map key combine <separator> action1 <separator> action2 <separator> action3 ...
|
||||
|
||||
For example::
|
||||
|
||||
map kitty_mod+e combine : new_window : next_layout
|
||||
|
||||
This will create a new window and switch to the next available layout. You can
|
||||
also run arbitrarily powerful scripts on a key press. There are two major
|
||||
techniques for doing this, using remote control scripts or using kittens.
|
||||
|
||||
Remote control scripts
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These can be written in any language and use the "kitten" binary to control
|
||||
kitty via its extensive :doc:`Remote control <remote-control>` API. First,
|
||||
if you just want to run a single remote control command on a key press,
|
||||
you can just do::
|
||||
|
||||
map f1 remote_control set-spacing margin=30
|
||||
|
||||
This will run the ``set-spacing`` command, changing window margins to 30 pixels. For
|
||||
more complex scripts, write a script file in any language you like and save it
|
||||
somewhere, preferably in the kitty configuration directory. Do not forget to make it
|
||||
executable. In the script file you run remote control commands by running the
|
||||
"kitten" binary, for example:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
kitten @ set-spacing margin=30
|
||||
kitten @ new_window
|
||||
...
|
||||
|
||||
The script can perform arbitrarily complex logic and actions, limited only by
|
||||
the remote control API, that you can browse by running ``kitten @ --help``.
|
||||
To run the script you created on a key press, use::
|
||||
|
||||
map f1 remote_control_script /path/to/myscript
|
||||
|
||||
|
||||
Kittens
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Here, kittens refer to Python scripts. The scripts have two parts, one that
|
||||
runs as a regular command line program inside a kitty window to, for example,
|
||||
ask the user for some input and a second part that runs inside the kitty
|
||||
process itself and can perform any operation on the kitty UI, which is itself
|
||||
implemented in Python. However, the kitty internal API is not documented and
|
||||
can (very rarely) change, so kittens are harder to get started with than remote
|
||||
control scripts. To run a kitten on a key press::
|
||||
|
||||
map f1 kitten mykitten.py
|
||||
|
||||
Many of kitty;s features are themselves implemented as kittens, for example,
|
||||
:doc:`/kittens/unicode_input`, :doc:`/kittens/hints` and
|
||||
:doc:`/kittens/themes`. To learn about writing your own kittens, see
|
||||
:doc:`/kittens/custom`.
|
||||
|
||||
Syntax for specifying keys
|
||||
-----------------------------
|
||||
|
||||
A mapping maps a key press to some action. In their most basic form, keypresses
|
||||
are :code:`modifier+key`. Keys are identified simply by their lowercase Unicode
|
||||
characters. For example: :code:`a` for the :kbd:`A` key, :code:`[` for the left
|
||||
square bracket key, etc. For functional keys, such as :kbd:`Enter` or
|
||||
:kbd:`Escape`, the names are present at :ref:`Functional key definitions
|
||||
<functional>`. For modifier keys, the names are :kbd:`ctrl` (:kbd:`control`,
|
||||
:kbd:`⌃`), :kbd:`shift` (:kbd:`⇧`), :kbd:`alt` (:kbd:`opt`, :kbd:`option`,
|
||||
:kbd:`⌥`), :kbd:`super` (:kbd:`cmd`, :kbd:`command`, :kbd:`⌘`).
|
||||
|
||||
Additionally, you can use the name :opt:`kitty_mod` as a modifier, the default
|
||||
value of which is :kbd:`ctrl+shift`. The default kitty shortcuts are defined
|
||||
using this value, so by changing it in :file:`kitty.conf` you can change
|
||||
all the modifiers used by all the default shortcuts.
|
||||
|
||||
On Linux, you can also use XKB names for functional keys that don't have kitty
|
||||
names. See :link:`XKB keys
|
||||
<https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h>`
|
||||
for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
|
||||
prefix. Note that you can only use an XKB key name for keys that are not known
|
||||
as kitty keys.
|
||||
|
||||
Finally, you can use raw system key codes to map keys, again only for keys that
|
||||
are not known as kitty keys. To see the system key code for a key, start kitty
|
||||
with the :option:`kitty --debug-input` option, kitty will output some debug text
|
||||
for every key event. In that text look for :code:`native_code`, the value
|
||||
of that becomes the key name in the shortcut. For example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
on_key_input: glfw key: 0x61 native_code: 0x61 action: PRESS mods: none text: 'a'
|
||||
|
||||
Here, the key name for the :kbd:`A` key is :code:`0x61` and you can use it with::
|
||||
|
||||
map ctrl+0x61 something
|
||||
|
||||
This maps :kbd:`Ctrl+A` to something.
|
||||
|
||||
|
||||
Multi-key mappings
|
||||
--------------------
|
||||
|
||||
A mapping in kitty can involve pressing multiple keys in sequence, with the
|
||||
syntax shown below::
|
||||
|
||||
map key1>key2>key3 action
|
||||
|
||||
For example::
|
||||
|
||||
map ctrl+f>2 set_font_size 20
|
||||
|
||||
The default mappings to run the :doc:`hints kitten </kittens/hints>` to select text on the screen are
|
||||
examples of multi-key mappings.
|
||||
|
||||
Unmapping default shortcuts
|
||||
-----------------------------
|
||||
|
||||
kitty comes with dozens of default keyboard mappings for common operations. See
|
||||
:doc:`actions` for the full list of actions and the default shortcuts that map
|
||||
to them. You can unmap an individual shortcut, so that it is passed on to the
|
||||
program running inside kitty, by mapping it to nothing, for example::
|
||||
|
||||
map kitty_mod+enter
|
||||
|
||||
This unmaps the default shortcut :sc:`new_window` to open a new window. Almost
|
||||
all default shortcuts are of the form ``modifier + key`` where the
|
||||
modifier defaults to :kbd:`Ctrl+Shift` and can be changed using the :opt:`kitty_mod` setting
|
||||
in :file:`kitty.conf`.
|
||||
|
||||
If you want to clear all default shortcuts, you can use
|
||||
:opt:`clear_all_shortcuts` in :file:`kitty.conf`.
|
||||
|
||||
If you would like kitty to completely ignore a key event, not even sending it to
|
||||
the program running in the terminal, map it to :ac:`discard_event`::
|
||||
|
||||
map kitty_mod+f1 discard_event
|
||||
|
||||
.. _conditional_mappings:
|
||||
|
||||
Conditional mappings depending on the state of the focused window
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Sometimes, you may want different mappings to be active when running a
|
||||
particular program in kitty, perhaps because it has some native functionality
|
||||
that duplicates kitty functions or there is a conflict, etc. kitty has the
|
||||
ability to create mappings that work only when the currently focused window
|
||||
matches some criteria, such as when it has a particular title or user variable.
|
||||
|
||||
Let's see some examples::
|
||||
|
||||
map --when-focus-on title:keyboard.protocol kitty_mod+t
|
||||
|
||||
This will cause :kbd:`kitty_mod+t` (the default shortcut for opening a new tab)
|
||||
to be unmapped only when the focused window
|
||||
has :code:`keyboard protocol` in its title. Run the show-key kitten as::
|
||||
|
||||
kitten show-key -m kitty
|
||||
|
||||
Press :kbd:`ctrl+shift+t` and instead of a new tab opening, you will
|
||||
see the key press being reported by the kitten. :code:`--when-focus-on` can test
|
||||
the focused window using very powerful criteria, see :ref:`search_syntax` for
|
||||
details. A more practical example unmaps the key when the focused window is running vim::
|
||||
|
||||
map --when-focus-on var:in_editor
|
||||
|
||||
In order to make this work, you need the following lines in your :file:`.vimrc`::
|
||||
|
||||
let &t_ti = &t_ti . "\\033]1337;SetUserVar=in_editor=MQo\\007"
|
||||
let &t_te = &t_te . "\\033]1337;SetUserVar=in_editor\\007"
|
||||
|
||||
These cause vim to set the :code:`in_editor` variable in kitty and unset it when leaving vim.
|
||||
|
||||
Sending arbitrary text or keys to the program running in kitty
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
This is accomplished by using ``map`` with :sc:`send_text <send_text>` in :file:`kitty.conf`.
|
||||
For example::
|
||||
|
||||
map f1 send_text normal,application Hello, world!
|
||||
|
||||
Now, pressing :kbd:`f1` will cause ``Hello, world!`` to show up at your shell
|
||||
prompt. To have the shell execute a command sent via ``send_text`` you need to
|
||||
also simulate pressing the enter key which is ``\r``. For example::
|
||||
|
||||
map f1 send_text normal,application echo Hello, world!\r
|
||||
|
||||
Now, if you press :kbd:`f1` when at shell prompt it will run the ``echo Hello,
|
||||
world!`` command.
|
||||
|
||||
To have one key press send another key press, use :ac:`send_key`::
|
||||
|
||||
map alt+s send_key ctrl+s
|
||||
|
||||
This causes the program running in kitty to receive the :kbd:`ctrl+s` key when
|
||||
you press the :kbd:`alt+s` key. To see this in action, run::
|
||||
|
||||
kitten show-key -m kitty
|
||||
|
||||
Which will print out what key events it receives.
|
||||
|
||||
.. _modal_mappings:
|
||||
|
||||
Modal mappings
|
||||
--------------------------
|
||||
|
||||
kitty has the ability, like vim, to use *modal* key maps. Except that unlike
|
||||
vim it allows you to define your own arbitrary number of modes. To create a new
|
||||
mode, use ``map --new-mode <my mode name> <shortcut to enter mode>``. For
|
||||
example, lets create a mode to manage windows: switching focus, moving the window, etc.::
|
||||
|
||||
# Create a new "manage windows" mode (mw)
|
||||
map --new-mode mw kitty_mod+f7
|
||||
|
||||
# Switch focus to the neighboring window in the indicated direction using arrow keys
|
||||
map --mode mw left neighboring_window left
|
||||
map --mode mw right neighboring_window right
|
||||
map --mode mw up neighboring_window up
|
||||
map --mode mw down neighboring_window down
|
||||
|
||||
# Move the active window in the indicated direction
|
||||
map --mode mw shift+up move_window up
|
||||
map --mode mw shift+left move_window left
|
||||
map --mode mw shift+right move_window right
|
||||
map --mode mw shift+down move_window down
|
||||
|
||||
# Resize the active window
|
||||
map --mode mw n resize_window narrower
|
||||
map --mode mw w resize_window wider
|
||||
map --mode mw t resize_window taller
|
||||
map --mode mw s resize_window shorter
|
||||
|
||||
# Exit the manage window mode
|
||||
map --mode mw esc pop_keyboard_mode
|
||||
|
||||
Now, if you run kitty as:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty -o enabled_layouts=vertical --session <(echo "launch\nlaunch\nlaunch")
|
||||
|
||||
Press :kbd:`Ctrl+Shift+F7` to enter the mode and then press the up and
|
||||
down arrow keys to focus the next/previous window. Press :kbd:`Shift+Up` or
|
||||
:kbd:`Shift+Down` to move the active window up and down. Press :kbd:`t` to make
|
||||
the active window taller and :kbd:`s` to make it shorter. To exit the mode
|
||||
press :kbd:`Esc`.
|
||||
|
||||
Pressing an unknown key while in a custom keyboard mode by default
|
||||
beeps. This can be controlled by the ``map --on-unknown`` option as shown
|
||||
below::
|
||||
|
||||
# Beep on unknown keys
|
||||
map --new-mode XXX --on-unknown beep ...
|
||||
# Ingore unknown keys silently
|
||||
map --new-mode XXX --on-unknown ignore ...
|
||||
# Beep and exit the keyboard mode on unknown key
|
||||
map --new-mode XXX --on-unknown end ...
|
||||
# Pass unknown keys to the program running in the active window
|
||||
map --new-mode XXX --on-unknown passthrough ...
|
||||
|
||||
When a key matches an action in a custom keyboard mode, the action is performed
|
||||
and the custom keyboard mode remains in effect. If you would rather have the
|
||||
keyboard mode end after the action you can use ``map --on-action`` as shown
|
||||
below::
|
||||
|
||||
# Have this keyboard mode automatically exit after performing any action
|
||||
map --new-mode XXX --on-action end ...
|
||||
|
||||
|
||||
All mappable actions
|
||||
------------------------
|
||||
|
||||
There is a list of :doc:`all mappable actions <actions>`.
|
||||
|
||||
Debugging mapping issues
|
||||
------------------------------
|
||||
|
||||
To debug mapping issues, kitty has several facilities. First, when you run
|
||||
kitty with the ``--debug-input`` command line flag it outputs details
|
||||
about all key events it receives form the system and how they are handled.
|
||||
|
||||
To see what key events are sent to applications, run kitty like this::
|
||||
|
||||
kitty kitten show-key
|
||||
|
||||
Press the keys you want to debug and the kitten will print out the bytes it
|
||||
receives. Note that this uses the legacy terminal keyboard protocol that does
|
||||
not support all keys and key events. To debug the :doc:`full kitty keyboard
|
||||
protocol that <keyboard-protocol>` that is nowadays being adopted by more and
|
||||
more programs, use::
|
||||
|
||||
kitty kitten show-key -m kitty
|
||||
@@ -98,6 +98,10 @@ You can :doc:`create your own kittens to scratch your own itches
|
||||
|
||||
For a list of all the builtin kittens, :ref:`see here <kittens>`.
|
||||
|
||||
Additionally, you can use the :ref:`watchers <Watchers>` framework
|
||||
to create Python scripts that run in response to various events such as windows
|
||||
being resized, closing, having their titles changed, etc.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
|
||||
174
docs/pointer-shapes.rst
Normal file
174
docs/pointer-shapes.rst
Normal file
@@ -0,0 +1,174 @@
|
||||
Mouse pointer shapes
|
||||
=======================
|
||||
|
||||
.. versionadded:: 0.31.0
|
||||
|
||||
This is a simple escape code that can be used by terminal programs to change
|
||||
the shape of the mouse pointer. This is useful for buttons/links, dragging to
|
||||
resize panes, etc. It is based on the original escape code proposal from xterm
|
||||
however, it properly specifies names for the different shapes in a system
|
||||
independent manner, adds a stack for easy push/pop of shapes, allows programs
|
||||
to query support and specifies interaction with other terminal state.
|
||||
|
||||
The escape code is of the form::
|
||||
|
||||
<OSC> 22 ; <optional first char> <comma-separates list of shape names> <ESC>\
|
||||
|
||||
Here, ``<OSC>`` is the bytes ``<ESC>]`` and ``<ESC>`` is the byte ``0x1b``.
|
||||
Spaces in the above are present for clarity only and should not be actually used.
|
||||
|
||||
First some examples::
|
||||
|
||||
# Set the pointer to a pointing hand
|
||||
<OSC> 22 ; pointer <ESC>\
|
||||
# Reset the pointer to default
|
||||
<OSC> 22 ; <ESC>\
|
||||
# Push a shape onto the stack making it the current shape
|
||||
<OSC> 22 ; >wait <ESC>\
|
||||
# Pop a shape off the stack restoring to the previous shape
|
||||
<OSC> 22 ; < <ESC>\
|
||||
# Query the terminal for what the currently set shape is
|
||||
<OSC> 22 ; ?__current__ <ESC>\
|
||||
|
||||
To demo the various shapes, simply run the following command inside kitty::
|
||||
|
||||
kitten mouse-demo
|
||||
|
||||
For more details see below.
|
||||
|
||||
Setting the pointer shape
|
||||
-------------------------------
|
||||
|
||||
For set operations, the optional first char can be either ``=`` or omitted.
|
||||
Follow the first char with the name of the shape. See the
|
||||
:ref:`pointer_shape_names` table.
|
||||
|
||||
|
||||
Pushing and popping shapes onto the stack
|
||||
---------------------------------------------
|
||||
|
||||
The terminal emulator maintains a stack of shapes. To add shapes to the stack,
|
||||
the optional first char must be ``>`` followed by a comma separated list of
|
||||
shape names. See the :ref:`pointer_shape_names` table. All the specified names
|
||||
are added to the stack, with the last name being the top of the stack and the
|
||||
current shape. If the stack is full, the entry at the bottom of the stack is
|
||||
evicted. Terminal implementations are free to choose an appropriate maximum
|
||||
stack size, with a minimum stack size of 16.
|
||||
|
||||
To pop shapes of the top of the stack the optional first char must be ``<``.
|
||||
The comma separated list of names is ignored. Once the stack is empty further
|
||||
pops have no effect. An empty stack means the terminal is free to use whatever
|
||||
pointer shape it likes.
|
||||
|
||||
|
||||
Querying support
|
||||
-------------------
|
||||
|
||||
Terminal programs can ask the terminal about this feature by setting the
|
||||
optional first char to ``?``. The comma separated list of names is then
|
||||
considered the query to which the terminal must respond with an OSC 22 code.
|
||||
For example::
|
||||
|
||||
<OSC> 22 ; ?__current__ <ESC>\
|
||||
results in
|
||||
<OSC> 22 ; shape_name <ESC>\
|
||||
|
||||
Here, ``shape_name`` will be a name from the table of shape names below or ``0``
|
||||
if the stack is empty, i.e., no shape is currently set.
|
||||
|
||||
To check if the terminal supports some shapes, pass the shape names and the
|
||||
terminal will reply with a comma separated list of zeros and ones where 1 means
|
||||
the shape name is supported and zero means it is not. For example::
|
||||
|
||||
<OSC> 22 ; ?pointer,crosshair,no-such-name,wait <ESC>\
|
||||
results in
|
||||
<OSC> 22 ; 1,1,0,1 <ESC>\
|
||||
|
||||
In addition to ``__current__`` there are a couple of other special names::
|
||||
|
||||
__default__ - The terminal responds with the shape name of the shape used by default
|
||||
__grabbed__ - The terminal responds with the shape name of the shape used when the mouse is "grabbed"
|
||||
|
||||
|
||||
Interaction with other terminal features
|
||||
---------------------------------------------
|
||||
|
||||
The terminal must maintain separate shape stacks for the *main* and *alternate*
|
||||
screens. This allows full screen programs, which are likely to be the main
|
||||
consumers of this feature, to easily temporarily switch back from the alternate screen,
|
||||
without needing to worry about pointer shape state. Think of suspending a
|
||||
terminal editor to get back to the shell, for example.
|
||||
|
||||
Resetting the terminal must empty both the shape stacks.
|
||||
|
||||
When dragging to select text, the terminal is free to ignore any mouse pointer
|
||||
shape specified using this escape code in favor of one appropriate for
|
||||
dragging. Similarly, when hovering over a URL or OSC 8 based hyperlink, the
|
||||
terminal may choose to change the mouse pointer regardless of the value set by
|
||||
this escape code.
|
||||
|
||||
This feature is independent of mouse reporting. The changed pointer shapes apply
|
||||
regardless of whether the terminal program has enabled mouse reporting or not.
|
||||
|
||||
|
||||
.. _pointer_shape_names:
|
||||
|
||||
Pointer shape names
|
||||
----------------------------------
|
||||
|
||||
There is a well defined set of shape names that all conforming terminal
|
||||
emulators must support. The list is based on the names used by the `cursor
|
||||
property in the CSS standard
|
||||
<https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>`__, click the link to
|
||||
see representative images for the names. Valid names must consist of only the
|
||||
characters from the set ``a-z0-9_-``.
|
||||
|
||||
.. start list of shape css names (auto generated by gen-key-constants.py do not edit)
|
||||
|
||||
#. alias
|
||||
#. cell
|
||||
#. copy
|
||||
#. crosshair
|
||||
#. default
|
||||
#. e-resize
|
||||
#. ew-resize
|
||||
#. grab
|
||||
#. grabbing
|
||||
#. help
|
||||
#. move
|
||||
#. n-resize
|
||||
#. ne-resize
|
||||
#. nesw-resize
|
||||
#. no-drop
|
||||
#. not-allowed
|
||||
#. ns-resize
|
||||
#. nw-resize
|
||||
#. nwse-resize
|
||||
#. pointer
|
||||
#. progress
|
||||
#. s-resize
|
||||
#. se-resize
|
||||
#. sw-resize
|
||||
#. text
|
||||
#. vertical-text
|
||||
#. w-resize
|
||||
#. wait
|
||||
#. zoom-in
|
||||
#. zoom-out
|
||||
|
||||
.. end list of shape css names
|
||||
|
||||
To demo the various shapes, simply run the following command inside kitty::
|
||||
|
||||
kitten mouse-demo
|
||||
|
||||
Legacy xterm compatibility
|
||||
----------------------------
|
||||
|
||||
The original xterm proposal for this escape code used shape names from the
|
||||
file:`X11/cursorfont.h` header on X11 based systems. Terminal implementations
|
||||
wishing to maintain compatibility with xterm can also implement these names as
|
||||
aliases for the CSS based names defined in the :ref:`pointer_shape_names` table.
|
||||
|
||||
The simplest mode of operation of this escape code, which is no leading
|
||||
optional char and a single shape name is compatible with xterm.
|
||||
@@ -29,6 +29,7 @@ please do so by opening issues in the `GitHub bug tracker
|
||||
keyboard-protocol
|
||||
file-transfer-protocol
|
||||
desktop-notifications
|
||||
pointer-shapes
|
||||
unscroll
|
||||
color-stack
|
||||
deccara
|
||||
|
||||
@@ -23,25 +23,25 @@ turn it on explicitly at the command line.
|
||||
|
||||
Now, in the new |kitty| window, enter the command::
|
||||
|
||||
kitty @ launch --title Output --keep-focus cat
|
||||
kitten @ launch --title Output --keep-focus cat
|
||||
|
||||
This will open a new window, running the :program:`cat` program that will appear
|
||||
next to the current window.
|
||||
|
||||
Let's send some text to this new window::
|
||||
|
||||
kitty @ send-text --match cmdline:cat Hello, World
|
||||
kitten @ send-text --match cmdline:cat Hello, World
|
||||
|
||||
This will make ``Hello, World`` show up in the window running the :program:`cat`
|
||||
program. The :option:`kitty @ send-text --match` option is very powerful, it
|
||||
program. The :option:`kitten @ send-text --match` option is very powerful, it
|
||||
allows selecting windows by their titles, the command line of the program
|
||||
running in the window, the working directory of the program running in the
|
||||
window, etc. See :ref:`kitty @ send-text --help <at-send-text>` for details.
|
||||
window, etc. See :ref:`kitten @ send-text --help <at-send-text>` for details.
|
||||
|
||||
More usefully, you can pipe the output of a command running in one window to
|
||||
another window, for example::
|
||||
|
||||
ls | kitty @ send-text --match 'title:^Output' --stdin
|
||||
ls | kitten @ send-text --match 'title:^Output' --stdin
|
||||
|
||||
This will show the output of :program:`ls` in the output window instead of the
|
||||
current window. You can use this technique to, for example, show the output of
|
||||
@@ -50,56 +50,55 @@ are endless.
|
||||
|
||||
You can even have things you type show up in a different window. Run::
|
||||
|
||||
kitty @ send-text --match 'title:^Output' --stdin
|
||||
kitten @ send-text --match 'title:^Output' --stdin
|
||||
|
||||
And type some text, it will show up in the output window, instead of the current
|
||||
window. Type :kbd:`Ctrl+D` when you are ready to stop.
|
||||
|
||||
Now, let's open a new tab::
|
||||
|
||||
kitty @ launch --type=tab --tab-title "My Tab" --keep-focus bash
|
||||
kitten @ launch --type=tab --tab-title "My Tab" --keep-focus bash
|
||||
|
||||
This will open a new tab running the bash shell with the title "My Tab".
|
||||
We can change the title of the tab to "New Title" with::
|
||||
|
||||
kitty @ set-tab-title --match 'title:^My' New Title
|
||||
kitten @ set-tab-title --match 'title:^My' New Title
|
||||
|
||||
Let's change the title of the current tab::
|
||||
|
||||
kitty @ set-tab-title Master Tab
|
||||
kitten @ set-tab-title Master Tab
|
||||
|
||||
Now lets switch to the newly opened tab::
|
||||
|
||||
kitty @ focus-tab --match 'title:^New'
|
||||
kitten @ focus-tab --match 'title:^New'
|
||||
|
||||
Similarly, to focus the previously opened output window (which will also switch
|
||||
back to the old tab, automatically)::
|
||||
|
||||
kitty @ focus-window --match 'title:^Output'
|
||||
kitten @ focus-window --match 'title:^Output'
|
||||
|
||||
You can get a listing of available tabs and windows, by running::
|
||||
|
||||
kitty @ ls
|
||||
kitten @ ls
|
||||
|
||||
This outputs a tree of data in JSON format. The top level of the tree is all
|
||||
:term:`OS windows <os_window>`. Each OS window has an id and a list of
|
||||
:term:`tabs <tab>`. Each tab has its own id, a title and a list of :term:`kitty
|
||||
windows <window>`. Each window has an id, title, current working directory,
|
||||
process id (PID) and command-line of the process running in the window. You can
|
||||
use this information with :option:`kitty @ focus-window --match` to control
|
||||
use this information with :option:`kitten @ focus-window --match` to control
|
||||
individual windows.
|
||||
|
||||
As you can see, it is very easy to control |kitty| using the ``kitty @``
|
||||
As you can see, it is very easy to control |kitty| using the ``kitten @``
|
||||
messaging system. This tutorial touches only the surface of what is possible.
|
||||
See ``kitty @ --help`` for more details.
|
||||
See ``kitten @ --help`` for more details.
|
||||
|
||||
In the example's above, ``kitty @`` messaging works only when run
|
||||
In the example's above, ``kitten @`` messaging works only when run
|
||||
inside a |kitty| window, not anywhere. But, within a |kitty| window it even
|
||||
works over SSH. If you want to control |kitty| from programs/scripts not running
|
||||
inside a |kitty| window, see the section on :ref:`using a socket for remote control <rc_via_socket>`
|
||||
below.
|
||||
|
||||
|
||||
Note that if 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 :option:`kitty --single-instance` option, see ``kitty --help`` for that.
|
||||
@@ -115,23 +114,23 @@ First, start |kitty| as::
|
||||
|
||||
The :option:`kitty --listen-on` option tells |kitty| to listen for control
|
||||
messages at the specified UNIX-domain socket. See ``kitty --help`` for details.
|
||||
Now you can control this instance of |kitty| using the :option:`kitty @ --to`
|
||||
command line argument to ``kitty @``. For example::
|
||||
Now you can control this instance of |kitty| using the :option:`kitten @ --to`
|
||||
command line argument to ``kitten @``. For example::
|
||||
|
||||
kitty @ --to unix:/tmp/mykitty ls
|
||||
kitten @ --to unix:/tmp/mykitty ls
|
||||
|
||||
|
||||
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
|
||||
|kitty| shell. Run ``kitten @`` 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 (:sc:`kitty_shell` by default).
|
||||
|
||||
.. note:: This has the added advantage that you don't need to use
|
||||
.. note:: Using the keyboard shortcut has the added advantage that you don't need to use
|
||||
:opt:`allow_remote_control` to make it work.
|
||||
|
||||
|
||||
@@ -144,7 +143,7 @@ create a shortcut such as::
|
||||
|
||||
map ctrl+k launch --allow-remote-control some_program
|
||||
|
||||
Then programs running in windows created with that shortcut can use ``kitty @``
|
||||
Then programs running in windows created with that shortcut can use ``kitten @``
|
||||
to control kitty. Note that any program with the right level of permissions can
|
||||
still write to the pipes of any other program on the same computer and therefore
|
||||
can control |kitty|. It can, however, be useful to block programs running on
|
||||
@@ -156,7 +155,7 @@ other computers (for example, over SSH) or as other users.
|
||||
kitty, as if you were running with :opt:`allow_remote_control` turned on.
|
||||
|
||||
You can further restrict what is allowed in these windows by using
|
||||
:option:`kitty @ launch --remote-control-password`.
|
||||
:option:`kitten @ launch --remote-control-password`.
|
||||
|
||||
|
||||
Fine grained permissions for remote control
|
||||
@@ -181,19 +180,19 @@ Let's see some examples:
|
||||
|
||||
Now, using this password, you can, in scripts run the command::
|
||||
|
||||
kitty @ --password="control colors" set-colors background=red
|
||||
kitten @ --password="control colors" set-colors background=red
|
||||
|
||||
Any script with access to the password can now change colors in kitty using
|
||||
remote control, but only that and nothing else. You can even supply the
|
||||
password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the
|
||||
file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly.
|
||||
See :option:`kitty @ --password-file` and :option:`kitty @ --password-env`.
|
||||
See :option:`kitten @ --password-file` and :option:`kitten @ --password-env`.
|
||||
|
||||
The :opt:`remote_control_password` can be specified multiple times to create
|
||||
different passwords with different capabilities. Run the following to get a
|
||||
list of all action names::
|
||||
|
||||
kitty @ --help
|
||||
kitten @ --help
|
||||
|
||||
You can even use glob patterns to match action names, for example:
|
||||
|
||||
@@ -203,7 +202,7 @@ You can even use glob patterns to match action names, for example:
|
||||
|
||||
If no action names are specified, all actions are allowed.
|
||||
|
||||
If ``kitty @`` is run with a password that is not present in
|
||||
If ``kitten @`` is run with a password that is not present in
|
||||
:file:`kitty.conf`, then kitty will interactively prompt the user to allow or
|
||||
disallow the remote control request. The user can choose to allow or disallow
|
||||
either just that request or all requests using that password. The user's
|
||||
@@ -216,7 +215,7 @@ decision is remembered for the duration of that kitty instance.
|
||||
using a password, :ref:`rc_crypto` is used to ensure the password
|
||||
is kept secure. This does mean that using password based authentication
|
||||
is slower as the entire command is encrypted before transmission. This
|
||||
can be noticeable when using a command like ``kitty @ set-background-image``
|
||||
can be noticeable when using a command like ``kitten @ set-background-image``
|
||||
which transmits large amounts of image data. Also, the clock on the remote
|
||||
system must match (within a few minutes) the clock on the local system.
|
||||
kitty uses a time based nonce to minimise the potential for replay attacks.
|
||||
@@ -274,7 +273,7 @@ you can map it in :file:`kitty.conf`. For example::
|
||||
|
||||
Then pressing the :kbd:`F1` key will set the active window margins to
|
||||
:code:`30`. The syntax for what follows :ac:`remote_control` is exactly the same
|
||||
as the syntax for what follows :code:`kitty @` above.
|
||||
as the syntax for what follows :code:`kitten @` above.
|
||||
|
||||
If you wish to ignore errors from the command, prefix the command with an
|
||||
``!``. For example, the following will not return an error when no windows
|
||||
@@ -282,6 +281,17 @@ are matched::
|
||||
|
||||
map f1 remote_control !focus-window --match XXXXXX
|
||||
|
||||
If you wish to run a more complex script, you can use::
|
||||
|
||||
map f1 remote_control_script /path/to/myscript
|
||||
|
||||
In this script you can use ``kitten @`` to run as many remote
|
||||
control commands as you like and process their output.
|
||||
:ac:`remote_control_script` is really just an alias for the
|
||||
:ac:`launch` command with ``--type=background --allow-remote-control``.
|
||||
For more advanced usage, including fine grained permissions, setting
|
||||
env vars, etc. see the docs for the :doc:`launch <launch>` command.
|
||||
|
||||
.. note:: You do not need :opt:`allow_remote_control` to use these mappings,
|
||||
as they are not actual remote programs, but are simply a way to reuse the
|
||||
remote control infrastructure via keybings.
|
||||
@@ -320,7 +330,7 @@ Matching windows and tabs
|
||||
|
||||
Many remote control operations operate on windows or tabs. To select these, the
|
||||
:code:`--match` option is often used. This allows matching using various
|
||||
sophisticated criteria such as title, ids, cmdlines, etc. These criteria are
|
||||
sophisticated criteria such as title, ids, command lines, etc. These criteria are
|
||||
expressions of the form :code:`field:query`. Where :italic:`field` is the field
|
||||
against which to match and :italic:`query` is the expression to match. They can
|
||||
be further combined using Boolean operators, best illustrated with some
|
||||
@@ -331,9 +341,11 @@ examples::
|
||||
not id:1
|
||||
(id:2 or id:3) and title:something
|
||||
|
||||
.. include:: generated/matching.rst
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
rc_protocol
|
||||
|
||||
.. include:: generated/cli-kitty-at.rst
|
||||
.. include:: generated/cli-kitten-at.rst
|
||||
|
||||
@@ -93,7 +93,11 @@ no-complete
|
||||
|
||||
no-sudo
|
||||
Do not alias :program:`sudo` to ensure the kitty terminfo files are
|
||||
available in the sudo environment
|
||||
available in the sudo environment. This is needed if you have sudo
|
||||
configured to disable setting of environment variables on the command line.
|
||||
By default, if sudo is configured to allow all commands for the current
|
||||
user, setting of environment variables at the command line is also allowed.
|
||||
Only if commands are restricted is this needed.
|
||||
|
||||
|
||||
More ways to browse command output
|
||||
|
||||
4
gen/README.rst
Normal file
4
gen/README.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Scripts to generate code for various things like keys, mouse cursors, unicode
|
||||
data etc. Some of these generate code that is checked into version control.
|
||||
Some generate ephemeral code used during builds. Ephemeral code is in files
|
||||
with a _generated.[h|go|c] extension.
|
||||
43
gen/__main__.py
Normal file
43
gen/__main__.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, os.getcwd())
|
||||
if len(args) == 1:
|
||||
raise SystemExit('usage: python gen which')
|
||||
which = args[1]
|
||||
del args[1]
|
||||
if which == 'apc-parsers':
|
||||
from gen.apc_parsers import main
|
||||
main(args)
|
||||
elif which == 'config':
|
||||
from gen.config import main
|
||||
main(args)
|
||||
elif which == 'srgb-lut':
|
||||
from gen.srgb_lut import main
|
||||
main(args)
|
||||
elif which == 'key-constants':
|
||||
from gen.key_constants import main
|
||||
main(args)
|
||||
elif which == 'go-code':
|
||||
from gen.go_code import main
|
||||
main(args)
|
||||
elif which == 'wcwidth':
|
||||
from gen.wcwidth import main
|
||||
main(args)
|
||||
elif which == 'cursors':
|
||||
from gen.cursors import main
|
||||
main(args)
|
||||
else:
|
||||
raise SystemExit(f'Unknown which: {which}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,11 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]]
|
||||
|
||||
|
||||
@@ -276,9 +282,20 @@ def graphics_parser() -> None:
|
||||
'z': ('z_index', 'int'),
|
||||
'C': ('cursor_movement', 'uint'),
|
||||
'U': ('unicode_placement', 'uint'),
|
||||
'P': ('parent_id', 'uint'),
|
||||
'Q': ('parent_placement_id', 'uint'),
|
||||
'H': ('offset_from_parent_x', 'int'),
|
||||
'V': ('offset_from_parent_y', 'int'),
|
||||
}
|
||||
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
||||
write_header(text, 'kitty/parse-graphics-command.h')
|
||||
|
||||
|
||||
graphics_parser()
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
graphics_parser()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'apc-parsers'])
|
||||
@@ -2,12 +2,19 @@
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from kitty.conf.generate import write_output
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
|
||||
with open(path, 'r+') as f:
|
||||
@@ -33,7 +40,7 @@ def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ')
|
||||
subprocess.check_call(['gofmt', '-w', path])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
from kitty.options.definition import definition
|
||||
write_output('kitty', definition)
|
||||
nullable_colors = []
|
||||
@@ -51,4 +58,6 @@ def main() -> None:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'config'])
|
||||
154
gen/cursors.py
Executable file
154
gen/cursors.py
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from .key_constants import patch_file
|
||||
|
||||
# References for these names:
|
||||
# CSS:choices_for_{option.name} https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
|
||||
# XCursor: https://tronche.com/gui/x/xlib/appendix/b/ + Absolute chaos
|
||||
# Wayland: https://wayland.app/protocols/cursor-shape-v1
|
||||
# Cocoa: https://developer.apple.com/documentation/appkit/nscursor + secret apple selectors + SDL_cocoamouse.m
|
||||
|
||||
# kitty_names CSS_name XCursor_names Wayland_name Cocoa_name
|
||||
cursors = '''\
|
||||
arrow default default,!left_ptr default arrowCursor
|
||||
beam,text text text,!xterm,ibeam text IBeamCursor
|
||||
pointer,hand pointer pointing_hand,pointer,!hand2,hand pointer pointingHandCursor
|
||||
help help help,!question_arrow,whats_this help help:arrowCursor
|
||||
wait wait wait,!clock,watch wait busybutclickable:arrowCursor
|
||||
progress progress progress,half-busy,left_ptr_watch progress busybutclickable:arrowCursor
|
||||
crosshair crosshair crosshair,!tcross crosshair crosshairCursor
|
||||
cell cell cell,!plus,!cross cell cell:crosshairCursor
|
||||
vertical-text vertical-text vertical-text vertical-text IBeamCursorForVerticalLayout
|
||||
move move move,!fleur,pointer-move move move:openHandCursor
|
||||
|
||||
e-resize e-resize e-resize,!right_side e_resize resizeRightCursor
|
||||
ne-resize ne-resize ne-resize,!top_right_corner ne_resize resizenortheast:_windowResizeNorthEastSouthWestCursor
|
||||
nw-resize nw-resize nw-resize,!top_left_corner nw_resize resizenorthwest:_windowResizeNorthWestSouthEastCursor
|
||||
n-resize n-resize n-resize,!top_side n_resize resizeUpCursor
|
||||
se-resize se-resize se-resize,!bottom_right_corner se_resize resizesoutheast:_windowResizeNorthWestSouthEastCursor
|
||||
sw-resize sw-resize sw-resize,!bottom_left_corner sw_resize resizesouthwest:_windowResizeNorthEastSouthWestCursor
|
||||
s-resize s-resize s-resize,!bottom_side s_resize resizeDownCursor
|
||||
w-resize w-resize w-resize,!left_side w_resize resizeLeftCursor
|
||||
|
||||
ew-resize ew-resize ew-resize,!sb_h_double_arrow,split_h ew_resize resizeLeftRightCursor
|
||||
ns-resize ns-resize ns-resize,!sb_v_double_arrow,split_v ns_resize resizeUpDownCursor
|
||||
nesw-resize nesw-resize nesw-resize,size_bdiag,size-bdiag nesw_resize _windowResizeNorthEastSouthWestCursor
|
||||
nwse-resize nwse-resize nwse-resize,size_fdiag,size-fdiag nwse_resize _windowResizeNorthWestSouthEastCursor
|
||||
|
||||
zoom-in zoom-in zoom-in,zoom_in zoom_in zoomin:arrowCursor
|
||||
zoom-out zoom-out zoom-out,zoom_out zoom_out zoomout:arrowCursor
|
||||
|
||||
alias alias dnd-link alias dragLinkCursor
|
||||
copy copy dnd-copy copy dragCopyCursor
|
||||
not-allowed not-allowed not-allowed,forbidden,crossed_circle not_allowed operationNotAllowedCursor
|
||||
no-drop no-drop no-drop,dnd-no-drop no_drop operationNotAllowedCursor
|
||||
grab grab grab,openhand,!hand1 grab openHandCursor
|
||||
grabbing grabbing grabbing,closedhand,dnd-none grabbing closedHandCursor
|
||||
'''
|
||||
|
||||
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
glfw_enum = []
|
||||
css_names = []
|
||||
glfw_xc_map = {}
|
||||
glfw_xfont_map = []
|
||||
kitty_to_enum_map = {}
|
||||
enum_to_glfw_map = {}
|
||||
enum_to_css_map = {}
|
||||
glfw_cocoa_map = {}
|
||||
glfw_css_map = {}
|
||||
css_to_enum = {}
|
||||
xc_to_enum = {}
|
||||
glfw_wayland = {}
|
||||
for line in cursors.splitlines():
|
||||
line = line.strip()
|
||||
if line:
|
||||
names_, css, xc_, wayland, cocoa = line.split()
|
||||
names, xc = names_.split(','), xc_.split(',')
|
||||
base = css.replace('-', '_').upper()
|
||||
glfw_name = 'GLFW_' + base + '_CURSOR'
|
||||
enum_name = base + '_POINTER'
|
||||
enum_to_glfw_map[enum_name] = glfw_name
|
||||
enum_to_css_map[enum_name] = css
|
||||
glfw_css_map[glfw_name] = css
|
||||
css_to_enum[css] = enum_name
|
||||
css_names.append(css)
|
||||
glfw_wayland[glfw_name] = 'WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_' + wayland.replace('-', '_').upper()
|
||||
for n in names:
|
||||
kitty_to_enum_map[n] = enum_name
|
||||
glfw_enum.append(glfw_name)
|
||||
glfw_xc_map[glfw_name] = ', '.join(f'''"{x.replace('!', '')}"''' for x in xc)
|
||||
for x in xc:
|
||||
if x.startswith('!'):
|
||||
glfw_xfont_map.append(f"case {glfw_name}: return set_cursor_from_font(cursor, {'XC_' + x[1:]});")
|
||||
break
|
||||
else:
|
||||
items = tuple('"' + x.replace('!', '') + '"' for x in xc)
|
||||
glfw_xfont_map.append(f'case {glfw_name}: return try_cursor_names(cursor, {len(items)}, {", ".join(items)});')
|
||||
for x in xc:
|
||||
x = x.lstrip('!')
|
||||
xc_to_enum[x] = enum_name
|
||||
parts = cocoa.split(':', 1)
|
||||
if len(parts) == 1:
|
||||
if parts[0].startswith('_'):
|
||||
glfw_cocoa_map[glfw_name] = f'U({glfw_name}, {parts[0]});'
|
||||
else:
|
||||
glfw_cocoa_map[glfw_name] = f'C({glfw_name}, {parts[0]});'
|
||||
else:
|
||||
glfw_cocoa_map[glfw_name] = f'S({glfw_name}, {parts[0]}, {parts[1]});'
|
||||
|
||||
for x, v in xc_to_enum.items():
|
||||
if x not in css_to_enum:
|
||||
css_to_enum[x] = v
|
||||
|
||||
glfw_enum.append('GLFW_INVALID_CURSOR')
|
||||
patch_file('glfw/glfw3.h', 'mouse cursor shapes', '\n'.join(f' {x},' for x in glfw_enum))
|
||||
patch_file('glfw/wl_window.c', 'glfw to wayland mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_wayland.items()))
|
||||
patch_file('glfw/wl_window.c', 'glfw to xc mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_xc_map.items()))
|
||||
patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map))
|
||||
patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map))
|
||||
patch_file(
|
||||
'kitty/options/definition.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map),
|
||||
start_marker='# ', end_marker='',
|
||||
)
|
||||
patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join(
|
||||
f' else if (strcmp(name, "{k}") == 0) return {v};' for k, v in kitty_to_enum_map.items()))
|
||||
patch_file('kitty/glfw.c', 'enum to glfw', '\n'.join(
|
||||
f' case {k}: set_glfw_mouse_cursor(w, {v}); break;' for k, v in enum_to_glfw_map.items()))
|
||||
patch_file('kitty/glfw.c', 'name to glfw', '\n'.join(
|
||||
f' if (strcmp(name, "{k}") == 0) return {enum_to_glfw_map[v]};' for k, v in kitty_to_enum_map.items()))
|
||||
patch_file('kitty/glfw.c', 'glfw to css', '\n'.join(
|
||||
f' case {g}: return "{c}";' for g, c in glfw_css_map.items()
|
||||
))
|
||||
patch_file('kitty/screen.c', 'enum to css', '\n'.join(
|
||||
f' case {e}: ans = "{c}"; break;' for e, c in enum_to_css_map.items()))
|
||||
patch_file('kitty/screen.c', 'css to enum', '\n'.join(
|
||||
f' else if (strcmp("{c}", css_name) == 0) s = {e};' for c, e in css_to_enum.items()))
|
||||
patch_file('glfw/cocoa_window.m', 'glfw to cocoa', '\n'.join(f' {x}' for x in glfw_cocoa_map.values()))
|
||||
patch_file('docs/pointer-shapes.rst', 'list of shape css names', '\n'.join(
|
||||
f'#. {x}' if x else '' for x in [''] + sorted(css_names) + ['']), start_marker='.. ', end_marker='')
|
||||
patch_file('tools/tui/loop/mouse.go', 'pointer shape enum', '\n'.join(
|
||||
f'\t{x} PointerShape = {i}' for i, x in enumerate(enum_to_glfw_map)), start_marker='// ', end_marker='')
|
||||
patch_file('tools/tui/loop/mouse.go', 'pointer shape tostring', '\n'.join(
|
||||
f'''\tcase {x}: return "{x.lower().rpartition('_')[0].replace('_', '-')}"''' for x in enum_to_glfw_map), start_marker='// ', end_marker='')
|
||||
patch_file('tools/cmd/mouse_demo/main.go', 'all pointer shapes', '\n'.join(
|
||||
f'\tloop.{x},' for x in enum_to_glfw_map), start_marker='// ', end_marker='')
|
||||
|
||||
subprocess.check_call(['glfw/glfw.py'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'cursors'])
|
||||
@@ -49,6 +49,12 @@ from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||
from kitty.remote_control import global_options_spec
|
||||
from kitty.rgb import color_names
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
changed: List[str] = []
|
||||
|
||||
|
||||
@@ -300,6 +306,7 @@ json_field_types: Dict[str, str] = {
|
||||
|
||||
|
||||
def go_field_type(json_field_type: str) -> str:
|
||||
json_field_type = json_field_type.partition('=')[0]
|
||||
q = json_field_types.get(json_field_type)
|
||||
if q:
|
||||
return q
|
||||
@@ -318,6 +325,7 @@ class JSONField:
|
||||
field_def = line.split(':', 1)[0]
|
||||
self.required = False
|
||||
self.field, self.field_type = field_def.split('/', 1)
|
||||
self.field_type, self.special_parser = self.field_type.partition('=')[::2]
|
||||
if self.field.endswith('+'):
|
||||
self.required = True
|
||||
self.field = self.field[:-1]
|
||||
@@ -364,14 +372,17 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
|
||||
if oq in option_map:
|
||||
o = option_map[oq]
|
||||
used_options.add(oq)
|
||||
optstring = f'options_{name}.{o.go_var_name}'
|
||||
if field.special_parser:
|
||||
optstring = f'{field.special_parser}({optstring})'
|
||||
if field.field_type == 'str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||
jc.append(f'payload.{field.struct_field_name} = escaped_string({optstring})')
|
||||
elif field.field_type == 'list.str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings(options_{name}.{o.go_var_name})')
|
||||
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings({optstring})')
|
||||
elif field.field_type == 'dict.str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings(options_{name}.{o.go_var_name})')
|
||||
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings({optstring})')
|
||||
else:
|
||||
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||
jc.append(f'payload.{field.struct_field_name} = {optstring}')
|
||||
elif field.field in handled_fields:
|
||||
pass
|
||||
else:
|
||||
@@ -493,12 +504,12 @@ def kitten_clis() -> None:
|
||||
od.append(opt.struct_declaration())
|
||||
if ac is not None:
|
||||
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
|
||||
if not kcd:
|
||||
print('specialize_command(ans)')
|
||||
if has_underscore:
|
||||
print("clone := root.AddClone(ans.Group, ans)")
|
||||
print('clone.Hidden = false')
|
||||
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
|
||||
if not kcd:
|
||||
print('specialize_command(ans)')
|
||||
print('}')
|
||||
print('type Options struct {')
|
||||
print('\n'.join(od))
|
||||
@@ -581,11 +592,11 @@ const VersionString string = "{kc.str_version}"
|
||||
const WebsiteBaseURL string = "{kc.website_base_url}"
|
||||
const FileTransferCode int = {FILE_TRANSFER_CODE}
|
||||
const ImagePlaceholderChar rune = {placeholder_char}
|
||||
const VCSRevision string = ""
|
||||
const SSHControlMasterTemplate = "{kc.ssh_control_master_template}"
|
||||
const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}"
|
||||
const IsFrozenBuild bool = false
|
||||
const IsStandaloneBuild bool = false
|
||||
var VCSRevision string = ""
|
||||
var IsFrozenBuild string = ""
|
||||
var IsStandaloneBuild string = ""
|
||||
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
||||
const HintsDefaultRegex = `{DEFAULT_REGEX}`
|
||||
const DefaultTermName = `{Options.term}`
|
||||
@@ -822,6 +833,7 @@ def generate_ssh_kitten_data() -> None:
|
||||
def normalize(t: tarfile.TarInfo) -> tarfile.TarInfo:
|
||||
t.uid = t.gid = 0
|
||||
t.uname = t.gname = ''
|
||||
t.mtime = 0
|
||||
return t
|
||||
|
||||
if newer(dest, *files):
|
||||
@@ -833,7 +845,7 @@ def generate_ssh_kitten_data() -> None:
|
||||
write_compressed_data(buf.getvalue(), d)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
with replace_if_needed('constants_generated.go') as f:
|
||||
f.write(generate_constants())
|
||||
with replace_if_needed('tools/utils/style/color-names_generated.go') as f:
|
||||
@@ -859,4 +871,7 @@ def main() -> None:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main() # }}}
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'go-code'])
|
||||
# }}}
|
||||
@@ -1,10 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
from pprint import pformat
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
functional_key_defs = '''# {{{
|
||||
# kitty XKB macVK macU
|
||||
escape Escape 0x35 -
|
||||
@@ -242,6 +250,8 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
|
||||
f.seek(0)
|
||||
f.truncate(0)
|
||||
f.write(raw)
|
||||
if path.endswith('.go'):
|
||||
subprocess.check_call(['go', 'fmt', path])
|
||||
|
||||
|
||||
def serialize_dict(x: Dict[Any, Any]) -> str:
|
||||
@@ -417,7 +427,7 @@ def generate_macos_mapping() -> None:
|
||||
patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
generate_glfw_header()
|
||||
generate_xkb_mapping()
|
||||
generate_functional_table()
|
||||
@@ -427,4 +437,6 @@ def main() -> None:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'key-constants'])
|
||||
@@ -1,10 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import List
|
||||
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def to_linear(a: float) -> float:
|
||||
if a <= 0.04045:
|
||||
@@ -41,11 +47,13 @@ def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] =
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
c = generate_srgb_gamma()
|
||||
with open(os.path.join('kitty', 'srgb_gamma.h'), 'w') as f:
|
||||
f.write(f'{c}\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'srgb-lut'])
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
@@ -26,7 +26,11 @@ from typing import (
|
||||
)
|
||||
from urllib.request import urlopen
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
if __name__ == '__main__' and not __package__:
|
||||
import __main__
|
||||
__main__.__package__ = 'gen'
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
non_characters = frozenset(range(0xfffe, 0x10ffff, 0x10000))
|
||||
non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
|
||||
@@ -136,7 +140,7 @@ def parse_ucd() -> None:
|
||||
# the future.
|
||||
marks.add(codepoint)
|
||||
|
||||
with open('nerd-fonts-glyphs.txt') as f:
|
||||
with open('gen/nerd-fonts-glyphs.txt') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
@@ -540,7 +544,7 @@ def gen_wcwidth() -> None:
|
||||
def gen_rowcolumn_diacritics() -> None:
|
||||
# codes of all row/column diacritics
|
||||
codes = []
|
||||
with open("./rowcolumn-diacritics.txt") as file:
|
||||
with open("gen/rowcolumn-diacritics.txt") as file:
|
||||
for line in file.readlines():
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
@@ -584,12 +588,19 @@ def gen_rowcolumn_diacritics() -> None:
|
||||
subprocess.check_call(['gofmt', '-w', '-s', go_file])
|
||||
|
||||
|
||||
parse_ucd()
|
||||
parse_prop_list()
|
||||
parse_emoji()
|
||||
parse_eaw()
|
||||
gen_ucd()
|
||||
gen_wcwidth()
|
||||
gen_emoji()
|
||||
gen_names()
|
||||
gen_rowcolumn_diacritics()
|
||||
def main(args: List[str]=sys.argv) -> None:
|
||||
parse_ucd()
|
||||
parse_prop_list()
|
||||
parse_emoji()
|
||||
parse_eaw()
|
||||
gen_ucd()
|
||||
gen_wcwidth()
|
||||
gen_emoji()
|
||||
gen_names()
|
||||
gen_rowcolumn_diacritics()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import runpy
|
||||
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
|
||||
m['main']([sys.executable, 'wcwidth'])
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
1
glfw/backend_utils.c
vendored
1
glfw/backend_utils.c
vendored
@@ -15,7 +15,6 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <float.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -1020,12 +1020,14 @@ static NSLock *tick_lock = NULL;
|
||||
|
||||
void _glfwDispatchTickCallback(void) {
|
||||
if (tick_lock && tick_callback) {
|
||||
[tick_lock lock];
|
||||
while(tick_callback_requested) {
|
||||
tick_callback_requested = false;
|
||||
tick_callback(tick_callback_data);
|
||||
while(true) {
|
||||
bool do_call = false;
|
||||
[tick_lock lock];
|
||||
if (tick_callback_requested) { do_call = true; tick_callback_requested = false; }
|
||||
[tick_lock unlock];
|
||||
if (do_call) tick_callback(tick_callback_data);
|
||||
else break;
|
||||
}
|
||||
[tick_lock unlock];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1259,6 +1259,15 @@ is_ascii_control_char(char x) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_mask, NSUInteger either_mask) {
|
||||
bool target_pressed = (flags & target_mask) != 0;
|
||||
bool other_pressed = (flags & other_mask) != 0;
|
||||
bool either_pressed = (flags & either_mask) != 0;
|
||||
if (either_pressed != (target_pressed || other_pressed)) return either_pressed;
|
||||
return target_pressed;
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event
|
||||
{
|
||||
int action = GLFW_RELEASE;
|
||||
@@ -1271,29 +1280,24 @@ is_ascii_control_char(char x) {
|
||||
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, modifierFlags) != 1);
|
||||
const char *mod_name = "unknown";
|
||||
|
||||
// Code for handling modifier key events copied form SDL_cocoakeyboard.m, with thanks. See IsModifierKeyPressedFunction()
|
||||
#define action_for(modname, target_mask, other_mask, either_mask) action = is_modifier_pressed([event modifierFlags], target_mask, other_mask, either_mask) ? GLFW_PRESS : GLFW_RELEASE; mod_name = #modname; break;
|
||||
switch(key) {
|
||||
case GLFW_FKEY_CAPS_LOCK:
|
||||
mod_name = "capslock";
|
||||
action = modifierFlags & NSEventModifierFlagCapsLock ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||
case GLFW_FKEY_LEFT_SUPER:
|
||||
case GLFW_FKEY_RIGHT_SUPER:
|
||||
mod_name = "super";
|
||||
action = modifierFlags & NSEventModifierFlagCommand ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||
case GLFW_FKEY_LEFT_CONTROL:
|
||||
case GLFW_FKEY_RIGHT_CONTROL:
|
||||
mod_name = "ctrl";
|
||||
action = modifierFlags & NSEventModifierFlagControl ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||
case GLFW_FKEY_LEFT_ALT:
|
||||
case GLFW_FKEY_RIGHT_ALT:
|
||||
mod_name = "alt";
|
||||
action = modifierFlags & NSEventModifierFlagOption ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||
case GLFW_FKEY_LEFT_SHIFT:
|
||||
case GLFW_FKEY_RIGHT_SHIFT:
|
||||
mod_name = "shift";
|
||||
action = modifierFlags & NSEventModifierFlagShift ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||
case GLFW_FKEY_LEFT_SUPER: action_for(super, NX_DEVICELCMDKEYMASK, NX_DEVICERCMDKEYMASK, NX_COMMANDMASK);
|
||||
case GLFW_FKEY_RIGHT_SUPER: action_for(super, NX_DEVICERCMDKEYMASK, NX_DEVICELCMDKEYMASK, NX_COMMANDMASK);
|
||||
case GLFW_FKEY_LEFT_CONTROL: action_for(ctrl, NX_DEVICELCTLKEYMASK, NX_DEVICERCTLKEYMASK, NX_CONTROLMASK);
|
||||
case GLFW_FKEY_RIGHT_CONTROL: action_for(ctrl, NX_DEVICERCTLKEYMASK, NX_DEVICELCTLKEYMASK, NX_CONTROLMASK);
|
||||
case GLFW_FKEY_LEFT_ALT: action_for(alt, NX_DEVICELALTKEYMASK, NX_DEVICERALTKEYMASK, NX_ALTERNATEMASK);
|
||||
case GLFW_FKEY_RIGHT_ALT: action_for(alt, NX_DEVICERALTKEYMASK, NX_DEVICELALTKEYMASK, NX_ALTERNATEMASK);
|
||||
case GLFW_FKEY_LEFT_SHIFT: action_for(shift, NX_DEVICELSHIFTKEYMASK, NX_DEVICERSHIFTKEYMASK, NX_SHIFTMASK);
|
||||
case GLFW_FKEY_RIGHT_SHIFT: action_for(shift, NX_DEVICERSHIFTKEYMASK, NX_DEVICELSHIFTKEYMASK, NX_SHIFTMASK);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
#undef action_for
|
||||
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = action, .mods = mods};
|
||||
debug_key("\x1b[33mflagsChanged:\x1b[m modifier: %s native_key: 0x%x (%s) glfw_key: 0x%x %s\n",
|
||||
mod_name, keycode, safe_name_for_keycode(keycode), key, format_mods(mods));
|
||||
@@ -1510,7 +1514,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
(void)range; (void)actualRange;
|
||||
if (_glfw.callbacks.get_ime_cursor_position) {
|
||||
GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION };
|
||||
if (_glfw.callbacks.get_ime_cursor_position((GLFWwindow*)window, &ev)) {
|
||||
if (window && _glfw.callbacks.get_ime_cursor_position((GLFWwindow*)window, &ev)) {
|
||||
const CGFloat left = (CGFloat)ev.cursor.left / window->ns.xscale;
|
||||
const CGFloat top = (CGFloat)ev.cursor.top / window->ns.yscale;
|
||||
const CGFloat cellWidth = (CGFloat)ev.cursor.width / window->ns.xscale;
|
||||
@@ -1716,6 +1720,24 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
static void
|
||||
update_titlebar_button_visibility_after_fullscreen_transition(_GLFWwindow* w, bool traditional, bool made_fullscreen) {
|
||||
// Update window button visibility
|
||||
if (w->ns.titlebar_hidden) {
|
||||
NSWindow *window = w->ns.object;
|
||||
// The hidden buttons might be automatically reset to be visible after going full screen
|
||||
// to show up in the auto-hide title bar, so they need to be set back to hidden.
|
||||
BOOL button_hidden = YES;
|
||||
// When title bar is configured to be hidden, it should be shown with buttons (auto-hide) after going to full screen.
|
||||
if (!traditional) {
|
||||
button_hidden = (BOOL) !made_fullscreen;
|
||||
}
|
||||
[[window standardWindowButton: NSWindowCloseButton] setHidden:button_hidden];
|
||||
[[window standardWindowButton: NSWindowMiniaturizeButton] setHidden:button_hidden];
|
||||
[[window standardWindowButton: NSWindowZoomButton] setHidden:button_hidden];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)toggleFullScreen:(nullable id)sender
|
||||
{
|
||||
if (glfw_window) {
|
||||
@@ -1723,6 +1745,8 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
if (glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1) return;
|
||||
glfw_window->ns.in_fullscreen_transition = true;
|
||||
}
|
||||
NSWindowStyleMask sm = [self styleMask];
|
||||
bool is_fullscreen_already = (sm & NSWindowStyleMaskFullScreen) != 0;
|
||||
// When resizeIncrements is set, Cocoa cannot restore the original window size after returning from fullscreen.
|
||||
const NSSize original = [self resizeIncrements];
|
||||
[self setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
@@ -1731,6 +1755,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
// When the window decoration is hidden, toggling fullscreen causes the style mask to be changed,
|
||||
// and causes the first responder to be cleared.
|
||||
if (glfw_window && !glfw_window->decorated && glfw_window->ns.view) [self makeFirstResponder:glfw_window->ns.view];
|
||||
update_titlebar_button_visibility_after_fullscreen_transition(glfw_window, false, !is_fullscreen_already);
|
||||
}
|
||||
|
||||
- (void)zoom:(id)sender
|
||||
@@ -1796,9 +1821,15 @@ static bool createNativeWindow(_GLFWwindow* window,
|
||||
else
|
||||
{
|
||||
[(NSWindow*) window->ns.object center];
|
||||
_glfw.ns.cascadePoint =
|
||||
NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
|
||||
NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
|
||||
CGRect screen_frame = [[(NSWindow*) window->ns.object screen] frame];
|
||||
if (CGRectContainsPoint(screen_frame, _glfw.ns.cascadePoint)
|
||||
|| CGPointEqualToPoint(CGPointZero, _glfw.ns.cascadePoint)) {
|
||||
_glfw.ns.cascadePoint =
|
||||
NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
|
||||
NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
|
||||
} else {
|
||||
_glfw.ns.cascadePoint = CGPointZero;
|
||||
}
|
||||
|
||||
if (wndconfig->resizable)
|
||||
{
|
||||
@@ -2505,27 +2536,83 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
return true;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
|
||||
{
|
||||
static NSCursor*
|
||||
load_hidden_system_cursor(NSString *name, SEL fallback) {
|
||||
// this implementation comes from SDL_cocoamouse.m
|
||||
NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:name];
|
||||
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]];
|
||||
/* we can't do animation atm. :/ */
|
||||
const int frames = (int)[[info valueForKey:@"frames"] integerValue];
|
||||
NSCursor *cursor;
|
||||
NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]];
|
||||
if ((image == nil) || (image.isValid == NO)) {
|
||||
return [NSCursor performSelector:fallback];
|
||||
}
|
||||
|
||||
if (frames > 1) {
|
||||
const NSCompositingOperation operation = NSCompositingOperationCopy;
|
||||
const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames));
|
||||
NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size];
|
||||
if (cropped == nil) {
|
||||
return [NSCursor performSelector:fallback];
|
||||
}
|
||||
|
||||
[cropped lockFocus];
|
||||
{
|
||||
const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height);
|
||||
[image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1];
|
||||
}
|
||||
[cropped unlockFocus];
|
||||
image = cropped;
|
||||
}
|
||||
|
||||
cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])];
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) {
|
||||
#define C(name, val) case name: cursor->ns.object = [NSCursor val]; break;
|
||||
#define U(name, val) case name: cursor->ns.object = [[NSCursor class] performSelector:@selector(val)]; break;
|
||||
#define U(name, val) case name: cursor->ns.object = [NSCursor performSelector:@selector(val)]; break;
|
||||
#define S(name, val, fallback) case name: cursor->ns.object = load_hidden_system_cursor(@#val, @selector(val)); break;
|
||||
switch(shape) {
|
||||
C(GLFW_ARROW_CURSOR, arrowCursor);
|
||||
C(GLFW_IBEAM_CURSOR, IBeamCursor);
|
||||
/* start glfw to cocoa (auto generated by gen-key-constants.py do not edit) */
|
||||
C(GLFW_DEFAULT_CURSOR, arrowCursor);
|
||||
C(GLFW_TEXT_CURSOR, IBeamCursor);
|
||||
C(GLFW_POINTER_CURSOR, pointingHandCursor);
|
||||
S(GLFW_HELP_CURSOR, help, arrowCursor);
|
||||
S(GLFW_WAIT_CURSOR, busybutclickable, arrowCursor);
|
||||
S(GLFW_PROGRESS_CURSOR, busybutclickable, arrowCursor);
|
||||
C(GLFW_CROSSHAIR_CURSOR, crosshairCursor);
|
||||
C(GLFW_HAND_CURSOR, pointingHandCursor);
|
||||
C(GLFW_HRESIZE_CURSOR, resizeLeftRightCursor);
|
||||
C(GLFW_VRESIZE_CURSOR, resizeUpDownCursor);
|
||||
U(GLFW_NW_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor);
|
||||
U(GLFW_NE_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor);
|
||||
U(GLFW_SW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor);
|
||||
U(GLFW_SE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor);
|
||||
S(GLFW_CELL_CURSOR, cell, crosshairCursor);
|
||||
C(GLFW_VERTICAL_TEXT_CURSOR, IBeamCursorForVerticalLayout);
|
||||
S(GLFW_MOVE_CURSOR, move, openHandCursor);
|
||||
C(GLFW_E_RESIZE_CURSOR, resizeRightCursor);
|
||||
S(GLFW_NE_RESIZE_CURSOR, resizenortheast, _windowResizeNorthEastSouthWestCursor);
|
||||
S(GLFW_NW_RESIZE_CURSOR, resizenorthwest, _windowResizeNorthWestSouthEastCursor);
|
||||
C(GLFW_N_RESIZE_CURSOR, resizeUpCursor);
|
||||
S(GLFW_SE_RESIZE_CURSOR, resizesoutheast, _windowResizeNorthWestSouthEastCursor);
|
||||
S(GLFW_SW_RESIZE_CURSOR, resizesouthwest, _windowResizeNorthEastSouthWestCursor);
|
||||
C(GLFW_S_RESIZE_CURSOR, resizeDownCursor);
|
||||
C(GLFW_W_RESIZE_CURSOR, resizeLeftCursor);
|
||||
C(GLFW_EW_RESIZE_CURSOR, resizeLeftRightCursor);
|
||||
C(GLFW_NS_RESIZE_CURSOR, resizeUpDownCursor);
|
||||
U(GLFW_NESW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor);
|
||||
U(GLFW_NWSE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor);
|
||||
S(GLFW_ZOOM_IN_CURSOR, zoomin, arrowCursor);
|
||||
S(GLFW_ZOOM_OUT_CURSOR, zoomout, arrowCursor);
|
||||
C(GLFW_ALIAS_CURSOR, dragLinkCursor);
|
||||
C(GLFW_COPY_CURSOR, dragCopyCursor);
|
||||
C(GLFW_NOT_ALLOWED_CURSOR, operationNotAllowedCursor);
|
||||
C(GLFW_NO_DROP_CURSOR, operationNotAllowedCursor);
|
||||
C(GLFW_GRAB_CURSOR, openHandCursor);
|
||||
C(GLFW_GRABBING_CURSOR, closedHandCursor);
|
||||
/* end glfw to cocoa */
|
||||
case GLFW_INVALID_CURSOR:
|
||||
return false;
|
||||
}
|
||||
#undef C
|
||||
#undef U
|
||||
|
||||
#undef S
|
||||
if (!cursor->ns.object)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -2601,19 +2688,7 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||
if (in_fullscreen) made_fullscreen = false;
|
||||
[window toggleFullScreen: nil];
|
||||
}
|
||||
// Update window button visibility
|
||||
if (w->ns.titlebar_hidden) {
|
||||
// The hidden buttons might be automatically reset to be visible after going full screen
|
||||
// to show up in the auto-hide title bar, so they need to be set back to hidden.
|
||||
BOOL button_hidden = YES;
|
||||
// When title bar is configured to be hidden, it should be shown with buttons (auto-hide) after going to full screen.
|
||||
if (!traditional) {
|
||||
button_hidden = (BOOL) !made_fullscreen;
|
||||
}
|
||||
[[window standardWindowButton: NSWindowCloseButton] setHidden:button_hidden];
|
||||
[[window standardWindowButton: NSWindowMiniaturizeButton] setHidden:button_hidden];
|
||||
[[window standardWindowButton: NSWindowZoomButton] setHidden:button_hidden];
|
||||
}
|
||||
update_titlebar_button_visibility_after_fullscreen_transition(w, traditional, made_fullscreen);
|
||||
return made_fullscreen;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
@@ -262,6 +262,7 @@ def generate_wrappers(glfw_header: str) -> None:
|
||||
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
|
||||
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
|
||||
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
|
||||
void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
|
||||
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
|
||||
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
|
||||
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
||||
|
||||
38
glfw/glfw3.h
vendored
38
glfw/glfw3.h
vendored
@@ -1099,17 +1099,39 @@ typedef enum {
|
||||
* @{ */
|
||||
|
||||
typedef enum {
|
||||
GLFW_ARROW_CURSOR,
|
||||
GLFW_IBEAM_CURSOR,
|
||||
/* start mouse cursor shapes (auto generated by gen-key-constants.py do not edit) */
|
||||
GLFW_DEFAULT_CURSOR,
|
||||
GLFW_TEXT_CURSOR,
|
||||
GLFW_POINTER_CURSOR,
|
||||
GLFW_HELP_CURSOR,
|
||||
GLFW_WAIT_CURSOR,
|
||||
GLFW_PROGRESS_CURSOR,
|
||||
GLFW_CROSSHAIR_CURSOR,
|
||||
GLFW_HAND_CURSOR,
|
||||
GLFW_HRESIZE_CURSOR,
|
||||
GLFW_VRESIZE_CURSOR,
|
||||
GLFW_NW_RESIZE_CURSOR,
|
||||
GLFW_CELL_CURSOR,
|
||||
GLFW_VERTICAL_TEXT_CURSOR,
|
||||
GLFW_MOVE_CURSOR,
|
||||
GLFW_E_RESIZE_CURSOR,
|
||||
GLFW_NE_RESIZE_CURSOR,
|
||||
GLFW_SW_RESIZE_CURSOR,
|
||||
GLFW_NW_RESIZE_CURSOR,
|
||||
GLFW_N_RESIZE_CURSOR,
|
||||
GLFW_SE_RESIZE_CURSOR,
|
||||
GLFW_INVALID_CURSOR
|
||||
GLFW_SW_RESIZE_CURSOR,
|
||||
GLFW_S_RESIZE_CURSOR,
|
||||
GLFW_W_RESIZE_CURSOR,
|
||||
GLFW_EW_RESIZE_CURSOR,
|
||||
GLFW_NS_RESIZE_CURSOR,
|
||||
GLFW_NESW_RESIZE_CURSOR,
|
||||
GLFW_NWSE_RESIZE_CURSOR,
|
||||
GLFW_ZOOM_IN_CURSOR,
|
||||
GLFW_ZOOM_OUT_CURSOR,
|
||||
GLFW_ALIAS_CURSOR,
|
||||
GLFW_COPY_CURSOR,
|
||||
GLFW_NOT_ALLOWED_CURSOR,
|
||||
GLFW_NO_DROP_CURSOR,
|
||||
GLFW_GRAB_CURSOR,
|
||||
GLFW_GRABBING_CURSOR,
|
||||
GLFW_INVALID_CURSOR,
|
||||
/* end mouse cursor shapes */
|
||||
} GLFWCursorShape;
|
||||
/*! @} */
|
||||
|
||||
|
||||
5
glfw/linux_desktop_settings.c
vendored
5
glfw/linux_desktop_settings.c
vendored
@@ -21,7 +21,6 @@
|
||||
static char theme_name[128] = {0};
|
||||
static int theme_size = -1;
|
||||
static uint32_t appearance = 0;
|
||||
static bool is_gnome = false;
|
||||
static bool cursor_theme_changed = false;
|
||||
|
||||
int
|
||||
@@ -102,7 +101,7 @@ HANDLER(process_desktop_settings)
|
||||
dbus_message_iter_recurse(&item, &settings);
|
||||
if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
|
||||
process_settings_dict(&settings, process_fdo_setting);
|
||||
} else if (is_gnome && strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
|
||||
} else if (strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
|
||||
process_settings_dict(&settings, process_gnome_setting);
|
||||
}
|
||||
}
|
||||
@@ -192,8 +191,6 @@ setting_changed(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data U
|
||||
void
|
||||
glfw_initialize_desktop_settings(void) {
|
||||
get_cursor_theme_from_env();
|
||||
const char *desktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
is_gnome = desktop && strstr(desktop, "GNOME");
|
||||
DBusConnection *session_bus = glfw_dbus_session_bus();
|
||||
if (session_bus) {
|
||||
if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running.");
|
||||
|
||||
60
glfw/wl_init.c
vendored
60
glfw/wl_init.c
vendored
@@ -99,7 +99,7 @@ static void pointerHandleEnter(void* data UNUSED,
|
||||
return;
|
||||
}
|
||||
window->wl.decorations.focus = focus;
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial; _glfw.wl.pointer_enter_serial = serial;
|
||||
_glfw.wl.pointerFocus = window;
|
||||
|
||||
window->wl.hovered = true;
|
||||
@@ -177,7 +177,7 @@ static void pointerHandleMotion(void* data UNUSED,
|
||||
wl_fixed_t sy)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR;
|
||||
GLFWCursorShape cursorShape = GLFW_DEFAULT_CURSOR;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
@@ -197,21 +197,21 @@ static void pointerHandleMotion(void* data UNUSED,
|
||||
return;
|
||||
case TOP_DECORATION:
|
||||
if (y < window->wl.decorations.metrics.width)
|
||||
cursorShape = GLFW_VRESIZE_CURSOR;
|
||||
cursorShape = GLFW_N_RESIZE_CURSOR;
|
||||
else
|
||||
cursorShape = GLFW_ARROW_CURSOR;
|
||||
cursorShape = GLFW_DEFAULT_CURSOR;
|
||||
break;
|
||||
case LEFT_DECORATION:
|
||||
if (y < window->wl.decorations.metrics.width)
|
||||
cursorShape = GLFW_NW_RESIZE_CURSOR;
|
||||
else
|
||||
cursorShape = GLFW_HRESIZE_CURSOR;
|
||||
cursorShape = GLFW_W_RESIZE_CURSOR;
|
||||
break;
|
||||
case RIGHT_DECORATION:
|
||||
if (y < window->wl.decorations.metrics.width)
|
||||
cursorShape = GLFW_NE_RESIZE_CURSOR;
|
||||
else
|
||||
cursorShape = GLFW_HRESIZE_CURSOR;
|
||||
cursorShape = GLFW_E_RESIZE_CURSOR;
|
||||
break;
|
||||
case BOTTOM_DECORATION:
|
||||
if (x < window->wl.decorations.metrics.width)
|
||||
@@ -219,7 +219,7 @@ static void pointerHandleMotion(void* data UNUSED,
|
||||
else if (x > window->wl.width + window->wl.decorations.metrics.width)
|
||||
cursorShape = GLFW_SE_RESIZE_CURSOR;
|
||||
else
|
||||
cursorShape = GLFW_VRESIZE_CURSOR;
|
||||
cursorShape = GLFW_S_RESIZE_CURSOR;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
@@ -311,7 +311,7 @@ static void pointerHandleButton(void* data UNUSED,
|
||||
if (window->wl.decorations.focus != CENTRAL_WINDOW)
|
||||
return;
|
||||
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial;
|
||||
|
||||
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
|
||||
* codes. */
|
||||
@@ -463,7 +463,7 @@ static void keyboardHandleEnter(void* data UNUSED,
|
||||
return;
|
||||
}
|
||||
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.keyboard_enter_serial = serial;
|
||||
_glfw.wl.keyboardFocusId = window->id;
|
||||
_glfwInputWindowFocus(window, true);
|
||||
uint32_t* key;
|
||||
@@ -572,9 +572,16 @@ static void seatHandleCapabilities(void* data UNUSED,
|
||||
{
|
||||
_glfw.wl.pointer = wl_seat_get_pointer(seat);
|
||||
wl_pointer_add_listener(_glfw.wl.pointer, &pointerListener, NULL);
|
||||
if (_glfw.wl.wp_cursor_shape_manager_v1) {
|
||||
if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1);
|
||||
_glfw.wl.wp_cursor_shape_device_v1 = NULL;
|
||||
_glfw.wl.wp_cursor_shape_device_v1 = wp_cursor_shape_manager_v1_get_pointer(_glfw.wl.wp_cursor_shape_manager_v1, _glfw.wl.pointer);
|
||||
}
|
||||
}
|
||||
else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && _glfw.wl.pointer)
|
||||
{
|
||||
if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1);
|
||||
_glfw.wl.wp_cursor_shape_device_v1 = NULL;
|
||||
wl_pointer_destroy(_glfw.wl.pointer);
|
||||
_glfw.wl.pointer = NULL;
|
||||
if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
@@ -622,28 +629,29 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
const char* interface,
|
||||
uint32_t version)
|
||||
{
|
||||
if (strcmp(interface, "wl_compositor") == 0)
|
||||
#define is(x) strcmp(interface, #x) == 0
|
||||
if (is(wl_compositor))
|
||||
{
|
||||
_glfw.wl.compositorVersion = min(3, version);
|
||||
_glfw.wl.compositor =
|
||||
wl_registry_bind(registry, name, &wl_compositor_interface,
|
||||
_glfw.wl.compositorVersion);
|
||||
}
|
||||
else if (strcmp(interface, "wl_subcompositor") == 0)
|
||||
else if (is(wl_subcompositor))
|
||||
{
|
||||
_glfw.wl.subcompositor =
|
||||
wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "wl_shm") == 0)
|
||||
else if (is(wl_shm))
|
||||
{
|
||||
_glfw.wl.shm =
|
||||
wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "wl_output") == 0)
|
||||
else if (is(wl_output))
|
||||
{
|
||||
_glfwAddOutputWayland(name, version);
|
||||
}
|
||||
else if (strcmp(interface, "wl_seat") == 0)
|
||||
else if (is(wl_seat))
|
||||
{
|
||||
if (!_glfw.wl.seat)
|
||||
{
|
||||
@@ -661,38 +669,38 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
_glfwWaylandInitTextInput();
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||
else if (is(xdg_wm_base))
|
||||
{
|
||||
_glfw.wl.wmBase =
|
||||
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
||||
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
|
||||
}
|
||||
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
|
||||
else if (is(zxdg_decoration_manager_v1))
|
||||
{
|
||||
_glfw.wl.decorationManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0)
|
||||
else if (is(zwp_relative_pointer_manager_v1))
|
||||
{
|
||||
_glfw.wl.relativePointerManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&zwp_relative_pointer_manager_v1_interface,
|
||||
1);
|
||||
}
|
||||
else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0)
|
||||
else if (is(zwp_pointer_constraints_v1))
|
||||
{
|
||||
_glfw.wl.pointerConstraints =
|
||||
wl_registry_bind(registry, name,
|
||||
&zwp_pointer_constraints_v1_interface,
|
||||
1);
|
||||
}
|
||||
else if (strcmp(interface, GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME) == 0)
|
||||
else if (is(zwp_text_input_manager_v3))
|
||||
{
|
||||
_glfwWaylandBindTextInput(registry, name);
|
||||
_glfwWaylandInitTextInput();
|
||||
}
|
||||
else if (strcmp(interface, "wl_data_device_manager") == 0)
|
||||
else if (is(wl_data_device_manager))
|
||||
{
|
||||
_glfw.wl.dataDeviceManager =
|
||||
wl_registry_bind(registry, name,
|
||||
@@ -702,7 +710,7 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
_glfwSetupWaylandDataDevice();
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0)
|
||||
else if (is(zwp_primary_selection_device_manager_v1))
|
||||
{
|
||||
_glfw.wl.primarySelectionDeviceManager =
|
||||
wl_registry_bind(registry, name,
|
||||
@@ -712,10 +720,13 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
_glfwSetupWaylandPrimarySelectionDevice();
|
||||
}
|
||||
}
|
||||
else if (strstr(interface, "xdg_activation_v1") != 0) {
|
||||
else if (is(xdg_activation_v1)) {
|
||||
_glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1);
|
||||
}
|
||||
|
||||
else if (is(wp_cursor_shape_manager_v1)) {
|
||||
_glfw.wl.wp_cursor_shape_manager_v1 = wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1);
|
||||
}
|
||||
#undef is
|
||||
}
|
||||
|
||||
static void registryHandleGlobalRemove(void *data UNUSED,
|
||||
@@ -949,6 +960,9 @@ void _glfwPlatformTerminate(void)
|
||||
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
|
||||
if (_glfw.wl.xdg_activation_v1)
|
||||
xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1);
|
||||
if (_glfw.wl.wp_cursor_shape_manager_v1)
|
||||
wp_cursor_shape_manager_v1_destroy(_glfw.wl.wp_cursor_shape_manager_v1);
|
||||
|
||||
if (_glfw.wl.registry)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
if (_glfw.wl.display)
|
||||
|
||||
5
glfw/wl_platform.h
vendored
5
glfw/wl_platform.h
vendored
@@ -60,6 +60,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
||||
#include "wl_text_input.h"
|
||||
#include "wayland-xdg-activation-v1-client-protocol.h"
|
||||
#include "wayland-cursor-shape-v1-client-protocol.h"
|
||||
|
||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
@@ -286,13 +287,15 @@ typedef struct _GLFWlibraryWayland
|
||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
||||
struct xdg_activation_v1* xdg_activation_v1;
|
||||
struct wp_cursor_shape_manager_v1* wp_cursor_shape_manager_v1;
|
||||
struct wp_cursor_shape_device_v1* wp_cursor_shape_device_v1;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
|
||||
struct wl_surface* cursorSurface;
|
||||
GLFWCursorShape cursorPreviousShape;
|
||||
uint32_t serial, input_serial;
|
||||
uint32_t serial, input_serial, pointer_serial, pointer_enter_serial, keyboard_enter_serial;
|
||||
|
||||
int32_t keyboardRepeatRate;
|
||||
monotonic_t keyboardRepeatDelay;
|
||||
|
||||
2
glfw/wl_text_input.h
vendored
2
glfw/wl_text_input.h
vendored
@@ -7,8 +7,6 @@
|
||||
#pragma once
|
||||
#include <wayland-client.h>
|
||||
|
||||
#define GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME "zwp_text_input_manager_v3"
|
||||
|
||||
void _glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name);
|
||||
void _glfwWaylandInitTextInput(void);
|
||||
void _glfwWaylandDestroyTextInput(void);
|
||||
|
||||
138
glfw/wl_window.c
vendored
138
glfw/wl_window.c
vendored
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include "backend_utils.h"
|
||||
#include "memfd.h"
|
||||
#include "linux_notify.h"
|
||||
#include "wl_client_side_decorations.h"
|
||||
#include "../kitty/monotonic.h"
|
||||
@@ -152,10 +151,58 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque,
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static int
|
||||
glfw_cursor_shape_to_wayland_cursor_shape(GLFWCursorShape g) {
|
||||
#define C(g, w) case g: return w;
|
||||
switch(g) {
|
||||
/* start glfw to wayland mapping (auto generated by gen-key-constants.py do not edit) */
|
||||
C(GLFW_DEFAULT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
|
||||
C(GLFW_TEXT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
|
||||
C(GLFW_POINTER_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
|
||||
C(GLFW_HELP_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP);
|
||||
C(GLFW_WAIT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT);
|
||||
C(GLFW_PROGRESS_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS);
|
||||
C(GLFW_CROSSHAIR_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR);
|
||||
C(GLFW_CELL_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL);
|
||||
C(GLFW_VERTICAL_TEXT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT);
|
||||
C(GLFW_MOVE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE);
|
||||
C(GLFW_E_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE);
|
||||
C(GLFW_NE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE);
|
||||
C(GLFW_NW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE);
|
||||
C(GLFW_N_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE);
|
||||
C(GLFW_SE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE);
|
||||
C(GLFW_SW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE);
|
||||
C(GLFW_S_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE);
|
||||
C(GLFW_W_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE);
|
||||
C(GLFW_EW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE);
|
||||
C(GLFW_NS_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE);
|
||||
C(GLFW_NESW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE);
|
||||
C(GLFW_NWSE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE);
|
||||
C(GLFW_ZOOM_IN_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN);
|
||||
C(GLFW_ZOOM_OUT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT);
|
||||
C(GLFW_ALIAS_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS);
|
||||
C(GLFW_COPY_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY);
|
||||
C(GLFW_NOT_ALLOWED_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED);
|
||||
C(GLFW_NO_DROP_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP);
|
||||
C(GLFW_GRAB_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB);
|
||||
C(GLFW_GRABBING_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING);
|
||||
/* end glfw to wayland mapping */
|
||||
default: return -1;
|
||||
}
|
||||
#undef C
|
||||
}
|
||||
|
||||
static void
|
||||
setCursorImage(_GLFWwindow* window, bool on_theme_change) {
|
||||
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
||||
_GLFWcursorWayland defaultCursor = {.shape = GLFW_DEFAULT_CURSOR};
|
||||
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
|
||||
if (_glfw.wl.wp_cursor_shape_device_v1) {
|
||||
int which = glfw_cursor_shape_to_wayland_cursor_shape(cursorWayland->shape);
|
||||
if (which > -1) {
|
||||
wp_cursor_shape_device_v1_set_shape(_glfw.wl.wp_cursor_shape_device_v1, _glfw.wl.pointer_enter_serial, (uint32_t)which);
|
||||
return;
|
||||
}
|
||||
}
|
||||
struct wl_cursor_image* image = NULL;
|
||||
struct wl_buffer* buffer = NULL;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
@@ -717,7 +764,7 @@ static void incrementCursorImage(_GLFWwindow* window)
|
||||
{
|
||||
if (window && window->wl.decorations.focus == CENTRAL_WINDOW && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
||||
_GLFWcursor* cursor = window->wl.currentCursor;
|
||||
if (cursor && cursor->wl.cursor)
|
||||
if (cursor && cursor->wl.cursor && cursor->wl.cursor->image_count)
|
||||
{
|
||||
cursor->wl.currentImage += 1;
|
||||
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
|
||||
@@ -831,16 +878,38 @@ struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme*
|
||||
struct wl_cursor* ans = NULL;
|
||||
switch (shape)
|
||||
{
|
||||
C(GLFW_ARROW_CURSOR, "left_ptr", "arrow", "default")
|
||||
C(GLFW_IBEAM_CURSOR, "xterm", "ibeam", "text")
|
||||
C(GLFW_CROSSHAIR_CURSOR, "crosshair", "cross")
|
||||
C(GLFW_HAND_CURSOR, "hand2", "grab", "grabbing", "closedhand")
|
||||
C(GLFW_HRESIZE_CURSOR, "sb_h_double_arrow", "h_double_arrow", "col-resize")
|
||||
C(GLFW_VRESIZE_CURSOR, "sb_v_double_arrow", "v_double_arrow", "row-resize")
|
||||
C(GLFW_NW_RESIZE_CURSOR, "top_left_corner", "nw-resize")
|
||||
C(GLFW_NE_RESIZE_CURSOR, "top_right_corner", "ne-resize")
|
||||
C(GLFW_SW_RESIZE_CURSOR, "bottom_left_corner", "sw-resize")
|
||||
C(GLFW_SE_RESIZE_CURSOR, "bottom_right_corner", "se-resize")
|
||||
/* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */
|
||||
C(GLFW_DEFAULT_CURSOR, "default", "left_ptr");
|
||||
C(GLFW_TEXT_CURSOR, "text", "xterm", "ibeam");
|
||||
C(GLFW_POINTER_CURSOR, "pointing_hand", "pointer", "hand2", "hand");
|
||||
C(GLFW_HELP_CURSOR, "help", "question_arrow", "whats_this");
|
||||
C(GLFW_WAIT_CURSOR, "wait", "clock", "watch");
|
||||
C(GLFW_PROGRESS_CURSOR, "progress", "half-busy", "left_ptr_watch");
|
||||
C(GLFW_CROSSHAIR_CURSOR, "crosshair", "tcross");
|
||||
C(GLFW_CELL_CURSOR, "cell", "plus", "cross");
|
||||
C(GLFW_VERTICAL_TEXT_CURSOR, "vertical-text");
|
||||
C(GLFW_MOVE_CURSOR, "move", "fleur", "pointer-move");
|
||||
C(GLFW_E_RESIZE_CURSOR, "e-resize", "right_side");
|
||||
C(GLFW_NE_RESIZE_CURSOR, "ne-resize", "top_right_corner");
|
||||
C(GLFW_NW_RESIZE_CURSOR, "nw-resize", "top_left_corner");
|
||||
C(GLFW_N_RESIZE_CURSOR, "n-resize", "top_side");
|
||||
C(GLFW_SE_RESIZE_CURSOR, "se-resize", "bottom_right_corner");
|
||||
C(GLFW_SW_RESIZE_CURSOR, "sw-resize", "bottom_left_corner");
|
||||
C(GLFW_S_RESIZE_CURSOR, "s-resize", "bottom_side");
|
||||
C(GLFW_W_RESIZE_CURSOR, "w-resize", "left_side");
|
||||
C(GLFW_EW_RESIZE_CURSOR, "ew-resize", "sb_h_double_arrow", "split_h");
|
||||
C(GLFW_NS_RESIZE_CURSOR, "ns-resize", "sb_v_double_arrow", "split_v");
|
||||
C(GLFW_NESW_RESIZE_CURSOR, "nesw-resize", "size_bdiag", "size-bdiag");
|
||||
C(GLFW_NWSE_RESIZE_CURSOR, "nwse-resize", "size_fdiag", "size-fdiag");
|
||||
C(GLFW_ZOOM_IN_CURSOR, "zoom-in", "zoom_in");
|
||||
C(GLFW_ZOOM_OUT_CURSOR, "zoom-out", "zoom_out");
|
||||
C(GLFW_ALIAS_CURSOR, "dnd-link");
|
||||
C(GLFW_COPY_CURSOR, "dnd-copy");
|
||||
C(GLFW_NOT_ALLOWED_CURSOR, "not-allowed", "forbidden", "crossed_circle");
|
||||
C(GLFW_NO_DROP_CURSOR, "no-drop", "dnd-no-drop");
|
||||
C(GLFW_GRAB_CURSOR, "grab", "openhand", "hand1");
|
||||
C(GLFW_GRABBING_CURSOR, "grabbing", "closedhand", "dnd-none");
|
||||
/* end glfw to xc mapping */
|
||||
case GLFW_INVALID_CURSOR:
|
||||
break;
|
||||
}
|
||||
@@ -1920,22 +1989,6 @@ static const struct zwp_primary_selection_device_v1_listener primary_selection_d
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
static void
|
||||
primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) {
|
||||
zwp_primary_selection_device_v1_set_selection(_glfw.wl.primarySelectionDevice, data, serial);
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
void _glfwSetupWaylandDataDevice(void) {
|
||||
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
|
||||
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
|
||||
@@ -2027,13 +2080,25 @@ _glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||
}
|
||||
f(data_source, cd->mime_types[i]);
|
||||
}
|
||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||
if (t == GLFW_CLIPBOARD) {
|
||||
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
|
||||
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||
// According to some interpretations of the Wayland spec only the application that has keyboard focus can set the clipboard.
|
||||
// Hurray for the Wayland nanny state!
|
||||
//
|
||||
// However in wl-roots based compositors, using the serial from the keyboard enter event doesn't work. No clue what
|
||||
// the correct serial to use here is. Given this Wayland there probably isn't one. What a joke.
|
||||
// Bug report: https://github.com/kovidgoyal/kitty/issues/6890
|
||||
// Ironically one of the contributors to wl_roots claims the keyboard enter serial is the correct one to use:
|
||||
// https://emersion.fr/blog/2020/wayland-clipboard-drag-and-drop/
|
||||
// The Wayland spec itself says "serial number of the event that triggered this request"
|
||||
// https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_data_device
|
||||
// So who the fuck knows. Just use the latest received serial and ask anybody that uses Wayland
|
||||
// to get their head examined.
|
||||
wl_data_device_set_selection(_glfw.wl.dataDevice, _glfw.wl.dataSourceForClipboard, _glfw.wl.serial);
|
||||
} else {
|
||||
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
|
||||
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
|
||||
// According to the Wayland spec we can only set the primary selection in response to a pointer button event
|
||||
// Hurray for the Wayland nanny state!
|
||||
zwp_primary_selection_device_v1_set_selection(
|
||||
_glfw.wl.primarySelectionDevice, _glfw.wl.dataSourceForPrimarySelection, _glfw.wl.pointer_serial);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2266,3 +2331,8 @@ GLFWAPI bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, boo
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
change_csd_title(window);
|
||||
}
|
||||
|
||||
87
glfw/x11_window.c
vendored
87
glfw/x11_window.c
vendored
@@ -2811,37 +2811,78 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
return true;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
|
||||
{
|
||||
int native = 0;
|
||||
#define C(name, val) case name: native = val; break;
|
||||
switch(shape) {
|
||||
C(GLFW_ARROW_CURSOR, XC_left_ptr);
|
||||
C(GLFW_IBEAM_CURSOR, XC_xterm);
|
||||
C(GLFW_CROSSHAIR_CURSOR, XC_crosshair);
|
||||
C(GLFW_HAND_CURSOR, XC_hand2);
|
||||
C(GLFW_HRESIZE_CURSOR, XC_sb_h_double_arrow);
|
||||
C(GLFW_VRESIZE_CURSOR, XC_sb_v_double_arrow);
|
||||
C(GLFW_NW_RESIZE_CURSOR, XC_top_left_corner);
|
||||
C(GLFW_NE_RESIZE_CURSOR, XC_top_right_corner);
|
||||
C(GLFW_SW_RESIZE_CURSOR, XC_bottom_left_corner);
|
||||
C(GLFW_SE_RESIZE_CURSOR, XC_bottom_right_corner);
|
||||
case GLFW_INVALID_CURSOR:
|
||||
return false;
|
||||
}
|
||||
#undef C
|
||||
|
||||
static int
|
||||
set_cursor_from_font(_GLFWcursor* cursor, int native) {
|
||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
|
||||
if (!cursor->x11.handle)
|
||||
{
|
||||
if (!cursor->x11.handle) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to create standard cursor");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
try_cursor_names(_GLFWcursor *cursor, int arg_count, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, arg_count);
|
||||
const char *first_name = "";
|
||||
for (int i = 0; i < arg_count; i++) {
|
||||
const char *name = va_arg(ap, const char *);
|
||||
first_name = name;
|
||||
cursor->x11.handle = XcursorLibraryLoadCursor(_glfw.x11.display, name);
|
||||
if (cursor->x11.handle) break;
|
||||
}
|
||||
va_end(ap);
|
||||
if (!cursor->x11.handle) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to load standard cursor: %s with %d aliases via Xcursor library", first_name, arg_count);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
|
||||
{
|
||||
switch(shape) {
|
||||
/* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */
|
||||
case GLFW_DEFAULT_CURSOR: return set_cursor_from_font(cursor, XC_left_ptr);
|
||||
case GLFW_TEXT_CURSOR: return set_cursor_from_font(cursor, XC_xterm);
|
||||
case GLFW_POINTER_CURSOR: return set_cursor_from_font(cursor, XC_hand2);
|
||||
case GLFW_HELP_CURSOR: return set_cursor_from_font(cursor, XC_question_arrow);
|
||||
case GLFW_WAIT_CURSOR: return set_cursor_from_font(cursor, XC_clock);
|
||||
case GLFW_PROGRESS_CURSOR: return try_cursor_names(cursor, 3, "progress", "half-busy", "left_ptr_watch");
|
||||
case GLFW_CROSSHAIR_CURSOR: return set_cursor_from_font(cursor, XC_tcross);
|
||||
case GLFW_CELL_CURSOR: return set_cursor_from_font(cursor, XC_plus);
|
||||
case GLFW_VERTICAL_TEXT_CURSOR: return try_cursor_names(cursor, 1, "vertical-text");
|
||||
case GLFW_MOVE_CURSOR: return set_cursor_from_font(cursor, XC_fleur);
|
||||
case GLFW_E_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_right_side);
|
||||
case GLFW_NE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_right_corner);
|
||||
case GLFW_NW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_left_corner);
|
||||
case GLFW_N_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_side);
|
||||
case GLFW_SE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_right_corner);
|
||||
case GLFW_SW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_left_corner);
|
||||
case GLFW_S_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_side);
|
||||
case GLFW_W_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_left_side);
|
||||
case GLFW_EW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_h_double_arrow);
|
||||
case GLFW_NS_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_v_double_arrow);
|
||||
case GLFW_NESW_RESIZE_CURSOR: return try_cursor_names(cursor, 3, "nesw-resize", "size_bdiag", "size-bdiag");
|
||||
case GLFW_NWSE_RESIZE_CURSOR: return try_cursor_names(cursor, 3, "nwse-resize", "size_fdiag", "size-fdiag");
|
||||
case GLFW_ZOOM_IN_CURSOR: return try_cursor_names(cursor, 2, "zoom-in", "zoom_in");
|
||||
case GLFW_ZOOM_OUT_CURSOR: return try_cursor_names(cursor, 2, "zoom-out", "zoom_out");
|
||||
case GLFW_ALIAS_CURSOR: return try_cursor_names(cursor, 1, "dnd-link");
|
||||
case GLFW_COPY_CURSOR: return try_cursor_names(cursor, 1, "dnd-copy");
|
||||
case GLFW_NOT_ALLOWED_CURSOR: return try_cursor_names(cursor, 3, "not-allowed", "forbidden", "crossed_circle");
|
||||
case GLFW_NO_DROP_CURSOR: return try_cursor_names(cursor, 2, "no-drop", "dnd-no-drop");
|
||||
case GLFW_GRAB_CURSOR: return set_cursor_from_font(cursor, XC_hand1);
|
||||
case GLFW_GRABBING_CURSOR: return try_cursor_names(cursor, 3, "grabbing", "closedhand", "dnd-none");
|
||||
/* end glfw to xc mapping */
|
||||
case GLFW_INVALID_CURSOR: return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
|
||||
{
|
||||
if (cursor->x11.handle)
|
||||
|
||||
16
go.mod
16
go.mod
@@ -4,19 +4,19 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.2.0
|
||||
github.com/alecthomas/chroma/v2 v2.9.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/seancfoley/ipaddress-go v1.5.5
|
||||
github.com/shirou/gopsutil/v3 v3.23.9
|
||||
github.com/shirou/gopsutil/v3 v3.23.12
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.12.0
|
||||
golang.org/x/sys v0.12.0
|
||||
howett.net/plist v1.0.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/sys v0.16.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
61
go.sum
61
go.sum
@@ -2,12 +2,12 @@ github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM
|
||||
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
|
||||
github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -18,10 +18,11 @@ github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -39,8 +40,8 @@ github.com/seancfoley/bintree v1.2.3 h1:6SPPax/9Dilcs3mDTj3CarRCWPZJV30KyP3cjcEw
|
||||
github.com/seancfoley/bintree v1.2.3/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
|
||||
github.com/seancfoley/ipaddress-go v1.5.5 h1:Q2isCacDQ3A46hxSbM9Q2+Gs4IopCVz1oH88L5eEgP4=
|
||||
github.com/seancfoley/ipaddress-go v1.5.5/go.mod h1:C+VHKCmxTfYODkkItOj9lMemZLMigwo28E+ARlPqpk0=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
|
||||
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
@@ -56,59 +57,31 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
|
||||
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"kitty/tools/cli/markup"
|
||||
"kitty/tools/tty"
|
||||
"kitty/tools/tui/loop"
|
||||
"kitty/tools/utils"
|
||||
"kitty/tools/utils/style"
|
||||
@@ -60,13 +61,16 @@ func extra_for(width, screen_width int) int {
|
||||
return max(0, screen_width-width)/2 + 1
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func GetChoices(o *Options) (response string, err error) {
|
||||
response = ""
|
||||
lp, err := loop.New()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
lp.MouseTrackingMode(loop.BUTTONS_ONLY_MOUSE_TRACKING)
|
||||
lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING)
|
||||
|
||||
prefix_style_pat := regexp.MustCompile("^(?:\x1b\\[[^m]*?m)+")
|
||||
choice_order := make([]Choice, 0, len(o.Choices))
|
||||
@@ -89,6 +93,9 @@ func GetChoices(o *Options) (response string, err error) {
|
||||
}
|
||||
letter = strings.ToLower(letter)
|
||||
idx := strings.Index(strings.ToLower(text), letter)
|
||||
if idx < 0 {
|
||||
return "", fmt.Errorf("The choice letter %#v is not present in the choice text: %#v", letter, text)
|
||||
}
|
||||
idx = len([]rune(strings.ToLower(text)[:idx]))
|
||||
allowed.Add(letter)
|
||||
c := Choice{text: text, idx: idx, color: color, letter: letter}
|
||||
@@ -123,6 +130,9 @@ func GetChoices(o *Options) (response string, err error) {
|
||||
}
|
||||
|
||||
draw_long_text := func(screen_width int, text string, msg_lines []string) []string {
|
||||
if screen_width < 3 {
|
||||
return msg_lines
|
||||
}
|
||||
if text == "" {
|
||||
msg_lines = append(msg_lines, "")
|
||||
} else {
|
||||
@@ -358,12 +368,15 @@ func GetChoices(o *Options) (response string, err error) {
|
||||
if hidden_text != "" && message != "" {
|
||||
message = message[:hidden_text_start_pos] + hidden_text + message[hidden_text_end_pos:]
|
||||
hidden_text = ""
|
||||
draw_screen()
|
||||
_ = draw_screen()
|
||||
}
|
||||
}
|
||||
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
lp.SetCursorVisible(false)
|
||||
if o.Title != "" {
|
||||
lp.SetWindowTitle(o.Title)
|
||||
}
|
||||
return "", draw_screen()
|
||||
}
|
||||
|
||||
@@ -398,16 +411,31 @@ func GetChoices(o *Options) (response string, err error) {
|
||||
}
|
||||
|
||||
lp.OnMouseEvent = func(ev *loop.MouseEvent) error {
|
||||
if ev.Event_type == loop.MOUSE_CLICK {
|
||||
for letter, ranges := range clickable_ranges {
|
||||
for _, r := range ranges {
|
||||
if r.has_point(ev.Cell.X, ev.Cell.Y) {
|
||||
response = letter
|
||||
lp.Quit(0)
|
||||
return nil
|
||||
}
|
||||
on_letter := ""
|
||||
for letter, ranges := range clickable_ranges {
|
||||
for _, r := range ranges {
|
||||
if r.has_point(ev.Cell.X, ev.Cell.Y) {
|
||||
on_letter = letter
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if on_letter != "" {
|
||||
if s, has_shape := lp.CurrentPointerShape(); !has_shape && s != loop.POINTER_POINTER {
|
||||
lp.PushPointerShape(loop.POINTER_POINTER)
|
||||
}
|
||||
} else {
|
||||
if _, has_shape := lp.CurrentPointerShape(); has_shape {
|
||||
lp.PopPointerShape()
|
||||
}
|
||||
}
|
||||
|
||||
if ev.Event_type == loop.MOUSE_CLICK {
|
||||
if on_letter != "" {
|
||||
response = on_letter
|
||||
lp.Quit(0)
|
||||
return nil
|
||||
}
|
||||
if hidden_text != "" && replacement_range.has_point(ev.Cell.X, ev.Cell.Y) {
|
||||
unhide()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
@@ -30,13 +30,20 @@ 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.
|
||||
|
||||
|
||||
--title --window-title
|
||||
The title for the window in which the question is displayed. Only implemented
|
||||
for yesno and choices types.
|
||||
|
||||
|
||||
--choice -c
|
||||
type=list
|
||||
dest=choices
|
||||
A choice for the choices type. Can be specified multiple times. Every choice has
|
||||
the syntax: ``letter[;color]:text``. Where :italic:`letter` is the accelerator key
|
||||
and :italic:`text` is the corresponding text. There can be an optional color
|
||||
specification after the letter to indicate what color it should be.
|
||||
the syntax: ``letter[;color]:text``, where :italic:`text` is the choice
|
||||
text and :italic:`letter` is the selection key. :italic:`letter` is a single letter
|
||||
belonging to :italic:`text`. This letter is highlighted within the choice text.
|
||||
There can be an optional color specification after the letter
|
||||
to indicate what color it should be.
|
||||
For example: :code:`y:Yes` and :code:`n;red:No`
|
||||
|
||||
|
||||
|
||||
@@ -160,3 +160,4 @@ elif __name__ == '__doc__':
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = 'Broadcast typed text to kitty windows'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
@@ -115,14 +115,16 @@ controls where to display the selected error message, other options are ignored.
|
||||
--regex
|
||||
default={default_regex}
|
||||
The regular expression to use when option :option:`--type` is set to
|
||||
:code:`regex`, in python syntax. If you specify a numbered group in the regular
|
||||
:code:`regex`, in Perl 5 syntax. If you specify a numbered 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. To
|
||||
match text over multiple lines, you should prefix the regular expression with
|
||||
:code:`(?ms)`, which turns on MULTILINE and DOTALL modes for the regex engine.
|
||||
If you specify named groups and a :option:`--program`, then the program will be
|
||||
passed arguments corresponding to each named group of the form
|
||||
:code:`key=value`.
|
||||
match text over multiple lines, things get a little tricky, as line endings
|
||||
are a sequence of zero or more null bytes followed by either a carriage return
|
||||
or a newline character. To have a pattern match over line endings you will need to
|
||||
match the character set ``[\0\r\n]``. The newlines and null bytes are automatically
|
||||
stripped from the returned text. If you specify named groups and a
|
||||
:option:`--program`, then the program will be passed arguments corresponding
|
||||
to each named group of the form :code:`key=value`.
|
||||
|
||||
|
||||
--linenum-action
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"kitty"
|
||||
"kitty/tools/config"
|
||||
"kitty/tools/tty"
|
||||
"kitty/tools/utils"
|
||||
)
|
||||
|
||||
@@ -221,7 +222,7 @@ func read_relevant_kitty_opts(path string) KittyOpts {
|
||||
return nil
|
||||
}
|
||||
cp := config.ConfigParser{LineHandler: handle_line}
|
||||
cp.ParseFiles(path)
|
||||
_ = cp.ParseFiles(path) // ignore errors and use defaults
|
||||
if ans.Url_prefixes == nil {
|
||||
ans.Url_prefixes = utils.NewSetWithItems(kitty.KittyConfigDefaults.Url_prefixes...)
|
||||
}
|
||||
@@ -232,6 +233,9 @@ var RelevantKittyOpts = sync.OnceValue(func() KittyOpts {
|
||||
return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf"))
|
||||
})
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func functions_for(opts *Options) (pattern string, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc) {
|
||||
switch opts.Type {
|
||||
case "url":
|
||||
|
||||
@@ -43,16 +43,18 @@ func get_options_for_rg() (expecting_args map[string]bool, alias_map map[string]
|
||||
if options_started {
|
||||
s := strings.TrimLeft(line, " ")
|
||||
indent := len(line) - len(s)
|
||||
if indent < 12 && indent > 0 {
|
||||
s, _, expecting_arg := strings.Cut(s, "<")
|
||||
if indent < 8 && indent > 0 {
|
||||
expecting_arg := strings.Contains(s, "=")
|
||||
single_letter_aliases := make([]string, 0, 1)
|
||||
long_option_names := make([]string, 0, 1)
|
||||
for _, x := range strings.Split(s, ",") {
|
||||
x = strings.TrimSpace(x)
|
||||
if strings.HasPrefix(x, "--") {
|
||||
long_option_names = append(long_option_names, x[2:])
|
||||
lon, _, _ := strings.Cut(x[2:], "=")
|
||||
long_option_names = append(long_option_names, lon)
|
||||
} else if strings.HasPrefix(x, "-") {
|
||||
single_letter_aliases = append(single_letter_aliases, x[1:])
|
||||
son, _, _ := strings.Cut(x[1:], " ")
|
||||
single_letter_aliases = append(single_letter_aliases, son)
|
||||
}
|
||||
}
|
||||
if len(long_option_names) == 0 {
|
||||
@@ -68,11 +70,15 @@ func get_options_for_rg() (expecting_args map[string]bool, alias_map map[string]
|
||||
expecting_args[long_option_names[0]] = expecting_arg
|
||||
}
|
||||
} else {
|
||||
if strings.HasPrefix(line, "OPTIONS:") {
|
||||
if strings.HasSuffix(line, "OPTIONS:") {
|
||||
options_started = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(expecting_args) == 0 || len(alias_map) == 0 {
|
||||
err = fmt.Errorf("Failed to parse rg help output, could not find any options")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error
|
||||
lp.OnInitialize = func() (string, error) {
|
||||
var iid uint32
|
||||
_, _ = lp.AddTimer(timeout, false, func(loop.IdType) error {
|
||||
return fmt.Errorf("Timed out waiting for a response form the terminal: %w", os.ErrDeadlineExceeded)
|
||||
return fmt.Errorf("Timed out waiting for a response from the terminal: %w", os.ErrDeadlineExceeded)
|
||||
})
|
||||
|
||||
g := func(t graphics.GRT_t, payload string) uint32 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
OPTIONS = '''\
|
||||
@@ -68,7 +68,7 @@ option.
|
||||
--detection-timeout
|
||||
type=float
|
||||
default=10
|
||||
The amount of time (in seconds) to wait for a response form the terminal, when
|
||||
The amount of time (in seconds) to wait for a response from the terminal, when
|
||||
detecting image display support.
|
||||
|
||||
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, SHIFT, SUPER
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop, MouseEvent
|
||||
from ..tui.operations import MouseTracking
|
||||
|
||||
mod_names = {
|
||||
SHIFT: 'Shift',
|
||||
ALT: 'Alt',
|
||||
CTRL: 'Ctrl',
|
||||
SUPER: 'Super',
|
||||
HYPER: 'Hyper',
|
||||
META: 'Meta',
|
||||
NUM_LOCK: 'NumLock',
|
||||
CAPS_LOCK: 'CapsLock',
|
||||
}
|
||||
|
||||
|
||||
def format_mods(mods: int) -> str:
|
||||
if not mods:
|
||||
return ''
|
||||
lmods = []
|
||||
for m, name in mod_names.items():
|
||||
if mods & m:
|
||||
lmods.append(name)
|
||||
return '+'.join(lmods)
|
||||
|
||||
|
||||
class Mouse(Handler):
|
||||
mouse_tracking = MouseTracking.full
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.current_mouse_event: Optional[MouseEvent] = None
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.cmd.set_cursor_visible(False)
|
||||
self.draw_screen()
|
||||
|
||||
def finalize(self) -> None:
|
||||
self.cmd.set_cursor_visible(True)
|
||||
|
||||
def on_mouse_event(self, ev: MouseEvent) -> None:
|
||||
self.current_mouse_event = ev
|
||||
self.draw_screen()
|
||||
|
||||
@Handler.atomic_update
|
||||
def draw_screen(self) -> None:
|
||||
self.cmd.clear_screen()
|
||||
ev = self.current_mouse_event
|
||||
if ev is None:
|
||||
self.print('Move the mouse or click to see mouse events')
|
||||
return
|
||||
self.print(f'Position: {ev.pixel_x}, {ev.pixel_y}')
|
||||
self.print(f'Cell: {ev.cell_x}, {ev.cell_y}')
|
||||
self.print(f'{ev.type}')
|
||||
if ev.buttons:
|
||||
self.print(ev.buttons)
|
||||
if ev.mods:
|
||||
self.print(f'Modifiers: {format_mods(ev.mods)}')
|
||||
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(0)
|
||||
on_eot = on_interrupt
|
||||
|
||||
|
||||
def main(args: List[str]) -> None:
|
||||
loop = Loop()
|
||||
handler = Mouse()
|
||||
loop.loop(handler)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
@@ -7,7 +7,7 @@ from typing import Any, Callable, Dict, List, Tuple
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import PanelCLIOptions
|
||||
from kitty.constants import appname, is_macos
|
||||
from kitty.constants import appname, is_macos, is_wayland
|
||||
from kitty.fast_data_types import make_x11_window_a_dock_window
|
||||
from kitty.os_window_size import WindowSizeData
|
||||
|
||||
@@ -103,15 +103,36 @@ def setup_x11_window(win_id: int) -> None:
|
||||
|
||||
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
||||
from kitty.fast_data_types import glfw_primary_monitor_size
|
||||
from kitty.typing import EdgeLiteral
|
||||
|
||||
def effective_margin(which: EdgeLiteral) -> float:
|
||||
ans: float = getattr(opts.single_window_margin_width, which)
|
||||
if ans < 0:
|
||||
ans = getattr(opts.window_margin_width, which)
|
||||
return ans
|
||||
|
||||
def effective_padding(which: EdgeLiteral) -> float:
|
||||
ans: float = getattr(opts.single_window_padding_width, which)
|
||||
if ans < 0:
|
||||
ans = getattr(opts.window_padding_width, which)
|
||||
return ans
|
||||
|
||||
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
|
||||
if not is_macos and not is_wayland():
|
||||
# Not sure what the deal with scaling on X11 is
|
||||
xscale = yscale = 1
|
||||
global window_width, window_height
|
||||
monitor_width, monitor_height = glfw_primary_monitor_size()
|
||||
|
||||
if args.edge in {'top', 'bottom'}:
|
||||
window_height = cell_height * args.lines + 1
|
||||
spacing = effective_margin('top') + effective_margin('bottom')
|
||||
spacing += effective_padding('top') + effective_padding('bottom')
|
||||
window_height = int(cell_height * args.lines / yscale + (dpi_y / 72) * spacing + 1)
|
||||
window_width = monitor_width
|
||||
else:
|
||||
window_width = cell_width * args.columns + 1
|
||||
spacing = effective_margin('left') + effective_margin('right')
|
||||
spacing += effective_padding('left') + effective_padding('right')
|
||||
window_width = int(cell_width * args.columns / xscale + (dpi_x / 72) * spacing + 1)
|
||||
window_height = monitor_height
|
||||
return window_width, window_height
|
||||
|
||||
@@ -149,3 +170,4 @@ elif __name__ == '__doc__':
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = help_text
|
||||
|
||||
@@ -258,3 +258,4 @@ elif __name__ == '__doc__':
|
||||
cd['usage'] = usage
|
||||
cd['options'] = options_spec
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = 'Query the terminal for various capabilities'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
|
||||
@@ -524,16 +524,20 @@ func get_remote_command(cd *connection_data) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var debugprintln = tty.DebugPrintln
|
||||
var _ = debugprintln
|
||||
|
||||
func drain_potential_tty_garbage(term *tty.Term) {
|
||||
err := term.ApplyOperations(tty.TCSANOW, tty.SetNoEcho)
|
||||
err := term.ApplyOperations(tty.TCSANOW, tty.SetRaw)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
canary, err := secrets.TokenBase64()
|
||||
canary, err := secrets.TokenHex()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dcs, err := tui.DCSToKitty("echo", canary+"\n\r")
|
||||
dcs, err := tui.DCSToKitty("echo", canary)
|
||||
q := utils.UnsafeStringToBytes(canary)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -541,7 +545,6 @@ func drain_potential_tty_garbage(term *tty.Term) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
q := utils.UnsafeStringToBytes(canary)
|
||||
data := make([]byte, 0)
|
||||
give_up_at := time.Now().Add(2 * time.Second)
|
||||
buf := make([]byte, 0, 8192)
|
||||
@@ -553,7 +556,7 @@ func drain_potential_tty_garbage(term *tty.Term) {
|
||||
}
|
||||
n, err := term.ReadWithTimeout(buf, timeout)
|
||||
if err != nil {
|
||||
return
|
||||
break
|
||||
}
|
||||
data = append(data, buf[:n]...)
|
||||
}
|
||||
@@ -727,14 +730,22 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
restore_escape_codes := loop.RESTORE_PRIVATE_MODE_VALUES
|
||||
restore_escape_codes := loop.RESTORE_PRIVATE_MODE_VALUES + loop.HANDLE_TERMIOS_SIGNALS.EscapeCodeToReset()
|
||||
if escape_codes_to_set_colors != "" {
|
||||
restore_escape_codes += "\x1b[#Q"
|
||||
}
|
||||
defer func() {
|
||||
_ = term.WriteAllString(restore_escape_codes)
|
||||
term.RestoreAndClose()
|
||||
}()
|
||||
sigs := make(chan os.Signal, 8)
|
||||
signal.Notify(sigs, unix.SIGINT, unix.SIGTERM)
|
||||
cleaned_up := false
|
||||
cleanup := func() {
|
||||
if !cleaned_up {
|
||||
_ = term.WriteAllString(restore_escape_codes)
|
||||
term.RestoreAndClose()
|
||||
signal.Reset()
|
||||
cleaned_up = true
|
||||
}
|
||||
}
|
||||
defer cleanup()
|
||||
err = get_remote_command(&cd)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
@@ -746,8 +757,6 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
sigs := make(chan os.Signal, 8)
|
||||
signal.Notify(sigs, unix.SIGINT, unix.SIGTERM)
|
||||
|
||||
if !cd.request_data {
|
||||
rq := fmt.Sprintf("id=%s:pwfile=%s:pw=%s", cd.replacements["REQUEST_ID"], cd.replacements["PASSWORD_FILENAME"], cd.replacements["DATA_PASSWORD"])
|
||||
@@ -772,11 +781,11 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
|
||||
}()
|
||||
err = c.Wait()
|
||||
drain_potential_tty_garbage(term)
|
||||
signal.Reset(unix.SIGINT, unix.SIGTERM)
|
||||
if err != nil {
|
||||
var exit_err *exec.ExitError
|
||||
if errors.As(err, &exit_err) {
|
||||
if state := exit_err.ProcessState.String(); state == "signal: interrupt" {
|
||||
cleanup()
|
||||
_ = unix.Kill(os.Getpid(), unix.SIGINT)
|
||||
// Give the signal time to be delivered
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
@@ -162,18 +162,18 @@ in the .conf files/themes are ignored.
|
||||
''')
|
||||
|
||||
opt('remote_kitty', 'if-needed', choices=('if-needed', 'no', 'yes'), long_text='''
|
||||
Make :program:`kitty` available on the remote host. Useful to run kittens such
|
||||
Make :program:`kitten` available on the remote host. Useful to run kittens such
|
||||
as the :doc:`icat kitten </kittens/icat>` to display images or the
|
||||
:doc:`transfer file kitten </kittens/transfer>` to transfer files. Only works if
|
||||
the remote host has an architecture for which :link:`pre-compiled kitty binaries
|
||||
<https://github.com/kovidgoyal/kitty/releases>` are available. Note that kitty
|
||||
the remote host has an architecture for which :link:`pre-compiled kitten binaries
|
||||
<https://github.com/kovidgoyal/kitty/releases>` are available. Note that kitten
|
||||
is not actually copied to the remote host, instead a small bootstrap script is
|
||||
copied which will download and run kitty when kitty is first executed on the
|
||||
remote host. A value of :code:`if-needed` means kitty is installed only if not
|
||||
already present in the system-wide PATH. A value of :code:`yes` means that kitty
|
||||
is installed even if already present, and the installed kitty takes precedence.
|
||||
Finally, :code:`no` means no kitty is installed on the remote host. The
|
||||
installed kitty can be updated by running: :code:`kitty +update-kitty` on the
|
||||
copied which will download and run kitten when kitten is first executed on the
|
||||
remote host. A value of :code:`if-needed` means kitten is installed only if not
|
||||
already present in the system-wide PATH. A value of :code:`yes` means that kitten
|
||||
is installed even if already present, and the installed kitten takes precedence.
|
||||
Finally, :code:`no` means no kitten is installed on the remote host. The
|
||||
installed kitten can be updated by running: :code:`kitten update-self` on the
|
||||
remote host.
|
||||
''')
|
||||
egr() # }}}
|
||||
|
||||
@@ -28,6 +28,9 @@ func TestParseSSHArgs(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ans) == 0 {
|
||||
ans = []string{}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
@@ -39,7 +42,7 @@ func TestParseSSHArgs(t *testing.T) {
|
||||
check := func(a, b any) {
|
||||
diff := cmp.Diff(a, b)
|
||||
if diff != "" {
|
||||
t.Fatalf("Unexpected value for args: %s\n%s", args, diff)
|
||||
t.Fatalf("Unexpected value for args: %#v\n%s", args, diff)
|
||||
}
|
||||
}
|
||||
check(split(expected_ssh_args), ssh_args)
|
||||
|
||||
@@ -110,9 +110,9 @@ init_rsync(Rsync *ans, size_t block_size, int strong_hash_type, int checksum_typ
|
||||
ans->hasher = ans->hasher_constructor();
|
||||
ans->checksummer = ans->checksummer_constructor();
|
||||
ans->hasher.state = ans->hasher.new();
|
||||
if (ans->hasher.state == NULL) { free(ans); return "Out of memory"; }
|
||||
if (ans->hasher.state == NULL) { free_rsync(ans); return "Out of memory"; }
|
||||
ans->checksummer.state = ans->checksummer.new();
|
||||
if (ans->checksummer.state == NULL) { free(ans); return "Out of memory"; }
|
||||
if (ans->checksummer.state == NULL) { free_rsync(ans); return "Out of memory"; }
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,8 +128,10 @@ def main(args: List[str]) -> None:
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
elif __name__ == '__doc__':
|
||||
from kitty.cli import CompletionSpec
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = option_text
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = help_text
|
||||
cd['short_desc'] = 'Transfer files easily over the TTY device'
|
||||
cd['args_completion'] = CompletionSpec.from_string('type:file group:"Files"')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import codecs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import Callable, Tuple
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import asyncio
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
|
||||
@@ -50,7 +50,7 @@ func build_sets() {
|
||||
}
|
||||
EMOTICONS_SET = make([]rune, 0, 0x1f64f-0x1f600+1)
|
||||
for i := 0x1f600; i <= 0x1f64f; i++ {
|
||||
DEFAULT_SET = append(DEFAULT_SET, rune(i))
|
||||
EMOTICONS_SET = append(EMOTICONS_SET, rune(i))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from enum import IntFlag
|
||||
|
||||
455
kitty/boss.py
455
kitty/boss.py
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import atexit
|
||||
@@ -23,6 +23,7 @@ from typing import (
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
@@ -65,7 +66,6 @@ from .fast_data_types import (
|
||||
GLFW_MOD_SUPER,
|
||||
GLFW_MOUSE_BUTTON_LEFT,
|
||||
GLFW_PRESS,
|
||||
GLFW_RELEASE,
|
||||
IMPERATIVE_CLOSE_REQUESTED,
|
||||
NO_CLOSE_REQUESTED,
|
||||
ChildMonitor,
|
||||
@@ -94,6 +94,7 @@ from .fast_data_types import (
|
||||
is_modifier_key,
|
||||
last_focused_os_window_id,
|
||||
mark_os_window_for_close,
|
||||
os_window_focus_counters,
|
||||
os_window_font_size,
|
||||
patch_global_colors,
|
||||
redirect_mouse_handling,
|
||||
@@ -104,7 +105,7 @@ from .fast_data_types import (
|
||||
set_application_quit_request,
|
||||
set_background_image,
|
||||
set_boss,
|
||||
set_in_sequence_mode,
|
||||
set_ignore_os_keyboard_processing,
|
||||
set_options,
|
||||
set_os_window_chrome,
|
||||
set_os_window_size,
|
||||
@@ -116,11 +117,11 @@ from .fast_data_types import (
|
||||
wrapped_kitten_names,
|
||||
)
|
||||
from .key_encoding import get_name_to_functional_number_map
|
||||
from .keys import get_shortcut, shortcut_matches
|
||||
from .keys import get_shortcut
|
||||
from .layout.base import set_layout_options
|
||||
from .notify import notification_activated
|
||||
from .options.types import Options
|
||||
from .options.utils import MINIMUM_FONT_SIZE, KeyMap, SubSequenceMap
|
||||
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition, KeyMap
|
||||
from .os_window_size import initial_window_size_func
|
||||
from .rgb import color_from_int
|
||||
from .session import Session, create_sessions, get_os_window_sizing_data
|
||||
@@ -167,6 +168,7 @@ class OSWindowDict(TypedDict):
|
||||
tabs: List[TabDict]
|
||||
wm_class: str
|
||||
wm_name: str
|
||||
background_opacity: float
|
||||
|
||||
|
||||
def listen_on(spec: str) -> Tuple[int, str]:
|
||||
@@ -295,7 +297,7 @@ class VisualSelect:
|
||||
set_os_window_title(self.os_window_id, '')
|
||||
boss = get_boss()
|
||||
redirect_mouse_handling(False)
|
||||
boss.clear_pending_sequences()
|
||||
boss.keyboard_mode_stack = []
|
||||
for wid in self.window_ids:
|
||||
w = boss.window_id_map.get(wid)
|
||||
if w is not None:
|
||||
@@ -332,6 +334,7 @@ class Boss:
|
||||
self.clipboard = Clipboard()
|
||||
self.primary_selection = Clipboard(ClipboardType.primary_selection)
|
||||
self.update_check_started = False
|
||||
self.peer_data_map: Dict[int, Optional[Dict[str, Sequence[str]]]] = {}
|
||||
self.encryption_key = EllipticCurveKey()
|
||||
self.encryption_public_key = f'{RC_ENCRYPTION_PROTOCOL_VERSION}:{base64.b85encode(self.encryption_key.public).decode("ascii")}'
|
||||
self.clipboard_buffers: Dict[str, str] = {}
|
||||
@@ -340,15 +343,13 @@ class Boss:
|
||||
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
||||
self.current_visual_select: Optional[VisualSelect] = None
|
||||
self.startup_cursor_text_color = opts.cursor_text_color
|
||||
self.pending_sequences: Optional[SubSequenceMap] = None
|
||||
# A list of events received so far that are potentially part of a sequence keybinding.
|
||||
self.current_sequence: List[KeyEvent] = []
|
||||
self.default_pending_action: str = ''
|
||||
self.cached_values = cached_values
|
||||
self.os_window_map: Dict[int, TabManager] = {}
|
||||
self.os_window_death_actions: Dict[int, Callable[[], None]] = {}
|
||||
self.cursor_blinking = True
|
||||
self.shutting_down = False
|
||||
self.misc_config_errors: List[str] = []
|
||||
talk_fd = getattr(single_instance, 'socket', None)
|
||||
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
|
||||
listen_fd = -1
|
||||
@@ -361,7 +362,11 @@ class Boss:
|
||||
self.allow_remote_control = 'n'
|
||||
self.listening_on = ''
|
||||
if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'):
|
||||
listen_fd, self.listening_on = listen_on(args.listen_on)
|
||||
try:
|
||||
listen_fd, self.listening_on = listen_on(args.listen_on)
|
||||
except Exception:
|
||||
self.misc_config_errors.append(f'Invalid listen_on={args.listen_on}, ignoring')
|
||||
log_error(self.misc_config_errors[-1])
|
||||
self.child_monitor = ChildMonitor(
|
||||
self.on_child_death,
|
||||
DumpCommands(args) if args.dump_commands or args.dump_bytes else None,
|
||||
@@ -370,6 +375,7 @@ class Boss:
|
||||
set_boss(self)
|
||||
self.args = args
|
||||
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
|
||||
self.keyboard_mode_stack: List[KeyboardMode] = []
|
||||
self.update_keymap(global_shortcuts)
|
||||
if is_macos:
|
||||
from .fast_data_types import cocoa_set_notification_activated_callback
|
||||
@@ -382,11 +388,13 @@ class Boss:
|
||||
global_shortcuts = set_cocoa_global_shortcuts(get_options())
|
||||
else:
|
||||
global_shortcuts = {}
|
||||
self.global_shortcuts_map: KeyMap = {v: k for k, v in global_shortcuts.items()}
|
||||
self.global_shortcuts_map: KeyMap = {v: [KeyDefinition(definition=k)] for k, v in global_shortcuts.items()}
|
||||
self.global_shortcuts = global_shortcuts
|
||||
self.keymap = get_options().keymap.copy()
|
||||
self.keyboard_modes = get_options().keyboard_modes.copy()
|
||||
km = self.keyboard_modes[''].keymap
|
||||
self.keyboard_modes[''].keymap = km = km.copy()
|
||||
for sc in self.global_shortcuts.values():
|
||||
self.keymap.pop(sc, None)
|
||||
km.pop(sc, None)
|
||||
|
||||
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
|
||||
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
|
||||
@@ -447,6 +455,9 @@ class Boss:
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
tabs = list(tm.list_tabs(self_window, tab_filter, window_filter))
|
||||
if tabs:
|
||||
bo = background_opacity_of(os_window_id)
|
||||
if bo is None:
|
||||
bo = 1
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'platform_window_id': platform_window_id(os_window_id),
|
||||
@@ -455,7 +466,8 @@ class Boss:
|
||||
'last_focused': os_window_id == last_focused_os_window_id(),
|
||||
'tabs': tabs,
|
||||
'wm_class': tm.wm_class,
|
||||
'wm_name': tm.wm_name
|
||||
'wm_name': tm.wm_name,
|
||||
'background_opacity': bo,
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -592,14 +604,16 @@ class Boss:
|
||||
self.window_id_map[window.id] = window
|
||||
|
||||
def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> RCResponse:
|
||||
from .remote_control import is_cmd_allowed, parse_cmd
|
||||
from .remote_control import is_cmd_allowed, parse_cmd, remote_control_allowed
|
||||
response = None
|
||||
window = window or None
|
||||
from_socket = peer_id > 0
|
||||
is_fd_peer = from_socket and peer_id in self.peer_data_map
|
||||
window_has_remote_control = bool(window and window.allow_remote_control)
|
||||
if not window_has_remote_control:
|
||||
if not window_has_remote_control and not is_fd_peer:
|
||||
if self.allow_remote_control == 'n':
|
||||
return {'ok': False, 'error': 'Remote control is disabled'}
|
||||
if self.allow_remote_control == 'socket-only' and peer_id == 0:
|
||||
if self.allow_remote_control == 'socket-only' and not from_socket:
|
||||
return {'ok': False, 'error': 'Remote control is allowed over a socket only'}
|
||||
try:
|
||||
pcmd = parse_cmd(cmd, self.encryption_key)
|
||||
@@ -623,13 +637,16 @@ class Boss:
|
||||
extra_data: Dict[str, Any] = {}
|
||||
try:
|
||||
allowed_unconditionally = (
|
||||
self.allow_remote_control == 'y' or (peer_id > 0 and self.allow_remote_control in ('socket-only', 'socket')) or
|
||||
(window and window.remote_control_allowed(pcmd, extra_data)))
|
||||
self.allow_remote_control == 'y' or
|
||||
(from_socket and not is_fd_peer and self.allow_remote_control in ('socket-only', 'socket')) or
|
||||
(window and window.remote_control_allowed(pcmd, extra_data)) or
|
||||
(is_fd_peer and remote_control_allowed(pcmd, self.peer_data_map.get(peer_id), None, extra_data))
|
||||
)
|
||||
except PermissionError:
|
||||
return {'ok': False, 'error': 'Remote control disallowed by window specific password'}
|
||||
if allowed_unconditionally:
|
||||
return self._execute_remote_command(pcmd, window, peer_id, self_window)
|
||||
q = is_cmd_allowed(pcmd, window, peer_id > 0, extra_data)
|
||||
q = is_cmd_allowed(pcmd, window, from_socket, extra_data)
|
||||
if q is True:
|
||||
return self._execute_remote_command(pcmd, window, peer_id, self_window)
|
||||
if q is None:
|
||||
@@ -666,7 +683,7 @@ class Boss:
|
||||
), dim=True, italic=True)),
|
||||
partial(self.remote_cmd_permission_received, pcmd, wid, peer_id, self_window),
|
||||
'a;green:Allow request', 'p;yellow:Allow password', 'r;magenta:Deny request', 'd;red:Deny password',
|
||||
window=window, default='a', hidden_text=hidden_text
|
||||
window=window, default='a', hidden_text=hidden_text, title=_('Allow remote control?'),
|
||||
)
|
||||
if overlay_window is None:
|
||||
return False
|
||||
@@ -710,7 +727,7 @@ class Boss:
|
||||
return response
|
||||
|
||||
@ac('misc', '''
|
||||
Run a remote control command
|
||||
Run a remote control command without needing to allow remote control
|
||||
|
||||
For example::
|
||||
|
||||
@@ -725,6 +742,22 @@ class Boss:
|
||||
import shlex
|
||||
self.show_error(_('remote_control mapping failed'), shlex.join(args) + '\n' + str(e))
|
||||
|
||||
@ac('misc', '''
|
||||
Run a remote control script without needing to allow remote control
|
||||
|
||||
For example::
|
||||
|
||||
map f1 remote_control_script arg1 arg2 ...
|
||||
|
||||
See :ref:`rc_mapping` for details.
|
||||
''')
|
||||
def remote_control_script(self, path: str, *args: str) -> None:
|
||||
path = which(path) or path
|
||||
if not os.access(path, os.X_OK):
|
||||
self.show_error('Remote control script not executable', f'The script {path} is not executable check its permissions')
|
||||
return
|
||||
self.run_background_process([path] + list(args), allow_remote_control=True)
|
||||
|
||||
def call_remote_control(self, self_window: Optional[Window], args: Tuple[str, ...]) -> 'ResponseType':
|
||||
from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
from .remote_control import parse_rc_args
|
||||
@@ -756,20 +789,30 @@ class Boss:
|
||||
return None
|
||||
raise
|
||||
|
||||
def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Union[bytes, bool, None]:
|
||||
cmd_prefix = b'\x1bP@kitty-cmd'
|
||||
terminator = b'\x1b\\'
|
||||
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
|
||||
cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8')
|
||||
response = self._handle_remote_command(cmd, peer_id=peer_id)
|
||||
if response is None:
|
||||
return None
|
||||
if isinstance(response, AsyncResponse):
|
||||
return True
|
||||
from kitty.remote_control import encode_response_for_peer
|
||||
return encode_response_for_peer(response)
|
||||
def peer_message_received(self, msg_bytes: bytes, peer_id: int, is_remote_control: bool) -> Union[bytes, bool, None]:
|
||||
if peer_id > 0 and msg_bytes == b'peer_death':
|
||||
self.peer_data_map.pop(peer_id, None)
|
||||
return False
|
||||
if is_remote_control:
|
||||
cmd_prefix = b'\x1bP@kitty-cmd'
|
||||
terminator = b'\x1b\\'
|
||||
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
|
||||
cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8')
|
||||
response = self._handle_remote_command(cmd, peer_id=peer_id)
|
||||
if response is None:
|
||||
return None
|
||||
if isinstance(response, AsyncResponse):
|
||||
return True
|
||||
from kitty.remote_control import encode_response_for_peer
|
||||
return encode_response_for_peer(response)
|
||||
log_error('Malformatted remote control message received from peer, ignoring')
|
||||
return None
|
||||
|
||||
data:SingleInstanceData = json.loads(msg_bytes.decode('utf-8'))
|
||||
try:
|
||||
data:SingleInstanceData = json.loads(msg_bytes.decode('utf-8'))
|
||||
except Exception:
|
||||
log_error('Malformed command received over single instance socket, ignoring')
|
||||
return None
|
||||
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
|
||||
from .cli_stub import CLIOptions
|
||||
startup_id = data['environ'].get('DESKTOP_STARTUP_ID', '')
|
||||
@@ -807,7 +850,7 @@ class Boss:
|
||||
elif activation_token and is_wayland() and os_window_id:
|
||||
focus_os_window(os_window_id, True, activation_token)
|
||||
else:
|
||||
log_error('Unknown message received from peer, ignoring')
|
||||
log_error('Unknown message received over single instance socket, ignoring')
|
||||
return None
|
||||
|
||||
def handle_remote_cmd(self, cmd: str, window: Optional[Window] = None) -> None:
|
||||
@@ -912,7 +955,7 @@ class Boss:
|
||||
msg = _('Are you sure you want to close this window?')
|
||||
if window.has_running_program:
|
||||
msg += ' ' + _('It is running a program.')
|
||||
self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window)
|
||||
self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window, title=_('Close window?'))
|
||||
else:
|
||||
self.mark_window_for_close(window)
|
||||
|
||||
@@ -942,6 +985,7 @@ class Boss:
|
||||
window: Optional[Window] = None, # the window associated with the confirmation
|
||||
confirm_on_cancel: bool = False, # on closing window
|
||||
confirm_on_accept: bool = True, # on pressing enter
|
||||
title: str = '' # window title
|
||||
) -> Window:
|
||||
result: bool = False
|
||||
|
||||
@@ -952,9 +996,11 @@ class Boss:
|
||||
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
|
||||
callback(result, *args)
|
||||
|
||||
cmd = ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n']
|
||||
if title:
|
||||
cmd += ['--title', title]
|
||||
w = self.run_kitten_with_metadata(
|
||||
'ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
|
||||
window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal,
|
||||
'ask', cmd, window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal,
|
||||
default_data={'response': 'y' if confirm_on_cancel else 'n'})
|
||||
assert isinstance(w, Window)
|
||||
return w
|
||||
@@ -968,6 +1014,7 @@ class Boss:
|
||||
hidden_text: str = '', # text to hide in the message
|
||||
hidden_text_placeholder: str = 'HIDDEN_TEXT_PLACEHOLDER', # placeholder text to insert in to message
|
||||
unhide_key: str = 'u', # key to press to unhide hidden text
|
||||
title: str = '' # window title
|
||||
) -> Optional[Window]:
|
||||
result: str = ''
|
||||
|
||||
@@ -987,6 +1034,8 @@ class Boss:
|
||||
input_data = hidden_text
|
||||
else:
|
||||
input_data = None
|
||||
if title:
|
||||
cmd += ['--title', title]
|
||||
|
||||
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
|
||||
callback(result)
|
||||
@@ -1004,7 +1053,8 @@ class Boss:
|
||||
callback: Callable[..., None], # called with the answer or empty string when aborted
|
||||
window: Optional[Window] = None, # the window associated with the confirmation
|
||||
prompt: str = '> ',
|
||||
is_password: bool = False
|
||||
is_password: bool = False,
|
||||
initial_value: str = ''
|
||||
) -> None:
|
||||
result: str = ''
|
||||
|
||||
@@ -1016,6 +1066,8 @@ class Boss:
|
||||
callback(result)
|
||||
|
||||
cmd = ['--type', 'password' if is_password else 'line', '--message', msg, '--prompt', prompt]
|
||||
if initial_value:
|
||||
cmd.append('--default=' + initial_value)
|
||||
self.run_kitten_with_metadata(
|
||||
'ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal
|
||||
)
|
||||
@@ -1039,7 +1091,7 @@ class Boss:
|
||||
w = self.confirm(ngettext('Are you sure you want to close this tab, it has one window running?',
|
||||
'Are you sure you want to close this tab, it has {} windows running?', num).format(num),
|
||||
self.handle_close_tab_confirmation, tab.id,
|
||||
window=tab.active_window,
|
||||
window=tab.active_window, title=_('Close tab?'),
|
||||
)
|
||||
tab.confirm_close_window_id = w.id
|
||||
|
||||
@@ -1285,68 +1337,110 @@ class Boss:
|
||||
t = self.active_tab
|
||||
return None if t is None else t.active_window
|
||||
|
||||
def set_pending_sequences(self, sequences: SubSequenceMap, default_pending_action: str = '') -> None:
|
||||
self.pending_sequences = sequences
|
||||
self.default_pending_action = default_pending_action
|
||||
set_in_sequence_mode(True)
|
||||
@ac('misc', '''
|
||||
End the current keyboard mode switching to the previous mode.
|
||||
''')
|
||||
def pop_keyboard_mode(self) -> bool:
|
||||
passthrough = True
|
||||
if self.keyboard_mode_stack:
|
||||
self.keyboard_mode_stack.pop()
|
||||
if not self.keyboard_mode_stack:
|
||||
set_ignore_os_keyboard_processing(False)
|
||||
passthrough = False
|
||||
return passthrough
|
||||
|
||||
@ac('misc', '''
|
||||
Switch to the specified keyboard mode, pushing it onto the stack of keyboard modes.
|
||||
''')
|
||||
def push_keyboard_mode(self, new_mode: str) -> None:
|
||||
mode = self.keyboard_modes[new_mode]
|
||||
self._push_keyboard_mode(mode)
|
||||
|
||||
def _push_keyboard_mode(self, mode: KeyboardMode) -> None:
|
||||
self.keyboard_mode_stack.append(mode)
|
||||
set_ignore_os_keyboard_processing(True)
|
||||
|
||||
def dispatch_possible_special_key(self, ev: KeyEvent) -> bool:
|
||||
# Handles shortcuts, return True if the key was consumed
|
||||
key_action = get_shortcut(self.keymap, ev)
|
||||
is_root_mode = not self.keyboard_mode_stack
|
||||
mode = self.keyboard_modes[''] if is_root_mode else self.keyboard_mode_stack[-1]
|
||||
key_action = get_shortcut(mode.keymap, ev)
|
||||
if key_action is None:
|
||||
sequences = get_shortcut(get_options().sequence_map, ev)
|
||||
if sequences and not isinstance(sequences, str):
|
||||
self.set_pending_sequences(sequences)
|
||||
self.current_sequence = [ev]
|
||||
return True
|
||||
if is_modifier_key(ev.key):
|
||||
return False
|
||||
if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev):
|
||||
return True
|
||||
elif isinstance(key_action, str):
|
||||
return self.combine(key_action)
|
||||
if not is_root_mode:
|
||||
if mode.on_unknown in ('beep', 'ignore'):
|
||||
if mode.on_unknown == 'beep' and get_options().enable_audio_bell:
|
||||
ring_bell()
|
||||
return True
|
||||
if mode.on_unknown == 'passthrough':
|
||||
return False
|
||||
if not self.pop_keyboard_mode():
|
||||
if get_options().enable_audio_bell:
|
||||
ring_bell()
|
||||
return True
|
||||
else:
|
||||
final_actions = self.matching_key_actions(key_action)
|
||||
if final_actions:
|
||||
mode_pos = len(self.keyboard_mode_stack) - 1
|
||||
if final_actions[0].is_sequence:
|
||||
if not mode.is_sequence:
|
||||
sm = KeyboardMode('__sequence__')
|
||||
sm.on_action = 'end'
|
||||
sm.is_sequence = True
|
||||
for fa in final_actions:
|
||||
sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
|
||||
self._push_keyboard_mode(sm)
|
||||
if self.args.debug_keyboard:
|
||||
print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='', flush=True)
|
||||
else:
|
||||
if len(final_actions) == 1:
|
||||
self.pop_keyboard_mode()
|
||||
return self.combine(final_actions[0].definition)
|
||||
if self.args.debug_keyboard:
|
||||
print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='', flush=True)
|
||||
mode.keymap.clear()
|
||||
for fa in final_actions:
|
||||
mode.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
|
||||
return True
|
||||
final_action = final_actions[0]
|
||||
consumed = self.combine(final_action.definition)
|
||||
if consumed and not is_root_mode and mode.on_action == 'end':
|
||||
if mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode:
|
||||
del self.keyboard_mode_stack[mode_pos]
|
||||
if not self.keyboard_mode_stack:
|
||||
set_ignore_os_keyboard_processing(False)
|
||||
return consumed
|
||||
return False
|
||||
|
||||
def clear_pending_sequences(self) -> None:
|
||||
self.pending_sequences = None
|
||||
self.current_sequence = []
|
||||
self.default_pending_action = ''
|
||||
set_in_sequence_mode(False)
|
||||
|
||||
def process_sequence(self, ev: KeyEvent) -> bool:
|
||||
# Process an event as part of a sequence. Returns whether the key
|
||||
# is consumed as part of a kitty sequence keybinding.
|
||||
if not self.pending_sequences:
|
||||
set_in_sequence_mode(False)
|
||||
return False
|
||||
|
||||
if self.current_sequence:
|
||||
self.current_sequence.append(ev)
|
||||
if ev.action == GLFW_RELEASE or is_modifier_key(ev.key):
|
||||
return True
|
||||
# For a press/repeat event that's not a modifier, try matching with
|
||||
# kitty bindings:
|
||||
remaining = {}
|
||||
matched_action = None
|
||||
for seq, key_action in self.pending_sequences.items():
|
||||
if shortcut_matches(seq[0], ev):
|
||||
seq = seq[1:]
|
||||
if seq:
|
||||
remaining[seq] = key_action
|
||||
else:
|
||||
matched_action = key_action
|
||||
|
||||
if remaining:
|
||||
self.pending_sequences = remaining
|
||||
return True
|
||||
matched_action = matched_action or self.default_pending_action
|
||||
if matched_action:
|
||||
self.clear_pending_sequences()
|
||||
self.combine(matched_action)
|
||||
return True
|
||||
def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> List[KeyDefinition]:
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
w.write_to_child(b''.join(w.encoded_key(ev) for ev in self.current_sequence))
|
||||
self.clear_pending_sequences()
|
||||
return False
|
||||
matches = []
|
||||
has_sequence_match = False
|
||||
for x in candidates:
|
||||
if x.options.when_focus_on:
|
||||
try:
|
||||
if w and w in self.match_windows(x.options.when_focus_on):
|
||||
matches.append(x)
|
||||
if x.is_sequence:
|
||||
has_sequence_match = True
|
||||
except Exception:
|
||||
self.show_error(_('Invalid key mapping'), _(
|
||||
'The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on'))
|
||||
return []
|
||||
else:
|
||||
if x.is_sequence:
|
||||
has_sequence_match = True
|
||||
matches.append(x)
|
||||
if has_sequence_match:
|
||||
matches = [x for x in matches if x.is_sequence]
|
||||
q = matches[-1].options.when_focus_on
|
||||
matches = [x for x in matches if x.options.when_focus_on == q]
|
||||
else:
|
||||
matches = [matches[-1]]
|
||||
return matches
|
||||
|
||||
def cancel_current_visual_select(self) -> None:
|
||||
if self.current_visual_select:
|
||||
@@ -1373,27 +1467,27 @@ class Boss:
|
||||
focus_os_window(tab.os_window_id, True)
|
||||
self.current_visual_select = VisualSelect(tab.id, tab.os_window_id, initial_tab_id, initial_os_window_id, choose_msg, callback, reactivate_prev_tab)
|
||||
if tab.current_layout.only_active_window_visible:
|
||||
w = self.select_window_in_tab_using_overlay(tab, choose_msg, only_window_ids)
|
||||
self.current_visual_select.window_used_for_selection_id = 0 if w is None else w.id
|
||||
self.select_window_in_tab_using_overlay(tab, choose_msg, only_window_ids)
|
||||
return
|
||||
pending_sequences: SubSequenceMap = {}
|
||||
km = KeyboardMode('__visual_select__')
|
||||
km.on_action = 'end'
|
||||
fmap = get_name_to_functional_number_map()
|
||||
alphanumerics = get_options().visual_window_select_characters
|
||||
for idx, window in tab.windows.iter_windows_with_number(only_visible=True):
|
||||
if only_window_ids and window.id not in only_window_ids:
|
||||
continue
|
||||
ac = f'visual_window_select_action_trigger {window.id}'
|
||||
ac = KeyDefinition(definition=f'visual_window_select_action_trigger {window.id}')
|
||||
if idx >= len(alphanumerics):
|
||||
break
|
||||
ch = alphanumerics[idx]
|
||||
window.screen.set_window_char(ch)
|
||||
self.current_visual_select.window_ids.append(window.id)
|
||||
for mods in (0, GLFW_MOD_CONTROL, GLFW_MOD_CONTROL | GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOD_ALT, GLFW_MOD_SHIFT):
|
||||
pending_sequences[(SingleKey(mods=mods, key=ord(ch.lower())),)] = ac
|
||||
km.keymap[SingleKey(mods=mods, key=ord(ch.lower()))].append(ac)
|
||||
if ch in string.digits:
|
||||
pending_sequences[(SingleKey(mods=mods, key=fmap[f'KP_{ch}']),)] = ac
|
||||
km.keymap[SingleKey(mods=mods, key=fmap[f'KP_{ch}'])].append(ac)
|
||||
if len(self.current_visual_select.window_ids) > 1:
|
||||
self.set_pending_sequences(pending_sequences, default_pending_action='visual_window_select_action_trigger 0')
|
||||
self._push_keyboard_mode(km)
|
||||
redirect_mouse_handling(True)
|
||||
self.mouse_handler = self.visual_window_select_mouse_handler
|
||||
else:
|
||||
@@ -1428,11 +1522,16 @@ class Boss:
|
||||
self.mouse_handler(ev)
|
||||
|
||||
def select_window_in_tab_using_overlay(self, tab: Tab, msg: str, only_window_ids: Container[int] = ()) -> Optional[Window]:
|
||||
windows = tuple((None, f'Current window: {w.title}' if w is self.active_window else w.title)
|
||||
if only_window_ids and w.id not in only_window_ids else (w.id, w.title)
|
||||
for i, w in tab.windows.iter_windows_with_number(only_visible=False))
|
||||
if len(windows) < 1:
|
||||
self.visual_window_select_action_trigger(windows[0][0] if windows and windows[0][0] is not None else 0)
|
||||
windows: List[Tuple[Optional[int], str]] = []
|
||||
selectable_windows: List[Tuple[int, str]] = []
|
||||
for i, w in tab.windows.iter_windows_with_number(only_visible=False):
|
||||
if only_window_ids and w.id not in only_window_ids:
|
||||
windows.append((None, f'Current window: {w.title}' if w is self.active_window else w.title))
|
||||
else:
|
||||
windows.append((w.id, w.title))
|
||||
selectable_windows.append((w.id, w.title))
|
||||
if len(selectable_windows) < 2:
|
||||
self.visual_window_select_action_trigger(selectable_windows[0][0] if selectable_windows else 0)
|
||||
if get_options().enable_audio_bell:
|
||||
ring_bell()
|
||||
return None
|
||||
@@ -1510,7 +1609,8 @@ class Boss:
|
||||
def report_match(f: Callable[..., Any]) -> None:
|
||||
if self.args.debug_keyboard:
|
||||
prefix = '\n' if dispatch_type == 'KeyPress' else ''
|
||||
print(f'{prefix}\x1b[35m{dispatch_type}\x1b[m matched action:', func_name(f), flush=True)
|
||||
end = ', ' if dispatch_type == 'KeyPress' else '\n'
|
||||
print(f'{prefix}\x1b[35m{dispatch_type}\x1b[m matched action:', func_name(f), end=end, flush=True)
|
||||
|
||||
if key_action is not None:
|
||||
f = getattr(self, key_action.func, None)
|
||||
@@ -1536,6 +1636,10 @@ class Boss:
|
||||
return True
|
||||
return False
|
||||
|
||||
def user_menu_action(self, defn: str) -> None:
|
||||
' Callback from user actions in the macOS global menu bar or other menus '
|
||||
self.combine(defn)
|
||||
|
||||
@ac('misc', '''
|
||||
Combine multiple actions and map to a single keypress
|
||||
|
||||
@@ -1553,8 +1657,6 @@ class Boss:
|
||||
try:
|
||||
actions = get_options().alias_map.resolve_aliases(action_definition, 'map' if dispatch_type == 'KeyPress' else 'mouse_map')
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.show_error('Failed to parse action', f'{action_definition}\n{e}')
|
||||
return True
|
||||
if actions:
|
||||
@@ -1564,8 +1666,6 @@ class Boss:
|
||||
if len(actions) > 1:
|
||||
self.drain_actions(list(actions[1:]), window_for_dispatch, dispatch_type)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.show_error('Key action failed', f'{actions[0].pretty()}\n{e}')
|
||||
consumed = True
|
||||
return consumed
|
||||
@@ -1606,12 +1706,41 @@ class Boss:
|
||||
text = '\n'.join(urls)
|
||||
w.paste_text(text)
|
||||
|
||||
@ac('win', 'Focus the nth OS window')
|
||||
@ac('win', '''
|
||||
Focus the nth OS window if positive or the previously active OS windows if negative. When the number is larger
|
||||
than the number of OS windows focus the last OS window. A value of zero will refocus the currently focused OS window,
|
||||
this is useful if focus is not on any kitty OS window at all, however, it will only work if the window manager
|
||||
allows applications to grab focus. For example::
|
||||
|
||||
# focus the previously active kitty OS window
|
||||
map ctrl+p nth_os_window -1
|
||||
# focus the current kitty OS window (grab focus)
|
||||
map ctrl+0 nth_os_window 0
|
||||
# focus the first kitty OS window
|
||||
map ctrl+1 nth_os_window 1
|
||||
# focus the last kitty OS window
|
||||
map ctrl+1 nth_os_window 999
|
||||
''')
|
||||
def nth_os_window(self, num: int = 1) -> None:
|
||||
if self.os_window_map and num > 0:
|
||||
ids = list(self.os_window_map.keys())
|
||||
if not self.os_window_map:
|
||||
return
|
||||
if num == 0:
|
||||
os_window_id = current_focused_os_window_id() or last_focused_os_window_id()
|
||||
focus_os_window(os_window_id, True)
|
||||
elif num > 0:
|
||||
ids = tuple(self.os_window_map.keys())
|
||||
os_window_id = ids[min(num, len(ids)) - 1]
|
||||
focus_os_window(os_window_id, True)
|
||||
elif num < 0:
|
||||
fc_map = os_window_focus_counters()
|
||||
s = sorted(fc_map.keys(), key=fc_map.__getitem__)
|
||||
if not s:
|
||||
return
|
||||
try:
|
||||
os_window_id = s[num-1]
|
||||
except IndexError:
|
||||
os_window_id = s[0]
|
||||
focus_os_window(os_window_id, True)
|
||||
|
||||
@ac('win', 'Close the currently active OS Window')
|
||||
def close_os_window(self) -> None:
|
||||
@@ -1639,7 +1768,7 @@ class Boss:
|
||||
ngettext('Are you sure you want to close this OS window, it has one window running?',
|
||||
'Are you sure you want to close this OS window, it has {} windows running', num).format(num),
|
||||
self.handle_close_os_window_confirmation, os_window_id,
|
||||
window=tm.active_window,
|
||||
window=tm.active_window, title=_('Close OS window'),
|
||||
)
|
||||
tm.confirm_close_window_id = w.id
|
||||
|
||||
@@ -1695,7 +1824,7 @@ class Boss:
|
||||
ngettext('Are you sure you want to quit kitty, it has one window running?',
|
||||
'Are you sure you want to quit kitty, it has {} windows running?', num).format(num),
|
||||
self.handle_quit_confirmation,
|
||||
window=tm.active_window,
|
||||
window=tm.active_window, title=_('Quit kitty?'),
|
||||
)
|
||||
self.quit_confirmation_window_id = w.id
|
||||
set_application_quit_request(CLOSE_BEING_CONFIRMED)
|
||||
@@ -1889,19 +2018,9 @@ class Boss:
|
||||
prefilled = tab.name or tab.title
|
||||
if title in ('" "', "' '"):
|
||||
prefilled = ''
|
||||
args = [
|
||||
'--name=tab-title', '--message', _('Enter the new title for this tab below.'),
|
||||
'--default', prefilled, 'do_set_tab_title', str(tab.id)]
|
||||
self.run_kitten_with_metadata('ask', args)
|
||||
|
||||
def do_set_tab_title(self, title: str, tab_id: int) -> None:
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tab_id = int(tab_id)
|
||||
for tab in tm.tabs:
|
||||
if tab.id == tab_id:
|
||||
tab.set_title(title)
|
||||
break
|
||||
self.get_line(
|
||||
_('Enter the new title for this tab below. An empty title will cause the default title to be used.'),
|
||||
tab.set_title, window=tab.active_window, initial_value=prefilled)
|
||||
|
||||
def create_special_window_for_show_error(self, title: str, msg: str, overlay_for: Optional[int] = None) -> SpecialWindowInstance:
|
||||
ec = sys.exc_info()
|
||||
@@ -1909,17 +2028,18 @@ class Boss:
|
||||
if ec != (None, None, None):
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
cmd = [kitten_exe(), '__show_error__', kitten_exe(), '__show_error__', '--title', title]
|
||||
cmd = [kitten_exe(), '__show_error__', '--title', title]
|
||||
env = {}
|
||||
env['KITTEN_RUNNING_AS_UI'] = '1'
|
||||
env['KITTY_CONFIG_DIRECTORY'] = config_dir
|
||||
return SpecialWindow(
|
||||
cmd,
|
||||
cmd, override_title=title,
|
||||
stdin=json.dumps({'msg': msg, 'tb': tb}).encode(),
|
||||
env=env,
|
||||
overlay_for=overlay_for,
|
||||
)
|
||||
|
||||
@ac('misc', 'Show an error message with the specified title and text')
|
||||
def show_error(self, title: str, msg: str) -> None:
|
||||
tab = self.active_tab
|
||||
w = self.active_window
|
||||
@@ -2223,7 +2343,9 @@ class Boss:
|
||||
cwd: Optional[str] = None,
|
||||
env: Optional[Dict[str, str]] = None,
|
||||
stdin: Optional[bytes] = None,
|
||||
cwd_from: Optional[CwdRequest] = None
|
||||
cwd_from: Optional[CwdRequest] = None,
|
||||
allow_remote_control: bool = False,
|
||||
remote_control_passwords: Optional[Dict[str, Sequence[str]]] = None,
|
||||
) -> None:
|
||||
import subprocess
|
||||
env = env or None
|
||||
@@ -2235,28 +2357,56 @@ class Boss:
|
||||
with suppress(Exception):
|
||||
cwd = cwd_from.cwd_of_child
|
||||
|
||||
def add_env(key: str, val: str) -> None:
|
||||
nonlocal env
|
||||
if env is None:
|
||||
env = default_env().copy()
|
||||
env[key] = val
|
||||
|
||||
def doit(activation_token: str = '') -> None:
|
||||
nonlocal env
|
||||
if activation_token:
|
||||
if env is None:
|
||||
env = default_env().copy()
|
||||
env['XDG_ACTIVATION_TOKEN'] = activation_token
|
||||
if stdin:
|
||||
r, w = safe_pipe(False)
|
||||
pass_fds: Tuple[int, ...] = ()
|
||||
if allow_remote_control:
|
||||
import socket
|
||||
local, remote = socket.socketpair()
|
||||
os.set_inheritable(remote.fileno(), True)
|
||||
lfd = os.dup(local.fileno())
|
||||
local.close()
|
||||
try:
|
||||
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals)
|
||||
peer_id = self.child_monitor.inject_peer(lfd)
|
||||
except Exception:
|
||||
os.close(w)
|
||||
os.close(lfd)
|
||||
remote.close()
|
||||
raise
|
||||
pass_fds = (remote.fileno(),)
|
||||
add_env('KITTY_LISTEN_ON', f'fd:{remote.fileno()}')
|
||||
self.peer_data_map[peer_id] = remote_control_passwords
|
||||
if activation_token:
|
||||
add_env('XDG_ACTIVATION_TOKEN', activation_token)
|
||||
try:
|
||||
if stdin:
|
||||
r, w = safe_pipe(False)
|
||||
try:
|
||||
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals, pass_fds=pass_fds, close_fds=True)
|
||||
except Exception:
|
||||
os.close(w)
|
||||
else:
|
||||
thread_write(w, stdin)
|
||||
finally:
|
||||
os.close(r)
|
||||
else:
|
||||
thread_write(w, stdin)
|
||||
finally:
|
||||
os.close(r)
|
||||
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals, pass_fds=pass_fds, close_fds=True)
|
||||
finally:
|
||||
if allow_remote_control:
|
||||
remote.close()
|
||||
|
||||
try:
|
||||
if is_wayland():
|
||||
run_with_activation_token(doit)
|
||||
else:
|
||||
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals)
|
||||
if is_wayland():
|
||||
run_with_activation_token(doit)
|
||||
else:
|
||||
doit()
|
||||
doit()
|
||||
except Exception as err:
|
||||
self.show_error(_('Failed to run background process'), _('Failed to run background process with error: {}').format(err))
|
||||
|
||||
def pipe(self, source: str, dest: str, exe: str, *args: str) -> Optional[Window]:
|
||||
cmd = [exe] + list(args)
|
||||
@@ -2498,7 +2648,7 @@ class Boss:
|
||||
from .cli import default_config_paths
|
||||
from .config import load_config
|
||||
old_opts = get_options()
|
||||
prev_paths = old_opts.config_paths or default_config_paths(self.args.config)
|
||||
prev_paths = old_opts.all_config_paths or default_config_paths(self.args.config)
|
||||
paths = paths or prev_paths
|
||||
bad_lines: List[BadLine] = []
|
||||
opts = load_config(*paths, overrides=old_opts.config_overrides if apply_overrides else None, accumulate_bad_lines=bad_lines)
|
||||
@@ -2550,7 +2700,7 @@ class Boss:
|
||||
assert isinstance(b, int)
|
||||
dbus_notification_created(a, b)
|
||||
|
||||
def show_bad_config_lines(self, bad_lines: Iterable[BadLine]) -> None:
|
||||
def show_bad_config_lines(self, bad_lines: Iterable[BadLine], misc_errors: Iterable[str] = ()) -> None:
|
||||
|
||||
def format_bad_line(bad_line: BadLine) -> str:
|
||||
return f'{bad_line.number}:{bad_line.exception} in line: {bad_line.line}\n'
|
||||
@@ -2564,7 +2714,10 @@ class Boss:
|
||||
if file:
|
||||
a(f'In file {file}:')
|
||||
[a(format_bad_line(x)) for x in groups[file]]
|
||||
|
||||
if misc_errors:
|
||||
a('In final effective configuration:')
|
||||
for line in misc_errors:
|
||||
a(line)
|
||||
msg = '\n'.join(ans).rstrip()
|
||||
self.show_error(_('Errors parsing configuration'), msg)
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ typedef struct {
|
||||
char *data;
|
||||
size_t sz;
|
||||
id_type peer_id;
|
||||
bool is_remote_control_peer;
|
||||
} Message;
|
||||
|
||||
typedef struct {
|
||||
@@ -232,8 +233,53 @@ static void* io_loop(void *data);
|
||||
static void* talk_loop(void *data);
|
||||
static void send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz);
|
||||
static void wakeup_talk_loop(bool);
|
||||
static bool add_peer_to_injection_queue(int peer_fd, int pipe_fd);
|
||||
static bool talk_thread_started = false;
|
||||
|
||||
static bool
|
||||
simple_read_from_pipe(int fd, void *data, size_t sz) {
|
||||
// read a small amount of data to a pipe handling only EINTR
|
||||
while (true) {
|
||||
ssize_t ret = read(fd, data, sz);
|
||||
if (ret == -1 && errno == EINTR) continue;
|
||||
return ret == (ssize_t)sz;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
inject_peer(PyObject *s, PyObject *a) {
|
||||
#define inject_peer_doc "inject_peer(fd) -> Start communication with a peer over the specified file descriptor"
|
||||
ChildMonitor *self = (ChildMonitor*)s;
|
||||
if (!PyLong_Check(a)) { PyErr_SetString(PyExc_TypeError, "peer fd must be an int"); return NULL; }
|
||||
long fd = PyLong_AsLong(a);
|
||||
if (fd < 0) { PyErr_Format(PyExc_ValueError, "Invalid peer fd: %ld", fd); return NULL; }
|
||||
if (!talk_thread_started) {
|
||||
int ret;
|
||||
if ((ret = pthread_create(&self->talk_thread, NULL, talk_loop, self)) != 0) {
|
||||
return PyErr_Format(PyExc_OSError, "Failed to start talk thread with error: %s", strerror(ret));
|
||||
}
|
||||
talk_thread_started = true;
|
||||
}
|
||||
int fds[2] = {0};
|
||||
if (!self_pipe(fds, false)) {
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
if (!add_peer_to_injection_queue(fd, fds[1])) {
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
safe_close(fds[0], __FILE__, __LINE__); safe_close(fds[1], __FILE__, __LINE__);
|
||||
PyErr_SetString(PyExc_RuntimeError, "Too many peers waiting to be injected");
|
||||
return NULL;
|
||||
}
|
||||
wakeup_talk_loop(false);
|
||||
id_type peer_id = 0;
|
||||
bool ok = simple_read_from_pipe(fds[0], &peer_id, sizeof(peer_id));
|
||||
safe_close(fds[0], __FILE__, __LINE__);
|
||||
if (!ok) { PyErr_SetString(PyExc_RuntimeError, "Failed to read peer id from self pipe"); return NULL; }
|
||||
return PyLong_FromUnsignedLongLong(peer_id);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
start(PyObject *s, PyObject *a UNUSED) {
|
||||
#define start_doc "start() -> Start the I/O thread"
|
||||
@@ -467,7 +513,7 @@ parse_input(ChildMonitor *self) {
|
||||
Message *msg = msgs + i;
|
||||
PyObject *resp = NULL;
|
||||
if (msg->data) {
|
||||
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#K", msg->data, (int)msg->sz, msg->peer_id);
|
||||
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#KO", msg->data, (int)msg->sz, msg->peer_id, msg->is_remote_control_peer ? Py_True : Py_False);
|
||||
free(msg->data);
|
||||
if (!resp) PyErr_Print();
|
||||
}
|
||||
@@ -1043,6 +1089,10 @@ close_os_window(ChildMonitor *self, OSWindow *os_window) {
|
||||
if (os_window->before_fullscreen.is_set && is_os_window_fullscreen(os_window)) {
|
||||
w = os_window->before_fullscreen.w; h = os_window->before_fullscreen.h;
|
||||
}
|
||||
// On GNOME Wayland w, h are the content area size, we need to add the frame size back
|
||||
int content_area_width, content_area_height;
|
||||
adjust_window_size_for_csd(os_window, w, h, &content_area_width, &content_area_height);
|
||||
w += w - content_area_width; h += h - content_area_height;
|
||||
destroy_os_window(os_window);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, w, h);
|
||||
for (size_t t=0; t < os_window->num_tabs; t++) {
|
||||
@@ -1148,6 +1198,7 @@ process_cocoa_pending_actions(void) {
|
||||
if (cocoa_pending_actions_data.wd) {
|
||||
if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); }
|
||||
if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); }
|
||||
if (cocoa_pending_actions[USER_MENU_ACTION]) { call_boss(user_menu_action, "s", cocoa_pending_actions_data.wd); }
|
||||
free(cocoa_pending_actions_data.wd);
|
||||
cocoa_pending_actions_data.wd = NULL;
|
||||
}
|
||||
@@ -1562,6 +1613,7 @@ typedef struct {
|
||||
size_t capacity, used;
|
||||
bool failed;
|
||||
} write;
|
||||
bool is_remote_control_peer;
|
||||
} Peer;
|
||||
static id_type peer_id_counter = 0;
|
||||
|
||||
@@ -1576,24 +1628,33 @@ typedef struct pollfd PollFD;
|
||||
#define PEER_LIMIT 256
|
||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
|
||||
|
||||
static bool
|
||||
accept_peer(int listen_fd, bool shutting_down) {
|
||||
int peer = accept(listen_fd, NULL, NULL);
|
||||
if (UNLIKELY(peer == -1)) {
|
||||
if (errno == EINTR) return true;
|
||||
if (!shutting_down) perror("accept() on talk socket failed!");
|
||||
return false;
|
||||
}
|
||||
static id_type
|
||||
add_peer(int peer, bool is_remote_control_peer) {
|
||||
id_type ans = 0;
|
||||
if (talk_data.num_peers < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, peers, Peer, talk_data.num_peers + 8, peers_capacity, 8, false);
|
||||
Peer *p = talk_data.peers + talk_data.num_peers++;
|
||||
memset(p, 0, sizeof(Peer));
|
||||
p->fd = peer; p->id = ++peer_id_counter;
|
||||
if (!p->id) p->id = ++peer_id_counter;
|
||||
ans = p->id;
|
||||
p->is_remote_control_peer = is_remote_control_peer;
|
||||
} else {
|
||||
log_error("Too many peers want to talk, ignoring one.");
|
||||
nuke_socket(peer);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static bool
|
||||
accept_peer(int listen_fd, bool shutting_down, bool is_remote_control_peer) {
|
||||
int peer = accept(listen_fd, NULL, NULL);
|
||||
if (UNLIKELY(peer == -1)) {
|
||||
if (errno == EINTR) return true;
|
||||
if (!shutting_down) perror("accept() on talk socket failed!");
|
||||
return false;
|
||||
}
|
||||
add_peer(peer, is_remote_control_peer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1620,11 +1681,23 @@ queue_peer_message(ChildMonitor *self, Peer *peer) {
|
||||
}
|
||||
}
|
||||
m->peer_id = peer->id;
|
||||
m->is_remote_control_peer = peer->is_remote_control_peer;
|
||||
peer->num_of_unresponded_messages_sent_to_main_thread++;
|
||||
talk_mutex(unlock);
|
||||
wakeup_main_loop();
|
||||
}
|
||||
|
||||
static void
|
||||
notify_on_peer_removal(ChildMonitor *self, const Peer *p) {
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
|
||||
Message *m = self->messages + self->messages_count++;
|
||||
memset(m, 0, sizeof(Message));
|
||||
m->data = strdup("peer_death");
|
||||
if (m->data) m->sz = strlen("peer_death");
|
||||
m->peer_id = p->id;
|
||||
m->is_remote_control_peer = p->id;
|
||||
}
|
||||
|
||||
static bool
|
||||
has_complete_peer_command(Peer *peer) {
|
||||
peer->read.command_end = 0;
|
||||
@@ -1701,17 +1774,51 @@ wakeup_talk_loop(bool in_signal_handler) {
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
prune_peers(void) {
|
||||
static bool
|
||||
prune_peers(ChildMonitor *self) {
|
||||
bool pruned = false;
|
||||
for (size_t idx = talk_data.num_peers; idx-- > 0;) {
|
||||
Peer *p = talk_data.peers + idx;
|
||||
if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) {
|
||||
notify_on_peer_removal(self, p);
|
||||
free_peer(p);
|
||||
remove_i_from_array(talk_data.peers, idx, talk_data.num_peers);
|
||||
pruned = true;
|
||||
}
|
||||
}
|
||||
return pruned;
|
||||
}
|
||||
|
||||
static struct {
|
||||
size_t num;
|
||||
struct { int peer_fd, pipe_fd; } fds[16];
|
||||
} peers_to_inject = {0};
|
||||
|
||||
static bool
|
||||
add_peer_to_injection_queue(int peer_fd, int pipe_fd) {
|
||||
bool added = false;
|
||||
talk_mutex(lock);
|
||||
if (peers_to_inject.num < arraysz(peers_to_inject.fds)) {
|
||||
peers_to_inject.fds[peers_to_inject.num].peer_fd = peer_fd;
|
||||
peers_to_inject.fds[peers_to_inject.num].pipe_fd = pipe_fd;
|
||||
peers_to_inject.num++;
|
||||
added = true;
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
return added;
|
||||
}
|
||||
|
||||
static void
|
||||
simple_write_to_pipe(int fd, void *data, size_t sz) {
|
||||
// write a small amount of data to a pipe handling only EINTR
|
||||
while (true) {
|
||||
ssize_t ret = write(fd, data, sz);
|
||||
if (ret == -1 && errno == EINTR) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void*
|
||||
talk_loop(void *data) {
|
||||
// The talk thread loop
|
||||
@@ -1730,9 +1837,18 @@ talk_loop(void *data) {
|
||||
|
||||
while (LIKELY(!self->shutting_down)) {
|
||||
num_peer_fds = 0;
|
||||
bool need_to_wakup_main_loop = false;
|
||||
talk_mutex(lock);
|
||||
if (peers_to_inject.num) {
|
||||
for (size_t i = 0; i < peers_to_inject.num; i++) {
|
||||
id_type added_peer_id = add_peer(peers_to_inject.fds[i].peer_fd, true);
|
||||
simple_write_to_pipe(peers_to_inject.fds[i].pipe_fd, &added_peer_id, sizeof(id_type));
|
||||
safe_close(peers_to_inject.fds[i].pipe_fd, __FILE__, __LINE__);
|
||||
}
|
||||
peers_to_inject.num = 0;
|
||||
}
|
||||
if (talk_data.num_peers > 0) {
|
||||
talk_mutex(lock);
|
||||
prune_peers();
|
||||
if (prune_peers(self)) need_to_wakup_main_loop = true;
|
||||
for (size_t i = 0; i < talk_data.num_peers; i++) {
|
||||
Peer *p = talk_data.peers + i;
|
||||
if (!p->read.finished || p->write.used) {
|
||||
@@ -1745,14 +1861,15 @@ talk_loop(void *data) {
|
||||
fds[p->fd_array_idx].events = flags;
|
||||
} else p->fd_array_idx = 0;
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
}
|
||||
talk_mutex(unlock);
|
||||
if (need_to_wakup_main_loop) wakeup_main_loop();
|
||||
for (size_t i = 0; i < num_listen_fds; i++) fds[i].revents = 0;
|
||||
int ret = poll(fds, num_listen_fds + num_peer_fds, -1);
|
||||
if (ret > 0) {
|
||||
for (size_t i = 0; i < num_listen_fds - 1; i++) {
|
||||
if (fds[i].revents & POLLIN) {
|
||||
if (!accept_peer(fds[i].fd, self->shutting_down)) goto end;
|
||||
if (!accept_peer(fds[i].fd, self->shutting_down, fds[i].fd == self->listen_fd)) goto end;
|
||||
}
|
||||
}
|
||||
if (fds[num_listen_fds - 1].revents & POLLIN) {
|
||||
@@ -1813,6 +1930,7 @@ send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz) {
|
||||
// Boilerplate {{{
|
||||
static PyMethodDef methods[] = {
|
||||
METHOD(add_child, METH_VARARGS)
|
||||
METHOD(inject_peer, METH_O)
|
||||
METHOD(needs_write, METH_VARARGS)
|
||||
METHOD(start, METH_NOARGS)
|
||||
METHOD(wakeup, METH_NOARGS)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
@@ -11,7 +11,7 @@ import kitty.fast_data_types as fast_data_types
|
||||
|
||||
from .constants import handled_signals, is_freebsd, is_macos, kitten_exe, kitty_base_dir, shell_path, terminfo_dir
|
||||
from .types import run_once
|
||||
from .utils import log_error, which
|
||||
from .utils import cmdline_for_hold, log_error, which
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
@@ -200,6 +200,7 @@ class Child:
|
||||
cwd_from: Optional['CwdRequest'] = None,
|
||||
is_clone_launch: str = '',
|
||||
add_listen_on_env_var: bool = True,
|
||||
hold: bool = False,
|
||||
):
|
||||
self.is_clone_launch = is_clone_launch
|
||||
self.add_listen_on_env_var = add_listen_on_env_var
|
||||
@@ -214,10 +215,12 @@ class Child:
|
||||
self.cwd = os.path.abspath(cwd)
|
||||
self.stdin = stdin
|
||||
self.env = env or {}
|
||||
self.final_env:Dict[str, str] = {}
|
||||
self.is_default_shell = bool(self.argv and self.argv[0] == shell_path)
|
||||
self.should_run_via_run_shell_kitten = is_macos and self.is_default_shell
|
||||
self.hold = hold
|
||||
|
||||
def final_env(self) -> Dict[str, str]:
|
||||
def get_final_env(self) -> Dict[str, str]:
|
||||
from kitty.options.utils import DELETE_ENV_VAR
|
||||
env = default_env().copy()
|
||||
opts = fast_data_types.get_options()
|
||||
@@ -273,9 +276,9 @@ class Child:
|
||||
os.set_inheritable(stdin_read_fd, True)
|
||||
else:
|
||||
stdin_read_fd = stdin_write_fd = -1
|
||||
final_env = self.final_env()
|
||||
env = tuple(f'{k}={v}' for k, v in final_env.items())
|
||||
self.final_env = self.get_final_env()
|
||||
argv = list(self.argv)
|
||||
cwd = self.cwd
|
||||
if self.should_run_via_run_shell_kitten:
|
||||
# bash will only source ~/.bash_profile if it detects it is a login
|
||||
# shell (see the invocation section of the bash man page), which it
|
||||
@@ -299,14 +302,23 @@ class Child:
|
||||
if is_macos:
|
||||
# In addition for getlogin() to work we need to run the shell
|
||||
# via the /usr/bin/login wrapper, sigh.
|
||||
# And login on macOS looks for .hushlogin in CWD instead of
|
||||
# HOME, bloody idiotic so we cant cwd when running it.
|
||||
# https://github.com/kovidgoyal/kitty/issues/6511
|
||||
import pwd
|
||||
user = pwd.getpwuid(os.geteuid()).pw_name
|
||||
if cwd:
|
||||
argv.append('--cwd=' + cwd)
|
||||
cwd = os.path.expanduser('~')
|
||||
argv = ['/usr/bin/login', '-f', '-l', '-p', user] + argv
|
||||
self.final_exe = which(argv[0]) or argv[0]
|
||||
self.final_exe = final_exe = which(argv[0]) or argv[0]
|
||||
self.final_argv0 = argv[0]
|
||||
if self.hold:
|
||||
argv = cmdline_for_hold(argv)
|
||||
final_exe = argv[0]
|
||||
env = tuple(f'{k}={v}' for k, v in self.final_env.items())
|
||||
pid = fast_data_types.spawn(
|
||||
self.final_exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
|
||||
final_exe, cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
|
||||
ready_read_fd, ready_write_fd, tuple(handled_signals), kitten_exe(), opts.forward_stdio)
|
||||
os.close(slave)
|
||||
self.pid = pid
|
||||
@@ -379,9 +391,9 @@ class Child:
|
||||
def environ(self) -> Dict[str, str]:
|
||||
try:
|
||||
assert self.pid is not None
|
||||
return environ_of_process(self.pid)
|
||||
return environ_of_process(self.pid) or self.final_env.copy()
|
||||
except Exception:
|
||||
return {}
|
||||
return self.final_env.copy()
|
||||
|
||||
@property
|
||||
def current_cwd(self) -> Optional[str]:
|
||||
|
||||
17
kitty/cli.py
17
kitty/cli.py
@@ -1,9 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
@@ -17,6 +16,7 @@ from .fast_data_types import wcswidth
|
||||
from .options.types import Options as KittyOpts
|
||||
from .types import run_once
|
||||
from .typing import BadLineType, TypedDict
|
||||
from .utils import shlex_split
|
||||
|
||||
|
||||
class CompletionType(Enum):
|
||||
@@ -45,7 +45,7 @@ class CompletionSpec:
|
||||
@staticmethod
|
||||
def from_string(raw: str) -> 'CompletionSpec':
|
||||
self = CompletionSpec()
|
||||
for x in shlex.split(raw):
|
||||
for x in shlex_split(raw):
|
||||
ck, vv = x.split(':', 1)
|
||||
if ck == 'type':
|
||||
self.type = getattr(CompletionType, vv)
|
||||
@@ -889,7 +889,8 @@ Change to the specified directory when launching.
|
||||
--detach
|
||||
type=bool-set
|
||||
condition=not is_macos
|
||||
Detach from the controlling terminal, if any. Not available on macOS.
|
||||
Detach from the controlling terminal, if any. Not available on macOS. On macOS
|
||||
use :code:`open -a kitty.app -n` instead.
|
||||
|
||||
|
||||
--session
|
||||
@@ -943,10 +944,10 @@ Listen on the specified socket address for control messages. For example,
|
||||
UNIX sockets, not associated with a file, like this: :option:`{appname}
|
||||
--listen-on`=unix:@mykitty. Environment variables are expanded and relative
|
||||
paths are resolved with respect to the temporary directory. To control kitty,
|
||||
you can send commands to it with :italic:`{appname} @` using the
|
||||
:option:`{appname} @ --to` option to specify this address. Note that if you run
|
||||
:italic:`{appname} @` within a kitty window, there is no need to specify the
|
||||
:option:`{appname} @ --to` option as it will automatically read from the
|
||||
you can send commands to it with :italic:`kitten @` using the
|
||||
:option:`kitten @ --to` option to specify this address. Note that if you run
|
||||
:italic:`kitten @` within a kitty window, there is no need to specify the
|
||||
:option:`kitten @ --to` option as it will automatically read from the
|
||||
environment. Note that this will be ignored unless :opt:`allow_remote_control`
|
||||
is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. This can
|
||||
also be specified in :file:`kitty.conf`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
# Replay the log from --dump-commands. To use first run
|
||||
|
||||
@@ -212,6 +212,16 @@ find_app_name(void) {
|
||||
@end
|
||||
// }}}
|
||||
|
||||
@interface UserMenuItem : NSMenuItem
|
||||
@property (nonatomic) size_t action_index;
|
||||
@end
|
||||
|
||||
@implementation UserMenuItem {
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
|
||||
@interface GlobalMenuTarget : NSObject
|
||||
+ (GlobalMenuTarget *) shared_instance;
|
||||
@end
|
||||
@@ -220,6 +230,13 @@ find_app_name(void) {
|
||||
|
||||
@implementation GlobalMenuTarget
|
||||
|
||||
- (void)user_menu_action:(id)sender {
|
||||
UserMenuItem *m = sender;
|
||||
if (m.action_index < OPT(global_menu).count && OPT(global_menu.entries)) {
|
||||
set_cocoa_pending_action(USER_MENU_ACTION, OPT(global_menu).entries[m.action_index].definition);
|
||||
}
|
||||
}
|
||||
|
||||
PENDING(edit_config_file, PREFERENCES_WINDOW)
|
||||
PENDING(new_os_window, NEW_OS_WINDOW)
|
||||
PENDING(detach_tab, DETACH_TAB)
|
||||
@@ -559,6 +576,35 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
|
||||
// global menu {{{
|
||||
|
||||
static void
|
||||
add_user_global_menu_entry(struct MenuItem *e, NSMenu *bar, size_t action_index) {
|
||||
NSMenu *parent = bar;
|
||||
UserMenuItem *final_item = nil;
|
||||
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||
for (size_t i = 0; i < e->location_count; i++) {
|
||||
NSMenuItem *item = [parent itemWithTitle:@(e->location[i])];
|
||||
if (!item) {
|
||||
final_item = [[UserMenuItem alloc] initWithTitle:@(e->location[i]) action:@selector(user_menu_action:) keyEquivalent:@""];
|
||||
final_item.target = global_menu_target;
|
||||
[parent addItem:final_item];
|
||||
item = final_item;
|
||||
[final_item release];
|
||||
}
|
||||
if (i + 1 < e->location_count) {
|
||||
if (![item hasSubmenu]) {
|
||||
NSMenu* sub_menu = [[NSMenu alloc] initWithTitle:item.title];
|
||||
[item setSubmenu:sub_menu];
|
||||
[sub_menu release];
|
||||
}
|
||||
parent = [item submenu];
|
||||
if (!parent) return;
|
||||
}
|
||||
}
|
||||
if (final_item != nil) {
|
||||
final_item.action_index = action_index;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_create_global_menu(void) {
|
||||
NSString* app_name = find_app_name();
|
||||
@@ -665,8 +711,17 @@ cocoa_create_global_menu(void) {
|
||||
[NSApp setHelpMenu:helpMenu];
|
||||
[helpMenu release];
|
||||
|
||||
if (OPT(global_menu.entries)) {
|
||||
for (size_t i = 0; i < OPT(global_menu.count); i++) {
|
||||
struct MenuItem *e = OPT(global_menu.entries) + i;
|
||||
if (e->definition && e->location && e->location_count > 1) {
|
||||
add_user_global_menu_entry(e, bar, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
[bar release];
|
||||
|
||||
|
||||
class_addMethod(
|
||||
object_getClass([NSApp delegate]),
|
||||
@selector(applicationDockMenu:),
|
||||
@@ -675,6 +730,7 @@ cocoa_create_global_menu(void) {
|
||||
|
||||
|
||||
[NSApp setServicesProvider:[[[ServiceProvider alloc] init] autorelease]];
|
||||
|
||||
#undef MENU_ITEM
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
||||
is_mutiple_vars = {}
|
||||
option_names = set()
|
||||
color_table = list(map(str, range(256)))
|
||||
choice_dedup: Dict[str, str] = {}
|
||||
choice_parser_dedup: Dict[str, str] = {}
|
||||
|
||||
def parser_function_declaration(option_name: str) -> None:
|
||||
t('')
|
||||
@@ -100,6 +102,10 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
||||
if option.choices:
|
||||
typ = 'typing.Literal[{}]'.format(', '.join(repr(x) for x in option.choices))
|
||||
ename = f'choices_for_{option.name}'
|
||||
if typ in choice_dedup:
|
||||
typ = choice_dedup[typ]
|
||||
else:
|
||||
choice_dedup[typ] = ename
|
||||
choices[ename] = typ
|
||||
typ = ename
|
||||
func = str
|
||||
@@ -134,12 +140,18 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
||||
imports.add(('kitty.constants', 'is_macos'))
|
||||
a(f' {option.name}: {typ} = {defval}')
|
||||
if option.choices:
|
||||
ecname = f'choices_for_{option.name}'
|
||||
crepr = f'frozenset({option.choices!r})'
|
||||
if crepr in choice_parser_dedup:
|
||||
crepr = choice_parser_dedup[crepr]
|
||||
else:
|
||||
choice_parser_dedup[crepr] = ecname
|
||||
t(' val = val.lower()')
|
||||
t(f' if val not in self.choices_for_{option.name}:')
|
||||
t(f' raise ValueError(f"The value {{val}} is not a valid choice for {option.name}")')
|
||||
t(f' ans["{option.name}"] = val')
|
||||
t('')
|
||||
t(f' choices_for_{option.name} = frozenset({option.choices!r})')
|
||||
t(f' {ecname} = {crepr}')
|
||||
|
||||
for option_name, (typ, mval) in is_mutiple_vars.items():
|
||||
a(f' {option_name}: {typ} = ' '{}')
|
||||
@@ -187,6 +199,7 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
||||
a(' ))')
|
||||
|
||||
a(' config_paths: typing.Tuple[str, ...] = ()')
|
||||
a(' all_config_paths: typing.Tuple[str, ...] = ()')
|
||||
a(' config_overrides: typing.Tuple[str, ...] = ()')
|
||||
a('')
|
||||
a(' def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:')
|
||||
@@ -368,12 +381,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
||||
output_imports(imports)
|
||||
a('')
|
||||
if choices:
|
||||
a('if typing.TYPE_CHECKING:')
|
||||
for name, cdefn in choices.items():
|
||||
a(f' {name} = {cdefn}')
|
||||
a('else:')
|
||||
for name in choices:
|
||||
a(f' {name} = str')
|
||||
a(f'{name} = {cdefn}')
|
||||
|
||||
a('')
|
||||
a('option_names = ( # {{''{')
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from typing import (
|
||||
@@ -29,7 +28,7 @@ from ..fast_data_types import Color
|
||||
from ..rgb import to_color as as_color
|
||||
from ..types import ConvertibleToNumbers, ParsedShortcut, run_once
|
||||
from ..typing import Protocol
|
||||
from ..utils import expandvars, log_error
|
||||
from ..utils import expandvars, log_error, shlex_split
|
||||
|
||||
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
||||
ItemParser = Callable[[str, str, Dict[str, Any]], bool]
|
||||
@@ -94,7 +93,6 @@ class ToCmdline:
|
||||
return self
|
||||
|
||||
def __call__(self, x: str, expand: bool = True) -> List[str]:
|
||||
ans = shlex.split(x)
|
||||
if expand:
|
||||
ans = list(
|
||||
map(
|
||||
@@ -103,9 +101,11 @@ class ToCmdline:
|
||||
os.environ if self.override_env is None else self.override_env,
|
||||
fallback_to_os_env=False
|
||||
),
|
||||
ans
|
||||
shlex_split(x)
|
||||
)
|
||||
)
|
||||
else:
|
||||
ans = list(shlex_split(x))
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
@@ -11,7 +11,7 @@ from .conf.utils import BadLine, parse_config_base
|
||||
from .conf.utils import load_config as _load_config
|
||||
from .constants import cache_dir, defconf
|
||||
from .options.types import Options, defaults, option_names
|
||||
from .options.utils import KeyDefinition, KeyMap, MouseMap, MouseMapping, SequenceMap, build_action_aliases
|
||||
from .options.utils import KeyboardMode, KeyboardModeMap, KeyDefinition, MouseMap, MouseMapping, build_action_aliases
|
||||
from .typing import TypedDict
|
||||
from .utils import log_error
|
||||
|
||||
@@ -100,28 +100,26 @@ def finalize_keys(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] =
|
||||
else:
|
||||
accumulate_bad_lines.append(BadLine(d.definition_location.number, d.definition_location.line, err, d.definition_location.file))
|
||||
|
||||
keymap: KeyMap = {}
|
||||
sequence_map: SequenceMap = {}
|
||||
modes: KeyboardModeMap = {'': KeyboardMode()}
|
||||
|
||||
for defn in defns:
|
||||
is_no_op = defn.is_no_op
|
||||
if defn.is_sequence:
|
||||
keymap.pop(defn.trigger, None)
|
||||
s = sequence_map.setdefault(defn.trigger, {})
|
||||
if is_no_op:
|
||||
s.pop(defn.rest, None)
|
||||
if not s:
|
||||
del sequence_map[defn.trigger]
|
||||
if defn.options.new_mode:
|
||||
modes[defn.options.new_mode] = nm = KeyboardMode(defn.options.new_mode)
|
||||
nm.on_unknown = defn.options.on_unknown
|
||||
nm.on_action = defn.options.on_action
|
||||
defn.definition = f'push_keyboard_mode {defn.options.new_mode}'
|
||||
try:
|
||||
m = modes[defn.options.mode]
|
||||
except KeyError:
|
||||
kerr = f'The keyboard mode {defn.options.mode} is unknown, ignoring the mapping'
|
||||
if accumulate_bad_lines is None:
|
||||
log_error(kerr)
|
||||
else:
|
||||
s[defn.rest] = defn.definition
|
||||
else:
|
||||
sequence_map.pop(defn.trigger, None)
|
||||
if is_no_op:
|
||||
keymap.pop(defn.trigger, None)
|
||||
else:
|
||||
keymap[defn.trigger] = defn.definition
|
||||
opts.keymap = keymap
|
||||
opts.sequence_map = sequence_map
|
||||
dl = defn.definition_location
|
||||
accumulate_bad_lines.append(BadLine(dl.number, dl.line, KeyError(kerr), dl.file))
|
||||
continue
|
||||
m.keymap[defn.trigger].append(defn)
|
||||
opts.keyboard_modes = modes
|
||||
|
||||
|
||||
def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] = None) -> None:
|
||||
@@ -140,11 +138,10 @@ def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[List[B
|
||||
mousemap: MouseMap = {}
|
||||
|
||||
for defn in defns:
|
||||
is_no_op = defn.is_no_op
|
||||
if is_no_op:
|
||||
mousemap.pop(defn.trigger, None)
|
||||
else:
|
||||
if defn.definition:
|
||||
mousemap[defn.trigger] = defn.definition
|
||||
else:
|
||||
mousemap.pop(defn.trigger, None)
|
||||
opts.mousemap = mousemap
|
||||
|
||||
|
||||
@@ -164,7 +161,8 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula
|
||||
from .options.parse import merge_result_dicts
|
||||
|
||||
overrides = tuple(overrides) if overrides is not None else ()
|
||||
opts_dict, paths = _load_config(defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines), merge_result_dicts, *paths, overrides=overrides)
|
||||
opts_dict, found_paths = _load_config(
|
||||
defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines), merge_result_dicts, *paths, overrides=overrides)
|
||||
opts = Options(opts_dict)
|
||||
|
||||
opts.alias_map = build_action_aliases(opts.kitten_alias, 'kitten')
|
||||
@@ -179,7 +177,8 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula
|
||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color > 0:
|
||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||
opts.macos_titlebar_color = 0
|
||||
opts.config_paths = paths
|
||||
opts.config_paths = found_paths
|
||||
opts.all_config_paths = paths
|
||||
opts.config_overrides = overrides
|
||||
return opts
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import errno
|
||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 30, 1)
|
||||
version: Version = Version(0, 32, 0)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -240,7 +240,7 @@ find_substitute_face(CFStringRef str, CTFontRef old_font, CPUCell *cpu_cell) {
|
||||
new_font = manually_search_fallback_fonts(old_font, cpu_cell);
|
||||
if (new_font) return new_font;
|
||||
}
|
||||
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
|
||||
PyErr_Format(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font for: %s", [(NSString *)str UTF8String]);
|
||||
return NULL;
|
||||
}
|
||||
return new_font;
|
||||
@@ -270,7 +270,22 @@ create_fallback_face(PyObject *base_face, CPUCell* cell, bool UNUSED bold, bool
|
||||
}
|
||||
else { search_for_fallback(); }
|
||||
if (new_font == NULL) return NULL;
|
||||
return (PyObject*)ct_face(new_font, fg);
|
||||
NSURL *url = (NSURL*)CTFontCopyAttribute(new_font, kCTFontURLAttribute);
|
||||
const char *font_path = [[url path] UTF8String];
|
||||
ssize_t idx = -1;
|
||||
PyObject *q, *ans = NULL;
|
||||
while ((q = iter_fallback_faces(fg, &idx))) {
|
||||
CTFace *qf = (CTFace*)q;
|
||||
const char *qpath;
|
||||
if (qf->path && (qpath = PyUnicode_AsUTF8(qf->path)) && strcmp(qpath, font_path) == 0) {
|
||||
ans = PyLong_FromSsize_t(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
[url release];
|
||||
if (ans == NULL) return (PyObject*)ct_face(new_font, fg);
|
||||
CFRelease(new_font);
|
||||
return ans;
|
||||
}
|
||||
|
||||
unsigned int
|
||||
@@ -319,6 +334,9 @@ harfbuzz_font_for_face(PyObject* s) {
|
||||
if (!self->hb_font) {
|
||||
self->hb_font = hb_coretext_font_create(self->ct_font);
|
||||
if (!self->hb_font) fatal("Failed to create hb_font");
|
||||
// dunno if we need this, harfbuzz docs say it is used by CoreText
|
||||
// for optical sizing which changes the look of glyphs at small and large sizes
|
||||
hb_font_set_ptem(self->hb_font, self->scaled_point_sz);
|
||||
hb_ot_font_set_funcs(self->hb_font);
|
||||
}
|
||||
return self->hb_font;
|
||||
@@ -421,13 +439,12 @@ struct RenderBuffers {
|
||||
CGGlyph *glyphs;
|
||||
CGRect *boxes;
|
||||
CGPoint *positions;
|
||||
CGSize *advances;
|
||||
};
|
||||
static struct RenderBuffers buffers = {0};
|
||||
|
||||
static void
|
||||
finalize(void) {
|
||||
free(buffers.render_buf); free(buffers.glyphs); free(buffers.boxes); free(buffers.positions); free(buffers.advances);
|
||||
free(buffers.render_buf); free(buffers.glyphs); free(buffers.boxes); free(buffers.positions);
|
||||
memset(&buffers, 0, sizeof(struct RenderBuffers));
|
||||
if (all_fonts_collection_data) CFRelease(all_fonts_collection_data);
|
||||
if (window_title_font) CFRelease(window_title_font);
|
||||
@@ -471,11 +488,10 @@ ensure_render_space(size_t width, size_t height, size_t num_glyphs) {
|
||||
}
|
||||
if (buffers.sz < num_glyphs) {
|
||||
buffers.sz = MAX(128, num_glyphs * 2);
|
||||
buffers.advances = calloc(sizeof(buffers.advances[0]), buffers.sz);
|
||||
buffers.boxes = calloc(sizeof(buffers.boxes[0]), buffers.sz);
|
||||
buffers.glyphs = calloc(sizeof(buffers.glyphs[0]), buffers.sz);
|
||||
buffers.positions = calloc(sizeof(buffers.positions[0]), buffers.sz);
|
||||
if (!buffers.advances || !buffers.boxes || !buffers.glyphs || !buffers.positions) fatal("Out of memory");
|
||||
if (!buffers.boxes || !buffers.glyphs || !buffers.positions) fatal("Out of memory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +628,7 @@ render_single_ascii_char_as_mask(const char ch, size_t *result_width, size_t *re
|
||||
|
||||
|
||||
static bool
|
||||
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
unsigned int canvas_width = cell_width * num_cells;
|
||||
ensure_render_space(canvas_width, cell_height, num_glyphs);
|
||||
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, buffers.glyphs, buffers.boxes, num_glyphs);
|
||||
@@ -626,17 +642,20 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
|
||||
CGFloat sz = CTFontGetSize(ct_font);
|
||||
sz *= canvas_width / right;
|
||||
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
|
||||
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
|
||||
bool ret = do_render(new_font, CTFontGetUnitsPerEm(new_font), bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
|
||||
CFRelease(new_font);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
CGFloat x = 0, y = 0;
|
||||
CTFontGetAdvancesForGlyphs(ct_font, kCTFontOrientationDefault, buffers.glyphs, buffers.advances, num_glyphs);
|
||||
CGFloat scale = CTFontGetSize(ct_font) / units_per_em;
|
||||
for (unsigned i=0; i < num_glyphs; i++) {
|
||||
buffers.positions[i].x = x; buffers.positions[i].y = y;
|
||||
if (debug_rendering) printf("x=%f origin=%f width=%f advance=%f\n", x, buffers.boxes[i].origin.x, buffers.boxes[i].size.width, buffers.advances[i].width);
|
||||
x += buffers.advances[i].width; y += buffers.advances[i].height;
|
||||
buffers.positions[i].x = x + hb_positions[i].x_offset * scale; buffers.positions[i].y = y + hb_positions[i].y_offset * scale;
|
||||
if (debug_rendering) printf("x=%f y=%f origin=%f width=%f x_advance=%f x_offset=%f y_advance=%f y_offset=%f\n",
|
||||
buffers.positions[i].x, buffers.positions[i].y, buffers.boxes[i].origin.x, buffers.boxes[i].size.width,
|
||||
hb_positions[i].x_advance * scale, hb_positions[i].x_offset * scale,
|
||||
hb_positions[i].y_advance * scale, hb_positions[i].y_offset * scale);
|
||||
x += hb_positions[i].x_advance * scale; y += hb_positions[i].y_advance * scale;
|
||||
}
|
||||
if (*was_colored) {
|
||||
render_color_glyph(ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline);
|
||||
@@ -661,7 +680,7 @@ render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
CTFace *self = (CTFace*)s;
|
||||
ensure_render_space(128, 128, num_glyphs);
|
||||
for (unsigned i=0; i < num_glyphs; i++) buffers.glyphs[i] = info[i].codepoint;
|
||||
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
|
||||
return do_render(self->ct_font, self->units_per_em, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
|
||||
}
|
||||
|
||||
|
||||
@@ -691,8 +710,8 @@ postscript_name_for_face(const PyObject *face_) {
|
||||
static PyObject *
|
||||
repr(CTFace *self) {
|
||||
char buf[1024] = {0};
|
||||
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
|
||||
(self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
|
||||
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
|
||||
(self->ascent), (self->descent), (self->leading), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
|
||||
return PyUnicode_FromFormat(
|
||||
"Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)",
|
||||
self->family_name, self->full_name, self->postscript_name, self->path, self->units_per_em, buf
|
||||
@@ -708,7 +727,6 @@ static PyMethodDef module_methods[] = {
|
||||
static PyMemberDef members[] = {
|
||||
#define MEM(name, type) {#name, type, offsetof(CTFace, name), READONLY, #name}
|
||||
MEM(units_per_em, T_UINT),
|
||||
MEM(point_sz, T_FLOAT),
|
||||
MEM(scaled_point_sz, T_FLOAT),
|
||||
MEM(ascent, T_FLOAT),
|
||||
MEM(descent, T_FLOAT),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user