Compare commits

...

336 Commits

Author SHA1 Message Date
Kovid Goyal
6fa4110da9 version 0.24.2 2022-02-03 11:44:12 +05:30
Kovid Goyal
43ceaf0b7e Merge branch 'shorten-long-line' of https://github.com/Luflosi/kitty 2022-02-01 17:15:09 +05:30
Luflosi
301ca76d14 Shorten a long line
This line is getting really close to the limit of 160 characters.
2022-02-01 12:16:13 +01:00
Kovid Goyal
a591304412 ... 2022-02-01 13:39:42 +05:30
Kovid Goyal
8fffd6070a Fix #4605 2022-02-01 12:01:08 +05:30
Kovid Goyal
a899a8ab6d Sanitize window sizes read from cached values 2022-02-01 11:23:36 +05:30
Kovid Goyal
9c13e8a549 Allow controlling placement of bell and activity symbols in the tab title template
Fixes #4581
2022-02-01 08:58:49 +05:30
Kovid Goyal
68d2ea3f27 Get config generation working with py3.10
The typing classes now have a __name__ attribute
2022-02-01 08:29:34 +05:30
Kovid Goyal
2c0269930f kitty @ get-text: Add a n option to clear the current selection after getting text
See #4600
2022-01-31 08:22:05 +05:30
Kovid Goyal
c4953504ba ... 2022-01-31 08:19:45 +05:30
Kovid Goyal
e7ee6d9e26 A new action to clear the current selection
Fixes #4600
2022-01-31 08:17:40 +05:30
Kovid Goyal
1cb3875cb5 Merge branch 'minor-tweaks' of https://github.com/page-down/kitty 2022-01-30 15:51:02 +05:30
pagedown
14ae2d0862 Make the colons colored like the others 2022-01-30 17:37:17 +08:00
pagedown
539ec69d3f Remove a space that was introduced by mistake 2022-01-30 17:34:58 +08:00
Kovid Goyal
bc873ad838 bash shell integration: To title and cursor escape codes at end of PS1/PS0 so that they override pre-existing ones
This is needed because some distros like Debian patch the default bashrc
to set a user@host:/pwd style title which is extremely unsuitable for
kitty.
2022-01-30 11:13:46 +05:30
Kovid Goyal
f056b7c1da Improve docs for splits layout
Fixes #4592
2022-01-30 09:44:33 +05:30
Kovid Goyal
2e790a119a Merge branch 'refactor-fstring' of https://github.com/page-down/kitty 2022-01-30 09:26:17 +05:30
pagedown
4a3ed62809 Refactor: More f-string for kittens 2022-01-29 20:23:34 +08:00
pagedown
4110074580 Refactor: More f-string for kitty client.py 2022-01-29 20:22:44 +08:00
pagedown
c21f00c476 Refactor: More f-string for kitty fonts and rc send_text 2022-01-29 20:22:22 +08:00
pagedown
1ca1c2edad Refactor: More f-string for conf 2022-01-29 20:18:11 +08:00
pagedown
ba0f61d752 Refactor: More f-string for kitty 2022-01-29 20:17:46 +08:00
pagedown
74e70d2548 Refactor: More f-string for bypy scripts 2022-01-29 20:15:17 +08:00
pagedown
74921c1373 Refactor: Sorting items when generating files
Make the files generated in different environments consistent.
2022-01-29 20:14:56 +08:00
pagedown
a22f37b919 Refactor: More f-string for tools 2022-01-29 20:14:36 +08:00
Kovid Goyal
9f250fdf26 Only dump env var names not values 2022-01-29 11:55:26 +05:30
Kovid Goyal
d92eba82bc Dump env vars seen by kitty for debug config 2022-01-29 11:36:58 +05:30
Kovid Goyal
a052958a20 Merge branch 'macos' of https://github.com/page-down/kitty 2022-01-29 11:26:09 +05:30
pagedown
81e7e6a356 macOS: Disable sudden termination
Since we have the quit confirmation feature, so we explicitly specify
that sudden termination is not supported.
2022-01-29 13:41:59 +08:00
pagedown
953cf26369 macOS: Fix info plist structure version
Set the InfoDictionary version explicitly, hopefully the behavior will
be unchanged when the default version is upgraded.
2022-01-29 13:37:21 +08:00
pagedown
7649e2f3ea Docs: Fix macOS shortcut for debug_config 2022-01-29 13:32:36 +08:00
Kovid Goyal
2a6a77467c Merge branch 'refactor-fstring' of https://github.com/page-down/kitty 2022-01-28 17:26:43 +05:30
pagedown
dc61adf9d8 Refactor: More f-string 2022-01-28 19:34:13 +08:00
Kovid Goyal
e0c4a90aa3 DRYer 2022-01-28 13:21:54 +05:30
Kovid Goyal
5cdff7d6d0 themes kitten: Allow writing to a different file than kitty.conf 2022-01-27 20:24:39 +05:30
Kovid Goyal
4f6c16984a Do not send arrow key presses to shells when clicking on a window with the mouse to focus it
Fixes #4128
2022-01-27 20:07:17 +05:30
Kovid Goyal
7e364b1606 Fix incorrect shortcut in docs 2022-01-27 19:45:35 +05:30
Kovid Goyal
a24df07d25 Merge branch 'fix-on-macOS-10.12' of https://github.com/Luflosi/kitty 2022-01-27 17:35:20 +05:30
Luflosi
347c0d4de4 Fix compilation on macOS 10.12
Without this commit, kitty would fail to compile on macOS 10.12 with the error "use of undeclared identifier".
This problem was introduced in f7be4fab48, where some code was moved to a different file, without moving the corresponding `#define` statements.
2022-01-27 12:21:17 +01:00
Kovid Goyal
ca2e9f571f Merge branch 'refactor' of https://github.com/page-down/kitty 2022-01-27 16:40:17 +05:30
pagedown
9be57de4a9 macOS: List kitty in the open with menu of an arbitrary file
Allow opening files without extension name following the rules in launch
actions configuration.
2022-01-27 18:16:35 +08:00
pagedown
cc84ee9734 Refactor: Replace string concat with f-string 2022-01-27 17:57:25 +08:00
pagedown
b5ad030a14 macOS: Categorize and update bundle info plist
Allow toggling input sources using CapsLock.
Allow programs to request photo, motion, speech recognition permissions.
Add local network unicast or multicast usage description.
Remove iOS-only items.
2022-01-27 17:55:43 +08:00
Kovid Goyal
40b5c43920 ... 2022-01-27 12:14:42 +05:30
Kovid Goyal
9f2be8b696 Fix #4573 2022-01-27 11:29:33 +05:30
Kovid Goyal
4397e60206 Add changelog entry for cursor shape restoration 2022-01-27 11:19:53 +05:30
Kovid Goyal
caab98e70b remove duplicate changelog entry 2022-01-27 11:18:15 +05:30
Kovid Goyal
11a4b75ffa Merge branch 'fix-ime' of https://github.com/page-down/kitty 2022-01-27 10:46:37 +05:30
Kovid Goyal
d25dceacdd Merge branch 'remote-control' of https://github.com/page-down/kitty 2022-01-27 10:38:43 +05:30
pagedown
b9a612fb9c macOS: Clean up any text that may not have been sent when loses focus 2022-01-27 12:37:17 +08:00
pagedown
3b9a7914ee Also add alias --dont-take-focus to new_window 2022-01-27 12:28:40 +08:00
pagedown
f646dfa238 Cancel IME for active kitty or OS window only 2022-01-27 11:48:48 +08:00
Kovid Goyal
0b2a533b44 Merge branch 'fix-ime' of https://github.com/page-down/kitty 2022-01-27 08:03:08 +05:30
Kovid Goyal
0e1246c15b Merge branch 'fix-cache-defrag' of https://github.com/Luflosi/kitty 2022-01-27 07:49:54 +05:30
pagedown
a987fb81a0 Emits an IME lost focus event when the window lost focus or closed 2022-01-27 10:13:57 +08:00
pagedown
4d920b182f macOS: Cancel IME composition when receive loss of focus event 2022-01-27 10:07:18 +08:00
pagedown
22f077b384 Clear overlay text after screen loses focus 2022-01-27 09:53:40 +08:00
Luflosi
4c78a50dcf Add more copy_file_range() and sendfile() errno exceptions
On ZFS `copy_file_range()` and `sendfile()` fail for some reason, so add `EINVAL` to the list of error codes for which the code will fall back to a slower implementation.
While we are at it, add all the error codes from https://go.dev/src/internal/poll/copy_file_range_linux.go for different filesystems and older kernel versions to the list of error codes from `copy_file_range()`.
Without this, the cache defragmentation and the test testing it (`test_disk_cache`) fail with the error message "Failed to copy data to new disk cache file during defrag: Invalid argument" when the cache directory is stored on ZFS.
Perhaps this is caused by me running ZFS 2.1.2 on Linux 5.16.0, which is not a supported combination. This ZFS version is only "compatible with 3.10 - 5.15 kernels" according to the release notes. Or maybe it's just not implemented.
To reproduce the problem, execute `KITTY_CACHE_DIRECTORY=/path/to/zfs python3 test.py` with `KITTY_CACHE_DIRECTORY` pointing to a directory on ZFS.
2022-01-27 00:45:30 +01:00
Luflosi
9d87b08c52 Fix logic error in file copy helper functions
If `errno` is `EAGAIN`, the second if statement is never true and if `errno` is not `EAGAIN`, the function returns immediately, not checking the second if statement.
This commit fixes this.
A logically equivalent change would have been to simply swap the two if statements but I find this version to be more readable.
2022-01-26 23:35:54 +01:00
Kovid Goyal
97e58c5b86 Merge branch 'macos-ime' of https://github.com/page-down/kitty 2022-01-26 16:49:10 +05:30
pagedown
b3f678cf6f Improve debug log 2022-01-26 18:28:37 +08:00
pagedown
7f3715cd3e Use the input context instance from GLFWContentView 2022-01-26 18:25:37 +08:00
pagedown
a3aaaa1efb macOS: Explicitly cancel IME composition when losing the focus 2022-01-26 18:21:20 +08:00
Kovid Goyal
d1531632a2 Add some notes on how to run IBUS for testing 2022-01-26 10:47:18 +05:30
Kovid Goyal
2151969ae5 IBUS: Hide the pre-edit text when receiving a HidePreeditText message 2022-01-26 10:12:48 +05:30
Kovid Goyal
44e3a6f0fb Shell integration: Set cursor shape to whatever is specified in the cursor_shape option when executing a command rather than the block shape 2022-01-25 13:30:48 +05:30
Kovid Goyal
364bad504c CSI 0 q should blink cursor as per xterm docs and behavior 2022-01-25 13:29:44 +05:30
Kovid Goyal
0648b4f568 Merge branch 'ksi-fish' of https://github.com/page-down/kitty 2022-01-25 11:33:06 +05:30
pagedown
a47b3bf578 Fix the blinking block cursor shape for commands in bash integration 2022-01-25 14:00:28 +08:00
pagedown
3aec573d40 Use blinking block for commands consistent with zsh and bash integration 2022-01-25 13:58:11 +08:00
pagedown
5d4b0c0bef Set the pre-exec cursor shape in fish vi mode and config on demand 2022-01-25 13:01:54 +08:00
pagedown
11c0343420 Add comments and minor tweaks to fish integration 2022-01-25 11:52:34 +08:00
Kovid Goyal
287b843d13 add a comment 2022-01-24 22:14:56 +05:30
Kovid Goyal
1fea4777fa zsh shell integration: Restore cursor to blinking block before running external commands 2022-01-24 22:06:52 +05:30
Kovid Goyal
b5a2f94185 The open_actions() test needs options set 2022-01-24 15:21:32 +05:30
Kovid Goyal
1000376a4f ... 2022-01-24 09:50:37 +05:30
Kovid Goyal
02e1272502 Slightly improve debug text 2022-01-23 20:40:41 +05:30
Kovid Goyal
393e5eb158 Merge branch 'fix-macos-ime' of https://github.com/page-down/kitty 2022-01-23 20:38:43 +05:30
pagedown
b2d0c35fc2 Clear the pre-edit text only when inserting non-control characters
Maintains the pre-edit input state after pressing the modifier key
combination or function key while using the IME.
2022-01-23 22:22:24 +08:00
pagedown
3ee12082f2 No need to update the pre-edit text 2022-01-23 22:16:26 +08:00
pagedown
1ce4fc9c8c Check for ASCII control chars before sending text 2022-01-23 21:40:27 +08:00
pagedown
57e2866b68 macOS: Fix caps_lock not clearing pre-edit text when using IME 2022-01-23 18:50:55 +08:00
pagedown
195fc1a270 Minor tweaks to macOS key event debug log 2022-01-23 18:45:18 +08:00
Kovid Goyal
39e56f1864 Dont check the return value of handleEvent
It's apparently meaningless, despite what the docs say
2022-01-23 12:47:29 +05:30
Kovid Goyal
da96dfcad2 Forgot to use override 2022-01-23 11:33:58 +05:30
Kovid Goyal
053bb999d3 Merge branch 'fix-macos-ime-modifiers' of https://github.com/page-down/kitty 2022-01-23 10:41:45 +05:30
Kovid Goyal
e914f0df8e Make FAIL_WARN a boolean 2022-01-23 10:12:11 +05:30
Kovid Goyal
78cd83845f Fix sphinx failing to highlight fish 2022-01-23 09:43:15 +05:30
Kovid Goyal
f6228cc81e Merge branch 'fix-macos-ime' of https://github.com/page-down/kitty 2022-01-23 08:17:16 +05:30
pagedown
83884f42f8 macOS: Fix text insertion by modifier keys when using IME 2022-01-23 01:50:37 +08:00
pagedown
fbaf3e2f49 Update preedit text with markedText
This fixes the preedit text being cleared when using shortcut keys to
switch candidate text in the input state.
2022-01-23 00:01:08 +08:00
Kovid Goyal
c0be0f74d8 Use commit text instead of a zero key event
Thanks @page-down
2022-01-22 21:11:26 +05:30
Kovid Goyal
a9e014226e Merge branch 'docs' of https://github.com/page-down/kitty 2022-01-22 20:58:30 +05:30
pagedown
b498d86436 Docs: Add modifier key names and aliases 2022-01-22 23:08:44 +08:00
Kovid Goyal
ff836a85aa Special case the IME injecting text in on_key_input 2022-01-22 20:00:03 +05:30
Kovid Goyal
688a3eaef7 Check if input context handles flagschanged event 2022-01-22 16:17:01 +05:30
Kovid Goyal
3b15b46d08 Also deal with setMarkedText called from event loop with actual marked text 2022-01-22 16:13:44 +05:30
Kovid Goyal
f21ddae94f Handle setMarkedText called from the event loop
Also pass through text with zero key
2022-01-22 16:08:50 +05:30
Kovid Goyal
cd9b752926 ... 2022-01-22 16:02:05 +05:30
Kovid Goyal
655a5a2808 Handle insertText called from the event loop by the text input system 2022-01-22 15:51:43 +05:30
Kovid Goyal
5f4e519d61 ... 2022-01-22 15:13:53 +05:30
Kovid Goyal
720bf7efd6 Fix mouse position reported incorrectly on click event in a newly created window if the mouse has never moved in that window 2022-01-22 15:10:14 +05:30
Kovid Goyal
3867620be5 ... 2022-01-22 14:58:10 +05:30
Kovid Goyal
03a7361325 Some debug prints for flagsChanged 2022-01-22 14:57:13 +05:30
Kovid Goyal
e8c683db8b A new approach to handling flagsChanged with IME
Bypass cocoa's flagsChanged handling and call handleevent ourselves,
this allows us to know exactly what the IME is doing. Hopefully fixes
issues with IMEs that change state on modifier key presses.

Fixes #4541
2022-01-22 14:47:57 +05:30
Kovid Goyal
0a3acd9738 ask kitten: Use buttons for choices as well 2022-01-22 13:57:25 +05:30
Kovid Goyal
08f159c2ba TypeCheck underline style names 2022-01-22 12:41:26 +05:30
Kovid Goyal
d4e7587686 use underline instead of color for default choice 2022-01-22 12:35:43 +05:30
Kovid Goyal
d219654387 Use a dedicated choose() method matching confirm() 2022-01-22 12:27:20 +05:30
Kovid Goyal
32e31a3c6b Cancelling the choice kitten should return the empty response 2022-01-22 12:13:32 +05:30
Kovid Goyal
9419125387 Highlight the default choice in yellow 2022-01-22 11:45:01 +05:30
Kovid Goyal
1bbab217d0 ask kitten: Always have a default choice 2022-01-22 11:39:54 +05:30
Kovid Goyal
05d648d411 ask kitten: Draw yes/no buttons 2022-01-22 11:32:26 +05:30
Kovid Goyal
5c832d3190 Allow multiple clickable ranges 2022-01-22 10:52:44 +05:30
Kovid Goyal
ce7282a53c DRYer 2022-01-22 10:47:52 +05:30
Kovid Goyal
0d33380e6a Cleanup previous PR
yesno default should be yes.
Pressing q to quit is undiscoverable
Clicking empty cells should not trigger buttons as that is
undiscoverable and possibly surprising
2022-01-22 10:44:01 +05:30
Kovid Goyal
9944b895a6 Merge branch 'kitten-ask' of https://github.com/page-down/kitty 2022-01-22 10:06:52 +05:30
Kovid Goyal
fe7045dd2f Ask kitten: Fix rendering of multiline messages 2022-01-22 09:40:05 +05:30
Kovid Goyal
d17757cc84 Merge branch 'feat-underline' of https://github.com/page-down/kitty 2022-01-22 09:28:13 +05:30
pagedown
7ef73babdd Use fixed values for unchanging dict to replace split and for loops 2022-01-22 11:36:04 +08:00
pagedown
a47aca6fdb Add new dotted and dashed underline enum
Also use "straight" for single underline, consistent with the protocol
extension document.
2022-01-22 11:00:49 +08:00
pagedown
bf9d47fddd ... 2022-01-22 09:51:28 +08:00
pagedown
4fe10fadb0 ... 2022-01-22 09:45:32 +08:00
pagedown
d29faa0986 Allow tab title to be set to empty to use window title 2022-01-22 03:28:42 +08:00
pagedown
88ee5e95fc Add default choices for features that use ask kitten 2022-01-22 03:21:59 +08:00
pagedown
9a119255fe ask kitten: Use the default value as input text
Prefill the current value when changing the tab title.
2022-01-22 03:11:39 +08:00
pagedown
7ce11050cd ask kitten: Esc to abort and Enter to confirm the default choice 2022-01-22 02:50:50 +08:00
pagedown
9b4d1219b8 Add default choice option for ask kitten 2022-01-22 02:46:37 +08:00
pagedown
4dd29c726a Make the clickable range of No the same as Yes
Since the two options are treated equally, the same amount of mouse
clickable range are given.
2022-01-22 02:41:52 +08:00
Kovid Goyal
17e31565e2 Get it working even with 2 row windows assuming they are wide enough to fit the title on a single line 2022-01-21 21:46:27 +05:30
Kovid Goyal
dd72deb546 Allow ask kitten to function in a 3 line window 2022-01-21 21:43:25 +05:30
Kovid Goyal
7e82179c79 ... 2022-01-21 21:20:49 +05:30
Kovid Goyal
d61e655953 Changelog entry for new underline styles 2022-01-21 21:16:23 +05:30
Kovid Goyal
cd0ca95877 Improve the UI of the ask kitten
Center the text in the window and allow pressing enter/esc when doing a
yes/no question

Fixes #4545
2022-01-21 20:02:13 +05:30
Kovid Goyal
d70610f5d9 more uses of extlinks 2022-01-21 08:45:03 +05:30
Kovid Goyal
f9cfb6f989 use sphinx extlinks instead of custon roles 2022-01-21 08:19:57 +05:30
Kovid Goyal
1f4372b34c Test if text is being inserted rather than if text is present 2022-01-20 11:16:05 +05:30
Kovid Goyal
76e7e7d61f ... 2022-01-20 08:45:03 +05:30
Kovid Goyal
c09dc73476 ... 2022-01-20 08:33:44 +05:30
Kovid Goyal
b090602fdb macOS: Fix the shift key not clearing pre-edit text in IME
Fixes #4541
2022-01-20 08:00:17 +05:30
Kovid Goyal
16d3e82515 Add debug output when ignoring key press during IME 2022-01-19 14:50:12 +05:30
Kovid Goyal
9caa6bb306 Nicer event printing 2022-01-19 14:32:03 +05:30
Kovid Goyal
acf227803f Move event formatting into the types 2022-01-19 14:30:08 +05:30
Kovid Goyal
f255c15fa9 DRYer 2022-01-19 14:04:14 +05:30
Kovid Goyal
7ce74ac688 Also change string based on event type 2022-01-19 13:58:45 +05:30
Kovid Goyal
6090673bb5 DRYer 2022-01-19 13:56:02 +05:30
Kovid Goyal
d1b5420b08 Display space as a word when printing keys 2022-01-19 13:33:53 +05:30
Kovid Goyal
e0f2b96fd2 Merge branch 'debug-config' of https://github.com/page-down/kitty 2022-01-19 13:16:26 +05:30
Kovid Goyal
47fdce6483 remove executable perms from boss.py 2022-01-19 13:12:52 +05:30
pagedown
bac84f31b3 Refactor the print functions to make it easier to use in kitten 2022-01-19 15:26:02 +08:00
Kovid Goyal
21bc5bf8c0 Dont leak a reference to input_source_at_last_key_event 2022-01-19 12:06:54 +05:30
Kovid Goyal
0ae928f409 Increase max cache size for distribute_dots
Since it is now also used to render dotted underlines
2022-01-19 12:00:17 +05:30
pagedown
7bf7cc284d Add the missing mouse triggers click (-2) and double-click (-3) 2022-01-19 14:12:39 +08:00
pagedown
ff1efebf70 Fix mouse maps are indented with one more space 2022-01-19 14:07:45 +08:00
pagedown
9334ed773e Adjust the order of modifier key names to match the config and docs 2022-01-19 14:05:51 +08:00
Kovid Goyal
5d8a0c4b09 Merge branch 'ksi-fish' of https://github.com/page-down/kitty 2022-01-19 08:38:15 +05:30
pagedown
c5e8a36f9b Shell integration: Improvements for fish integration
Move the completion function to the autocomplete script.
Fix some issues with cursor shape setting, respect user configuration.
Functions and vars start with two underscores, following the convention.
Other minor refactoring.
2022-01-19 09:39:23 +08:00
Kovid Goyal
39f9efc816 Add changelog entry for last PR 2022-01-18 21:10:02 +05:30
Kovid Goyal
17a3be8cb1 Merge branch 'dotted-dashed-underline' of https://github.com/jcla1/kitty 2022-01-18 21:03:30 +05:30
Joseph Adams
ee2c9775a7 Use distribute_dots() to even out spacing in underline 2022-01-18 15:46:31 +01:00
Joseph Adams
da486153de Add (possibly erroneous) test for new underline.
Also make more clear *what* exactly is rendered in the cell (i.e. a
strikethrough).
2022-01-18 12:52:59 +01:00
Joseph Adams
dd192ad0b7 Make old tests run
Previously, because of the new underline styles a couple of tests were
failing due to an unexpected number of sprites being returned from the
test-set-up. No new tests were added.
2022-01-18 12:28:24 +01:00
Joseph Adams
3c6c36487f Update underline documentation 2022-01-18 12:13:51 +01:00
Kovid Goyal
85eedb96ac Merge branch 'docs' of https://github.com/page-down/kitty 2022-01-18 16:37:11 +05:30
pagedown
3a8d867c87 Docs: Add macOS shortcuts and nth_os_window 2022-01-18 18:59:26 +08:00
Kovid Goyal
6a2522452f Allow including config files matching glob patterns in conf files
Fixes #4533
2022-01-18 16:28:19 +05:30
Kovid Goyal
f9a4b6bb0d Use an enum for state as well 2022-01-18 13:58:55 +05:30
Kovid Goyal
f1fbfe297d Use an enum for background work 2022-01-18 13:50:05 +05:30
Kovid Goyal
2c0ac895e6 diff kitten: Fix exiting while highlighting is incomplete causes kitten to hang until highlighting is done 2022-01-18 13:43:39 +05:30
Joseph Adams
e86c7d668c Add rendering functions for dotted and dashed underline.
Dashed underline looks pretty good regardless of conditions, but the
dotted underline only looks good/correct on certain font-sizes. This is
due to the underline being rendered on a per cell/glyph basis (so one
can not place a dot directly between two letters, say. Could be remedied
by pulling the rendering of the underlines into the shader, but that is
more work.
2022-01-17 13:25:08 +01:00
Kovid Goyal
4778e0eb23 Move the section about integration to a better location 2022-01-17 10:41:35 +05:30
Kovid Goyal
c3ded5eb72 ... 2022-01-17 10:39:13 +05:30
Kovid Goyal
323355d4bb Add a press mentions section to the kitty website 2022-01-17 10:26:49 +05:30
Kovid Goyal
92075a44d8 Add a note about using icat from other programs 2022-01-17 08:35:48 +05:30
Kovid Goyal
386bb837d8 Merge branch 'fix-macos-kbd-shortcuts' of https://github.com/page-down/kitty 2022-01-17 08:23:34 +05:30
Kovid Goyal
ea28029581 ... 2022-01-17 08:05:00 +05:30
pagedown
507e570887 No longer need the patch for Sphinx KeyboardTransform
The issue with KeyboardTransform with dirhtml has been fixed in Sphinx 4.4.0.
2022-01-17 02:49:25 +08:00
pagedown
5182f79152 macOS: Fix global shortcuts with shift modifier key not working 2022-01-17 02:23:00 +08:00
Kovid Goyal
71106bcd39 Shell integration: Dont override fish's native title setting functionality
See discussion: https://github.com/fish-shell/fish-shell/issues/8641
2022-01-16 15:58:11 +05:30
Kovid Goyal
cf91e1973b Show number of windows per tab in the select_tab action
Fixes #4523
2022-01-16 10:03:20 +05:30
Kovid Goyal
2170d4e21a DRYer 2022-01-16 09:47:44 +05:30
Kovid Goyal
2538e94390 Use a single SGR regex pattern cache 2022-01-16 09:46:52 +05:30
Kovid Goyal
0be0836890 Add changelog entry for last PR 2022-01-16 03:48:00 +05:30
Kovid Goyal
725ddf4f8b Merge branch 'fix-ksi-last-cmd' of https://github.com/page-down/kitty 2022-01-16 03:46:17 +05:30
pagedown
ce6f99044e ... 2022-01-16 04:14:59 +08:00
pagedown
30b81d98f1 Add tests for finding command output 2022-01-16 04:10:16 +08:00
pagedown
f0d2d01a36 Fix calculating the end of the command output 2022-01-16 04:02:13 +08:00
pagedown
ce32e64692 Check the continued attribute when searching for prompt marks 2022-01-15 20:19:12 +08:00
pagedown
7ea8a7d45f Fix searching the last command output 2022-01-15 18:38:05 +08:00
Kovid Goyal
b32edf54f8 hints kitten: Document how to choose the empty hint for multi key hints 2022-01-15 15:17:30 +05:30
Kovid Goyal
06ff685f8e oops 2022-01-15 14:54:30 +05:30
Kovid Goyal
c6ecdf0fa4 Ensure ignoreOSKeyboardProcessing is initialized 2022-01-15 14:43:10 +05:30
Kovid Goyal
ca4840717b macOS: Fix using shortcuts from the global menu bar as subsequent key presses in a multi key mapping not working
Fixes #4519
2022-01-15 13:56:18 +05:30
Kovid Goyal
c473df4393 Ignore NSEventModifierFlagFunction when parsing global shortcuts 2022-01-15 12:16:18 +05:30
Kovid Goyal
fa6c74a9b1 ... 2022-01-15 11:49:02 +05:30
Kovid Goyal
8174ecb318 Use only bash builtins to get currently executing command 2022-01-15 11:46:25 +05:30
Kovid Goyal
5f20d69309 Move extraction of last cmd into its own function 2022-01-15 10:58:16 +05:30
Kovid Goyal
aab121b35d Also check for shiftable shortcuts in the virtual key code case 2022-01-15 10:23:18 +05:30
Kovid Goyal
1a374a7678 Merge branch 'ksi-fish' of https://github.com/page-down/kitty 2022-01-15 10:17:18 +05:30
Kovid Goyal
396906860d macOS: When checking for global shortcuts handle shifted form of cmd+` 2022-01-15 10:14:06 +05:30
pagedown
85254746bf Shell integration: Remove functions that run only once at start 2022-01-15 12:34:52 +08:00
Kovid Goyal
c3c7ad78c1 Merge branch 'ksi-fish-cursor' of https://github.com/page-down/kitty 2022-01-15 09:40:37 +05:30
pagedown
77787bdc7c Shell integration: Improvements in fish integration 2022-01-15 12:01:47 +08:00
pagedown
9813ad04c2 Shell integration: Set the fish vi mode cursor to blink 2022-01-15 10:29:26 +08:00
Kovid Goyal
d2c20f9548 Add changelog entry for previous PR 2022-01-14 23:31:10 +05:30
Kovid Goyal
e51b520897 Merge branch 'ksi-fish-title' of https://github.com/page-down/kitty 2022-01-14 23:29:48 +05:30
Kovid Goyal
35207af3de Another use of builtin 2022-01-14 23:28:10 +05:30
Kovid Goyal
96269510d3 Abort before defining anything and dont touch global state via set 2022-01-14 23:26:40 +05:30
pagedown
ceb7c440a1 Shell integration: Only override default fish_title 2022-01-15 01:46:14 +08:00
Kovid Goyal
25e1edf52e ... 2022-01-14 23:03:44 +05:30
Kovid Goyal
7a903adeea let _ksi_debug_print handle multiple args 2022-01-14 22:53:39 +05:30
Kovid Goyal
914f4e9ec9 Dont break if user aliases or redefines various builtins we use 2022-01-14 22:47:49 +05:30
Kovid Goyal
4c5673dc4d Freeze _ksi_prompt to prevent it from being edited 2022-01-14 22:35:37 +05:30
Kovid Goyal
8529a60d77 Use issue number in changelog 2022-01-14 22:26:54 +05:30
Kovid Goyal
58da5d4d29 Placate mypy 2022-01-14 22:26:05 +05:30
Kovid Goyal
4ce6d718c9 icat kitten: Add options to mirror images and remove their transparency before displaying them
Fixes #4513
2022-01-14 22:02:35 +05:30
Kovid Goyal
dd31ee60f2 Fix typing of to_color 2022-01-14 21:54:36 +05:30
Kovid Goyal
8bb2da3c37 Use with instead of closing manually 2022-01-14 21:12:08 +05:30
Kovid Goyal
47dbaac54f Add changelog entry for previous PR 2022-01-14 20:36:15 +05:30
Kovid Goyal
346405299b Merge branch 'ksi-fish-cursor' of https://github.com/page-down/kitty 2022-01-14 20:35:23 +05:30
pagedown
a0476c03c3 Improve fish cursor shape integration 2022-01-14 22:57:31 +08:00
Kovid Goyal
9dd5410433 Dont rely on declare -g existing in bash 2022-01-14 18:21:21 +05:30
Kovid Goyal
72745dfb62 Use red fg for activity symbol by default
Can be overriden via template, but for backwards compat keep it red
2022-01-14 13:27:35 +05:30
Kovid Goyal
3a0d8ada6a Merge branch 'feat-macos-kbd-shortcuts' of https://github.com/page-down/kitty 2022-01-14 12:36:01 +05:30
Kovid Goyal
b53d756bc2 Shell integration: Fix bash integration not working when PROMPT_COMMAND is used to change the prompt variables
Fixes #4476
2022-01-14 12:14:48 +05:30
Kovid Goyal
bb8ebf32ae Merge branch 'fix-macos-text-input-beep' of https://github.com/page-down/kitty 2022-01-14 11:53:37 +05:30
pagedown
704487f0ce macOS: Release input context 2022-01-14 14:18:03 +08:00
pagedown
a9bdf70275 macOS: Watch for system preferences changes and update global_shortcuts 2022-01-14 13:51:46 +08:00
Kovid Goyal
017da1159c Allow using templates with text formatting for tab_activity_symbol
Fixes #4507
2022-01-14 08:18:04 +05:30
pagedown
ab5b9fb329 macOS: Only check the "standard" type of the system shortcuts 2022-01-14 10:07:38 +08:00
pagedown
e0700d45de macOS: Update to the latest and categorize the system shortcut names 2022-01-14 10:05:49 +08:00
Joseph Adams
a2c4f830b3 Enable use of higher options for underlining text.
In `Colored and styled underlines` it's proposed that the SGR codes
\e[4:4m and \e[4:5m are used to add a dotted or dashed underline to the
rendering context respectively. This commit prepares the necessary
changes to add the two additional underline style, while still rendering
them as a normal underline and curly underline.
2022-01-13 17:27:02 +01:00
Kovid Goyal
ecf4fcdeb0 Add changelog entry for previous PR
Fixes #4489
2022-01-13 20:24:17 +05:30
Kovid Goyal
0a89fe2e17 Merge branch 'fix-macos-text-input-beep' of https://github.com/page-down/kitty 2022-01-13 20:23:23 +05:30
pagedown
5b5651ca7d macOS: Suppress the key combinations beep sound 2022-01-13 22:51:53 +08:00
Kovid Goyal
5e5cd1acc8 Simplify flagsChanged and make it more robust
Now it only sends a key event to kitty if an actual modifier key was
pressed/released.
2022-01-13 20:13:55 +05:30
Kovid Goyal
79fd01093a fix typo in generating lookup keys for vk code based shortcuts 2022-01-13 19:21:49 +05:30
Kovid Goyal
ce63ddfe54 macOS: Print out unhandled exceptions 2022-01-13 16:54:36 +05:30
Kovid Goyal
a56471f7ed Validate the parameters array 2022-01-13 16:44:43 +05:30
Kovid Goyal
96b46c554e Add type annotations to dictionaries 2022-01-13 16:29:13 +05:30
Kovid Goyal
a819023ab7 remove redundant cast 2022-01-13 16:05:48 +05:30
Kovid Goyal
9504f50bd4 ... 2022-01-13 16:04:50 +05:30
Kovid Goyal
8fe71e3dc8 Dont use a block for dictionary enumeration
Maybe that will fix the mysterious issue @page-down is having
2022-01-13 16:02:29 +05:30
Kovid Goyal
34d7643bed Ensure global_shosrtcuts is initialized 2022-01-13 15:10:19 +05:30
Kovid Goyal
682eb7d802 Log when events are ignored because no keyWindow is present 2022-01-13 14:22:52 +05:30
Kovid Goyal
f86a954131 ooops 2022-01-13 14:21:58 +05:30
Kovid Goyal
8e3f6d37d9 ... 2022-01-13 14:15:39 +05:30
Kovid Goyal
2c38360ca0 Handle no keyWindow case 2022-01-13 13:43:01 +05:30
Kovid Goyal
f9d2167c97 macOS: Respect the users system-wide global keyboard shortcut preferences
Note that we currently dont handle live reload of global shortcut
preferences. I cant be bothered. Patches welcome.
Fixes #4501
2022-01-13 13:36:58 +05:30
Kovid Goyal
17cada7a6a Micro-optimization 2022-01-12 23:32:03 +05:30
Kovid Goyal
19e6f70655 Draw the dots for braille characters more evenly spaced at all font sizes
Fixes #4499
2022-01-12 23:18:18 +05:30
Kovid Goyal
d106089cb9 Micro-optimization 2022-01-12 22:25:58 +05:30
Kovid Goyal
0f7f69c218 move menu case outside auto-generated section 2022-01-11 22:57:05 +05:30
Kovid Goyal
f9171a767c Also document menu key encoding 2022-01-11 22:48:53 +05:30
Kovid Goyal
34e939f9a1 Encode menu key the same as xterm in legacy mode
See #597
2022-01-11 22:44:56 +05:30
Kovid Goyal
ba76cacd74 Add changelog entry for previous PR 2022-01-11 20:10:38 +05:30
Kovid Goyal
fd1b64dcc6 Merge branch 'fixes-fullscreen-hide-macos-10.15' of https://github.com/npearson72/kitty 2022-01-11 20:09:10 +05:30
Nathan Pearson
7c33d7ef17 Bumps version from 10.15 to 10.15.7 (latest minor) 2022-01-11 07:31:37 -07:00
Nathan Pearson
2ccfecb5ae Fixes comment 2022-01-11 06:39:48 -07:00
Nathan Pearson
a40ff85e1a Fixes traditional fullscreen hide for Mac OS >= 10.15 2022-01-11 06:25:39 -07:00
Kovid Goyal
62953c2057 Mark parameters as unused 2022-01-11 11:26:08 +05:30
Kovid Goyal
0e6091773d Add a poster for the demo video 2022-01-11 11:09:43 +05:30
Kovid Goyal
4c8ef26141 For the vertical and horizontal layouts have the windows arranged on a ring rather than a plane. This means the first and last window are considered neighbors
Fixes #4494
2022-01-11 08:08:11 +05:30
Kovid Goyal
7023c7a8ae ... 2022-01-10 21:15:24 +05:30
Kovid Goyal
b2bfc4408e Fix common single letter extension files not being detected
Fixes #4491
Fixes #4492
2022-01-10 14:20:18 +05:30
Kovid Goyal
5b8aca3a2f Use an f-string 2022-01-10 08:35:43 +05:30
Kovid Goyal
6715ee888e Merge branch 'cleanup' of https://github.com/page-down/kitty 2022-01-10 08:01:13 +05:30
pagedown
4293325539 Cleanup previous PR
Revert the changes related to set_options debug_keyboard.
Remove the Carbon framework flag for glfw.
2022-01-10 10:24:10 +08:00
Kovid Goyal
24c44861ef Cleanup previous PR
Simplify wrapping of toggle_secure_input
Dont duplicate debug_keyboard flag in two places
2022-01-09 22:11:22 +05:30
Kovid Goyal
eafd20b4b3 Merge branch 'feat-macos-sec-kbd' of https://github.com/page-down/kitty 2022-01-09 22:03:31 +05:30
pagedown
d9e5edac01 Refactor: Replace double quotes with single quotes 2022-01-09 22:54:33 +08:00
pagedown
f7be4fab48 macOS: Allow mapping secure keyboard entry shortcut 2022-01-09 22:54:03 +08:00
Kovid Goyal
50c822e05a Merge branch 'zsh-integration' of https://github.com/romkatv/kitty 2022-01-09 15:44:43 +05:30
Roman Perepelitsa
6c1fd3015f Fix a minor bug in zsh integration
This bug is extremely unlikely to cause issues.
2022-01-09 10:45:47 +01:00
Kovid Goyal
de3b9634ac Add links to third party shell integrations 2022-01-09 04:30:32 +05:30
Kovid Goyal
722f6163aa Use tabs for zsh/fish in hg kitten docs 2022-01-09 03:54:33 +05:30
Kovid Goyal
82a4e72c85 Merge branch 'patch-1' of https://github.com/ronjouch/kitty 2022-01-09 03:51:54 +05:30
Ronan Jouchet
c40bd7ce82 kittens/hyperlinked_grep: document how to tell fish to autocomplete with rg
It was documented for zsh but not fish (2 lines above).

While looking for how to do the same in fish, I couldn't find how to do
*exactly the same*, but turns out there's a neat way to do both the
aliasing and autocompletion mapping at the same time.

See https://fishshell.com/docs/current/cmds/function.html :

> `-w WRAPPED_COMMAND` or `--wraps=WRAPPED_COMMAND` causes the function
> to inherit completions from the given wrapped command. See the
> documentation for [complete](https://fishshell.com/docs/current/cmds/complete.html#cmd-complete)
> for more information.
2022-01-08 14:43:36 -05:00
Kovid Goyal
356897a32d macOS: Persist "Secure Keyboard Entry" across restarts to match the behavior of Terminal.app
Fixes #4471
2022-01-08 14:13:14 +05:30
Kovid Goyal
ac0c10c170 Simplify if condition 2022-01-08 14:06:22 +05:30
Kovid Goyal
362c4147e4 ... 2022-01-08 12:21:55 +05:30
Kovid Goyal
9b9eae7923 icat: Fix --hold not working with critical errors 2022-01-08 10:22:22 +05:30
Kovid Goyal
8622c47348 Fix #4430 2022-01-08 10:00:25 +05:30
Kovid Goyal
1bfc46a24f ... 2022-01-08 09:16:32 +05:30
Kovid Goyal
8de787a2f3 Create shlex.join if it doesnt exist 2022-01-07 20:17:23 +05:30
Kovid Goyal
fcc75689d8 ... 2022-01-07 18:48:26 +05:30
Kovid Goyal
1eed35b8bb Document launch-actions.conf 2022-01-07 18:47:37 +05:30
Kovid Goyal
1454af2d41 macOS: Allow customizing the launch actions 2022-01-07 18:43:07 +05:30
Kovid Goyal
cbb2597667 Merge branch 'feat-macos-open-with' of https://github.com/page-down/kitty 2022-01-07 17:11:42 +05:30
pagedown
9ca691c0c6 macOS: Add kitty to the open with list for text files and image files 2022-01-07 19:33:29 +08:00
Kovid Goyal
b9f21f285c Remove non-namespaced launch points
As far as I can tell these were non-namespaced by accident
2022-01-07 14:34:41 +05:30
Kovid Goyal
bf84f95a94 macOS: When launching a shell script use its shebang, if present 2022-01-07 13:52:13 +05:30
Kovid Goyal
d42f2071c3 hold windows when running shell scripts via launch services 2022-01-07 13:45:02 +05:30
Kovid Goyal
5c343c918d Add a --hold option for the launch command 2022-01-07 13:32:25 +05:30
Kovid Goyal
5bfed4fe2f Also launch bash/zsh/fish scripts in their respective shells 2022-01-07 12:48:59 +05:30
Kovid Goyal
6c139600a0 macOS: Also open .sh and .command files in the default shell 2022-01-07 12:32:26 +05:30
Kovid Goyal
b80b8524e6 oops 2022-01-07 12:29:57 +05:30
Kovid Goyal
ffa63e27ea macOS: Allow opening text files, images and directories with kitty when launched using "Open with" in Finder
Fixes #4460
2022-01-07 12:25:30 +05:30
Kovid Goyal
a155b081b7 Make hold_till_enter re-useable 2022-01-07 12:13:20 +05:30
Kovid Goyal
04807453ec version 0.24.1 2022-01-06 10:40:59 +05:30
Kovid Goyal
1b7582e85c Merge branch 'patch-1' of https://github.com/Silent-Crafter/kitty 2022-01-06 10:03:45 +05:30
Silent Crafter
e106562ef1 Bash: for loop array iteration fix
the array iteration wasn't implemented correctly for KITTY_SHELL_INTEGRATION variable. the i variable in for loop wasn't iterating through all the elements of that array
2022-01-06 09:52:59 +05:30
Kovid Goyal
d3a20f00d6 Add a comment explaining where we use PS2 marks 2022-01-06 09:25:41 +05:30
Kovid Goyal
de5443a4db Add a reference to the discussion of using custom tab bar themes to the docs 2022-01-06 09:16:22 +05:30
Kovid Goyal
e8b19e08fa Fix non-renderable combining chars causing some text to not be rendered on Linux
The test for non-renderable chars was broken and the variation selectors
were not included in the test. Fixes #4444
2022-01-05 22:33:53 +05:30
Kovid Goyal
a574081012 Merge branch 'zsh-integration' of https://github.com/romkatv/kitty 2022-01-05 21:29:30 +05:30
Roman Perepelitsa
4f06ce9d72 Once again start embedding marks in PS2 on zsh 2022-01-05 16:36:00 +01:00
Kovid Goyal
6713580455 Explicitly turn off aliases in a more functions
Not really needed, but I like to be explicit
2022-01-05 21:04:27 +05:30
Kovid Goyal
86f6c946b3 Merge branch 'zsh-integration-aliases' of https://github.com/romkatv/kitty 2022-01-05 20:49:08 +05:30
Roman Perepelitsa
9194d1db6b Disable aliases when patching functions in zsh integration 2022-01-05 16:16:18 +01:00
Kovid Goyal
420a1b018a Fix #4448 2022-01-05 20:14:23 +05:30
Roman Perepelitsa
8009b85073 Make writing to the TTY more robust in zsh integration
See https://github.com/kovidgoyal/kitty/issues/4440.
2022-01-05 09:55:19 +01:00
Kovid Goyal
2a58af2be9 Ignore all errors rendering /etc/issue 2022-01-05 14:04:56 +05:30
Roman Perepelitsa
4decac26f6 Fix a typo in _ksi_debug_print 2022-01-05 09:22:00 +01:00
Roman Perepelitsa
a6b74f190f Revert "Get _ksi_debug_print working again"
This reverts commit ff63e58f95.
2022-01-05 09:20:33 +01:00
Kovid Goyal
bfbe60c90e Update changelog 2022-01-05 09:05:20 +05:30
Kovid Goyal
384b2f8462 Add roundtrip and cursor positioning tests for all three zero width chars 2022-01-05 08:57:14 +05:30
Kovid Goyal
d875615c03 Fix a regression in the handling of some combining characters such as zero width joiners
Fixes #4439
2022-01-05 08:50:55 +05:30
Kovid Goyal
9aefcfe56f Make --hold a bit more robust 2022-01-05 08:14:22 +05:30
Kovid Goyal
266e70222f Avoid hooking LE widgets at all when using no-cursor 2022-01-05 07:46:20 +05:30
Kovid Goyal
d4639e2aea Merge branch 'zsh-integration' of https://github.com/romkatv/kitty 2022-01-05 07:40:10 +05:30
Roman Perepelitsa
ca8975891b Replace add-zle-hook-widget with a series of hacks in zsh integration 2022-01-04 19:56:57 +01:00
Kovid Goyal
bed3d2eb50 Fix #4434 2022-01-04 23:38:58 +05:30
Kovid Goyal
78495a7f64 spelling 2022-01-04 20:35:21 +05:30
Kovid Goyal
f66914f7bb ... 2022-01-04 20:29:53 +05:30
Kovid Goyal
58e1f6ee1f Use ctermid() for tty_name 2022-01-04 20:25:16 +05:30
Kovid Goyal
0625035202 oops forgot to re-order second tab group 2022-01-04 20:18:49 +05:30
Kovid Goyal
f8fc272f58 Make loading IssueData more robust
Fixes #4424
2022-01-04 19:57:23 +05:30
Kovid Goyal
6d61de8a8f Merge branch 'fladson/fix-documentation-typo' of https://github.com/fladson/kitty 2022-01-04 19:52:27 +05:30
Kovid Goyal
4c08709df1 Use same orders for all tab groups 2022-01-04 19:49:55 +05:30
Fladson Gomes
58a28da94d Fix documentation typo 2022-01-04 14:46:25 +01:00
Kovid Goyal
26b976d17d Merge branch 'fix-mouse-scroll' of https://github.com/page-down/kitty 2022-01-04 18:01:30 +05:30
pagedown
3141fc3f05 Update mouse cursor position when scrolling without OS window focus 2022-01-04 19:39:36 +08:00
Kovid Goyal
a45d6c8b55 Fix #4421 2022-01-04 16:21:00 +05:30
Kovid Goyal
dadf496a68 remove leftover debug print 2022-01-04 12:01:23 +05:30
Kovid Goyal
dae8ae33f0 hints kitten: Fix matching of filenames enclosed in quotes or brackets not stripping the surrounding quotes properly. Fixes #4419 2022-01-04 10:58:31 +05:30
148 changed files with 3301 additions and 1831 deletions

View File

@@ -48,7 +48,7 @@ def install_deps():
def build_kitty():
python = shutil.which('python3') if is_bundle else sys.executable
cmd = '{} setup.py build --verbose'.format(python)
cmd = f'{python} setup.py build --verbose'
if os.environ.get('KITTY_SANITIZE') == '1':
cmd += ' --debug --sanitize'
run(cmd)
@@ -59,8 +59,8 @@ def test_kitty():
def package_kitty():
py = 'python3' if is_macos else 'python'
run(py + ' setup.py linux-package --update-check-interval=0 --verbose')
python = 'python3' if is_macos else 'python'
run(f'{python} setup.py linux-package --update-check-interval=0 --verbose')
if is_macos:
run('python3 setup.py kitty.app --update-check-interval=0 --verbose')
run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"')
@@ -76,11 +76,11 @@ def replace_in_file(path, src, dest):
def setup_bundle_env():
global SW
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig'
os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig')
if is_macos:
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
else:
os.environ['LD_LIBRARY_PATH'] = SW + '/lib'
os.environ['LD_LIBRARY_PATH'] = os.path.join(SW, 'lib')
os.environ['PYTHONHOME'] = SW
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
@@ -111,7 +111,7 @@ def main():
setup_bundle_env()
else:
if not is_macos and 'pythonLocation' in os.environ:
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
os.environ['LD_LIBRARY_PATH'] = os.path.join(os.environ['pythonLocation'], 'lib')
action = sys.argv[-1]
if action in ('build', 'package'):
install_deps()
@@ -122,7 +122,7 @@ def main():
elif action == 'test':
test_kitty()
else:
raise SystemExit('Unknown action: ' + action)
raise SystemExit(f'Unknown action: {action}')
if __name__ == '__main__':

View File

@@ -88,10 +88,10 @@ jobs:
run: python test.py mypy
- name: Build man page
run: make FAIL_WARN=-W man
run: make FAIL_WARN=1 man
- name: Build HTML docs
run: make FAIL_WARN=-W html
run: make FAIL_WARN=1 html
bundle:
name: Bundle test (${{ matrix.os }})

View File

@@ -5,6 +5,10 @@ ifdef VERBOSE
VVAL=--verbose
endif
ifdef FAIL_WARN
export FAIL_WARN
endif
all:
python3 setup.py $(VVAL)
@@ -32,13 +36,16 @@ app:
python3 setup.py kitty.app $(VVAL)
man:
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs man
$(MAKE) -C docs man
html:
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs html
$(MAKE) -C docs html
dirhtml:
$(MAKE) -C docs dirhtml
linkcheck:
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs linkcheck
$(MAKE) -C docs linkcheck
website:
./publish.py --only website

View File

@@ -31,14 +31,9 @@ def runpy(args: List[str]) -> None:
def hold(args: List[str]) -> None:
import subprocess
import tty
from contextlib import suppress
ret = subprocess.Popen(args[1:]).wait()
with suppress(BaseException):
print('\n\x1b[1;32mPress any key to exit', end='', flush=True)
with suppress(BaseException):
tty.setraw(sys.stdin.fileno())
sys.stdin.buffer.read(1)
from kitty.utils import hold_till_enter
hold_till_enter()
raise SystemExit(ret)
@@ -69,6 +64,27 @@ def launch(args: List[str]) -> None:
runpy.run_path(exe, run_name='__main__')
def shebang(args: List[str]) -> None:
script_path = args[1]
cmd = args[2:]
if cmd == ['__ext__']:
cmd = [os.path.splitext(script_path)[1][1:].lower()]
try:
f = open(script_path)
except FileNotFoundError:
raise SystemExit(f'The file {script_path} does not exist')
with f:
if f.read(2) == '#!':
line = f.readline().strip()
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat
if is_macos:
cmd = line.split(' ')
else:
cmd = line.split(' ', maxsplit=1)
os.execvp(cmd[0], cmd + [script_path])
def run_kitten(args: List[str]) -> None:
try:
kitten = args[1]
@@ -104,10 +120,6 @@ entry_points = {
# These two are here for backwards compat
'icat': icat,
'list-fonts': list_fonts,
'runpy': runpy,
'launch': launch,
'kitten': run_kitten,
'edit-config': edit_config_file,
'@': remote_control,
'+': namespaced,
@@ -115,6 +127,11 @@ entry_points = {
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
namespaced_entry_points['hold'] = hold
namespaced_entry_points['complete'] = complete
namespaced_entry_points['runpy'] = runpy
namespaced_entry_points['launch'] = launch
namespaced_entry_points['kitten'] = run_kitten
namespaced_entry_points['edit-config'] = edit_config_file
namespaced_entry_points['shebang'] = shebang
def setup_openssl_environment() -> None:

View File

@@ -13,7 +13,7 @@ import tempfile
def compile_terminfo(base):
with tempfile.TemporaryDirectory() as tdir:
proc = subprocess.run(['tic', '-x', '-o' + tdir, 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
proc = subprocess.run(['tic', '-x', f'-o{tdir}', 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
regex = '^"terminfo/kitty.terminfo", line [0-9]+, col [0-9]+, terminal \'xterm-kitty\': older tic versions may treat the description field as an alias$'
for error in proc.stderr.decode('utf-8').splitlines():
if not re.match(regex, error):

View File

@@ -26,7 +26,7 @@ def initialize_constants():
kitty_constants = {}
src = read_src_file('constants.py')
nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src)
kitty_constants['version'] = '%s.%s.%s' % (nv.group(1), nv.group(2), nv.group(3))
kitty_constants['version'] = f'{nv.group(1)}.{nv.group(2)}.{nv.group(3)}'
kitty_constants['appname'] = re.search(
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
).group(2)

View File

@@ -38,7 +38,7 @@ def binary_includes():
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec'
))) + (
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
get_dll_path('python' + py_ver, 2),
get_dll_path(f'python{py_ver}', 2),
)
@@ -47,7 +47,7 @@ class Env:
def __init__(self, package_dir):
self.base = package_dir
self.lib_dir = j(self.base, 'lib')
self.py_dir = j(self.lib_dir, 'python' + py_ver)
self.py_dir = j(self.lib_dir, f'python{py_ver}')
os.makedirs(self.py_dir)
self.bin_dir = j(self.base, 'bin')
self.obj_dir = mkdtemp('launchers-')
@@ -107,7 +107,7 @@ def add_ca_certs(env):
def copy_python(env):
print('Copying python...')
srcdir = j(PREFIX, 'lib/python' + py_ver)
srcdir = j(PREFIX, f'lib/python{py_ver}')
for x in os.listdir(srcdir):
y = j(srcdir, x)
@@ -187,11 +187,11 @@ def strip_files(files, argv_max=(256 * 1024)):
def strip_binaries(files):
print('Stripping %d files...' % len(files))
print(f'Stripping {len(files)} files...')
before = sum(os.path.getsize(x) for x in files)
strip_files(files)
after = sum(os.path.getsize(x) for x in files)
print('Stripped %.1f MB' % ((before - after) / (1024 * 1024.)))
print('Stripped {:.1f} MB'.format((before - after) / (1024 * 1024.)))
def create_tarfile(env, compression_level='9'):
@@ -203,7 +203,7 @@ def create_tarfile(env, compression_level='9'):
if err.errno != errno.ENOENT:
raise
os.mkdir(base)
dist = os.path.join(base, '%s-%s-%s.tar' % (kitty_constants['appname'], kitty_constants['version'], arch))
dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar')
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
cwd = os.getcwd()
os.chdir(env.base)
@@ -213,13 +213,13 @@ def create_tarfile(env, compression_level='9'):
finally:
os.chdir(cwd)
print('Compressing archive...')
ans = dist.rpartition('.')[0] + '.txz'
ans = f'{dist.rpartition(".")[0]}.txz'
start_time = time.time()
subprocess.check_call(['xz', '--threads=0', '-f', '-' + compression_level, dist])
subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
secs = time.time() - start_time
print('Compressed in %d minutes %d seconds' % (secs // 60, secs % 60))
os.rename(dist + '.xz', ans)
print('Archive %s created: %.2f MB' % (
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
os.rename(f'{dist}.xz', ans)
print('Archive {} created: {:.2f} MB'.format(
os.path.basename(ans), os.stat(ans).st_size / (1024.**2)))

View File

@@ -90,7 +90,7 @@ def strip_files(files, argv_max=(256 * 1024)):
def files_in(folder):
for record in os.walk(folder):
for f in record[-1]:
yield os.path.join(record[0], f)
yield join(record[0], f)
def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
@@ -103,7 +103,7 @@ def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
def do_sign(app_dir):
with current_dir(os.path.join(app_dir, 'Contents')):
with current_dir(join(app_dir, 'Contents')):
# Sign all .so files
so_files = {x for x in files_in('.') if x.endswith('.so')}
codesign(so_files)
@@ -153,7 +153,7 @@ class Freeze(object):
self.to_strip = []
self.warnings = []
self.py_ver = py_ver
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', 'python' + self.py_ver)
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', f'python{self.py_ver}')
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
self.obj_dir = mkdtemp('launchers-')
@@ -177,7 +177,7 @@ class Freeze(object):
self.run_tests()
# self.run_shell()
ret = self.makedmg(self.build_dir, APPNAME + '-' + VERSION)
ret = self.makedmg(self.build_dir, f'{APPNAME}-{VERSION}')
return ret
@@ -186,7 +186,7 @@ class Freeze(object):
print('\nDownloading CA certs...')
from urllib.request import urlopen
cdata = urlopen(kitty_constants['cacerts_url']).read()
dest = os.path.join(self.contents_dir, 'Resources', 'cacert.pem')
dest = join(self.contents_dir, 'Resources', 'cacert.pem')
with open(dest, 'wb') as f:
f.write(cdata)
@@ -197,7 +197,7 @@ class Freeze(object):
@flush
def run_tests(self):
iv['run_tests'](os.path.join(self.contents_dir, 'MacOS', 'kitty'))
iv['run_tests'](join(self.contents_dir, 'MacOS', 'kitty'))
@flush
def set_id(self, path_to_lib, new_id):
@@ -222,10 +222,10 @@ class Freeze(object):
@flush
def get_local_dependencies(self, path_to_lib):
for x, is_id in self.get_dependencies(path_to_lib):
for y in (PREFIX + '/lib/', PREFIX + '/python/Python.framework/', '@rpath/'):
for y in (f'{PREFIX}/lib/', f'{PREFIX}/python/Python.framework/', '@rpath/'):
if x.startswith(y):
if y == PREFIX + '/python/Python.framework/':
y = PREFIX + '/python/'
if y == f'{PREFIX}/python/Python.framework/':
y = f'{PREFIX}/python/'
yield x, x[len(y):], is_id
break
@@ -239,7 +239,7 @@ class Freeze(object):
self.to_strip.append(path_to_lib)
old_mode = flipwritable(path_to_lib)
for dep, bname, is_id in self.get_local_dependencies(path_to_lib):
ndep = self.FID + '/' + bname
ndep = f'{self.FID}/{bname}'
self.change_dep(dep, ndep, is_id, path_to_lib)
ldeps = list(self.get_local_dependencies(path_to_lib))
if ldeps:
@@ -252,7 +252,7 @@ class Freeze(object):
@flush
def add_python_framework(self):
print('\nAdding Python framework')
src = join(PREFIX + '/python', 'Python.framework')
src = join(f'{PREFIX}/python', 'Python.framework')
x = join(self.frameworks_dir, 'Python.framework')
curr = os.path.realpath(join(src, 'Versions', 'Current'))
currd = join(x, 'Versions', basename(curr))
@@ -262,12 +262,12 @@ class Freeze(object):
shutil.copy2(join(curr, 'Python'), currd)
self.set_id(
join(currd, 'Python'),
self.FID + '/Python.framework/Versions/%s/Python' % basename(curr))
f'{self.FID}/Python.framework/Versions/{basename(curr)}/Python')
# The following is needed for codesign
with current_dir(x):
os.symlink(basename(curr), 'Versions/Current')
for y in ('Python', 'Resources'):
os.symlink('Versions/Current/%s' % y, y)
os.symlink(f'Versions/Current/{y}', y)
@flush
def install_dylib(self, path, set_id=True):
@@ -275,7 +275,7 @@ class Freeze(object):
if set_id:
self.set_id(
join(self.frameworks_dir, basename(path)),
self.FID + '/' + basename(path))
f'{self.FID}/{basename(path)}')
self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path)))
@flush
@@ -291,11 +291,11 @@ class Freeze(object):
'rsync.2',
):
print('\nAdding', x)
x = 'lib%s.dylib' % x
x = f'lib{x}.dylib'
src = join(PREFIX, 'lib', x)
shutil.copy2(src, self.frameworks_dir)
dest = join(self.frameworks_dir, x)
self.set_id(dest, self.FID + '/' + x)
self.set_id(dest, f'{self.FID}/{x}')
self.fix_dependencies_in_lib(dest)
@flush
@@ -321,7 +321,7 @@ class Freeze(object):
@flush
def add_stdlib(self):
print('\nAdding python stdlib')
src = PREFIX + '/python/Python.framework/Versions/Current/lib/python' + self.py_ver
src = f'{PREFIX}/python/Python.framework/Versions/Current/lib/python{self.py_ver}'
dest = self.python_stdlib
if not os.path.exists(dest):
os.makedirs(dest)
@@ -345,19 +345,19 @@ class Freeze(object):
kitty_dir = join(self.resources_dir, 'kitty')
bases = ('kitty', 'kittens', 'kitty_tests')
for x in bases:
dest = os.path.join(self.python_stdlib, x)
os.rename(os.path.join(kitty_dir, x), dest)
dest = join(self.python_stdlib, x)
os.rename(join(kitty_dir, x), dest)
if x == 'kitty':
shutil.rmtree(os.path.join(dest, 'launcher'))
os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(self.python_stdlib, 'kitty_main.py'))
shutil.rmtree(os.path.join(kitty_dir, '__pycache__'))
pdir = os.path.join(dirname(self.python_stdlib), 'kitty-extensions')
shutil.rmtree(join(dest, 'launcher'))
os.rename(join(kitty_dir, '__main__.py'), join(self.python_stdlib, 'kitty_main.py'))
shutil.rmtree(join(kitty_dir, '__pycache__'))
pdir = join(dirname(self.python_stdlib), 'kitty-extensions')
os.mkdir(pdir)
print('Extracting extension modules from', self.python_stdlib, 'to', pdir)
ext_map = extract_extension_modules(self.python_stdlib, pdir)
shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(self.python_stdlib, 'site.py'))
shutil.copy(join(os.path.dirname(self_dir), 'site.py'), join(self.python_stdlib, 'site.py'))
for x in bases:
iv['sanitize_source_folder'](os.path.join(self.python_stdlib, x))
iv['sanitize_source_folder'](join(self.python_stdlib, x))
self.compile_py_modules()
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
@@ -434,23 +434,23 @@ class Freeze(object):
''' Copy a directory d into a dmg named volname '''
print('\nMaking dmg...')
sys.stdout.flush()
destdir = os.path.join(SW, 'dist')
destdir = join(SW, 'dist')
try:
shutil.rmtree(destdir)
except FileNotFoundError:
pass
os.mkdir(destdir)
dmg = os.path.join(destdir, volname + '.dmg')
dmg = join(destdir, f'{volname}.dmg')
if os.path.exists(dmg):
os.unlink(dmg)
tdir = tempfile.mkdtemp()
appdir = os.path.join(tdir, os.path.basename(d))
appdir = join(tdir, os.path.basename(d))
shutil.copytree(d, appdir, symlinks=True)
if self.sign_installers:
with timeit() as times:
sign_app(appdir, self.notarize)
print('Signing completed in %d minutes %d seconds' % tuple(times))
os.symlink('/Applications', os.path.join(tdir, 'Applications'))
print('Signing completed in {} minutes {} seconds'.format(*times))
os.symlink('/Applications', join(tdir, 'Applications'))
size_in_mb = int(
subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8')
.split()[0]) / 1024.
@@ -466,10 +466,10 @@ class Freeze(object):
print('\nCreating dmg...')
with timeit() as times:
subprocess.check_call(cmd + [dmg])
print('dmg created in %d minutes and %d seconds' % tuple(times))
print('dmg created in {} minutes and {} seconds'.format(*times))
shutil.rmtree(tdir)
size = os.stat(dmg).st_size / (1024 * 1024.)
print('\nInstaller size: %.2fMB\n' % size)
print(f'\nInstaller size: {size:.2f}MB\n')
return dmg
@@ -477,7 +477,7 @@ def main():
args = globals()['args']
ext_dir = globals()['ext_dir']
Freeze(
os.path.join(ext_dir, kitty_constants['appname'] + '.app'),
join(ext_dir, f'{kitty_constants["appname"]}.app'),
dont_strip=args.dont_strip,
sign_installers=args.sign_installers,
notarize=args.notarize,

View File

@@ -1,6 +1,10 @@
# Minimal makefile for Sphinx documentation
#
ifdef FAIL_WARN
override FAIL_WARN=-W
endif
# You can set these variables from the command line.
SPHINXOPTS = -n -q -j auto -T $(FAIL_WARN) $(OPTS)
SPHINXBUILD = sphinx-build

View File

@@ -28,3 +28,9 @@ details > summary {
text-decoration-color: var(--color-link-underline);
text-decoration-line: underline;
}
/* pygments adds an underline to some white-space, this is particularly visible in
* dark mode. Remove it. */
.highlight .w {
text-decoration: none
}

BIN
docs/_static/poster.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -77,8 +77,11 @@ You can also define a shortcut to switch to the previously active window::
map ctrl+p nth_window -1
``nth_window`` will focus the nth window for positive numbers and the
previously active windows for negative numbers.
``nth_window`` will focus the nth window for positive numbers (starting from
zero) and the previously active windows for negative numbers.
To switch to the nth OS window, you can define ``nth_os_window``. Only positive
numbers are accepted, starting from one.
.. _detach_window:
@@ -127,12 +130,12 @@ Restore font size :sc:`reset_font_size` (also :kbd:`⌘+0` on
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`⌃+⌘+f` on macOS)
Toggle maximized :sc:`toggle_maximized`
Input unicode character :sc:`input_unicode_character` (also :kbd:`⌃+⌘+space` on macOS)
Click URL using the keyboard :sc:`open_url`
Reset the terminal :sc:`reset_terminal`
Open URL in web browser :sc:`open_url`
Reset the terminal :sc:`reset_terminal` (also :kbd:`⌥+⌘+r` on macOS)
Reload :file:`kitty.conf` :sc:`reload_config_file` (also :kbd:`⌃+⌘+,` on macOS)
Debug :file:`kitty.conf` :sc:`debug_config` (also :kbd:`⌘+⌥+f6` on macOS)
Debug :file:`kitty.conf` :sc:`debug_config` (also :kbd:`⌥+⌘+,` on macOS)
Pass current selection to program :sc:`pass_selection_to_program`
Edit |kitty| config file :sc:`edit_config_file`
Edit |kitty| config file :sc:`edit_config_file` (also :kbd:`⌘+,` on macOS)
Open a |kitty| shell :sc:`kitty_shell`
Increase background opacity :sc:`increase_background_opacity`
Decrease background opacity :sc:`decrease_background_opacity`

View File

@@ -4,6 +4,8 @@ Changelog
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
To update |kitty|, :doc:`follow the instructions <binary>`.
.. recent major features {{{
Recent major new features
---------------------------
@@ -16,7 +18,7 @@ Recent major new features
.. raw:: html
<video controls width="640" height="360">
<video controls width="640" height="360" poster="../_static/poster.png">
<source src="https://download.calibre-ebook.com/videos/kitty.mp4" type="video/mp4">
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
</video>
@@ -68,11 +70,98 @@ command.
Hints for selecting kitty windows visually
.. }}}
Detailed list of changes
-------------------------------------
0.24.2 [2022-02-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- macOS: Allow opening text files, images and directories with kitty when
launched using "Open with" in Finder (:iss:`4460`)
- Allow including config files matching glob patterns in :file:`kitty.conf`
(:iss:`4533`)
- Shell integration: Fix bash integration not working when ``PROMPT_COMMAND``
is used to change the prompt variables (:iss:`4476`)
- Shell integration: Fix cursor shape not being restored to default when
running commands in the shell
- Improve the UI of the ask kitten (:iss:`4545`)
- Allow customizing the placement and formatting of the
:opt:`tab_activity_symbol` and :opt:`bell_on_tab` symbols
by adding them to the :opt:`tab_title_template` (:iss:`4581`, :pull:`4507`)
- macOS: Persist "Secure Keyboard Entry" across restarts to match the behavior
of Terminal.app (:iss:`4471`)
- hints kitten: Fix common single letter extension files not being detected
(:iss:`4491`)
- Support dotted and dashed underline styles (:pull:`4529`)
- For the vertical and horizontal layouts have the windows arranged on a ring
rather than a plane. This means the first and last window are considered
neighbors (:iss:`4494`)
- A new action to clear the current selection (:iss:`4600`)
- Shell integration: fish: Fix cursor shape not working with fish's vi mode
(:iss:`4508`)
- Shell integration: fish: Dont override fish's native title setting functionality.
See `discussion <https://github.com/fish-shell/fish-shell/issues/8641>`__.
- macOS: Fix hiding via :kbd:`cmd+h` not working on macOS 10.15.7 (:iss:`4472`)
- Draw the dots for braille characters more evenly spaced at all font sizes (:iss:`4499`)
- icat kitten: Add options to mirror images and remove their transparency
before displaying them (:iss:`4513`)
- macOS: Respect the users system-wide global keyboard shortcut preferences
(:iss:`4501`)
- macOS: Fix a few key-presses causing beeps from Cocoa's text input system
(:iss:`4489`)
- macOS: Fix using shortcuts from the global menu bar as subsequent key presses
in a multi key mapping not working (:iss:`4519`)
- Fix getting last command output not working correctly when the screen is
scrolled (:pull:`4522`)
- Show number of windows per tab in the :ac:`select_tab` action (:pull:`4523`)
- macOS: Fix the shift key not clearing pre-edit text in IME (:iss:`4541`)
- Fix clicking in a window to focus it and typing immediately sometimes having
unexpected effects if at a shell prompt (:iss:`4128`)
- themes kitten: Allow writing to a different file than :file:`kitty.conf`.
0.24.1 [2022-01-06]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Shell integration: Work around conflicts with some zsh plugins (:iss:`4428`)
- Have the zero width space and various other characters from the *Other,
formatting* Unicode category be treated as combining characters (:iss:`4439`)
- Fix using ``--shell-integration`` with :file:`setup.py` broken (:iss:`4434`)
- Fix showing debug information not working if kitty's :file:`STDIN` is not a tty
(:iss:`4424`)
- Linux: Fix a regression that broke rendering of emoji with variation selectors
(:iss:`4444`)
0.24.0 [2022-01-04]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -13,7 +13,7 @@ import subprocess
import sys
import time
from functools import partial
from typing import Any, Callable, Dict, Iterable, List, Match, Optional, Tuple
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
from docutils import nodes
from docutils.parsers.rst.roles import set_classes
@@ -22,16 +22,13 @@ from pygments.token import ( # type: ignore
Comment, Keyword, Literal, Name, Number, String, Whitespace
)
from sphinx import addnodes, version_info
from sphinx.builders.html.transforms import KeyboardTransform
from sphinx.util.logging import getLogger
KeyboardTransform.builders = ('html', 'dirhtml') # type: ignore
kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if kitty_src not in sys.path:
sys.path.insert(0, kitty_src)
from kitty.conf.types import Definition # noqa
from kitty.conf.types import Definition, expand_opt_references # noqa
from kitty.constants import str_version, website_url # noqa
# config {{{
@@ -62,6 +59,7 @@ extensions = [
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode',
'sphinx.ext.githubpages',
'sphinx.ext.extlinks',
'sphinx_copybutton',
'sphinx_inline_tabs',
"sphinxext.opengraph",
@@ -170,24 +168,11 @@ texinfo_documents = [
# GitHub linking inline roles {{{
def num_role(
which: str, name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []
) -> Tuple[List[nodes.reference], List[nodes.problematic]]:
' Link to a github issue '
try:
issue_num = int(text)
if issue_num <= 0:
raise ValueError
except ValueError:
msg = inliner.reporter.error(
'GitHub issue number must be a number greater than or equal to 1; '
'"%s" is invalid.' % text, line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
url = f'https://github.com/kovidgoyal/kitty/{which}/{issue_num}'
set_classes(options)
node = nodes.reference(rawtext, f'#{issue_num}', refuri=url, **options)
return [node], []
extlinks = {
'iss': ('https://github.com/kovidgoyal/kitty/issues/%s', '#%s'),
'pull': ('https://github.com/kovidgoyal/kitty/pull/%s', '#%s'),
'disc': ('https://github.com/kovidgoyal/kitty/discussions/%s', '#%s'),
}
def commit_role(
@@ -232,7 +217,8 @@ if you specify a program-to-run you can use the special placeholder
from kitty.remote_control import cli_msg, global_options_spec
with open('generated/cli-kitty-at.rst', 'w') as f:
p = partial(print, file=f)
p('kitty @\n' + '-' * 80)
p('kitty @')
p('-' * 80)
p('.. program::', 'kitty @')
p('\n\n' + as_rst(
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
@@ -240,7 +226,8 @@ if you specify a program-to-run you can use the special placeholder
for cmd_name in sorted(all_command_names()):
func = command_for_name(cmd_name)
p(f'.. _at_{func.name}:\n')
p('kitty @', func.name + '\n' + '-' * 120)
p('kitty @', func.name)
p('-' * 120)
p('.. program::', 'kitty @', func.name)
p('\n\n' + as_rst(*cli_params_for(func)))
from kittens.runner import get_kitten_cli_docs
@@ -249,12 +236,12 @@ if you specify a program-to-run you can use the special placeholder
if data:
with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f:
p = partial(print, file=f)
p('.. program::', f'kitty +kitten {kitten}')
p(f'\nSource code for {kitten}')
p('.. program::', 'kitty +kitten', kitten)
p('\nSource code for', kitten)
p('-' * 72)
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
p('\nCommand Line Interface')
p('-' * 72, file=f)
p('-' * 72)
p('\n\n' + option_spec_as_rst(
data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
heading_char='^'))
@@ -379,19 +366,6 @@ def link_role(
return [node], []
def expand_opt_references(conf_name: str, text: str) -> str:
conf_name += '.'
def expand(m: Match[str]) -> str:
ref = m.group(1)
if '<' not in ref and '.' not in ref:
full_ref = conf_name + ref
return ':opt:`{} <{}>`'.format(ref, full_ref)
return str(m.group())
return re.sub(r':opt:`(.+?)`', expand, text)
opt_aliases: Dict[str, str] = {}
shortcut_slugs: Dict[str, Tuple[str, str]] = {}
@@ -414,7 +388,7 @@ def parse_opt_node(env: Any, sig: str, signode: Any) -> str:
opt_aliases[optname] = firstname
count += 1
if not firstname:
raise ValueError('{} is not a valid opt'.format(sig))
raise ValueError(f'{sig} is not a valid opt')
return firstname
@@ -429,7 +403,7 @@ def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: st
conf_name, opt = target.partition('.')[::2]
if not opt:
conf_name, opt = 'kitty', conf_name
full_name = conf_name + '.' + opt
full_name = f'{conf_name}.{opt}'
return title, opt_aliases.get(full_name, full_name)
@@ -437,11 +411,11 @@ def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, titl
conf_name, slug = target.partition('.')[::2]
if not slug:
conf_name, slug = 'kitty', conf_name
full_name = conf_name + '.' + slug
full_name = f'{conf_name}.{slug}'
try:
target, stitle = shortcut_slugs[full_name]
except KeyError:
logger.warning('Unknown shortcut: {}'.format(target), location=refnode)
logger.warning(f'Unknown shortcut: {target}', location=refnode)
else:
if not has_explicit_title:
title = stitle
@@ -520,9 +494,6 @@ def setup(app: Any) -> None:
app.connect('html-page-context', add_html_context)
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
app.add_role('link', link_role)
app.add_role('iss', partial(num_role, 'issues'))
app.add_role('pull', partial(num_role, 'pull'))
app.add_role('disc', partial(num_role, 'discussions'))
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

View File

@@ -7,11 +7,11 @@ kitty.conf
rendering frames-per-second. See below for an overview of all customization
possibilities.
You can open the config file within kitty by pressing :sc:`edit_config_file`.
You can open the config file within kitty by pressing :sc:`edit_config_file` (:kbd:`⌘+,` on macOS).
You can reload the config file within kitty by pressing
:sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending kitty the ``SIGUSR1`` signal.
You can also display the current configuration by pressing the :sc:`debug_config`
(:kbd:`⌘+⌥+f6` on macOS) key.
You can also display the current configuration by pressing :sc:`debug_config`
(:kbd:`⌥+⌘+,` on macOS).
.. _confloc:
@@ -25,12 +25,14 @@ character. This works only if the ``#`` character is the first character
in the line.
You can include secondary config files via the :code:`include` directive. If
you use a relative path for include, it is resolved with respect to the
you use a relative path for :code:`include`, it is resolved with respect to the
location of the current config file. Note that environment variables are
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
:code:`USER=name`. For example::
:code:`USER=name`. Also, you can use :code:`globinclude` to include files
matching a shell glob pattern. For example::
include other.conf
globinclude kitty.d/**/*.conf
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via

View File

@@ -11,7 +11,7 @@ region of the screen.
The motivation for this extension is the various problems with the existing
solution for erasing to background color, namely the *background color erase
(bce)* capability. See
`this discussion <https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545>`_
:iss:`this discussion <160#issuecomment-346470545>`
and `this FAQ <https://invisible-island.net/ncurses/ncurses.faq.html#bce_mismatches>`_
for a summary of problems with *bce*.

View File

@@ -245,7 +245,7 @@ How can I assign a single global shortcut to bring up the kitty terminal?
Bringing up applications on a single key press is the job of the window
manager/desktop environment. For ways to do it with kitty (or indeed any
terminal) in different environments,
see `here <https://github.com/kovidgoyal/kitty/issues/45>`_.
see :iss:`here <45>`.
I do not like the kitty icon!
@@ -337,8 +337,7 @@ sorts of places where they may or may not work.
I am using tmux and have a problem
--------------------------------------
First, terminal multiplexers are `a bad idea
<https://github.com/kovidgoyal/kitty/issues/391#issuecomment-638320745>`_, do
First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`, do
not use them, if at all possible. kitty contains features that do all of what
tmux does, but better, with the exception of remote persistence (:iss:`391`).
If you still want to use tmux, read on.

View File

@@ -12,8 +12,7 @@ emulator. The major design goals are:
below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically.
* Should use optimizations when the client is running on the same computer as the terminal emulator.
For some discussion regarding the design choices, see `#33
<https://github.com/kovidgoyal/kitty/issues/33>`_.
For some discussion regarding the design choices, see :iss:`33`.
To see a quick demo, inside a |kitty| terminal run::
@@ -109,7 +108,7 @@ features of the graphics protocol:
def serialize_gr_command(**cmd):
payload = cmd.pop('payload', None)
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
ans = []
w = ans.append
w(b'\033_G'), w(cmd.encode('ascii'))

View File

@@ -14,13 +14,13 @@ kitty
changelog
integrations
protocol-extensions
press-mentions
.. tab:: Fast
* Offloads rendering to the GPU for :doc:`lower system load <performance>`
* Uses threaded rendering for
`absolutely minimal latency <https://github.com/kovidgoyal/kitty/issues/2701#issuecomment-636497270>`_
* Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>`
* Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>`
.. tab:: Capable
@@ -61,7 +61,7 @@ kitty
.. raw:: html
<video controls width="640" height="360">
<video controls width="640" height="360" poster="_static/poster.png">
<source src="https://download.calibre-ebook.com/videos/kitty.mp4" type="video/mp4">
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
</video>

View File

@@ -441,6 +441,7 @@ mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in
"F10", "kf10", "CSI 21 ~"
"F11", "kf11", "CSI 23 ~"
"F12", "kf12", "CSI 24 ~"
"MENU", "kf16", "CSI 29 ~"
There are a few more functional keys that have special cased legacy encodings.
These are present because they are commonly used and for the sake of legacy

View File

@@ -325,7 +325,7 @@ Kittens created by kitty users
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
Makes the kitty scroll bindings work in full screen applications
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
:iss:`insert password <1222>`
Insert a password from a CLI password manager, taking care to only do it at
a password prompt.

View File

@@ -33,6 +33,12 @@ that has been marked as such by the program running in the terminal,
for example, by ``ls --hyperlink=auto``. You can also :doc:`customize what actions are
taken for different types of URLs <../open_actions>`.
.. note:: If there are more hints than letters, hints will use multiple
letters. In this case, when you press the first letter, only hints
starting with that letter are displayed. Pressing the second letter will
select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty
hint.
The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text

View File

@@ -45,17 +45,31 @@ You can now run searches with::
hg some-search-term
If you want to enable completion, for the kitten, you can delegate completion
to rg. For that, instead of using an alias create a simple wrapper script named
:file:`hg` somewhere in your ``PATH``:
to rg. How to do that varies based on the shell:
.. code-block:: sh
#!/bin/sh
exec kitty +kitten hyperlinked_grep "$@"
.. tab:: zsh
Then, for example, for ZSH, add the following to :file:`.zshrc`::
Instead of using an alias create a simple wrapper script named
:file:`hg` somewhere in your ``PATH``:
compdef _rg hg
.. code-block:: sh
#!/bin/sh
exec kitty +kitten hyperlinked_grep "$@"
Then, add the following to :file:`.zshrc`::
compdef _rg hg
.. tab:: fish
You can combine both the aliasing/wrapping and pointing fish
to rg's autocompletion with a fish "wrapper" function in your :file:`config.fish`:
.. code-block:: sh
function hg --wraps rg; kitty +kitten hyperlinked_grep $argv; end
To learn more about kitty's powerful framework for customizing URL click
actions, :doc:`see here </open_actions>`.

View File

@@ -35,6 +35,18 @@ Then you can simply use ``icat image.png`` to view images.
The ``icat`` kitten has various command line arguments to allow it to be used
from inside other programs to display images. In particular, :option:`--place`,
:option:`--detect-support` and :option:`--print-window-size`.
:option:`--detect-support`, :option:`--silent` and :option:`--print-window-size`.
If you are trying to integrate icat into a complex program like a file
manager or editor, there are a few things to keep in mind. icat works by
communicating over the TTY device, it both writes to and reads from the TTY.
So it is imperative that while it is running the host program does not do
any TTY I/O. Any key presses or other input from the user on the TTY device
will be discarded. At a minimum, you should use the :option:`--silent` and
:option:`--transfer-mode` command line arguments. To be
really robust you should consider writing proper support for the
:doc:`../graphics-protocol` in the program instead. Nowadays there are many
libraries that have support for it.
.. include:: /generated/cli-kitten-icat.rst

View File

@@ -128,33 +128,42 @@ The Splits Layout
This is the most flexible layout. You can create any arrangement of windows
by splitting exiting windows repeatedly. To best use this layout you should
define a few extra keybindings in :file:`kitty.conf`::
define a few extra key bindings in :file:`kitty.conf`::
# Create a new window splitting the space used by the existing one so that
# the two windows are placed one above the other
map F5 launch --location=hsplit
# Create a new window splitting the space used by the existing one so that
# the two windows are placed side by side
map F6 launch --location=vsplit
# Rotate the current split, chaging its split axis from vertical to
# horizontal or vice versa
map F7 layout_action rotate
# Move the active window in the indicated direction
map shift+up move_window up
map shift+left move_window left
map shift+right move_window right
map shift+down move_window down
# Switch focus to the neighboring window in the indicated direction
map ctrl+left neighboring_window left
map ctrl+right neighboring_window right
map ctrl+up neighboring_window up
map ctrl+down neighboring_window down
Now you can create horizontal and vertical splits by using :kbd:`F5` and
:kbd:`F6`. You can move them around using :kbd:`shift+arrow` keys
and you can move focus to neighboring windows using :kbd:`ctrl+arrow` keys.
You can switch an existing split from horizontal to vertical and vice versa
using :kbd:`F7`. Finally, windows can be resized using :ref:`window_resizing`.
Note that you can swap the windows in a split using the ``rotate`` action with
an argument of ``180`` and rotate and swap with an argument of ``270``.
Windows can be resized using :ref:`window_resizing`. You can swap the windows
in a split using the ``rotate`` action with an argument of ``180`` and rotate
and swap with an argument of ``270``.
This layout takes one option, ``split_axis`` that controls whether new windows
are placed into vertical or horizontal splits, by default::
are placed into vertical or horizontal splits when a ``--location`` is not
specified. A value of ``horizontal`` (same as ``--location=vsplit``)
means when a new split is created the two windows will be placed side by side
and a value of ``vertical`` (same as ``--location=hsplit``) means the two
windows will be placed one on top of the other. By default::
enabled_layouts splits:split_axis=horizontal

View File

@@ -100,3 +100,41 @@ lines. The various available criteria are:
``file``
A shell glob pattern that must match the filename, for example:
``image-??.png``
Scripting the opening of files with kitty on macOS
-------------------------------------------------------
On macOS you can use :guilabel:`Open With` in Finder or drag and drop files
onto the kitty dock icon to open them with kitty. The default action
is to open text files in your editor and images using the icat kitten.
Shell scripts are run in a shell. You can customize these actions by creating
a :file:`launch-actions.conf` file in the kitty config directory, just like
the :file:`open-actions.conf` file above. For example:
.. code:: conf
# Open script files based on their shebangs
protocol file
ext sh,command,tool
action launch --hold --type=os-window kitty +shebang $FILE_PATH {SHELL}
# Open shell specific script files
protocol file
ext fish,bash,zsh
action launch --hold --type=os-window kitty +shebang $FILE_PATH __ext__
# Open directories
protocol file
mime inode/directory
action launch --type=os-window --cwd $FILE_PATH
# Open text files without fragments in the editor
protocol file
mime text/*
action launch --type=os-window $EDITOR $FILE_PATH
# Open image files with icat
protocol file
mime image/*
action launch --type=os-window kitty +kitten icat --hold $FILE_PATH

View File

@@ -247,8 +247,7 @@ Would open the scrollback buffer in a new :term:`window` when you press the :kbd
key. See :sc:`show_scrollback <show_scrollback>` for details.
If you want to use it with an editor such as vim to get more powerful features,
you can see tips for doing so, in
`this thread <https://github.com/kovidgoyal/kitty/issues/719>`_.
you can see tips for doing so, in :iss:`this thread <719>`.
If you wish to store very large amounts of scrollback to view using the piping or
:sc:`show_scrollback <show_scrollback>` features, you can use the

5
docs/press-mentions.rst Normal file
View File

@@ -0,0 +1,5 @@
Press mentions of kitty
========================
`Console #88 <https://console.substack.com/p/console-88>`__
An interview with Kovid about kitty

View File

@@ -26,7 +26,7 @@ Features
to view it in a pager
* The current working directory or the command being executed are automatically
displayed in the kitty window titlebar/tab title.
displayed in the kitty window titlebar/tab title
* The text cursor is changed to a bar when editing commands at the shell prompt
@@ -58,7 +58,9 @@ no-cursor
Turn off changing of the text cursor to a bar when editing text
no-title
Turn off setting the kitty window/tab title based on shell state
Turn off setting the kitty window/tab title based on shell state.
Note that for the ``fish`` shell kitty relies on fish's native title
setting functionality instead.
no-prompt-mark
Turn off marking of prompts. This disables jumping to prompt, browsing
@@ -66,6 +68,8 @@ no-prompt-mark
no-complete
Turn off completion for the kitty command.
Note that for the ``fish`` shell this does not take effect, since fish
already comes with a kitty completion script.
More ways to browse command output
@@ -129,11 +133,6 @@ different shells.
and sources the original :file:`.zshenv`. It then loads the shell integration code.
The remainder of zsh's startup process proceeds as normal.
.. tab:: bash
For bash, kitty adds a couple of lines to the bottom of :file:`~/.bashrc`
(in an atomic manner) to load the shell integration code.
.. tab:: fish
For fish, to make it automatically load the integration code provided by
@@ -142,6 +141,12 @@ different shells.
process and will be cleaned up by the integration script after startup. No files
are added or modified.
.. tab:: bash
For bash, kitty adds a couple of lines to the bottom of :file:`~/.bashrc`
(in an atomic manner) to load the shell integration code.
Then, when launching the shell, kitty sets the environment variable
:envvar:`KITTY_SHELL_INTEGRATION` to the value of the :opt:`shell_integration`
option. The shell integration code reads the environment variable, turns on the
@@ -168,6 +173,7 @@ code used for each shell below:
.. literalinclude:: ../shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish
:language: fish
:force:
.. tab:: bash
@@ -197,15 +203,6 @@ First, in :file:`kitty.conf` set:
Then in your shell's rc file, add the lines:
.. tab:: bash
.. code-block:: sh
if test -n "$KITTY_INSTALLATION_DIR"; then
export KITTY_SHELL_INTEGRATION="enabled"
source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash"
fi
.. tab:: zsh
.. code-block:: sh
@@ -228,17 +225,35 @@ Then in your shell's rc file, add the lines:
end
.. tab:: bash
.. code-block:: sh
if test -n "$KITTY_INSTALLATION_DIR"; then
export KITTY_SHELL_INTEGRATION="enabled"
source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash"
fi
The value of :envvar:`KITTY_SHELL_INTEGRATION` is the same as that for
:opt:`shell_integration`, except if you want to disable shell integration
completely, in which case simply do not set the
:envvar:`KITTY_SHELL_INTEGRATION` variable at all.
If you want this to work while SSHing into a remote system, then you will
need to add some code to the snippets above to check if :code:`KITTY_INSTALLATION_DIR`
need to add some code to the snippets above to check if :envvar:`KITTY_INSTALLATION_DIR`
is empty and if so to set it to some hard coded location with the shell
integration scripts that need to be copied onto the remote system.
Integration with other shells
-------------------------------
There exist third-party integrations to use these features for various other shells:
* Jupyter console via a patch (:iss:`4475`)
* `xonsh <https://github.com/xonsh/xonsh/issues/4623>`__
Notes for shell developers
-----------------------------

View File

@@ -13,8 +13,8 @@ To set the underline style::
<ESC>[4:1m # this is a straight underline
<ESC>[4:2m # this is a double underline
<ESC>[4:3m # this is a curly underline
<ESC>[4:4m # this is a dotted underline (not implemented in kitty)
<ESC>[4:5m # this is a dashed underline (not implemented in kitty)
<ESC>[4:4m # this is a dotted underline
<ESC>[4:5m # this is a dashed underline
<ESC>[4m # this is a straight underline (for backwards compat)
<ESC>[24m # this is no underline (for backwards compat)

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python3
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess
from collections import defaultdict
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
@@ -42,7 +43,7 @@ def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str)
lines = []
for ch in type_map['flag']:
attr, allowed_values = keymap[ch]
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values)
q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
lines.append(f'''
case {attr}: {{
g.{attr} = screen->parser_buf[pos++] & 0xff;
@@ -67,7 +68,7 @@ def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any
flag_fmt, flag_attrs = [], []
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
for ch in type_map[atype]:
flag_fmt.append('s' + cv)
flag_fmt.append(f's{cv}')
attr = keymap[ch][0]
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
return ' '.join(flag_fmt), ', '.join(flag_attrs)
@@ -240,7 +241,7 @@ static inline void
def write_header(text: str, path: str) -> None:
with open(path, 'w') as f:
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
print('#pragma once', file=f)
print(text, file=f)
subprocess.check_call(['clang-format', '-i', path])

View File

@@ -238,7 +238,7 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
end = raw.index(end_q)
except ValueError:
raise SystemExit(f'Failed to find "{end_q}" in {path}')
raw = raw[:start] + start_q + '\n' + text + '\n' + raw[end:]
raw = f'{raw[:start]}{start_q}\n{text}\n{raw[end:]}'
f.seek(0)
f.truncate(0)
f.write(raw)
@@ -368,7 +368,7 @@ def generate_ctrl_mapping() -> None:
val = str(ctrl_mapping[k])
items.append(val)
if k in "\\'":
k = '\\' + k
k = f'\\{k}'
mi.append(f" case '{k}': return {val};")
for line_items in chunks(items, 6):

View File

@@ -49,11 +49,10 @@ class_maps: Dict[str, Set[int]] = {}
all_symbols: Set[int] = set()
name_map: Dict[int, str] = {}
word_search_map: DefaultDict[str, Set[int]] = defaultdict(set)
zwj = 0x200d
soft_hyphen = 0xad
flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26))
# See https://github.com/harfbuzz/harfbuzz/issues/169
marks = set(emoji_skin_tone_modifiers) | {zwj} | flag_codepoints
marks = set(emoji_skin_tone_modifiers) | flag_codepoints
not_assigned = set(range(0, sys.maxunicode))
property_maps: Dict[str, Set[int]] = defaultdict(set)
@@ -69,7 +68,6 @@ def parse_prop_list() -> None:
property_maps[name] |= chars
# see https://www.unicode.org/faq/unsup_char.html#3
marks |= property_maps['Other_Default_Ignorable_Code_Point']
marks.add(soft_hyphen)
def parse_ucd() -> None:
@@ -113,6 +111,12 @@ def parse_ucd() -> None:
marks.add(codepoint)
elif category.startswith('S'):
all_symbols.add(codepoint)
elif category == 'Cf':
# we add Cf to marks as it contains things like tags and zero
# width chars. Not sure if *all* of Cf should be treated as
# combining chars, might need to add individual exceptions in
# the future.
marks.add(codepoint)
with open('nerd-fonts-glyphs.txt') as f:
for line in f:
@@ -382,9 +386,9 @@ def gen_ucd() -> None:
ascii_range='false'
)
category_test(
'is_non_rendered_char', p, 'Cc Cs'.split(),
'is_non_rendered_char', p, 'Cc Cs Cf'.split(),
'Other_Default_Ignorable_Code_Point and soft hyphen',
extra_chars=property_maps['Other_Default_Ignorable_Code_Point'] | {soft_hyphen},
extra_chars=property_maps['Other_Default_Ignorable_Code_Point'] | set(range(0xfe00, 0xfe0f + 1)),
ascii_range='false'
)
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')

View File

@@ -264,6 +264,8 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla
}
}
static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
@interface GLFWHelper : NSObject
@end
@@ -280,6 +282,16 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla
(void)object;
}
// watch for settings change and rebuild global_shortcuts using key/value observing on NSUserDefaults
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
(void)keyPath; (void)object; (void)change; (void)context;
if (global_shortcuts != nil) {
[global_shortcuts release];
global_shortcuts = nil;
}
}
@end // GLFWHelper
// Delegate for application related notifications {{{
@@ -453,72 +465,253 @@ void* _glfwLoadLocalVulkanLoaderNS(void)
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
static bool
is_modified_tab(NSEvent *event, NSEventModifierFlags modifierFlags) {
switch ((NSUInteger)modifierFlags) {
// No need to handle shift+tab, [shift]+option+tab
case NSEventModifierFlagShift:
case NSEventModifierFlagOption:
case (NSEventModifierFlagShift | NSEventModifierFlagOption):
// Do not intercept cmd+tab, shift+cmd+tab
case NSEventModifierFlagCommand:
case (NSEventModifierFlagShift | NSEventModifierFlagCommand):
return false;
default:
break;
}
// ctrl+whatever+tab, option+cmd+tab
if (
(
(modifierFlags & NSEventModifierFlagControl) ||
modifierFlags == (NSEventModifierFlagOption | NSEventModifierFlagCommand)
) && [event.charactersIgnoringModifiers isEqualToString:@"\t"]
) return true;
// shift+whatever+tab
if (
(modifierFlags & NSEventModifierFlagShift) &&
[event.charactersIgnoringModifiers isEqualToString:@"\x19"]
) return true;
return false;
}
/**
* Apple Symbolic HotKeys Ids
* To find this symbolic hot keys indices do:
* 1. open Terminal
* 2. restore defaults in System Preferences > Keyboard > Shortcuts
* 3. defaults read com.apple.symbolichotkeys > current.txt
* 4. enable/disable given symbolic hot key in System Preferences > Keyboard > Shortcuts
* 5. defaults read com.apple.symbolichotkeys | diff -C 5 current.txt -
* 6. restore defaults in System Preferences > Keyboard > Shortcuts
*/
typedef enum AppleShortcutNames {
// launchpad & dock
kSHKTurnDockHidingOnOrOff = 52, // Opt, Cmd, D
kSHKShowLaunchpad = 160, //
// display
kSHKDecreaseDisplayBrightness1 = 53, // F14 (Fn)
kSHKDecreaseDisplayBrightness2 = 55, // F14 (Fn, Ctrl)
kSHKIncreaseDisplayBrightness1 = 54, // F15 (Fn)
kSHKIncreaseDisplayBrightness2 = 56, // F15 (Fn, Ctrl)
// mission control
kSHKMissionControl = 32, // Ctrl, Arrow Up
kSHKShowNotificationCenter = 163, //
kSHKTurnDoNotDisturbOnOrOff = 175, //
kSHKApplicationWindows = 33, // Ctrl, Arrow Down
kSHKShowDesktop = 36, // F11
kSHKMoveLeftASpace = 79, // Ctrl, Arrow Left
kSHKMoveRightASpace = 81, // Ctrl, Arrow Right
kSHKSwitchToDesktop1 = 118, // Ctrl, 1
kSHKSwitchToDesktop2 = 119, // Ctrl, 2
kSHKSwitchToDesktop3 = 120, // Ctrl, 3
kSHKSwitchToDesktop4 = 121, // Ctrl, 4
kSHKQuickNote = 190, // Fn, Q
// keyboard
kSHKChangeTheWayTabMovesFocus = 13, // Ctrl, F7
kSHKTurnKeyboardAccessOnOrOff = 12, // Ctrl, F1
kSHKMoveFocusToTheMenuBar = 7, // Ctrl, F2
kSHKMoveFocusToTheDock = 8, // Ctrl, F3
kSHKMoveFocusToActiveOrNextWindow = 9, // Ctrl, F4
kSHKMoveFocusToTheWindowToolbar = 10, // Ctrl, F5
kSHKMoveFocusToTheFloatingWindow = 11, // Ctrl, F6
kSHKMoveFocusToNextWindow = 27, // Cmd, `
kSHKMoveFocusToStatusMenus = 57, // Ctrl, F8
// input sources
kSHKSelectThePreviousInputSource = 60, // Ctrl, Space bar
kSHKSelectNextSourceInInputMenu = 61, // Ctrl, Opt, Space bar
// screenshots
kSHKSavePictureOfScreenAsAFile = 28, // Shift, Cmd, 3
kSHKCopyPictureOfScreenToTheClipboard = 29, // Ctrl, Shift, Cmd, 3
kSHKSavePictureOfSelectedAreaAsAFile = 30, // Shift, Cmd, 4
kSHKCopyPictureOfSelectedAreaToTheClipboard = 31, // Ctrl, Shift, Cmd, 4
kSHKScreenshotAndRecordingOptions = 184, // Shift, Cmd, 5
// spotlight
kSHKShowSpotlightSearch = 64, // Cmd, Space bar
kSHKShowFinderSearchWindow = 65, // Opt, Cmd, Space bar
// accessibility
kSHKTurnZoomOnOrOff = 15, // Opt, Cmd, 8
kSHKTurnImageSmoothingOnOrOff = 23, // Opt, Cmd, Backslash "\"
kSHKZoomOut = 19, // Opt, Cmd, -
kSHKZoomIn = 17, // Opt, Cmd, =
kSHKTurnFocusFollowingOnOrOff = 179, //
kSHKIncreaseContrast = 25, // Ctrl, Opt, Cmd, .
kSHKDecreaseContrast = 26, // Ctrl, Opt, Cmd, ,
kSHKInvertColors = 21, // Ctrl, Opt, Cmd, 8
kSHKTurnVoiceOverOnOrOff = 59, // Cmd, F5
kSHKShowAccessibilityControls = 162, // Opt, Cmd, F5
// app shortcuts
kSHKShowHelpMenu = 98, // Shift, Cmd, /
// deprecated (Not shown on macOS Monterey)
kSHKMoveFocusToTheWindowDrawer = 51, // Opt, Cmd, `
kSHKShowDashboard = 62, // F12
kSHKLookUpInDictionary = 70, // Shift, Cmd, E
kSHKHideAndShowFrontRow = 73, // Cmd, Esc
kSHKActivateSpaces = 75, // F8
// unknown
kSHKUnknown = 0, //
} AppleShortcutNames;
static bool
is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) {
if (modifierFlags != NSEventModifierFlagCommand) return false;
if ([event.charactersIgnoringModifiers isEqualToString:@"."]) return true;
return false;
is_shiftable_shortcut(int scv) {
return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow;
}
static bool
is_modified_special_key(NSEvent *event, NSEventModifierFlags modifierFlags) {
// really one should use [[NSUserDefaults standardUserDefaults] valueForDefaultsDomain:@"com.apple.symbolichotkeys" key:@"AppleSymbolicHotKeys"]
// to get the list of global shortcuts and pass through the important ones,
// see https://stackoverflow.com/questions/21878482/what-do-the-parameter-values-in-applesymbolichotkeys-plist-dict-represent
// however given that in order to know which integers are which actions in that dict one needs reverse engineering
// see https://stackoverflow.com/questions/866056/how-do-i-programmatically-get-the-shortcut-keys-reserved-by-mac-os-x
// it's too much effort.
if ([event.charactersIgnoringModifiers length] != 1) return false;
const unichar ch = [event.charactersIgnoringModifiers characterAtIndex:0];
if (modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift)) {
switch (ch) {
case 0x1b: // Esc
case NSF1FunctionKey: case NSF2FunctionKey: case NSF3FunctionKey: case NSF4FunctionKey:
case NSF5FunctionKey: case NSF6FunctionKey: case NSF7FunctionKey: case NSF8FunctionKey:
case NSF9FunctionKey: case NSF10FunctionKey: case NSF11FunctionKey: case NSF12FunctionKey:
case NSF13FunctionKey: case NSF14FunctionKey: case NSF15FunctionKey: case NSF16FunctionKey:
case NSF17FunctionKey: case NSF18FunctionKey: case NSF19FunctionKey:
return true;
#define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl))
static void
build_global_shortcuts_lookup(void) {
NSMutableDictionary<NSString*, NSNumber*> *temp = [NSMutableDictionary dictionaryWithCapacity:128]; // will be autoreleased
NSDictionary *apple_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.symbolichotkeys"];
if (apple_settings) {
NSDictionary<NSString*, id> *symbolic_hotkeys = [apple_settings objectForKey:@"AppleSymbolicHotKeys"];
if (symbolic_hotkeys) {
for (NSString *key in symbolic_hotkeys) {
id obj = symbolic_hotkeys[key];
if (![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSDictionary class]]) continue;
NSInteger sc = [key integerValue];
NSDictionary *sc_value = obj;
id enabled = [sc_value objectForKey:@"enabled"];
if (!enabled || ![enabled isKindOfClass:[NSNumber class]] || ![(NSNumber*)enabled boolValue]) continue;
id v = [sc_value objectForKey:@"value"];
if (!v || ![v isKindOfClass:[NSDictionary class]]) continue;
NSDictionary *value = v;
id t = [value objectForKey:@"type"];
if (!t || ![t isKindOfClass:[NSString class]] || ![t isEqualToString:@"standard"]) continue;
id p = [value objectForKey:@"parameters"];
if (!p || ![p isKindOfClass:[NSArray class]] || [(NSArray*)p count] < 2) continue;
NSArray<NSNumber*> *parameters = p;
NSInteger ch = [parameters[0] isKindOfClass:[NSNumber class]] ? [parameters[0] integerValue] : 0xffff;
NSInteger vk = [parameters[1] isKindOfClass:[NSNumber class]] ? [parameters[1] integerValue] : 0xffff;
NSEventModifierFlags mods = ([parameters count] > 2 && [parameters[2] isKindOfClass:[NSNumber class]]) ? [parameters[2] unsignedIntegerValue] : 0;
mods = USEFUL_MODS(mods);
static char buf[64];
#define S(x, k) snprintf(buf, sizeof(buf) - 1, #x":%lx:%ld", (unsigned long)mods, (long)k)
if (ch == 0xffff) { if (vk == 0xffff) continue; S(v, vk); } else S(c, ch);
temp[@(buf)] = @(sc);
// the move to next window shortcuts also respond to the same shortcut + shift
if (is_shiftable_shortcut([key intValue]) && !(mods & NSEventModifierFlagShift)) {
mods |= NSEventModifierFlagShift;
if (ch == 0xffff) S(v, vk); else S(c, ch);
temp[@(buf)] = @(sc);
}
#undef S
}
}
}
switch (ch) {
case 0x1b: // Esc
if (modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagControl)) return true;
break;
case NSHelpFunctionKey: // For some reason keyboards with an insert key have it mapped to help
if (!modifierFlags || modifierFlags == NSEventModifierFlagShift) return true;
break;
global_shortcuts = [[NSDictionary dictionaryWithDictionary:temp] retain];
/* NSLog(@"global_shortcuts: %@", global_shortcuts); */
}
static int
is_active_apple_global_shortcut(NSEvent *event) {
if (global_shortcuts == nil) build_global_shortcuts_lookup();
NSEventModifierFlags modifierFlags = USEFUL_MODS([event modifierFlags]);
static char lookup_key[64];
#define LOOKUP(t, k) \
snprintf(lookup_key, sizeof(lookup_key) - 1, #t":%lx:%ld", (unsigned long)modifierFlags, (long)k); \
NSNumber *sc = global_shortcuts[@(lookup_key)]; \
if (sc != nil) return [sc intValue];
if ([event.charactersIgnoringModifiers length] == 1) {
if (modifierFlags & NSEventModifierFlagShift) {
const uint32_t ch_without_shift = vk_to_unicode_key_with_current_layout([event keyCode]);
if (ch_without_shift < GLFW_FKEY_FIRST || ch_without_shift > GLFW_FKEY_LAST) {
LOOKUP(c, ch_without_shift);
}
}
const unichar ch = [event.charactersIgnoringModifiers characterAtIndex:0];
LOOKUP(c, ch);
}
unsigned short vk = [event keyCode];
if (vk != 0xffff) {
LOOKUP(v, vk);
}
#undef LOOKUP
return kSHKUnknown;
}
static bool
is_useful_apple_global_shortcut(int sc) {
switch(sc) {
// launchpad & dock
case kSHKTurnDockHidingOnOrOff: // Opt, Cmd, D
case kSHKShowLaunchpad: //
// display
case kSHKDecreaseDisplayBrightness1: // F14 (Fn)
case kSHKDecreaseDisplayBrightness2: // F14 (Fn, Ctrl)
case kSHKIncreaseDisplayBrightness1: // F15 (Fn)
case kSHKIncreaseDisplayBrightness2: // F14 (Fn, Ctrl)
// mission control
case kSHKMissionControl: // Ctrl, Arrow Up
case kSHKShowNotificationCenter: //
case kSHKTurnDoNotDisturbOnOrOff: //
case kSHKApplicationWindows: // Ctrl, Arrow Down
case kSHKShowDesktop: // F11
case kSHKMoveLeftASpace: // Ctrl, Arrow Left
case kSHKMoveRightASpace: // Ctrl, Arrow Right
case kSHKSwitchToDesktop1: // Ctrl, 1
case kSHKSwitchToDesktop2: // Ctrl, 2
case kSHKSwitchToDesktop3: // Ctrl, 3
case kSHKSwitchToDesktop4: // Ctrl, 4
case kSHKQuickNote: // Fn, Q
// keyboard
/* case kSHKChangeTheWayTabMovesFocus: // Ctrl, F7 */
/* case kSHKTurnKeyboardAccessOnOrOff: // Ctrl, F1 */
case kSHKMoveFocusToTheMenuBar: // Ctrl, F2
case kSHKMoveFocusToTheDock: // Ctrl, F3
case kSHKMoveFocusToActiveOrNextWindow: // Ctrl, F4
/* case kSHKMoveFocusToTheWindowToolbar: // Ctrl, F5 */
/* case kSHKMoveFocusToTheFloatingWindow: // Ctrl, F6 */
case kSHKMoveFocusToNextWindow: // Cmd, `
case kSHKMoveFocusToStatusMenus: // Ctrl, F8
// input sources
case kSHKSelectThePreviousInputSource: // Ctrl, Space bar
case kSHKSelectNextSourceInInputMenu: // Ctrl, Opt, Space bar
// screenshots
/* case kSHKSavePictureOfScreenAsAFile: // Shift, Cmd, 3 */
/* case kSHKCopyPictureOfScreenToTheClipboard: // Ctrl, Shift, Cmd, 3 */
/* case kSHKSavePictureOfSelectedAreaAsAFile: // Shift, Cmd, 4 */
/* case kSHKCopyPictureOfSelectedAreaToTheClipboard: // Ctrl, Shift, Cmd, 4 */
/* case kSHKScreenshotAndRecordingOptions: // Shift, Cmd, 5 */
// spotlight
case kSHKShowSpotlightSearch: // Cmd, Space bar
case kSHKShowFinderSearchWindow: // Opt, Cmd, Space bar
// accessibility
/* case kSHKTurnZoomOnOrOff: // Opt, Cmd, 8 */
/* case kSHKTurnImageSmoothingOnOrOff: // Opt, Cmd, Backslash "\" */
/* case kSHKZoomOut: // Opt, Cmd, - */
/* case kSHKZoomIn: // Opt, Cmd, = */
/* case kSHKTurnFocusFollowingOnOrOff: // */
/* case kSHKIncreaseContrast: // Ctrl, Opt, Cmd, . */
/* case kSHKDecreaseContrast: // Ctrl, Opt, Cmd, , */
/* case kSHKInvertColors: // Ctrl, Opt, Cmd, 8 */
/* case kSHKTurnVoiceOverOnOrOff: // Cmd, F5 */
/* case kSHKShowAccessibilityControls: // Opt, Cmd, F5 */
// app shortcuts
/* case kSHKShowHelpMenu: // Shift, Cmd, / */
// deprecated (Not shown on macOS Monterey)
/* case kSHKMoveFocusToTheWindowDrawer: // Opt, Cmd, ` */
/* case kSHKShowDashboard: // F12 */
/* case kSHKLookUpInDictionary: // Shift, Cmd, E */
/* case kSHKHideAndShowFrontRow: // Cmd, Esc */
/* case kSHKActivateSpaces: // F8 */
return true;
default:
return false;
}
return false;
}
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
@@ -557,35 +750,65 @@ int _glfwPlatformInit(void)
}
[NSApp setDelegate:_glfw.ns.delegate];
static struct {
unsigned short virtual_key_code;
NSTimeInterval timestamp;
} last_keydown_shortcut_event;
last_keydown_shortcut_event.virtual_key_code = 0xffff;
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
NSEventModifierFlags modifierFlags = [event modifierFlags] & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl);
if (is_modified_special_key(event, modifierFlags) || is_modified_tab(event, modifierFlags) || is_cmd_period(event, modifierFlags)) {
// Cocoa swallows various key presses, so route them explicitly
[[NSApp keyWindow].contentView keyDown:event];
return nil;
debug_key("---------------- key down -------------------\n");
debug_key("%s\n", [[event description] UTF8String]);
if (!_glfw.ignoreOSKeyboardProcessing) {
// first check if there is a global menu bar shortcut
if ([[NSApp mainMenu] performKeyEquivalent:event]) {
debug_key("keyDown triggerred global menu bar action ignoring\n");
last_keydown_shortcut_event.virtual_key_code = [event keyCode];
last_keydown_shortcut_event.timestamp = [event timestamp];
return nil;
}
// now check if there is a useful apple shortcut
int global_shortcut = is_active_apple_global_shortcut(event);
if (is_useful_apple_global_shortcut(global_shortcut)) {
debug_key("keyDown triggerred global macOS shortcut ignoring\n");
last_keydown_shortcut_event.virtual_key_code = [event keyCode];
last_keydown_shortcut_event.timestamp = [event timestamp];
return event;
}
}
return event;
last_keydown_shortcut_event.virtual_key_code = 0xffff;
NSWindow *kw = [NSApp keyWindow];
if (kw && kw.contentView) [kw.contentView keyDown:event];
else debug_key("keyDown ignored as no keyWindow present\n");
return nil;
};
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
NSEventModifierFlags modifierFlags = [event modifierFlags] & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl);
if (modifierFlags & NSEventModifierFlagCommand) {
// From https://cocoadev.github.io/GameKeyboardHandlingAlmost/
// This works around an AppKit bug, where key up events while holding
// down the command key don't get sent to the key window.
[[NSApp keyWindow] sendEvent:event];
}
if (is_modified_special_key(event, modifierFlags) || is_modified_tab(event, modifierFlags) || is_cmd_period(event, modifierFlags)) {
// Cocoa swallows various key presses, so route them explicitly
[[NSApp keyWindow].contentView keyUp:event];
debug_key("----------------- key up --------------------\n");
debug_key("%s\n", [[event description] UTF8String]);
if (last_keydown_shortcut_event.virtual_key_code != 0xffff && last_keydown_shortcut_event.virtual_key_code == [event keyCode]) {
// ignore as the corresponding key down event triggered a menu bar or macOS shortcut
last_keydown_shortcut_event.virtual_key_code = 0xffff;
debug_key("keyUp ignored as corresponds to previous keyDown that trigerred a shortcut\n");
return nil;
}
NSWindow *kw = [NSApp keyWindow];
if (kw && kw.contentView) [kw.contentView keyUp:event];
else debug_key("keyUp ignored as no keyWindow present\n");
return nil;
};
return event;
NSEvent* (^flags_changed_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
debug_key("-------------- flags changed -----------------\n");
debug_key("%s\n", [[event description] UTF8String]);
last_keydown_shortcut_event.virtual_key_code = 0xffff;
NSWindow *kw = [NSApp keyWindow];
if (kw && kw.contentView) [kw.contentView flagsChanged:event];
else debug_key("flagsChanged ignored as no keyWindow present\n");
return nil;
};
_glfw.ns.keyUpMonitor =
@@ -594,6 +817,9 @@ int _glfwPlatformInit(void)
_glfw.ns.keyDownMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
handler:keydown_block];
_glfw.ns.flagsChangedMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged
handler:flags_changed_block];
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
@@ -606,6 +832,13 @@ int _glfwPlatformInit(void)
};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
NSUserDefaults *apple_settings = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"];
[apple_settings addObserver:_glfw.ns.helper
forKeyPath:@"AppleSymbolicHotKeys"
options:NSKeyValueObservingOptionNew
context:NULL];
_glfw.ns.appleSettings = apple_settings;
[[NSNotificationCenter defaultCenter]
addObserver:_glfw.ns.helper
selector:@selector(selectedKeyboardInputSourceChanged:)
@@ -661,6 +894,8 @@ void _glfwPlatformTerminate(void)
object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:_glfw.ns.helper];
if (_glfw.ns.appleSettings)
[_glfw.ns.appleSettings removeObserver:_glfw.ns.helper forKeyPath:@"AppleSymbolicHotKeys"];
[_glfw.ns.helper release];
_glfw.ns.helper = nil;
}
@@ -669,10 +904,18 @@ void _glfwPlatformTerminate(void)
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
if (_glfw.ns.keyDownMonitor)
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
if (_glfw.ns.flagsChangedMonitor)
[NSEvent removeMonitor:_glfw.ns.flagsChangedMonitor];
if (_glfw.ns.appleSettings != nil) {
[_glfw.ns.appleSettings release];
_glfw.ns.appleSettings = nil;
}
free(_glfw.ns.clipboardString);
_glfwTerminateNSGL();
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }
} // autoreleasepool
}

View File

@@ -64,6 +64,7 @@ typedef void* CVDisplayLinkRef;
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
#endif
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
@@ -173,8 +174,8 @@ typedef struct _GLFWlibraryNS
IOHIDManagerRef hidManager;
id unicodeData;
id helper;
id keyUpMonitor;
id keyDownMonitor;
id keyUpMonitor, keyDownMonitor, flagsChangedMonitor;
id appleSettings;
id nibObjects;
char keyName[64];
@@ -231,7 +232,6 @@ typedef struct _GLFWtimerNS
} _GLFWtimerNS;
void _glfwPollMonitorsNS(void);
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
@@ -248,3 +248,4 @@ void _glfwShutdownCVDisplayLink(unsigned long long, void*);
void _glfwCocoaPostEmptyEvent(void);
void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry);
_GLFWDisplayLinkNS* _glfw_create_display_link(CGDirectDisplayID);
uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode);

View File

@@ -32,11 +32,6 @@
#include <float.h>
#include <string.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300)
#define NSControlStateValueOn NSOnState
#define NSControlStateValueOff NSOffState
#define NSControlStateValueMixed NSMixedState
#endif
static const char*
polymorphic_string_as_utf8(id string) {
@@ -259,7 +254,7 @@ is_pua_char(uint32_t ch) {
return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
static uint32_t
uint32_t
vk_to_unicode_key_with_current_layout(uint16_t keycode)
{
UInt32 dead_key_state = 0;
@@ -513,8 +508,6 @@ translateFlags(NSUInteger flags)
return mods;
}
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
static const char*
format_mods(int mods) {
static char buf[128];
@@ -576,166 +569,11 @@ get_window_size_without_border_in_logical_pixels(_GLFWwindow *window) {
return [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
}
// Translate a GLFW keycode to a Cocoa modifier flag
//
static NSUInteger
translateKeyToModifierFlag(uint32_t key)
{
switch (key)
{
case GLFW_FKEY_LEFT_SHIFT:
case GLFW_FKEY_RIGHT_SHIFT:
return NSEventModifierFlagShift;
case GLFW_FKEY_LEFT_CONTROL:
case GLFW_FKEY_RIGHT_CONTROL:
return NSEventModifierFlagControl;
case GLFW_FKEY_LEFT_ALT:
case GLFW_FKEY_RIGHT_ALT:
return NSEventModifierFlagOption;
case GLFW_FKEY_LEFT_SUPER:
case GLFW_FKEY_RIGHT_SUPER:
return NSEventModifierFlagCommand;
case GLFW_FKEY_CAPS_LOCK:
return NSEventModifierFlagCapsLock;
}
return 0;
}
// Defines a constant for empty ranges in NSTextInputClient
//
static const NSRange kEmptyRange = { NSNotFound, 0 };
// SecureKeyboardEntryController {{{
@interface SecureKeyboardEntryController : NSObject
@property (nonatomic, readonly) BOOL isDesired;
@property (nonatomic, readonly, getter=isEnabled) BOOL enabled;
+ (instancetype)sharedInstance;
- (void)toggle;
- (void)update;
@end
@implementation SecureKeyboardEntryController {
int _count;
BOOL _desired;
}
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_desired = false;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidResignActive:)
name:NSApplicationDidResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:NSApplicationDidBecomeActiveNotification
object:nil];
if ([NSApp isActive]) {
[self update];
}
}
return self;
}
#pragma mark - API
- (void)toggle {
// Set _desired to the opposite of the current state.
_desired = !_desired;
debug_key("toggle called. Setting desired to %d", _desired);
// Try to set the system's state of secure input to the desired state.
[self update];
}
- (BOOL)isEnabled {
return !!IsSecureEventInputEnabled();
}
- (BOOL)isDesired {
return _desired;
}
#pragma mark - Notifications
- (void)applicationDidResignActive:(NSNotification *)notification {
(void)notification;
if (_count > 0) {
debug_key("Application resigning active.");
[self update];
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
(void)notification;
if (self.isDesired) {
debug_key("Application became active.");
[self update];
}
}
#pragma mark - Private
- (BOOL)allowed {
return [NSApp isActive];
}
- (void)update {
debug_key("Update secure keyboard entry. desired=%d active=%d\n",
(int)self.isDesired, (int)[NSApp isActive]);
const BOOL secure = self.isDesired && [self allowed];
if (secure && _count > 0) {
debug_key("Want to turn on secure input but it's already on\n");
return;
}
if (!secure && _count == 0) {
debug_key("Want to turn off secure input but it's already off\n");
return;
}
debug_key("Before: IsSecureEventInputEnabled returns %d ", (int)self.isEnabled);
if (secure) {
OSErr err = EnableSecureEventInput();
debug_key("EnableSecureEventInput err=%d ", (int)err);
if (err) {
debug_key("EnableSecureEventInput failed with error %d ", (int)err);
} else {
_count += 1;
}
} else {
OSErr err = DisableSecureEventInput();
debug_key("DisableSecureEventInput err=%d ", (int)err);
if (err) {
debug_key("DisableSecureEventInput failed with error %d ", (int)err);
} else {
_count -= 1;
}
}
debug_key("After: IsSecureEventInputEnabled returns %d\n", (int)self.isEnabled);
}
@end
// }}}
// Delegate for window related notifications {{{
@interface GLFWWindowDelegate : NSObject
@@ -866,9 +704,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
_glfwInputWindowFocus(window, false);
// IME is cancelled when losing the focus
if ([window->ns.view hasMarkedText]) {
[[window->ns.view inputContext] discardMarkedText];
[window->ns.view unmarkText];
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
_glfwInputKeyboard(window, &dummy);
_glfw.ns.text[0] = 0;
}
}
@@ -884,6 +724,20 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
@end // }}}
// Text input context class for the GLFW content view {{{
@interface GLFWTextInputContext : NSTextInputContext
@end
@implementation GLFWTextInputContext
- (void)doCommandBySelector:(SEL)selector
{
// interpretKeyEvents: May call insertText: or doCommandBySelector:.
// With the default macOS keybindings, pressing certain key combinations
// (e.g. Ctrl+/, Ctrl+Cmd+Down/Left/Right) will produce a beep sound.
debug_key("\n\tTextInputCtx: doCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
}
@end // }}}
// Content view class for the GLFW window {{{
@@ -891,9 +745,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
{
_GLFWwindow* window;
NSTrackingArea* trackingArea;
GLFWTextInputContext* input_context;
NSMutableAttributedString* markedText;
NSRect markedRect;
bool marked_text_cleared_by_insert;
int in_key_handler;
NSString *input_source_at_last_key_event;
}
@@ -911,9 +767,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
{
window = initWindow;
trackingArea = nil;
input_context = [[GLFWTextInputContext alloc] initWithClient:self];
markedText = [[NSMutableAttributedString alloc] init];
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
input_source_at_last_key_event = nil;
in_key_handler = 0;
[self updateTrackingAreas];
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
@@ -927,6 +785,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[trackingArea release];
[markedText release];
if (input_source_at_last_key_event) [input_source_at_last_key_event release];
[input_context release];
[super dealloc];
}
@@ -1166,6 +1025,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[super updateTrackingAreas];
}
- (NSTextInputContext *)inputContext
{
return input_context;
}
static UInt32
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
UInt32 mods = 0;
@@ -1231,15 +1095,16 @@ is_ascii_control_char(char x) {
#define UPDATE_PRE_EDIT_TEXT glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent);
const bool previous_has_marked_text = [self hasMarkedText];
NSTextInputContext *inpctx = [NSTextInputContext currentInputContext];
if (inpctx && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:inpctx.selectedKeyboardInputSource])) {
if (input_context && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:input_context.selectedKeyboardInputSource])) {
if (input_source_at_last_key_event) {
debug_key("Input source changed, clearing pre-edit text and resetting deadkey state\n");
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
window->ns.deadKeyState = 0;
_glfwInputKeyboard(window, &dummy); // clear pre-edit text
[input_source_at_last_key_event release];
input_source_at_last_key_event = nil;
}
input_source_at_last_key_event = [inpctx.selectedKeyboardInputSource retain];
input_source_at_last_key_event = [input_context.selectedKeyboardInputSource retain];
[self unmarkText];
}
@@ -1247,7 +1112,7 @@ is_ascii_control_char(char x) {
const NSUInteger flags = [event modifierFlags];
const int mods = translateFlags(flags);
const uint32_t key = translateKey(keycode, true);
const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1;
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1);
_glfw.ns.text[0] = 0;
if (keycode == 0x33 /* backspace */ || keycode == 0x35 /* escape */) [self unmarkText];
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_PRESS, .mods = mods};
@@ -1257,7 +1122,7 @@ is_ascii_control_char(char x) {
// keyboard unicode data is not available.
if (process_text) {
// this will call insertText with the text for this event, if any
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
[self interpretKeyEvents:@[event]];
}
} else {
static UniChar text[256];
@@ -1284,8 +1149,10 @@ is_ascii_control_char(char x) {
keycode, safe_name_for_keycode(keycode), key, format_mods(mods), char_count, window->ns.deadKeyState, event.ARepeat);
marked_text_cleared_by_insert = false;
if (process_text) {
in_key_handler = 1;
// this will call insertText which will fill up _glfw.ns.text
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
[self interpretKeyEvents:@[event]];
in_key_handler = 0;
} else {
window->ns.deadKeyState = 0;
}
@@ -1314,6 +1181,7 @@ is_ascii_control_char(char x) {
}
if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) {
// do not pass keys like BACKSPACE while there's pre-edit text, let IME handle it
debug_key("Ignoring key press as IME is active and it generated no text\n");
return;
}
}
@@ -1329,35 +1197,60 @@ is_ascii_control_char(char x) {
// insertText followed by setMarkedText
UPDATE_PRE_EDIT_TEXT;
}
#undef CLEAR_PRE_EDIT_TEXT
#undef UPDATE_PRE_EDIT_TEXT
}
- (void)flagsChanged:(NSEvent *)event
{
int action = GLFW_RELEASE;
const unsigned int modifierFlags =
[event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
const uint32_t key = translateKey([event keyCode], false);
const char old_first_char = _glfw.ns.text[0];
_glfw.ns.text[0] = 0;
const NSUInteger modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
const uint32_t key = vk_code_to_functional_key_code([event keyCode]);
const unsigned int keycode = [event keyCode];
const int mods = translateFlags(modifierFlags);
const NSUInteger keyFlag = translateKeyToModifierFlag(key);
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, modifierFlags) != 1);
const char *mod_name = "unknown";
if (keyFlag & modifierFlags)
{
int current_action = GLFW_RELEASE;
for (unsigned i = 0; i < arraysz(window->activated_keys); i++) {
if (window->activated_keys[i].key == key) {
current_action = window->activated_keys[i].action;
break;
}
}
if (current_action == GLFW_PRESS)
action = GLFW_RELEASE;
else
action = GLFW_PRESS;
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;
default:
return;
}
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = [event keyCode], .native_key_id = [event keyCode], .action = action, .mods = mods};
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));
marked_text_cleared_by_insert = false;
if (process_text && input_context) {
// this will call insertText which will fill up _glfw.ns.text
in_key_handler = 2;
[input_context handleEvent:event];
in_key_handler = 0;
if (marked_text_cleared_by_insert) {
debug_key("Clearing pre-edit text because insertText called from flagsChanged\n");
CLEAR_PRE_EDIT_TEXT;
if (_glfw.ns.text[0]) glfw_keyevent.text = _glfw.ns.text;
else _glfw.ns.text[0] = old_first_char;
}
}
glfw_keyevent.ime_state = GLFW_IME_NONE;
_glfwInputKeyboard(window, &glfw_keyevent);
}
@@ -1374,6 +1267,9 @@ is_ascii_control_char(char x) {
_glfwInputKeyboard(window, &glfw_keyevent);
}
#undef CLEAR_PRE_EDIT_TEXT
#undef UPDATE_PRE_EDIT_TEXT
- (void)scrollWheel:(NSEvent *)event
{
double deltaX = [event scrollingDeltaX];
@@ -1472,8 +1368,19 @@ is_ascii_control_char(char x) {
selectedRange:(NSRange)selectedRange
replacementRange:(NSRange)replacementRange
{
debug_key("\n\tsetMarkedText: %s selectedRange: (%lu, %lu) replacementRange: (%lu, %lu)\n", polymorphic_string_as_utf8(string), selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
if (string == nil) { [self unmarkText]; return; }
const char *s = polymorphic_string_as_utf8(string);
debug_key("\n\tsetMarkedText: %s selectedRange: (%lu, %lu) replacementRange: (%lu, %lu)\n", s, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
if (string == nil || !s[0]) {
bool had_marked_text = [self hasMarkedText];
[self unmarkText];
if (had_marked_text && (!in_key_handler || in_key_handler == 2)) {
debug_key("Clearing pre-edit because setMarkedText called from %s\n", in_key_handler ? "flagsChanged" : "event loop");
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
_glfwInputKeyboard(window, &glfw_keyevent);
_glfw.ns.text[0] = 0;
}
return;
}
if ([string isKindOfClass:[NSAttributedString class]]) {
if (((NSMutableAttributedString*)string).length == 0) { [self unmarkText]; return; }
[markedText release];
@@ -1483,6 +1390,12 @@ is_ascii_control_char(char x) {
[markedText release];
markedText = [[NSMutableAttributedString alloc] initWithString:string];
}
if (!in_key_handler || in_key_handler == 2) {
debug_key("Updating IME text in kitty from setMarkedText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
GLFWkeyevent glfw_keyevent = {.text=[[markedText string] UTF8String], .ime_state = GLFW_IME_PREEDIT_CHANGED};
_glfwInputKeyboard(window, &glfw_keyevent);
_glfw.ns.text[0] = 0;
}
}
- (void)unmarkText
@@ -1491,15 +1404,23 @@ is_ascii_control_char(char x) {
}
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
[w->ns.view updateIMEStateFor: ev->type left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height];
[w->ns.view updateIMEStateFor: ev->type focused:(bool)ev->focused left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height];
}
- (void)updateIMEStateFor:(GLFWIMEUpdateType)which
focused:(bool)focused
left:(CGFloat)left
top:(CGFloat)top
cellWidth:(CGFloat)cellWidth
cellHeight:(CGFloat)cellHeight
{
if (which == GLFW_IME_UPDATE_FOCUS && !focused && [self hasMarkedText] && window) {
[input_context discardMarkedText];
[self unmarkText];
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
_glfwInputKeyboard(window, &glfw_keyevent);
_glfw.ns.text[0] = 0;
}
if (which != GLFW_IME_UPDATE_CURSOR_POSITION) return;
left /= window->ns.xscale;
top /= window->ns.yscale;
@@ -1543,14 +1464,28 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
{
const char *utf8 = polymorphic_string_as_utf8(string);
debug_key("\n\tinsertText: %s replacementRange: (%lu, %lu)\n", utf8, replacementRange.location, replacementRange.length);
if ([self hasMarkedText]) {
if ([self hasMarkedText] && !is_ascii_control_char(utf8[0])) {
[self unmarkText];
marked_text_cleared_by_insert = true;
if (!in_key_handler) {
debug_key("Clearing pre-edit because insertText called from event loop\n");
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
_glfwInputKeyboard(window, &glfw_keyevent);
_glfw.ns.text[0] = 0;
}
}
// insertText can be called multiple times for a single key event
char *s = _glfw.ns.text + strnlen(_glfw.ns.text, sizeof(_glfw.ns.text));
snprintf(s, sizeof(_glfw.ns.text) - (s - _glfw.ns.text), "%s", utf8);
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
if ((!in_key_handler || in_key_handler == 2) && _glfw.ns.text[0]) {
if (!is_ascii_control_char(_glfw.ns.text[0])) {
debug_key("Sending text to kitty from insertText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
GLFWkeyevent glfw_keyevent = {.text=_glfw.ns.text, .ime_state=GLFW_IME_COMMIT_TEXT};
_glfwInputKeyboard(window, &glfw_keyevent);
}
_glfw.ns.text[0] = 0;
}
}
- (void)doCommandBySelector:(SEL)selector
@@ -1597,19 +1532,6 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
- (BOOL)validateMenuItem:(NSMenuItem *)item {
if (item.action == @selector(performMiniaturize:)) return YES;
if (item.action == @selector(toggleSecureInput:)) {
SecureKeyboardEntryController *controller = [SecureKeyboardEntryController sharedInstance];
if (controller.isEnabled) {
if (controller.isDesired) {
item.state = NSControlStateValueOn;
} else {
item.state = NSControlStateValueMixed;
}
} else {
item.state = controller.isDesired ? NSControlStateValueOn : NSControlStateValueOff;
}
return YES;
}
return [super validateMenuItem:item];
}
@@ -1619,11 +1541,6 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
else [super performMiniaturize:sender];
}
- (void)toggleSecureInput:(id)sender {
(void)sender;
[[SecureKeyboardEntryController sharedInstance] toggle];
}
- (BOOL)canBecomeKeyWindow
{
// Required for NSWindowStyleMaskBorderless windows
@@ -2449,7 +2366,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
bool _glfwPlatformIsFullscreen(_GLFWwindow* w, unsigned int flags) {
NSWindow *window = w->ns.object;
bool traditional = !(flags & 1);
if (traditional) { if(@available(macOS 11.0, *)) return w->ns.in_traditional_fullscreen; }
if (traditional) { if(@available(macOS 10.15.7, *)) return w->ns.in_traditional_fullscreen; }
NSWindowStyleMask sm = [window styleMask];
return sm & NSWindowStyleMaskFullScreen;
}
@@ -2460,8 +2377,9 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
bool traditional = !(flags & 1);
NSWindowStyleMask sm = [window styleMask];
if (traditional) {
if (@available(macOS 11.0, *)) {
if (@available(macOS 10.15.7, *)) {
// As of Big Turd NSWindowStyleMaskFullScreen is no longer useable
// Also no longer compatible after a minor release of macOS 10.15.7
if (!w->ns.in_traditional_fullscreen) {
w->ns.pre_full_screen_style_mask = sm;
[window setStyleMask: NSWindowStyleMaskBorderless];

View File

@@ -67,7 +67,7 @@ class Env:
def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
base = os.path.basename(base).rpartition('.')[0]
return 'wayland-{}-client-protocol.{}'.format(base, ext)
return f'wayland-{base}-client-protocol.{ext}'
def init_env(
@@ -80,7 +80,7 @@ def init_env(
) -> Env:
ans = env.copy()
ans.cflags.append('-fPIC')
ans.cppflags.append('-D_GLFW_' + module.upper())
ans.cppflags.append(f'-D_GLFW_{module.upper()}')
ans.cppflags.append('-D_GLFW_BUILD_DLL')
with open(os.path.join(base, 'source-info.json')) as f:
@@ -111,7 +111,7 @@ def init_env(
elif module == 'cocoa':
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
for f_ in 'Cocoa Carbon IOKit CoreFoundation CoreVideo'.split():
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
ans.ldpaths.extend(('-framework', f_))
elif module == 'wayland':
@@ -150,14 +150,14 @@ def build_wayland_protocols(
for protocol in env.wayland_protocols:
src = os.path.join(env.wayland_packagedir, protocol)
if not os.path.exists(src):
raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol))
raise SystemExit(f'The wayland-protocols package on your system is missing the {protocol} protocol definition file')
for ext in 'hc':
dest = wayland_protocol_file_name(src, ext)
dest = os.path.join(dest_dir, dest)
if newer(dest, src):
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
items.append(Command(
'Generating {} ...'.format(emphasis(os.path.basename(dest))),
f'Generating {emphasis(os.path.basename(dest))} ...',
[env.wayland_scanner, q, src, dest], lambda: True))
if items:
parallel_run(items)
@@ -174,7 +174,7 @@ class Arg:
self.type = self.type + '*'
def __repr__(self) -> str:
return 'Arg({}, {})'.format(self.type, self.name)
return f'Arg({self.type}, {self.name})'
class Function:

3
glfw/glfw3.h vendored
View File

@@ -4119,6 +4119,9 @@ GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow*
*/
GLFWAPI void glfwPostEmptyEvent(void);
GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void);
GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled);
/*! @brief Returns the value of an input option for the specified window.
*
* This function returns the value of an input option for the specified window.

12
glfw/ibus_glfw.c vendored
View File

@@ -24,6 +24,17 @@
//
//========================================================================
/* To test under X11 start IBUS as:
* ibus-daemon -drxR
* Setup the input sources you want with:
* ibus-setup
* Switch to the input source you want to test with:
* ibus engine name
* You can list available engines with:
* ibus list-engine
* Then run kitty as:
* GLFW_IM_MODULE=ibus kitty
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
@@ -226,6 +237,7 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data)
break;
case 2:
debug("IBUS: HidePreeditText\n");
send_text("", GLFW_IME_PREEDIT_CHANGED);
break;
case 3:
debug("IBUS: ShowPreeditText\n");

1
glfw/init.c vendored
View File

@@ -227,6 +227,7 @@ GLFWAPI int glfwInit(monotonic_t start_time)
memset(&_glfw, 0, sizeof(_glfw));
_glfw.hints.init = _glfwInitHints;
_glfw.ignoreOSKeyboardProcessing = false;
if (!_glfwPlatformInit())
{

8
glfw/input.c vendored
View File

@@ -667,6 +667,14 @@ void _glfwCenterCursorInContentArea(_GLFWwindow* window)
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void) {
return _glfw.ignoreOSKeyboardProcessing;
}
GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled) {
_glfw.ignoreOSKeyboardProcessing = enabled;
}
GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
{
_GLFWwindow* window = (_GLFWwindow*) handle;

2
glfw/internal.h vendored
View File

@@ -584,6 +584,8 @@ struct _GLFWlibrary
_GLFWtls contextSlot;
_GLFWmutex errorLock;
bool ignoreOSKeyboardProcessing;
struct {
bool available;
void* handle;

2
glfw/xkb_glfw.c vendored
View File

@@ -827,7 +827,7 @@ glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) {
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
last_handled_press_keycode = 0;
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
debug("From IBUS: native_key: 0x%x name: %s is_release: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release);
debug("From IBUS: native_key: 0x%x name: %s is_release: %d handled_by_ime: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release, handled_by_ime);
if (window && !handled_by_ime && !(is_release && ev->glfw_ev.native_key == (int) prev_handled_press)) {
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
ev->glfw_ev.native_key, _glfwGetKeyName(ev->glfw_ev.native_key), ev->glfw_ev.key, glfw_xkb_keysym_name(ev->glfw_ev.key),

View File

@@ -4,13 +4,15 @@
import os
import sys
from contextlib import suppress
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple
from typing import (
TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple
)
from kitty.cli import parse_args
from kitty.cli_stub import AskCLIOptions
from kitty.constants import cache_dir
from kitty.fast_data_types import wcswidth
from kitty.typing import BossType, TypedDict
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.typing import BossType, KeyEventType, TypedDict
from kitty.utils import ScreenSize
from ..tui.handler import Handler, result_handler
@@ -95,6 +97,12 @@ letter is the accelerator key and text is the corresponding text. There can be
an optional color specification after the letter to indicate what color it should
be.
For example: y:Yes and n;red:No
--default -d
A default choice or text. If unspecified, it is "y" for :code:`yesno`, the first choice
for :code:`choices` and empty for others. The default choice is selected when the user
presses the Enter key.
'''
@@ -107,6 +115,7 @@ class Choice(NamedTuple):
text: str
idx: int
color: str
letter: str
class Range(NamedTuple):
@@ -118,27 +127,44 @@ class Range(NamedTuple):
return y == self.y and self.start <= x <= self.end
def truncate_at_space(text: str, width: int) -> Tuple[str, str]:
p = truncate_point_for_length(text, width)
if p < len(text):
i = text.rfind(' ', 0, p + 1)
if i > 0 and p - i < 12:
p = i + 1
return text[:p], text[p:]
def extra_for(width: int, screen_width: int) -> int:
return max(0, screen_width - width) // 2 + 1
class Choose(Handler):
mouse_tracking = MouseTracking.buttons_only
def __init__(self, cli_opts: AskCLIOptions) -> None:
self.cli_opts = cli_opts
self.response = 'n' if cli_opts.type == 'yesno' else ''
self.allowed = frozenset('yn')
self.choices: Dict[str, Choice] = {}
self.clickable_ranges: Dict[str, Range] = {}
if cli_opts.type != 'yesno':
self.clickable_ranges: Dict[str, List[Range]] = {}
if cli_opts.type == 'yesno':
self.allowed = frozenset('yn')
else:
allowed = []
for choice in cli_opts.choices:
color = 'green'
letter, text = choice.split(':', maxsplit=1)
color = ''
if ';' in letter:
letter, color = letter.split(';', maxsplit=1)
letter = letter.lower()
idx = text.lower().index(letter)
allowed.append(letter)
self.choices[letter] = Choice(text, idx, color)
self.choices[letter] = Choice(text, idx, color, letter)
self.allowed = frozenset(allowed)
self.response = ''
self.response_on_accept = cli_opts.default or ''
if cli_opts.type in ('yesno', 'choices') and self.response_on_accept not in self.allowed:
self.response_on_accept = 'y' if cli_opts.type == 'yesno' else tuple(self.choices.keys())[0]
def initialize(self) -> None:
self.cmd.set_cursor_visible(False)
@@ -147,52 +173,164 @@ class Choose(Handler):
def finalize(self) -> None:
self.cmd.set_cursor_visible(True)
def draw_long_text(self, text: str) -> int:
y = 0
width = self.screen_size.cols - 2
while text:
t, text = truncate_at_space(text, width)
t = t.strip()
self.print(' ' * extra_for(wcswidth(t), width), styled(t, bold=True), sep='')
y += 1
return y
@Handler.atomic_update
def draw_screen(self) -> None:
self.cmd.clear_screen()
y = 1
y = max(0, self.screen_size.rows // 2 - 2)
self.print(end='\r\n'*y)
if self.cli_opts.message:
self.cmd.styled(self.cli_opts.message, bold=True)
y += wcswidth(self.cli_opts.message) // self.screen_size.cols
self.print()
for line in self.cli_opts.message.splitlines():
y += self.draw_long_text(line)
if self.screen_size.rows > 2:
self.print()
y += 1
if self.cli_opts.type == 'yesno':
self.draw_yesno(y)
else:
self.draw_choice(y)
def draw_choice(self, y: int) -> None:
x = 0
def draw_choice_boxes(self, y: int, *choices: Choice) -> None:
self.clickable_ranges.clear()
width = self.screen_size.cols - 2
current_line_length = 0
current_line: List[Tuple[str, str]] = []
lines: List[List[Tuple[str, str]]] = []
sep, sep_sz = ' ', 2
for choice in choices:
self.clickable_ranges[choice.letter] = []
text = ' ' + choice.text[:choice.idx]
text += styled(choice.text[choice.idx], fg=choice.color or 'green')
text += choice.text[choice.idx + 1:] + ' '
sz = wcswidth(text)
if sz + sep_sz + current_line_length > width:
lines.append(current_line)
current_line = []
current_line_length = 0
current_line.append((choice.letter, text))
current_line_length += sz
if current_line:
lines.append(current_line)
def top(text: str) -> str:
return '' + '' * wcswidth(text) + ''
def middle(text: str) -> str:
return f'{text}'
def bottom(text: str) -> str:
return '' + '' * wcswidth(text) + ''
def highlight(text: str, only_edges: bool = False) -> str:
if only_edges:
return styled(text[0], fg='yellow') + text[1:-1] + styled(text[-1], fg='yellow')
return styled(text, fg='yellow')
def print_line(add_borders: Callable[[str], str], *items: Tuple[str, str], is_last: bool = False) -> None:
nonlocal y
texts = []
positions = []
x = 0
for (letter, text) in items:
positions.append((letter, x, wcswidth(text) + 2))
text = add_borders(text)
if letter == self.response_on_accept:
text = highlight(text, only_edges=add_borders is middle)
text += sep
x += wcswidth(text)
texts.append(text)
line = ''.join(texts).rstrip()
offset = extra_for(wcswidth(line), width)
for (letter, x, sz) in positions:
x += offset
self.clickable_ranges[letter].append(Range(x, x + sz - 1, y))
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
y += 1
for boxed_line in lines:
print_line(top, *boxed_line)
print_line(middle, *boxed_line)
print_line(bottom, *boxed_line, is_last=boxed_line is lines[-1])
def draw_choice(self, y: int) -> None:
if y + 3 <= self.screen_size.rows:
self.draw_choice_boxes(y, *self.choices.values())
return
self.clickable_ranges.clear()
current_line = ''
current_ranges: Dict[str, int] = {}
width = self.screen_size.cols - 2
def commit_line(end: str = '\r\n') -> None:
nonlocal current_line, y
x = extra_for(wcswidth(current_line), width)
self.print(' ' * x + current_line, end=end)
for letter, sz in current_ranges.items():
self.clickable_ranges[letter] = [Range(x, x + sz - 3, y)]
x += sz
current_ranges.clear()
y += 1
current_line = ''
for letter, choice in self.choices.items():
text = choice.text[:choice.idx]
text += styled(choice.text[choice.idx], fg=choice.color)
text += styled(choice.text[choice.idx], fg=choice.color or 'green', underline='straight' if letter == self.response_on_accept else None)
text += choice.text[choice.idx + 1:]
text += ' '
sz = wcswidth(text)
if sz + x >= self.screen_size.cols:
y += 1
x = 0
self.print()
self.clickable_ranges[letter] = Range(x, x + sz - 1, y)
x += sz
self.print(text, end='')
if sz + wcswidth(current_line) >= width:
commit_line()
current_line += text
current_ranges[letter] = sz
if current_line:
commit_line(end='')
def draw_yesno(self, y: int) -> None:
self.clickable_ranges = {'y': Range(2, 4, y), 'n': Range(8, 9, y)}
self.print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o')
yes = styled('Y', fg='green') + 'es'
no = styled('N', fg='red') + 'o'
if y + 3 <= self.screen_size.rows:
self.draw_choice_boxes(y, Choice('Yes', 0, 'green', 'y'), Choice('No', 0, 'red', 'n'))
return
sep = ' ' * 3
text = yes + sep + no
w = wcswidth(text)
x = extra_for(w, self.screen_size.cols - 2)
nx = x + wcswidth(yes) + len(sep)
self.clickable_ranges = {'y': [Range(x, x + wcswidth(yes) - 1, y)], 'n': [Range(nx, nx + wcswidth(no) - 1, y)]}
self.print(' ' * x + text, end='')
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
text = text.lower()
if text in self.allowed:
self.response = text
self.quit_loop(0)
elif self.cli_opts.type == 'yesno':
self.on_interrupt()
def on_key(self, key_event: KeyEventType) -> None:
if key_event.matches('esc'):
self.on_interrupt()
elif key_event.matches('enter'):
self.response = self.response_on_accept
self.quit_loop(0)
def on_click(self, ev: MouseEvent) -> None:
for letter, r in self.clickable_ranges.items():
if r.has_point(ev.cell_x, ev.cell_y):
self.response = letter
self.quit_loop(0)
break
for letter, ranges in self.clickable_ranges.items():
for r in ranges:
if r.has_point(ev.cell_x, ev.cell_y):
self.response = letter
self.quit_loop(0)
return
def on_resize(self, screen_size: ScreenSize) -> None:
self.screen_size = screen_size
@@ -235,7 +373,15 @@ def main(args: List[str]) -> Response:
prompt = '> '
with suppress(KeyboardInterrupt, EOFError):
response = input(prompt)
if cli_opts.default:
def prefill_text() -> None:
readline.insert_text(cli_opts.default or '')
readline.redisplay()
readline.set_pre_input_hook(prefill_text)
response = input(prompt)
readline.set_pre_input_hook()
else:
response = input(prompt)
return {'items': items, 'response': response}

View File

@@ -32,8 +32,7 @@ class Segment:
self.end_code: Optional[str] = None
def __repr__(self) -> str:
return 'Segment(start={!r}, start_code={!r}, end={!r}, end_code={!r})'.format(
self.start, self.start_code, self.end, self.end_code)
return f'Segment(start={self.start!r}, start_code={self.start_code!r}, end={self.end!r}, end_code={self.end_code!r})'
class Collection:
@@ -102,7 +101,7 @@ def remote_hostname(path: str) -> Tuple[Optional[str], Optional[str]]:
def resolve_remote_name(path: str, default: str) -> str:
remote_dir, rh = remote_hostname(path)
if remote_dir and rh:
return rh + ':' + os.path.relpath(path, remote_dir)
return f'{rh}:{os.path.relpath(path, remote_dir)}'
return default

View File

@@ -4,7 +4,10 @@
import concurrent
import os
import re
from typing import IO, Dict, Iterable, List, Optional, Tuple, Union, cast
from concurrent.futures import ProcessPoolExecutor
from typing import (
IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
)
from pygments import highlight # type: ignore
from pygments.formatter import Formatter # type: ignore
@@ -94,7 +97,7 @@ def highlight_data(code: str, filename: str, aliases: Optional[Dict[str, str]] =
base, ext = os.path.splitext(filename)
alias = aliases.get(ext[1:])
if alias is not None:
filename = base + '.' + alias
filename = f'{base}.{alias}'
try:
lexer = get_lexer_for_filename(filename, stripnl=False)
except ClassNotFound:
@@ -136,10 +139,22 @@ def highlight_for_diff(path: str, aliases: Dict[str, str]) -> DiffHighlight:
return ans
process_pool_executor: Optional[ProcessPoolExecutor] = None
def get_highlight_processes() -> Iterator[int]:
if process_pool_executor is None:
return
for pid in process_pool_executor._processes:
yield pid
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
global process_pool_executor
jobs = {}
ans: Dict[str, DiffHighlight] = {}
with get_process_pool_executor(prefer_fork=True) as executor:
process_pool_executor = executor
for path, item_type, other_path in collection:
if item_type != 'rename':
for p in (path, other_path):
@@ -159,8 +174,9 @@ def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str
def main() -> None:
# kitty +runpy "from kittens.diff.highlight import main; main()" file
from .options.types import defaults
import sys
from .options.types import defaults
initialize_highlighter()
with open(sys.argv[-1]) as f:
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)

View File

@@ -10,10 +10,11 @@ import tempfile
import warnings
from collections import defaultdict
from contextlib import suppress
from enum import Enum, auto
from functools import partial
from gettext import gettext as _
from typing import (
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
)
from kitty.cli import CONFIG_HELP, parse_args
@@ -44,7 +45,8 @@ from .search import BadRegex, Search
try:
from .highlight import (
DiffHighlight, highlight_collection, initialize_highlighter
DiffHighlight, get_highlight_processes, highlight_collection,
initialize_highlighter
)
has_highlighter = True
DiffHighlight
@@ -54,8 +56,24 @@ except ImportError:
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
return ''
def get_highlight_processes() -> Iterator[int]:
if has_highlighter:
yield -1
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
class State(Enum):
initializing = auto()
collected = auto()
diffed = auto()
command = auto()
message = auto()
class BackgroundWork(Enum):
none = auto()
collecting = auto()
diffing = auto()
highlighting = auto()
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
@@ -76,7 +94,7 @@ class DiffHandler(Handler):
image_manager_class = ImageManager
def __init__(self, args: DiffCLIOptions, opts: DiffOptions, left: str, right: str) -> None:
self.state = INITIALIZING
self.state = State.initializing
self.message = ''
self.current_search_is_regex = True
self.current_search: Optional[Search] = None
@@ -90,16 +108,20 @@ class DiffHandler(Handler):
if self.current_context_count < 0:
self.current_context_count = self.original_context_count = self.opts.num_context_lines
self.highlighting_done = False
self.doing_background_work = BackgroundWork.none
self.restore_position: Optional[Reference] = None
for key_def, action in self.opts.key_definitions.items():
self.add_shortcut(action, key_def)
def terminate(self, return_code: int = 0) -> None:
self.quit_loop(return_code)
def perform_action(self, action: KeyAction) -> None:
func, args = action
if func == 'quit':
self.quit_loop(0)
self.terminate()
return
if self.state <= DIFFED:
if self.state.value <= State.diffed.value:
if func == 'scroll_by':
return self.scroll_lines(int(args[0] or 0))
if func == 'scroll_to':
@@ -130,8 +152,9 @@ class DiffHandler(Handler):
def create_collection(self) -> None:
def collect_done(collection: Collection) -> None:
self.doing_background_work = BackgroundWork.none
self.collection = collection
self.state = COLLECTED
self.state = State.collected
self.generate_diff()
def collect(left: str, right: str) -> None:
@@ -139,15 +162,17 @@ class DiffHandler(Handler):
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
self.doing_background_work = BackgroundWork.collecting
def generate_diff(self) -> None:
def diff_done(diff_map: Union[str, Dict[str, Patch]]) -> None:
self.doing_background_work = BackgroundWork.none
if isinstance(diff_map, str):
self.report_traceback_on_exit = diff_map
self.quit_loop(1)
self.terminate(1)
return
self.state = DIFFED
self.state = State.diffed
self.diff_map = diff_map
self.calculate_statistics()
self.render_diff()
@@ -163,7 +188,7 @@ class DiffHandler(Handler):
initialize_highlighter(self.opts.pygments_style)
except StyleNotFound as e:
self.report_traceback_on_exit = str(e)
self.quit_loop(1)
self.terminate(1)
return
self.syntax_highlight()
@@ -172,13 +197,15 @@ class DiffHandler(Handler):
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
self.doing_background_work = BackgroundWork.diffing
def syntax_highlight(self) -> None:
def highlighting_done(hdata: Union[str, Dict[str, 'DiffHighlight']]) -> None:
self.doing_background_work = BackgroundWork.none
if isinstance(hdata, str):
self.report_traceback_on_exit = hdata
self.quit_loop(1)
self.terminate(1)
return
set_highlight_data(hdata)
self.render_diff()
@@ -189,6 +216,7 @@ class DiffHandler(Handler):
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
self.doing_background_work = BackgroundWork.highlighting
def calculate_statistics(self) -> None:
self.added_count = self.collection.added_count
@@ -304,7 +332,7 @@ class DiffHandler(Handler):
self.create_collection()
def enforce_cursor_state(self) -> None:
self.cmd.set_cursor_visible(self.state == COMMAND)
self.cmd.set_cursor_visible(self.state is State.command)
def draw_lines(self, num: int, offset: int = 0) -> None:
offset += self.scroll_pos
@@ -319,7 +347,7 @@ class DiffHandler(Handler):
text = line.text
if line.image_data is not None:
image_involved = True
self.write('\r\x1b[K' + text + '\x1b[0m')
self.write(f'\r\x1b[K{text}\x1b[0m')
if self.current_search is not None:
self.current_search.highlight_line(self.write, lpos)
if i < num - 1:
@@ -407,7 +435,7 @@ class DiffHandler(Handler):
def draw_screen(self) -> None:
self.enforce_cursor_state()
if self.state < DIFFED:
if self.state.value < State.diffed.value:
self.cmd.clear_screen()
self.write(_('Calculating diff, please wait...'))
return
@@ -417,14 +445,14 @@ class DiffHandler(Handler):
self.draw_status_line()
def draw_status_line(self) -> None:
if self.state < DIFFED:
if self.state.value < State.diffed.value:
return
self.enforce_cursor_state()
self.cmd.set_cursor_position(0, self.num_lines)
self.cmd.clear_to_eol()
if self.state is COMMAND:
if self.state is State.command:
self.line_edit.write(self.write)
elif self.state is MESSAGE:
elif self.state is State.message:
self.cmd.styled(self.message, reverse=True)
else:
sp = f'{self.scroll_pos/self.max_scroll_pos:.0%}' if self.scroll_pos and self.max_scroll_pos else '0%'
@@ -437,7 +465,7 @@ class DiffHandler(Handler):
)
else:
counts = styled(f'{len(self.current_search)} matches', fg=self.opts.margin_fg)
suffix = counts + ' ' + scroll_frac
suffix = f'{counts} {scroll_frac}'
prefix = styled(':', fg=self.opts.margin_fg)
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
@@ -447,16 +475,16 @@ class DiffHandler(Handler):
new_ctx = max(0, new_ctx)
if new_ctx != self.current_context_count:
self.current_context_count = new_ctx
self.state = COLLECTED
self.state = State.collected
self.generate_diff()
self.restore_position = self.current_position
self.draw_screen()
def start_search(self, is_regex: bool, is_backward: bool) -> None:
if self.state != DIFFED:
if self.state is not State.diffed:
self.cmd.bell()
return
self.state = COMMAND
self.state = State.command
self.line_edit.clear()
self.line_edit.add_text('?' if is_backward else '/')
self.current_search_is_regex = is_regex
@@ -470,50 +498,50 @@ class DiffHandler(Handler):
try:
self.current_search = Search(self.opts, query[1:], self.current_search_is_regex, query[0] == '?')
except BadRegex:
self.state = MESSAGE
self.state = State.message
self.message = sanitize(_('Bad regex: {}').format(query[1:]))
self.cmd.bell()
else:
if self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols):
self.scroll_to_next_match(include_current=True)
else:
self.state = MESSAGE
self.state = State.message
self.message = sanitize(_('No matches found'))
self.cmd.bell()
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
if key_event.text:
if self.state is COMMAND:
if self.state is State.command:
self.line_edit.on_text(key_event.text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
if self.state is State.message:
self.state = State.diffed
self.draw_status_line()
return
else:
if self.state is MESSAGE:
if self.state is State.message:
if key_event.type is not EventType.RELEASE:
self.state = DIFFED
self.state = State.diffed
self.draw_status_line()
return
if self.state is COMMAND:
if self.state is State.command:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
self.state = DIFFED
self.state = State.diffed
self.draw_status_line()
return
if key_event.matches('enter'):
self.state = DIFFED
self.state = State.diffed
self.do_search()
self.line_edit.clear()
self.draw_screen()
return
if key_event.matches('esc'):
self.state = DIFFED
self.state = State.diffed
self.draw_status_line()
return
if self.state >= DIFFED and self.current_search is not None and key_event.matches('esc'):
if self.state.value >= State.diffed.value and self.current_search is not None and key_event.matches('esc'):
self.current_search = None
self.draw_screen()
return
@@ -526,16 +554,16 @@ class DiffHandler(Handler):
def on_resize(self, screen_size: ScreenSize) -> None:
self.screen_size = screen_size
self.set_scrolling_region()
if self.state > COLLECTED:
if self.state.value > State.collected.value:
self.image_manager.delete_all_sent_images()
self.render_diff()
self.draw_screen()
def on_interrupt(self) -> None:
self.quit_loop(1)
self.terminate(1)
def on_eot(self) -> None:
self.quit_loop(1)
self.terminate(1)
OPTIONS = partial('''\
@@ -639,9 +667,10 @@ def main(args: List[str]) -> None:
for message in showwarning.warnings:
from kitty.utils import safe_print
safe_print(message, file=sys.stderr)
highlight_processes = getattr(highlight_collection, 'processes', ())
terminate_processes(tuple(highlight_processes))
terminate_processes(tuple(worker_processes))
if handler.doing_background_work is BackgroundWork.highlighting:
terminate_processes(tuple(get_highlight_processes()))
elif handler.doing_background_work == BackgroundWork.diffing:
terminate_processes(tuple(worker_processes))
if loop.return_code != 0:
if handler.report_traceback_on_exit:
print(handler.report_traceback_on_exit, file=sys.stderr)

View File

@@ -244,14 +244,14 @@ class Differ:
except Exception as e:
return f'Running git diff for {left_path} vs. {right_path} generated an exception: {e}'
if not ok:
return output + f'\nRunning git diff for {left_path} vs. {right_path} failed'
return f'{output}\nRunning git diff for {left_path} vs. {right_path} failed'
left_lines = lines_for_path(left_path)
right_lines = lines_for_path(right_path)
try:
patch = parse_patch(output)
except Exception:
import traceback
return traceback.format_exc() + f'\nParsing diff for {left_path} vs. {right_path} failed'
return f'{traceback.format_exc()}\nParsing diff for {left_path} vs. {right_path} failed'
else:
ans[key] = patch
return ans

View File

@@ -101,7 +101,7 @@ def human_readable(size: int, sep: str = ' ') -> str:
s = s[:s.find(".")+2]
if s.endswith('.0'):
s = s[:-2]
return s + sep + suffix
return f'{s}{sep}{suffix}'
def fit_in(text: str, count: int) -> str:
@@ -110,7 +110,7 @@ def fit_in(text: str, count: int) -> str:
return text
if count > 1:
p = truncate_point_for_length(text, count - 1)
return text[:p] + ''
return f'{text[:p]}'
def fill_in(text: str, sz: int) -> str:
@@ -127,8 +127,8 @@ def place_in(text: str, sz: int) -> str:
def format_func(which: str) -> Callable[[str], str]:
def formatted(text: str) -> str:
fmt = formats[which]
return '\x1b[' + fmt + 'm' + text + '\x1b[0m'
formatted.__name__ = which + '_format'
return f'\x1b[{fmt}m{text}\x1b[0m'
formatted.__name__ = f'{which}_format'
return formatted
@@ -148,8 +148,8 @@ highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_high
def highlight_boundaries(ltype: str) -> Tuple[str, str]:
s, e = highlight_map[ltype]
start = '\x1b[' + formats[s] + 'm'
stop = '\x1b[' + formats[e] + 'm'
start = f'\x1b[{formats[s]}m'
stop = f'\x1b[{formats[e]}m'
return start, stop

View File

@@ -40,6 +40,8 @@ def kitty_common_opts() -> KittyCommonOpts:
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
FILE_EXTENSION = r'\.(?:[a-zA-Z0-9]{2,7}|[ahcmo])(?!\.)'
PATH_REGEX = fr'(?:\S*?/[\r\S]+)|(?:\S[\r\S]*{FILE_EXTENSION})\b'
class Mark:
@@ -184,7 +186,7 @@ class Hints(Handler):
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event.matches('enter') and self.current_input:
elif (key_event.matches('enter') or key_event.matches('space')) and self.current_input:
try:
idx = decode_hint(self.current_input, self.alphabet)
self.chosen.append(self.index_map[idx])
@@ -271,9 +273,13 @@ def brackets(text: str, s: int, e: int) -> Tuple[int, int]:
# Remove matching brackets
if s < e <= len(text):
before = text[s]
if before in '({[<' and text[e-1] == closing_bracket_map[before]:
s += 1
e -= 1
if before in '({[<':
q = closing_bracket_map[before]
if text[e-1] == q:
s += 1
e -= 1
elif text[e:e+1] == q:
s += 1
return s, e
@@ -282,9 +288,12 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
# Remove matching quotes
if s < e <= len(text):
before = text[s]
if before in '\'"' and text[e-1] == before:
s += 1
e -= 1
if before in '\'"':
if text[e-1] == before:
s += 1
e -= 1
elif text[e:e+1] == before:
s += 1
return s, e
@@ -348,7 +357,7 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
)
post_processors.append(url)
elif args.type == 'path':
pattern = r'(?:\S*?/[\r\S]+)|(?:\S[\r\S]*\.[a-zA-Z0-9\r]{2,7})'
pattern = PATH_REGEX
post_processors.extend((brackets, quotes))
elif args.type == 'line':
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
@@ -405,7 +414,7 @@ def parse_input(text: str) -> str:
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
regex = args.regex
if regex == DEFAULT_REGEX:
regex = r'(?P<path>(?:\S*/\S+?)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
regex = fr'(?P<path>{PATH_REGEX}):(?P<line>\d+)'
yield from mark(regex, [brackets, quotes], text, args)
@@ -720,7 +729,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
else:
import shlex
text = ' '.join(shlex.quote(arg) for arg in cmd)
w.paste_bytes(text + '\r')
w.paste_bytes(f'{text}\r')
elif action == 'background':
import subprocess
subprocess.Popen(cmd, cwd=data['cwd'])

View File

@@ -19,6 +19,7 @@ from kitty.cli import parse_args
from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname
from kitty.guess_mime_type import guess_type
from kitty.rgb import to_color
from kitty.types import run_once
from kitty.typing import GRT_f, GRT_t
from kitty.utils import (
@@ -55,6 +56,19 @@ are smaller than the specified area to be scaled up to use as much
of the specified area as possible.
--background
default=none
Specify a background color, this will cause transparent images to be composited on
top of the specified color.
--mirror
default=none
type=choices
choices=none,horizontal,vertical,both
Mirror the image about a horizontal or vertical axis or both.
--clear
type=bool-set
Remove all images currently displayed on the screen.
@@ -307,6 +321,9 @@ class ParsedOpts:
place: Optional['Place'] = None
z_index: int = 0
remove_alpha: str = ''
flip: bool = False
flop: bool = False
def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
@@ -316,9 +333,10 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
available_height = parsed_opts.place.height * (ss.height // ss.rows) if parsed_opts.place else 10 * m.height
needs_scaling = m.width > available_width or m.height > available_height
needs_scaling = needs_scaling or args.scale_up
needs_conversion = needs_scaling or bool(parsed_opts.remove_alpha) or parsed_opts.flip or parsed_opts.flop
file_removed = False
use_number = 0
if m.fmt == 'png' and not needs_scaling:
if m.fmt == 'png' and not needs_conversion:
outfile = path
transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f'
fmt: 'GRT_f' = 100
@@ -328,13 +346,17 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
fmt = 24 if m.mode == 'rgb' else 32
transmit_mode = 't'
if len(m) == 1 or args.loop == 0:
outfile, width, height = render_as_single_image(path, m, available_width, available_height, args.scale_up)
outfile, width, height = render_as_single_image(
path, m, available_width, available_height, args.scale_up,
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
else:
import struct
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
with NamedTemporaryFile() as f:
prefix = f.name
frame_data = render_image(path, prefix, m, available_width, available_height, args.scale_up)
frame_data = render_image(
path, prefix, m, available_width, available_height, args.scale_up,
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
show(
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
@@ -449,9 +471,8 @@ def process_single_item(
file_removed = False
try:
if isinstance(item, bytes):
tf = NamedTemporaryFile(prefix='stdin-image-data-', delete=False)
tf.write(item)
tf.close()
with NamedTemporaryFile(prefix='stdin-image-data-', delete=False) as tf:
tf.write(item)
item = tf.name
is_tempfile = True
if url_pat is not None and url_pat.match(item) is not None:
@@ -527,6 +548,13 @@ def main(args: List[str] = sys.argv) -> None:
parsed_opts.z_index = parse_z_index(cli_opts.z_index)
except Exception:
raise SystemExit(f'Not a valid z-index specification: {cli_opts.z_index}')
if cli_opts.background != 'none':
ra = to_color(cli_opts.background)
if ra is None:
raise SystemExit(f'Not a valid color specification: {cli_opts.background}')
parsed_opts.remove_alpha = ra.as_sharp
parsed_opts.flip = cli_opts.mirror in ('both', 'vertical')
parsed_opts.flop = cli_opts.mirror in ('both', 'horizontal')
if cli_opts.detect_support:
if not detect_support(wait_for=cli_opts.detection_timeout, silent=True):
@@ -550,16 +578,26 @@ def main(args: List[str] = sys.argv) -> None:
raise SystemExit(f'The --place option can only be used with a single image, not {items}')
sys.stdout.buffer.write(b'\0337') # save cursor
url_pat = re.compile(r'(?:https?|ftp)://', flags=re.I)
def hold_if_needed(exit_code_or_msg: Union[int, str]) -> None:
if cli_opts.hold:
if isinstance(exit_code_or_msg, str):
print(exit_code_or_msg, file=sys.stderr, flush=True)
exit_code_or_msg = 1
with open(os.ctermid()) as tty, raw_mode(tty.fileno()):
tty.buffer.read(1)
raise SystemExit(exit_code_or_msg)
for item in items:
try:
process_single_item(item, cli_opts, parsed_opts, url_pat)
except NoImageMagick as e:
raise SystemExit(str(e))
hold_if_needed(str(e))
except OutdatedImageMagick as e:
print(e.detailed_error, file=sys.stderr)
raise SystemExit(str(e))
hold_if_needed(str(e))
except ConvertFailed as e:
raise SystemExit(str(e))
hold_if_needed(str(e))
except OpenFailed as e:
errors.append(e)
if parsed_opts.place:
@@ -567,11 +605,8 @@ def main(args: List[str] = sys.argv) -> None:
if errors:
for err in errors:
print(err, file=sys.stderr)
if cli_opts.hold:
with open(os.ctermid()) as tty:
with raw_mode(tty.fileno()):
tty.buffer.read(1)
raise SystemExit(1 if errors else 0)
hold_if_needed(1 if errors else 0)
raise SystemExit()
if __name__ == '__main__':

View File

@@ -85,9 +85,9 @@ def create_strut(
'-id',
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
'-set', '_NET_WM_STRUT_PARTIAL',
'{left},{right},{top},{bottom},'
'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'.format(**locals())
f'{left},{right},{top},{bottom},'
f'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
f'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'
)

View File

@@ -245,7 +245,7 @@ def main(args: List[str] = sys.argv) -> None:
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
for key, val in do_queries(queries, cli_opts).items():
print(key + ':', val)
print(f'{key}:', val)
if __name__ == '__main__':

View File

@@ -52,7 +52,7 @@ class KeysHandler(Handler):
self.cmd.colored(etype + ' ', 'yellow')
self.cmd.styled(key_event.text, italic=True)
self.print()
rep = 'CSI ' + encode_key_event(key_event)[2:]
rep = f'CSI {encode_key_event(key_event)[2:]}'
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
self.cmd.styled(rep, fg='magenta')
if (key_event.shifted_key or key_event.alternate_key):

View File

@@ -17,7 +17,7 @@ def print_key(raw: bytearray) -> None:
unix = ''
for ch in raw:
if ch < len(ctrl_keys):
unix += '^' + ctrl_keys[ch]
unix += f'^{ctrl_keys[ch]}'
elif ch == 127:
unix += '^?'
else:

View File

@@ -281,7 +281,7 @@ def complete_choices(ans: Completions, prefix: str, title: str, choices: Iterabl
if q.startswith(effective_prefix):
if comma_separated:
tq = q
q = hidden_prefix + q + ','
q = f'{hidden_prefix}{q},'
word_transforms[q] = tq
matches[q] = ''
ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms)

View File

@@ -131,7 +131,7 @@ def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
other_ssh_args: Set[str] = set()
boolean_ssh_args: Set[str] = set()
for k, v in ssh_options().items():
k = '-' + k
k = f'-{k}'
if v:
other_ssh_args.add(k)
else:
@@ -213,7 +213,7 @@ class InvalidSSHArgs(ValueError):
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
boolean_ssh_args, other_ssh_args = get_ssh_cli()
passthrough_args = {'-' + x for x in 'Nnf'}
passthrough_args = {f'-{x}' for x in 'Nnf'}
ssh_args = []
server_args: List[str] = []
expecting_option_val = False
@@ -230,7 +230,7 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
# could be a multi-character option
all_args = argument[1:]
for i, arg in enumerate(all_args):
arg = '-' + arg
arg = f'-{arg}'
if arg in passthrough_args:
passthrough = True
if arg in boolean_ssh_args:

View File

@@ -376,7 +376,7 @@ def fetch_themes(
needs_delete = False
try:
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(dest_path), dir=os.path.dirname(dest_path), delete=False) as f:
with tempfile.NamedTemporaryFile(suffix=f'-{os.path.basename(dest_path)}', dir=os.path.dirname(dest_path), delete=False) as f:
needs_delete = True
shutil.copyfileobj(res, f)
f.flush()
@@ -405,7 +405,7 @@ def theme_name_from_file_name(fname: str) -> str:
ans = ans.replace('_', ' ')
def camel_case(m: 'Match[str]') -> str:
return str(m.group(1) + ' ' + m.group(2))
return f'{m.group(1)} {m.group(2)}'
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
return ' '.join(x.capitalize() for x in filter(None, ans.split()))
@@ -522,10 +522,10 @@ class Theme:
def save_in_dir(self, dirpath: str) -> None:
atomic_save(self.raw.encode('utf-8'), os.path.join(dirpath, f'{self.name}.conf'))
def save_in_conf(self, confdir: str, reload_in: str) -> None:
def save_in_conf(self, confdir: str, reload_in: str, config_file_name: str = 'kitty.conf') -> None:
os.makedirs(confdir, exist_ok=True)
atomic_save(self.raw.encode('utf-8'), os.path.join(confdir, 'current-theme.conf'))
confpath = os.path.realpath(os.path.join(confdir, 'kitty.conf'))
confpath = os.path.realpath(os.path.join(confdir, config_file_name))
try:
with open(confpath) as f:
raw = f.read()
@@ -533,7 +533,7 @@ class Theme:
raw = ''
nraw = patch_conf(raw, self.name)
if raw:
with open(confpath + '.bak', 'w') as f:
with open(f'{confpath}.bak', 'w') as f:
f.write(raw)
atomic_save(nraw.encode('utf-8'), confpath)
if reload_in == 'parent':

View File

@@ -37,7 +37,7 @@ def limit_length(text: str, limit: int = 32) -> str:
x = truncate_point_for_length(text, limit - 1)
if x >= len(text):
return text
return text[:x] + ''
return f'{text[:x]}'
class State(Enum):
@@ -332,7 +332,7 @@ class ThemesHandler(Handler):
for line, width, is_current in self.themes_list.lines(num_rows):
num_rows -= 1
if is_current:
line = line.replace(MARK_AFTER, '\033[' + color_code('green') + 'm')
line = line.replace(MARK_AFTER, f'\033[{color_code("green")}m')
self.cmd.styled('>' if is_current else ' ', fg='green')
self.cmd.styled(line, bold=is_current, fg='green' if is_current else None)
self.cmd.move_cursor_by(mw - width, 'right')
@@ -433,7 +433,7 @@ class ThemesHandler(Handler):
def draw_accepting_screen(self) -> None:
name = self.themes_list.current_theme.name
name = styled(name, bold=True, fg="green")
kc = styled('kitty.conf', italic=True)
kc = styled(self.cli_opts.config_file_name, italic=True)
def ac(x: str) -> str:
return styled(x, fg='red')
@@ -465,7 +465,7 @@ class ThemesHandler(Handler):
self.quit_on_next_key_release = 0
return
if key_event.matches_text('m'):
self.themes_list.current_theme.save_in_conf(config_dir, self.cli_opts.reload_in)
self.themes_list.current_theme.save_in_conf(config_dir, self.cli_opts.reload_in, self.cli_opts.config_file_name)
self.update_recent()
self.quit_on_next_key_release = 0
return
@@ -536,6 +536,13 @@ type=bool-set
default=false
When running non-interactively, dump the specified theme to STDOUT
instead of changing kitty.conf.
--config-file-name
default=kitty.conf
The name or path to the config file to edit. Relative paths are interpreted
with respect to the kitty config directory. By default the kitty config file,
kitty.conf is edited.
'''.format
@@ -559,7 +566,7 @@ def non_interactive(cli_opts: ThemesCLIOptions, theme_name: str) -> None:
if cli_opts.dump_theme:
print(theme.raw)
return
theme.save_in_conf(config_dir, cli_opts.reload_in)
theme.save_in_conf(config_dir, cli_opts.reload_in, cli_opts.config_file_name)
def main(args: List[str]) -> None:

View File

@@ -155,12 +155,12 @@ def develop() -> None:
import sys
src = sys.argv[-1]
sig_loader = LoadSignature()
with open(src + '.sig', 'wb') as f:
with open(f'{src}.sig', 'wb') as f:
for chunk in signature_of_file(src):
sig_loader.add_chunk(chunk)
f.write(chunk)
sig_loader.commit()
with open(src + '.delta', 'wb') as f, PatchFile(src, src + '.output') as patcher:
with open(f'{src}.delta', 'wb') as f, PatchFile(src, f'{src}.output') as patcher:
for chunk in delta_for_file(src, sig_loader.signature):
f.write(chunk)
patcher.write(chunk)

View File

@@ -45,7 +45,7 @@ def render_path_in_width(path: str, width: int) -> str:
if wcswidth(path) <= width:
return path
x = truncate_point_for_length(path, width - 1)
return path[:x] + ''
return f'{path[:x]}'
def render_seconds(val: float) -> str:

View File

@@ -331,7 +331,7 @@ class Dircolors:
# change .xyz to *.xyz
yield '*' + pair[0], pair[1]
return ':'.join('%s=%s' % pair for pair in gen_pairs())
return ':'.join('{}={}'.format(*pair) for pair in gen_pairs())
def _format_code(self, text: str, code: str) -> str:
val = self.codes.get(code)

View File

@@ -189,7 +189,9 @@ def render_image(
m: ImageData,
available_width: int, available_height: int,
scale_up: bool,
only_first_frame: bool = False
only_first_frame: bool = False,
remove_alpha: str = '',
flip: bool = False, flop: bool = False,
) -> RenderedImage:
import tempfile
has_multiple_frames = len(m) > 1
@@ -202,7 +204,15 @@ def render_image(
if exe is None:
raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH')
cmd = [exe]
cmd += ['-background', 'none', '--', path]
if remove_alpha:
cmd += ['-background', remove_alpha, '-alpha', 'remove']
else:
cmd += ['-background', 'none']
if flip:
cmd.append('-flip')
if flop:
cmd.append('-flop')
cmd += ['--', path]
if only_first_frame and has_multiple_frames:
cmd[-1] += '[0]'
cmd.append('-auto-orient')
@@ -287,12 +297,15 @@ def render_as_single_image(
path: str, m: ImageData,
available_width: int, available_height: int,
scale_up: bool,
tdir: Optional[str] = None
tdir: Optional[str] = None,
remove_alpha: str = '', flip: bool = False, flop: bool = False,
) -> Tuple[str, int, int]:
import tempfile
fd, output = tempfile.mkstemp(prefix='icat-', suffix=f'.{m.mode}', dir=tdir)
os.close(fd)
result = render_image(path, output, m, available_width, available_height, scale_up, only_first_frame=True)
result = render_image(
path, output, m, available_width, available_height, scale_up,
only_first_frame=True, remove_alpha=remove_alpha, flip=flip, flop=flop)
os.rename(result.frames[0].path, output)
return output, result.width, result.height

View File

@@ -5,13 +5,13 @@ import sys
from contextlib import contextmanager
from enum import Enum, auto
from functools import wraps
from typing import (
IO, Any, Callable, Dict, Generator, Optional, TypeVar, Union
)
from typing import IO, Any, Callable, Dict, Generator, Optional, TypeVar, Union
from kitty.fast_data_types import Color
from kitty.rgb import color_as_sharp, to_color
from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize
from kitty.typing import (
GraphicsCommandType, HandlerType, ScreenSize, UnderlineLiteral
)
from .operations_stub import CMD
@@ -163,14 +163,11 @@ def set_scrolling_region(screen_size: Optional['ScreenSize'] = None, top: Option
@cmd
def scroll_screen(amt: int = 1) -> str:
return '\033[' + str(abs(amt)) + ('T' if amt < 0 else 'S')
return f'\033[{abs(amt)}{"T" if amt < 0 else "S"}'
STANDARD_COLORS = {name: i for i, name in enumerate(
'black red green yellow blue magenta cyan gray'.split())}
STANDARD_COLORS['white'] = STANDARD_COLORS['gray']
UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
'straight double curly'.split())}
STANDARD_COLORS = {'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'gray': 7, 'white': 7}
UNDERLINE_STYLES = {'straight': 1, 'double': 2, 'curly': 3, 'dotted': 4, 'dashed': 5}
ColorSpec = Union[int, str, Color]
@@ -217,7 +214,7 @@ def styled(
bg_intense: bool = False,
italic: Optional[bool] = None,
bold: Optional[bool] = None,
underline: Optional[str] = None,
underline: Optional[UnderlineLiteral] = None,
underline_color: Optional[ColorSpec] = None,
reverse: Optional[bool] = None,
dim: Optional[bool] = None,
@@ -291,7 +288,7 @@ class MouseTracking(Enum):
full = auto()
def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = MouseTracking.none) -> str:
def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = MouseTracking.none, kitty_keyboard_mode: bool = True) -> str:
sc = SAVE_CURSOR if alternate_screen else ''
ans = (
S7C1T + sc + SAVE_PRIVATE_MODE_VALUES + reset_mode(Mode.LNM) +
@@ -314,7 +311,10 @@ def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = Mo
ans += set_mode(Mode.MOUSE_MOTION_TRACKING)
elif mouse_tracking is MouseTracking.full:
ans += set_mode(Mode.MOUSE_MOVE_TRACKING)
ans += '\033[>31u' # extended keyboard mode
if kitty_keyboard_mode:
ans += '\033[>31u' # extended keyboard mode
else:
ans += '\033[>u' # legacy keyboard mode
return ans
@@ -465,7 +465,7 @@ def as_type_stub() -> str:
for name, func in all_cmds.items():
args = ', '.join(func_sig(func))
if args:
args = ', ' + args
args = f', {args}'
methods.append(f' def {name}(self{args}) -> str: pass')
ans += ['', '', 'class CMD:'] + methods

View File

@@ -192,7 +192,7 @@ class Table:
if w < 2:
text += ' ' * (2 - w)
if len(desc) > space_for_desc:
text += desc[:space_for_desc - 1] + ''
text += f'{desc[:space_for_desc - 1]}'
else:
text += desc
extra = space_for_desc - len(desc)

View File

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

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

@@ -39,7 +39,7 @@ from .fast_data_types import (
set_application_quit_request, set_background_image, set_boss,
set_clipboard_string, set_in_sequence_mode, set_options,
set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen,
toggle_maximized
toggle_maximized, toggle_secure_input
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches
@@ -653,18 +653,13 @@ class Boss:
msg = _('Are you sure you want to close this window?')
if window.has_running_program:
msg += ' ' + _('It is running a program.')
self._run_kitten(
'ask', ['--type=yesno', '--message', msg],
window=window,
custom_callback=partial(self.handle_close_window_confirmation, window.id)
)
self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window)
else:
self.mark_window_for_close(window)
def handle_close_window_confirmation(self, window_id: int, data: Dict[str, Any], *a: Any) -> None:
if data['response'] != 'y':
return
self.mark_window_for_close(window_id)
def handle_close_window_confirmation(self, allowed: bool, window_id: int) -> None:
if allowed:
self.mark_window_for_close(window_id)
@ac('tab', 'Close the current tab')
def close_tab(self, tab: Optional[Tab] = None) -> None:
@@ -672,13 +667,35 @@ class Boss:
if tab:
self.confirm_tab_close(tab)
def confirm(self, msg: str, callback: Callable[..., None], *args: Any,
window: Optional[Window] = None, confirm_on_cancel: bool = False) -> None:
def confirm(
self, msg: str, # can contain newlines and ANSI formatting
callback: Callable[..., None], # called with True or False and *args
*args: Any, # passed to the callback function
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
) -> None:
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') == 'y', *args)
self._run_kitten('ask', ['--type=yesno', '--message', msg],
self._run_kitten('ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
window=window, custom_callback=callback_, default_data={'response': 'y' if confirm_on_cancel else 'n'})
def choose(
self, msg: str, # can contain newlines and ANSI formatting
callback: Callable[..., None], # called with the choice or empty string when aborted
*choices: str, # The choices, see the help for the ask kitten for format of a choice
window: Optional[Window] = None, # the window associated with the confirmation
default: str = '', # the default choice when the user presses Enter
) -> None:
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') or '')
cmd = ['--type=choices', '--message', msg]
if default:
cmd += ['-d', default]
for c in choices:
cmd += ['-c', c]
self._run_kitten('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''})
def confirm_tab_close(self, tab: Tab) -> None:
x = get_options().confirm_os_window_close
num = tab.number_of_windows_with_running_programs if x < 0 else len(tab)
@@ -721,6 +738,10 @@ class Boss:
def toggle_maximized(self, os_window_id: int = 0) -> None:
toggle_maximized(os_window_id)
@ac('misc', 'Toggle macOS secure keyboard entry')
def toggle_macos_secure_keyboard_entry(self) -> None:
toggle_secure_input()
def start(self, first_os_window_id: int) -> None:
if not getattr(self, 'io_thread_started', False):
self.child_monitor.start()
@@ -1414,12 +1435,14 @@ class Boss:
def set_tab_title(self) -> None:
tab = self.active_tab
if tab:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
args = [
'--name=tab-title', '--message', _('Enter the new title for this tab below.'),
'--default', tab.name or tab.title, 'do_set_tab_title', str(tab.id)]
self._run_kitten('ask', args)
def do_set_tab_title(self, title: str, tab_id: int) -> None:
tm = self.active_tab_manager
if tm is not None and title:
if tm is not None:
tab_id = int(tab_id)
for tab in tm.tabs:
if tab.id == tab_id:
@@ -2072,9 +2095,10 @@ class Boss:
def choose_entry(
self, title: str, entries: Iterable[Tuple[Union[_T, str, None], str]],
callback: Callable[[Union[_T, str, None]], None]
callback: Callable[[Union[_T, str, None]], None],
subtitle: str = ''
) -> Optional[Window]:
lines = [title, '']
lines = [title, subtitle, '']
idx_map: List[Union[_T, str]] = []
ans: Union[str, _T, None] = None
fmt = ': {1}'
@@ -2110,8 +2134,19 @@ class Boss:
if tab.id == ans:
self.set_active_tab(tab)
def format_tab_title(tab: Tab) -> str:
w = 'windows' if tab.num_window_groups > 1 else 'window'
return f'{tab.title} [{tab.num_window_groups} {w}]'
ct = self.active_tab
self.choose_entry('Choose a tab to switch to', ((t.id, t.title) for t in self.all_tabs if t is not ct), chosen)
st = ''
if ct is not None:
st = f'Current tab: {format_tab_title(ct)}'
self.choose_entry(
'Choose a tab to switch to',
((t.id, format_tab_title(t)) for t in self.all_tabs if t is not ct),
chosen, subtitle=st
)
@ac('win', '''
Detach a window, moving it to another tab or OS Window
@@ -2200,19 +2235,31 @@ class Boss:
if path == ":cocoa::application launched::":
self.cocoa_application_launched = True
return
def new_os_window() -> None:
self.new_os_window(path)
if self.cocoa_application_launched or not self.os_window_map:
return new_os_window()
from .open_actions import actions_for_launch
from .launch import force_window_launch
actions = list(actions_for_launch(path))
tab = self.active_tab
if tab is None:
return new_os_window()
w = tab.active_window
self.new_window(path)
if w is not None:
tab.remove_window(w)
if tab is not None:
w = tab.active_window
else:
w = None
needs_window_replaced = not self.cocoa_application_launched or not self.os_window_map and w is not None and w.id == 1
def clear_initial_window() -> None:
if needs_window_replaced and tab is not None and w is not None:
tab.remove_window(w)
if not actions:
with force_window_launch(needs_window_replaced):
self.launch(kitty_exe(), '+runpy', f'print("The file:", {path!r}, "is of unknown type, cannot open it.");'
'from kitty.utils import hold_till_enter; hold_till_enter(); raise SystemExit(1)')
clear_initial_window()
else:
with force_window_launch(needs_window_replaced):
self.dispatch_action(actions.pop(0))
clear_initial_window()
if actions:
self.drain_actions(actions)
@ac('debug', 'Show the effective configuration kitty is running with')
def debug_config(self) -> None:

View File

@@ -79,8 +79,8 @@ const uint COLOR_MASK = uint(0x4000);
const uint ZERO = uint(0);
const uint ONE = uint(1);
const uint TWO = uint(2);
const uint THREE = uint(3);
const uint FOUR = uint(4);
const uint SEVEN = uint(7);
vec3 color_to_vec(uint c) {
uint r, g, b;
@@ -193,7 +193,7 @@ void main() {
foreground = choose_color(float(is_selected & ONE), selection_color, foreground);
decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg);
// Underline and strike through (rendered via sprites)
underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & THREE, ZERO, ZERO));
underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & SEVEN, ZERO, ZERO));
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO);
// Cursor

View File

@@ -1039,6 +1039,7 @@ process_cocoa_pending_actions(void) {
if (cocoa_pending_actions[CLOSE_WINDOW]) { call_boss(close_window, NULL); }
if (cocoa_pending_actions[RESET_TERMINAL]) { call_boss(clear_terminal, "sO", "reset", Py_True ); }
if (cocoa_pending_actions[RELOAD_CONFIG]) { call_boss(load_config_file, NULL); }
if (cocoa_pending_actions[TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY]) { call_boss(toggle_macos_secure_keyboard_entry, NULL); }
if (cocoa_pending_actions_data.wd) {
if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_data.wd); }
if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_data.wd); }

View File

@@ -62,7 +62,7 @@ else:
except Exception:
continue
try:
with open('/proc/' + x + '/stat', 'rb') as f:
with open(f'/proc/{x}/stat', 'rb') as f:
raw = f.read().decode('utf-8')
except OSError:
continue
@@ -268,7 +268,7 @@ class Child:
# https://github.com/kovidgoyal/kitty/issues/1870
# xterm, urxvt, konsole and gnome-terminal do not do it in my
# testing.
argv[0] = ('-' + exe.split('/')[-1])
argv[0] = (f'-{exe.split("/")[-1]}')
exe = which(exe) or exe
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
os.close(slave)

View File

@@ -82,7 +82,7 @@ def generate_stub() -> None:
for cmd_name in all_command_names():
cmd = command_for_name(cmd_name)
if cmd.options_spec:
do(cmd.options_spec, cmd.__class__.__name__ + 'RCOptions')
do(cmd.options_spec, f'{cmd.__class__.__name__}RCOptions')
save_type_stub(text, __file__)

View File

@@ -12,8 +12,8 @@ from contextlib import suppress
from typing import Any
CSI = '\033['
OSC = '\033]'
CSI = '\x1b['
OSC = '\x1b]'
def write(x: str) -> None:
@@ -42,11 +42,11 @@ def screen_alternate_keypad_mode() -> None:
def screen_cursor_position(y: int, x: int) -> None:
write(CSI + f'{y};{x}H')
write(f'{CSI}{y};{x}H')
def screen_cursor_forward(amt: int) -> None:
write(CSI + '%sC' % amt)
write(f'{CSI}{amt}C')
def screen_save_cursor() -> None:
@@ -58,105 +58,105 @@ def screen_restore_cursor() -> None:
def screen_cursor_back1(amt: int) -> None:
write(CSI + '%sD' % amt)
write(f'{CSI}{amt}D')
def screen_save_modes() -> None:
write(CSI + '?s')
write(f'{CSI}?s')
def screen_restore_modes() -> None:
write(CSI + '?r')
write(f'{CSI}?r')
def screen_designate_charset(which: int, to: int) -> None:
w = '()'[int(which)]
t = chr(int(to))
write('\033' + w + t)
write(f'\x1b{w}{t}')
def select_graphic_rendition(*a: int) -> None:
write(CSI + '%sm' % ';'.join(map(str, a)))
write(f'{CSI}{";".join(map(str, a))}m')
def screen_cursor_to_column(c: int) -> None:
write(CSI + '%dG' % c)
write(f'{CSI}{c}G')
def screen_cursor_to_line(ln: int) -> None:
write(CSI + '%dd' % ln)
write(f'{CSI}{ln}d')
def screen_set_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 'h')
write(f'{CSI}{"?" if private else ""}{x}h')
def screen_save_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 's')
write(f'{CSI}{"?" if private else ""}{x}s')
def screen_reset_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 'l')
write(f'{CSI}{"?" if private else ""}{x}l')
def screen_restore_mode(x: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(x) + 'r')
write(f'{CSI}{"?" if private else ""}{x}r')
def screen_set_margins(t: int, b: int) -> None:
write(CSI + '%d;%dr' % (t, b))
write(f'{CSI}{t};{b}r')
def screen_indexn(n: int) -> None:
write(CSI + '%dS' % n)
write(f'{CSI}{n}S')
def screen_delete_characters(count: int) -> None:
write(CSI + '%dP' % count)
write(f'{CSI}{count}P')
def screen_push_colors(which: int) -> None:
write(CSI + '%d#P' % which)
write(f'{CSI}{which}#P')
def screen_pop_colors(which: int) -> None:
write(CSI + '%d#Q' % which)
write(f'{CSI}{which}#Q')
def screen_report_colors() -> None:
write(CSI + '#R')
write(f'{CSI}#R')
def screen_repeat_character(num: int) -> None:
write(CSI + '%db' % num)
write(f'{CSI}{num}b')
def screen_insert_characters(count: int) -> None:
write(CSI + '%d@' % count)
write(f'{CSI}{count}@')
def screen_scroll(count: int) -> None:
write(CSI + '%dS' % count)
write(f'{CSI}{count}S')
def screen_erase_in_display(how: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(how) + 'J')
write(f'{CSI}{"?" if private else ""}{how}J')
def screen_erase_in_line(how: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(how) + 'K')
write(f'{CSI}{"?" if private else ""}{how}K')
def screen_delete_lines(num: int) -> None:
write(CSI + str(num) + 'M')
write(f'{CSI}{num}M')
def screen_cursor_up2(count: int) -> None:
write(CSI + '%dA' % count)
write(f'{CSI}{count}A')
def screen_cursor_down(count: int) -> None:
write(CSI + '%dB' % count)
write(f'{CSI}{count}B')
def screen_carriage_return() -> None:
@@ -176,11 +176,11 @@ def screen_backspace() -> None:
def screen_set_cursor(mode: int, secondary: int) -> None:
write(CSI + '%d q' % secondary)
write(f'{CSI}{secondary} q')
def screen_insert_lines(num: int) -> None:
write(CSI + '%dL' % num)
write(f'{CSI}{num}L')
def draw(*a: str) -> None:
@@ -188,7 +188,7 @@ def draw(*a: str) -> None:
def screen_manipulate_title_stack(op: int, which: int) -> None:
write(CSI + '%d;%dt' % (op, which))
write(f'{CSI}{op};{which}t')
def report_device_attributes(mode: int, char: int) -> None:
@@ -197,21 +197,22 @@ def report_device_attributes(mode: int, char: int) -> None:
x += chr(char)
if mode:
x += str(mode)
write(CSI + x + 'c')
write(f'{CSI}{x}c')
def screen_decsace(mode: int) -> None:
write(CSI + str(mode) + '*x')
write(f'{CSI}{mode}*x')
def screen_set_8bit_controls(mode: int) -> None:
write('\x1b ' + ('G' if mode else 'F'))
write(f'\x1b {"G" if mode else "F"}')
def write_osc(code: int, string: str = '') -> None:
if string:
string = ';' + string
write(OSC + str(code) + string + '\x07')
write(f'{OSC}{code};{string}\x07')
else:
write(f'{OSC}{code}\x07')
set_dynamic_color = set_color_table_color = process_cwd_notification = write_osc
@@ -235,7 +236,7 @@ def clipboard_control(payload: str) -> None:
clipboard_control_pending += data.lstrip(';')
payload = clipboard_control_pending
clipboard_control_pending = ''
write(OSC + payload + '\x07')
write(f'{OSC}{payload}\x07')
def replay(raw: str) -> None:

View File

@@ -9,6 +9,7 @@
#include "state.h"
#include "cleanup.h"
#include "monotonic.h"
#include <Carbon/Carbon.h>
#include <Cocoa/Cocoa.h>
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
#include <UserNotifications/UserNotifications.h>
@@ -19,6 +20,11 @@
#include <crt_externs.h>
#include <objc/runtime.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300)
#define NSControlStateValueOn NSOnState
#define NSControlStateValueOff NSOffState
#define NSControlStateValueMixed NSMixedState
#endif
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
#define NSEventModifierFlagOption NSAlternateKeyMask
@@ -75,6 +81,137 @@ find_app_name(void) {
return @"kitty";
}
#define debug_key(...) if (OPT(debug_keyboard)) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
// SecureKeyboardEntryController {{{
@interface SecureKeyboardEntryController : NSObject
@property (nonatomic, readonly) BOOL isDesired;
@property (nonatomic, readonly, getter=isEnabled) BOOL enabled;
+ (instancetype)sharedInstance;
- (void)toggle;
- (void)update;
@end
@implementation SecureKeyboardEntryController {
int _count;
BOOL _desired;
}
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
_desired = false;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidResignActive:)
name:NSApplicationDidResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidBecomeActive:)
name:NSApplicationDidBecomeActiveNotification
object:nil];
if ([NSApp isActive]) {
[self update];
}
}
return self;
}
#pragma mark - API
- (void)toggle {
// Set _desired to the opposite of the current state.
_desired = !_desired;
debug_key("SecureKeyboardEntry: toggle called. Setting desired to %d ", _desired);
// Try to set the system's state of secure input to the desired state.
[self update];
}
- (BOOL)isEnabled {
return !!IsSecureEventInputEnabled();
}
- (BOOL)isDesired {
return _desired;
}
#pragma mark - Notifications
- (void)applicationDidResignActive:(NSNotification *)notification {
(void)notification;
if (_count > 0) {
debug_key("SecureKeyboardEntry: Application resigning active.");
[self update];
}
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
(void)notification;
if (self.isDesired) {
debug_key("SecureKeyboardEntry: Application became active.");
[self update];
}
}
#pragma mark - Private
- (BOOL)allowed {
return [NSApp isActive];
}
- (void)update {
debug_key("Update secure keyboard entry. desired=%d active=%d\n",
(int)self.isDesired, (int)[NSApp isActive]);
const BOOL secure = self.isDesired && [self allowed];
if (secure && _count > 0) {
debug_key("Want to turn on secure input but it's already on\n");
return;
}
if (!secure && _count == 0) {
debug_key("Want to turn off secure input but it's already off\n");
return;
}
debug_key("Before: IsSecureEventInputEnabled returns %d ", (int)self.isEnabled);
if (secure) {
OSErr err = EnableSecureEventInput();
debug_key("EnableSecureEventInput err=%d ", (int)err);
if (err) {
debug_key("EnableSecureEventInput failed with error %d ", (int)err);
} else {
_count += 1;
}
} else {
OSErr err = DisableSecureEventInput();
debug_key("DisableSecureEventInput err=%d ", (int)err);
if (err) {
debug_key("DisableSecureEventInput failed with error %d ", (int)err);
} else {
_count -= 1;
}
}
debug_key("After: IsSecureEventInputEnabled returns %d\n", (int)self.isEnabled);
}
@end
// }}}
@interface GlobalMenuTarget : NSObject
+ (GlobalMenuTarget *) shared_instance;
@end
@@ -95,12 +232,20 @@ PENDING(new_window, NEW_WINDOW)
PENDING(close_window, CLOSE_WINDOW)
PENDING(reset_terminal, RESET_TERMINAL)
PENDING(reload_config, RELOAD_CONFIG)
PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY)
- (void)open_kitty_website_url:(id)sender {
(void)sender;
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]];
}
- (BOOL)validateMenuItem:(NSMenuItem *)item {
if (item.action == @selector(toggle_macos_secure_keyboard_entry:)) {
item.state = [SecureKeyboardEntryController sharedInstance].isDesired ? NSControlStateValueOn : NSControlStateValueOff;
}
return YES;
}
#undef PENDING
+ (GlobalMenuTarget *) shared_instance
@@ -108,8 +253,11 @@ PENDING(reload_config, RELOAD_CONFIG)
static GlobalMenuTarget *sharedGlobalMenuTarget = nil;
@synchronized(self)
{
if (!sharedGlobalMenuTarget)
if (!sharedGlobalMenuTarget) {
sharedGlobalMenuTarget = [[GlobalMenuTarget alloc] init];
SecureKeyboardEntryController *k = [SecureKeyboardEntryController sharedInstance];
if (!k.isDesired && [[NSUserDefaults standardUserDefaults] boolForKey:@"SecureKeyboardEntry"]) [k toggle];
}
return sharedGlobalMenuTarget;
}
}
@@ -123,6 +271,7 @@ typedef struct {
typedef struct {
GlobalShortcut new_os_window, close_os_window, close_tab, edit_config_file, reload_config;
GlobalShortcut previous_tab, next_tab, new_tab, new_window, close_window, reset_terminal;
GlobalShortcut toggle_macos_secure_keyboard_entry;
} GlobalShortcuts;
static GlobalShortcuts global_shortcuts;
@@ -137,6 +286,7 @@ cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) {
Q(new_os_window); else Q(close_os_window); else Q(close_tab); else Q(edit_config_file);
else Q(new_tab); else Q(next_tab); else Q(previous_tab);
else Q(new_window); else Q(close_window); else Q(reset_terminal); else Q(reload_config);
else Q(toggle_macos_secure_keyboard_entry);
#undef Q
if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; }
int cocoa_mods;
@@ -229,6 +379,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
(void)(center); (void)notification;
UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound;
if (@available(macOS 11.0, *)) options |= UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner;
else options |= (1 << 2); // UNNotificationPresentationOptionAlert avoid deprecated warning
@@ -419,10 +570,7 @@ cocoa_create_global_menu(void) {
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[[appMenu addItemWithTitle:@"Secure Keyboard Entry"
action:@selector(toggleSecureInput:)
keyEquivalent:@"s"]
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
MENU_ITEM(appMenu, @"Secure Keyboard Entry", toggle_macos_secure_keyboard_entry);
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
@@ -547,6 +695,13 @@ cocoa_alt_option_key_pressed(NSUInteger flags) {
return (q & flags) == q;
}
void
cocoa_toggle_secure_keyboard_entry(void) {
SecureKeyboardEntryController *k = [SecureKeyboardEntryController sharedInstance];
[k toggle];
[[NSUserDefaults standardUserDefaults] setBool:k.isDesired forKey:@"SecureKeyboardEntry"];
}
void
cocoa_focus_window(void *w) {
NSWindow *window = (NSWindow*)w;
@@ -711,10 +866,17 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */
};
static void
uncaughtExceptionHandler(NSException *exception) {
log_error("Unhandled exception in Cocoa: %s", [[exception description] UTF8String]);
log_error("Stack trace:\n%s", [[exception.callStackSymbols description] UTF8String]);
}
bool
init_cocoa(PyObject *module) {
memset(&global_shortcuts, 0, sizeof(global_shortcuts));
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup);
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
return true;
}

View File

@@ -270,7 +270,7 @@ def zsh_output_serializer(ans: Completions) -> str:
yield ans
for description, matches in ans.match_groups.items():
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote('%B' + description + '%b')]
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(f'%B{description}%b')]
if not matches.trailing_space:
cmd += ['-S', '""']
if matches.is_files:
@@ -350,10 +350,10 @@ def fish2_output_serializer(ans: Completions) -> str:
def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None:
cmds = ['@' + c for c in remote_control_command_names()]
cmds = [f'@{c}' for c in remote_control_command_names()]
ans.add_match_group('Entry points', {
k: '' for k in
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
list(entry_points) + cmds + [f'+{k}' for k in namespaced_entry_points]
if not prefix or k.startswith(prefix)
})
if prefix:
@@ -443,7 +443,7 @@ def complete_alias_map(
long_opt = option_map.get(parts[0])
if long_opt is not None and complete_args is not None:
complete_args(ans, long_opt, parts[1], Delegate())
ans.add_prefix(parts[0] + '=')
ans.add_prefix(f'{parts[0]}=')
return
opt = option_map.get(w)
if w is last_word and not new_word:
@@ -682,7 +682,7 @@ def find_completions(words: Sequence[str], new_word: bool, entry_points: Iterabl
if words[0].startswith('@'):
if len(words) == 1 and not new_word:
prefix = words[0]
ans.add_match_group('Remote control commands', {'@' + c: '' for c in remote_control_command_names() if c.startswith(prefix)})
ans.add_match_group('Remote control commands', {f'@{c}': '' for c in remote_control_command_names() if c.startswith(prefix)})
else:
complete_remote_command(ans, words[0][1:], words[1:], new_word)
if words[0] == '+':

View File

@@ -39,17 +39,16 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
imports: Set[Tuple[str, str]] = set()
tc_imports: Set[Tuple[str, str]] = set()
def type_name(x: type) -> str:
ans = x.__name__
if x.__module__ and x.__module__ != 'builtins':
imports.add((x.__module__, x.__name__))
return ans
def option_type_as_str(x: Any) -> str:
if hasattr(x, '__name__'):
return type_name(x)
ans = repr(x)
ans = ans.replace('NoneType', 'None')
needs_import = False
if type(x) is type:
ans = x.__name__
needs_import = True
else:
ans = repr(x)
ans = ans.replace('NoneType', 'None')
if needs_import and getattr(x, '__module__', None) and x.__module__ not in ('builtins', 'typing'):
imports.add((x.__module__, x.__name__))
return ans
def option_type_data(option: Union[Option, MultiOption]) -> Tuple[Callable[[Any], Any], str]:
@@ -418,9 +417,9 @@ def generate_c_conversion(loc: str, ctypes: List[Option]) -> str:
def write_output(loc: str, defn: Definition) -> None:
cls, tc = generate_class(defn, loc)
with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f:
f.write(cls + '\n')
f.write(f'{cls}\n')
with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f:
f.write(tc + '\n')
f.write(f'{tc}\n')
ctypes = []
for opt in defn.root_group.iter_all_non_groups():
if isinstance(opt, Option) and opt.ctype:
@@ -428,7 +427,7 @@ def write_output(loc: str, defn: Definition) -> None:
if ctypes:
c = generate_c_conversion(loc, ctypes)
with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f:
f.write(c + '\n')
f.write(f'{c}\n')
def main() -> None:
@@ -454,6 +453,6 @@ def main() -> None:
loc = package_name
cls, tc = generate_class(defn, loc)
with open(os.path.join(os.path.dirname(path), 'kitten_options_types.py'), 'w') as f:
f.write(cls + '\n')
f.write(f'{cls}\n')
with open(os.path.join(os.path.dirname(path), 'kitten_options_parse.py'), 'w') as f:
f.write(tc + '\n')
f.write(f'{tc}\n')

View File

@@ -37,8 +37,8 @@ def expand_opt_references(conf_name: str, text: str) -> str:
def expand(m: 'Match[str]') -> str:
ref = m.group(1)
if '<' not in ref and '.' not in ref:
full_ref = conf_name + ref
return f':opt:`{ref} <{full_ref}>`'
# full ref
return f':opt:`{ref} <{conf_name}{ref}>`'
return str(m.group())
return re.sub(r':opt:`(.+?)`', expand, text)
@@ -214,7 +214,7 @@ class Option:
if not self.documented:
return ans
mopts = [self] + option_group
a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts))
a('.. opt:: ' + ', '.join(f'{conf_name}.{mo.name}' for mo in mopts))
a('.. code-block:: conf')
a('')
sz = max(len(x.name) for x in mopts)
@@ -330,7 +330,7 @@ class Mapping:
raise ValueError(f'The shortcut for {self.name} has no short_text')
sc_text = f'{conf_name}.{self.short_text}'
shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod))
a('.. shortcut:: ' + sc_text)
a(f'.. shortcut:: {sc_text}')
block_started = False
for sc in [self] + action_group:
if sc.add_to_default and sc.documented:
@@ -534,7 +534,7 @@ class Group:
ans[i] = ' '.join(parts)
if commented:
ans = [x if x.startswith('#') or not x.strip() else ('# ' + x) for x in ans]
ans = [x if x.startswith('#') or not x.strip() else (f'# {x}') for x in ans]
return ans

View File

@@ -72,9 +72,10 @@ class ToCmdline:
def __exit__(self, *a: Any) -> None:
self.override_env = None
def filter_env_vars(self, *a: str) -> 'ToCmdline':
def filter_env_vars(self, *a: str, **override: str) -> 'ToCmdline':
remove = frozenset(a)
self.override_env = {k: v for k, v in os.environ.items() if k not in remove}
self.override_env.update(override)
return self
def __call__(self, x: str) -> List[str]:
@@ -169,24 +170,30 @@ def parse_line(
log_error(f'Ignoring invalid config line: {line}')
return
key, val = m.groups()
if key == 'include':
if key in ('include', 'globinclude'):
val = os.path.expandvars(os.path.expanduser(val.strip()))
if not os.path.isabs(val):
val = os.path.join(base_path_for_includes, val)
try:
with open(val, encoding='utf-8', errors='replace') as include:
with currently_parsing.set_file(val):
_parse(include, parse_conf_item, ans, accumulate_bad_lines)
except FileNotFoundError:
log_error(
'Could not find included config file: {}, ignoring'.
format(val)
)
except OSError:
log_error(
'Could not read from included config file: {}, ignoring'.
format(val)
)
if key == 'globinclude':
from pathlib import Path
vals = tuple(map(lambda x: str(os.fspath(x)), Path(base_path_for_includes).glob(val)))
else:
if not os.path.isabs(val):
val = os.path.join(base_path_for_includes, val)
vals = (val,)
for val in vals:
try:
with open(val, encoding='utf-8', errors='replace') as include:
with currently_parsing.set_file(val):
_parse(include, parse_conf_item, ans, accumulate_bad_lines)
except FileNotFoundError:
log_error(
'Could not find included config file: {}, ignoring'.
format(val)
)
except OSError:
log_error(
'Could not read from included config file: {}, ignoring'.
format(val)
)
return
if not parse_conf_item(key, val, ans):
log_error(f'Ignoring unknown config key: {key}')
@@ -316,8 +323,7 @@ def parse_kittens_func_args(action: str, args_funcs: Dict[str, KeyFunc[Tuple[str
parser = args_funcs[func]
except KeyError as e:
raise KeyError(
'Unknown action: {}. Check if map action: '
'{} is valid'.format(func, action)
f'Unknown action: {func}. Check if map action: {action} is valid'
) from e
try:

View File

@@ -49,13 +49,12 @@ def atomic_save(data: bytes, path: str) -> None:
except FileNotFoundError:
pass
except Exception as err:
log_error('Failed to delete temp file {} for atomic save with error: {}'.format(
p, err))
log_error(f'Failed to delete temp file {p} for atomic save with error: {err}')
@contextmanager
def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
cached_path = os.path.join(cache_dir(), name + '.json')
cached_path = os.path.join(cache_dir(), f'{name}.json')
cached_values: Dict[str, Any] = {}
try:
with open(cached_path, 'rb') as f:
@@ -63,8 +62,7 @@ def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
except FileNotFoundError:
pass
except Exception as err:
log_error('Failed to load cached in {} values with error: {}'.format(
name, err))
log_error(f'Failed to load cached in {name} values with error: {err}')
yield cached_values
@@ -72,8 +70,7 @@ def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
data = json.dumps(cached_values).encode('utf-8')
atomic_save(data, cached_path)
except Exception as err:
log_error('Failed to save cached values with error: {}'.format(
err))
log_error(f'Failed to save cached values with error: {err}')
def commented_out_default_config() -> str:

View File

@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 24, 0)
version: Version = Version(0, 24, 2)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -90,7 +90,7 @@ START_ALLOW_CASE_RANGE
case 3:
self->italic = true; break;
case 4:
if (i < count) { self->decoration = MIN(3, params[i]); i++; }
if (i < count) { self->decoration = MIN(5, params[i]); i++; }
else self->decoration = 1;
break;
case 7:
@@ -161,7 +161,7 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un
S(italic, true);
case 4: {
uint8_t val = 1;
if (i < count) { val = MIN(3, params[i]); i++; }
if (i < count) { val = MIN(5, params[i]); i++; }
S(decoration, val);
}
case 7:

View File

@@ -142,7 +142,7 @@ typedef struct {
typedef union CellAttrs {
struct {
uint16_t width : 2;
uint16_t decoration : 2;
uint16_t decoration : 3;
uint16_t bold : 1;
uint16_t italic : 1;
uint16_t reverse : 1;

View File

@@ -7,11 +7,10 @@ import socket
import sys
import termios
import time
from contextlib import suppress
from functools import partial
from pprint import pformat
from typing import (
IO, Callable, Dict, Generator, Iterable, Iterator, Optional, Set, Tuple
)
from typing import IO, Callable, Dict, Iterator, Optional, Set, TypeVar
from kittens.tui.operations import colored, styled
@@ -19,14 +18,15 @@ from .cli import version
from .constants import (
extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path
)
from .fast_data_types import num_users, Color
from .fast_data_types import Color, num_users
from .options.types import Options as KittyOpts, defaults
from .options.utils import MouseMap
from .options.utils import SequenceMap
from .rgb import color_as_sharp
from .types import MouseEvent, SingleKey
from .typing import SequenceMap
from .types import MouseEvent, Shortcut
ShortcutMap = Dict[Tuple[SingleKey, ...], str]
AnyEvent = TypeVar('AnyEvent', MouseEvent, Shortcut)
Print = Callable[..., None]
ShortcutMap = Dict[Shortcut, str]
def green(x: str) -> str:
@@ -41,81 +41,41 @@ def title(x: str) -> str:
return colored(x, 'blue', intense=True)
def mod_to_names(mods: int) -> Generator[str, None, None]:
from .fast_data_types import (
GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER
)
modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER,
'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK}
for name, val in modmap.items():
if mods & val:
yield name
def print_event(ev: AnyEvent, defn: str, print: Print) -> None:
print(f'\t{ev.human_repr}{defn}')
def print_shortcut(key_sequence: Iterable[SingleKey], defn: str, print: Callable[..., None]) -> None:
from .fast_data_types import glfw_get_key_name
keys = []
for key_spec in key_sequence:
names = []
mods, is_native, key = key_spec
names = list(mod_to_names(mods))
if key:
kname = glfw_get_key_name(0, key) if is_native else glfw_get_key_name(key, 0)
names.append(kname or f'{key}')
keys.append('+'.join(names))
print('\t' + ' > '.join(keys), defn)
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[SingleKey, ...]], print: Callable[..., None]) -> None:
def print_mapping_changes(defns: Dict[AnyEvent, str], changes: Set[AnyEvent], text: str, print: Print) -> None:
if changes:
print(title(text))
for k in sorted(changes):
print_shortcut(k, defns[k], print)
print_event(k, defns[k], print)
def compare_keymaps(final: ShortcutMap, initial: ShortcutMap, print: Callable[..., None]) -> None:
def compare_maps(final: Dict[AnyEvent, str], initial: Dict[AnyEvent, str], print: Print) -> None:
is_mouse = False
for k in initial:
if isinstance(k, MouseEvent):
is_mouse = True
break
added = set(final) - set(initial)
removed = set(initial) - set(final)
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
print_shortcut_changes(final, 'Added shortcuts:', added, print)
print_shortcut_changes(initial, 'Removed shortcuts:', removed, print)
print_shortcut_changes(final, 'Changed shortcuts:', changed, print)
which = 'mouse actions' if is_mouse else 'shortcuts'
print_mapping_changes(final, added, f'Added {which}:', print)
print_mapping_changes(initial, removed, f'Removed {which}:', print)
print_mapping_changes(final, changed, f'Changed {which}:', print)
def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
ans = {}
for key_spec, rest_map in m.items():
for r, action in rest_map.items():
ans[(key_spec,) + (r)] = action
ans[Shortcut((key_spec,) + (r))] = action
return ans
def compare_mousemaps(final: MouseMap, initial: MouseMap, print: Callable[..., None]) -> None:
added = set(final) - set(initial)
removed = set(initial) - set(final)
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
def print_mouse_action(trigger: MouseEvent, defn: str) -> None:
names = list(mod_to_names(trigger.mods)) + [f'b{trigger.button+1}']
when = {-1: 'repeat', 1: 'press', 2: 'doublepress', 3: 'triplepress'}.get(trigger.repeat_count, trigger.repeat_count)
grabbed = 'grabbed' if trigger.grabbed else 'ungrabbed'
print('\t', '+'.join(names), when, grabbed, defn)
def print_changes(defns: MouseMap, changes: Set[MouseEvent], text: str) -> None:
if changes:
print(title(text))
for k in sorted(changes):
print_mouse_action(k, defns[k])
print_changes(final, added, 'Added mouse actions:')
print_changes(initial, removed, 'Removed mouse actions:')
print_changes(final, changed, 'Changed mouse actions:')
def compare_opts(opts: KittyOpts, print: Callable[..., None]) -> None:
def compare_opts(opts: KittyOpts, print: Print) -> None:
from .config import load_config
print()
print('Config options different from defaults:')
@@ -131,7 +91,7 @@ def compare_opts(opts: KittyOpts, print: Callable[..., None]) -> None:
for f in changed_opts:
val = getattr(opts, f)
if isinstance(val, dict):
print(f'{title(f)}:')
print(title(f'{f}:'))
if f == 'symbol_map':
for k in sorted(val):
print(f'\tU+{k[0]:04x} - U+{k[1]:04x}{val[k]}')
@@ -144,14 +104,14 @@ def compare_opts(opts: KittyOpts, print: Callable[..., None]) -> None:
else:
print(fmt.format(f), str(getattr(opts, f)))
compare_mousemaps(opts.mousemap, default_opts.mousemap, print)
compare_maps(opts.mousemap, default_opts.mousemap, print)
final_, initial_ = opts.keymap, default_opts.keymap
final: ShortcutMap = {(k,): v for k, v in final_.items()}
initial: ShortcutMap = {(k,): v for k, v in initial_.items()}
final: ShortcutMap = {Shortcut((k,)): v for k, v in final_.items()}
initial: ShortcutMap = {Shortcut((k,)): v for k, v in initial_.items()}
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
final.update(final_s)
initial.update(initial_s)
compare_keymaps(final, initial, print)
compare_maps(final, initial, print)
if colors:
print(f'{title("Colors")}:', end='\n\t')
print('\n\t'.join(sorted(colors)))
@@ -162,16 +122,22 @@ class IssueData:
def __init__(self) -> None:
self.uname = os.uname()
self.s, self.n, self.r, self.v, self.m = self.uname
self.hostname = self.o = socket.gethostname()
try:
self.hostname = self.o = socket.gethostname()
except Exception:
self.hostname = self.o = 'localhost'
_time = time.localtime()
self.formatted_time = self.d = time.strftime('%a %b %d %Y', _time)
self.formatted_date = self.t = time.strftime('%H:%M:%S', _time)
try:
self.tty_name = format_tty_name(os.ttyname(sys.stdin.fileno()))
self.tty_name = format_tty_name(os.ctermid())
except OSError:
self.tty_name = '(none)'
self.l = self.tty_name # noqa
self.baud_rate = termios.tcgetattr(sys.stdin.fileno())[5]
self.baud_rate = 0
if sys.stdin.isatty():
with suppress(OSError):
self.baud_rate = termios.tcgetattr(sys.stdin.fileno())[5]
self.b = str(self.baud_rate)
try:
self.num_users = num_users()
@@ -218,13 +184,23 @@ def debug_config(opts: KittyOpts) -> str:
import subprocess
p(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
if os.path.exists('/etc/issue'):
with open('/etc/issue', encoding='utf-8', errors='replace') as f:
p(end=''.join(IssueData().parse_issue_file(f)))
try:
idata = IssueData()
except Exception:
pass
else:
with open('/etc/issue', encoding='utf-8', errors='replace') as f:
try:
datums = idata.parse_issue_file(f)
except Exception:
pass
else:
p(end=''.join(datums))
if os.path.exists('/etc/lsb-release'):
with open('/etc/lsb-release', encoding='utf-8', errors='replace') as f:
p(f.read().strip())
if not is_macos:
p('Running under:' + green('Wayland' if is_wayland() else 'X11'))
p('Running under:', green('Wayland' if is_wayland() else 'X11'))
p(green('Frozen:'), 'True' if getattr(sys, 'frozen', False) else 'False')
p(green('Paths:'))
p(yellow(' kitty:'), os.path.realpath(kitty_exe()))
@@ -238,4 +214,7 @@ def debug_config(opts: KittyOpts) -> str:
p(green('Loaded config overrides:'))
p(' ', '\n '.join(opts.config_overrides))
compare_opts(opts, p)
p()
p(green('Environment variable names seen by the kitty process:'))
p('\t' + '\n\t'.join(sorted(os.environ)))
return out.getvalue()

26
kitty/emoji.h generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2021-10-07
// unicode data, built from the unicode standard on: 2022-01-05
// see gen-wcwidth.py
#pragma once
#include "data-types.h"
@@ -276,7 +276,7 @@ is_emoji(char_type code) {
return true;
case 0x1f6d5 ... 0x1f6d7:
return true;
case 0x1f6e0 ... 0x1f6e5:
case 0x1f6dd ... 0x1f6e5:
return true;
case 0x1f6e9:
return true;
@@ -288,29 +288,31 @@ is_emoji(char_type code) {
return true;
case 0x1f7e0 ... 0x1f7eb:
return true;
case 0x1f7f0:
return true;
case 0x1f90c ... 0x1f93a:
return true;
case 0x1f93c ... 0x1f945:
return true;
case 0x1f947 ... 0x1f978:
return true;
case 0x1f97a ... 0x1f9cb:
return true;
case 0x1f9cd ... 0x1f9ff:
case 0x1f947 ... 0x1f9ff:
return true;
case 0x1fa70 ... 0x1fa74:
return true;
case 0x1fa78 ... 0x1fa7a:
case 0x1fa78 ... 0x1fa7c:
return true;
case 0x1fa80 ... 0x1fa86:
return true;
case 0x1fa90 ... 0x1faa8:
case 0x1fa90 ... 0x1faac:
return true;
case 0x1fab0 ... 0x1fab6:
case 0x1fab0 ... 0x1faba:
return true;
case 0x1fac0 ... 0x1fac2:
case 0x1fac0 ... 0x1fac5:
return true;
case 0x1fad0 ... 0x1fad6:
case 0x1fad0 ... 0x1fad9:
return true;
case 0x1fae0 ... 0x1fae7:
return true;
case 0x1faf0 ... 0x1faf6:
return true;
default: return false;
}

View File

@@ -60,9 +60,12 @@ copy_with_sendfile(int infd, int outfd, off_t in_pos, size_t len, FastFileCopyBu
off_t r = in_pos;
ssize_t n = sendfile(outfd, infd, &r, len);
if (n < 0) {
if (errno != EAGAIN) return false;
if (errno == ENOSYS || errno == EPERM) return copy_with_buffer(infd, outfd, in_pos, len, fcb);
continue;
if (errno == EAGAIN) continue;
if (errno == ENOSYS || // No kernel support
errno == EPERM ||
errno == EINVAL) // ZFS for some reason
return copy_with_buffer(infd, outfd, in_pos, len, fcb);
return false;
}
if (n == 0) {
// happens if input file is truncated
@@ -83,9 +86,15 @@ copy_with_file_range(int infd, int outfd, off_t in_pos, size_t len, FastFileCopy
off64_t r = in_pos;
ssize_t n = copy_file_range(infd, &r, outfd, NULL, len, 0);
if (n < 0) {
if (errno != EAGAIN) return false;
if (errno == ENOSYS || errno == EPERM) return copy_with_sendfile(infd, outfd, in_pos, len, fcb);
continue;
if (errno == EAGAIN) continue;
if (errno == ENOSYS || // Linux < 4.5
errno == EPERM || // Possibly Docker
errno == EINVAL || // ZFS for some reason
errno == EIO || // CIFS
errno == EOPNOTSUPP || // NFS
errno == EXDEV) // Prior to Linux 5.3, it was not possible to copy_file_range across file systems
return copy_with_sendfile(infd, outfd, in_pos, len, fcb);
return false;
}
if (n == 0) {
// happens if input file is truncated

View File

@@ -14,7 +14,7 @@ typedef struct FastFileCopyBuffer {
} FastFileCopyBuffer;
static inline void
free_fast_file_copy_buffer(FastFileCopyBuffer *fcb) { free(fcb->buf); }
free_fast_file_copy_buffer(FastFileCopyBuffer *fcb) { free(fcb->buf); fcb->buf = NULL; }
#define FREE_FCB_AFTER_FUNCTION __attribute__ ((__cleanup__(free_fast_file_copy_buffer)))
#define AutoFreeFastFileCopyBuffer FREE_FCB_AFTER_FUNCTION FastFileCopyBuffer

View File

@@ -764,6 +764,10 @@ def focus_os_window(os_window_id: int, also_raise: bool = True) -> bool:
pass
def toggle_secure_input() -> None:
pass
def start_profiler(path: str) -> None:
pass
@@ -1339,5 +1343,5 @@ def set_os_window_title(os_window_id: int, title: str) -> None:
pass
def update_ime_position_for_window(window_id: int, force: bool = False) -> bool:
def update_ime_position_for_window(window_id: int, force: bool = False, lost_focus: bool = False) -> bool:
pass

View File

@@ -387,7 +387,8 @@ has_cell_text(Font *self, CPUCell *cell) {
char_type combining_chars[arraysz(cell->cc_idx)];
unsigned num_cc = 0;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
if (!is_non_rendered_char(cell->cc_idx[i])) combining_chars[num_cc++] = codepoint_for_mark(cell->cc_idx[i]);
const char_type ccp = codepoint_for_mark(cell->cc_idx[i]);
if (!is_non_rendered_char(ccp)) combining_chars[num_cc++] = ccp;
}
if (num_cc == 0) return true;
if (num_cc == 1) {

View File

@@ -7,7 +7,7 @@
#
import math
from functools import partial as p, wraps
from functools import partial as p, wraps, lru_cache
from itertools import repeat
from typing import (
Any, Callable, Dict, Iterable, Iterator, List, MutableSequence, Optional,
@@ -754,19 +754,32 @@ def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: Tu
eight_bar(buf, width, height, level, x, horizontal)
@lru_cache(maxsize=64)
def distribute_dots(available_space: int, num_of_dots: int) -> Tuple[Tuple[int, ...], int]:
dot_size = max(1, available_space // (2 * num_of_dots))
extra = available_space - 2 * num_of_dots * dot_size
gaps = list(repeat(dot_size, num_of_dots))
if extra > 0:
idx = 0
while extra > 0:
gaps[idx] += 1
idx = (idx + 1) % len(gaps)
extra -= 1
gaps[0] //= 2
summed_gaps = tuple(sum(gaps[:i + 1]) for i in range(len(gaps)))
return summed_gaps, dot_size
def braille_dot(buf: BufType, width: int, height: int, col: int, row: int) -> None:
dot_height = max(1, height // 8)
dot_width = max(1, width // 4)
top_margin = (height - 7 * dot_height) // 2
left_margin = (width - 3 * dot_width) // 2
x_start = left_margin + (col * 2 * dot_width)
y_start = top_margin + (row * 2 * dot_height)
if y_start < height:
x_gaps, dot_width = distribute_dots(width, 2)
y_gaps, dot_height = distribute_dots(height, 4)
x_start = x_gaps[col] + col * dot_width
y_start = y_gaps[row] + row * dot_height
if y_start < height and x_start < width:
for y in range(y_start, min(height, y_start + dot_height)):
if x_start < width:
offset = y * width
for x in range(x_start, min(width, x_start + dot_width)):
buf[offset + x] = 255
offset = y * width
for x in range(x_start, min(width, x_start + dot_width)):
buf[offset + x] = 255
def braille(buf: BufType, width: int, height: int, which: int = 0) -> None:

View File

@@ -29,7 +29,7 @@ def create_font_map(all_fonts: Iterable[CoreTextFont]) -> FontMap:
ps = (x['postscript_name'] or '').lower()
ans['family_map'].setdefault(f, []).append(x)
ans['ps_map'].setdefault(ps, []).append(x)
ans['full_map'].setdefault(f + ' ' + s, []).append(x)
ans['full_map'].setdefault(f'{f} {s}', []).append(x)
return ans
@@ -45,7 +45,7 @@ def list_fonts() -> Generator[ListedFont, None, None]:
for fd in coretext_all_fonts():
f = fd['family']
if f:
fn = (f + ' ' + (fd['style'] or '')).strip()
fn = f'{f} {fd.get("style", "")}'.strip()
is_mono = bool(fd['monospace'])
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}

View File

@@ -58,7 +58,7 @@ def list_fonts() -> Generator[ListedFont, None, None]:
if fn_:
fn = str(fn_)
else:
fn = (f + ' ' + str(fd.get('style', ''))).strip()
fn = f'{f} {fd.get("style", "")}'.strip()
is_mono = fd.get('spacing') in ('MONO', 'DUAL')
yield {'family': f, 'full_name': fn, 'postscript_name': str(fd.get('postscript_name', '')), 'is_monospace': is_mono}

View File

@@ -28,13 +28,13 @@ def main(argv: Sequence[str]) -> None:
groups = create_family_groups()
for k in sorted(groups, key=lambda x: x.lower()):
if isatty:
print('\033[1;32m' + k + '\033[m')
print(f'\033[1;32m{k}\033[m')
else:
print(k)
for f in sorted(groups[k], key=lambda x: x['full_name'].lower()):
p = f['full_name']
if isatty:
p = '\033[3m' + p + '\033[m'
p = f'\033[3m{p}\033[m'
if psnames:
p += ' ({})'.format(f['postscript_name'])
print(' ', p)

View File

@@ -17,7 +17,7 @@ from kitty.fast_data_types import (
test_render_line, test_shape
)
from kitty.fonts.box_drawing import (
BufType, render_box_char, render_missing_glyph
BufType, distribute_dots, render_box_char, render_missing_glyph
)
from kitty.options.types import Options, defaults
from kitty.typing import CoreTextFont, FontConfigPattern
@@ -279,6 +279,23 @@ def add_curl(buf: CBufType, cell_width: int, position: int, thickness: int, cell
add_intensity(x, y1 + t, 255)
def add_dots(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
spacing, size = distribute_dots(cell_width, cell_width // (2 * thickness))
y = 1 + position - thickness // 2
for i in range(y, min(y + thickness, cell_height)):
for j, s in enumerate(spacing):
buf[cell_width * i + j * size + s: cell_width * i + (j + 1) * size + s] = [255] * size
def add_dashes(buf: CBufType, cell_width: int, position: int, thickness: int, cell_height: int) -> None:
halfspace_width = cell_width // 4
y = 1 + position - thickness // 2
for i in range(y, min(y + thickness, cell_height)):
buf[cell_width * i:cell_width * i + (cell_width - 3 * halfspace_width)] = [255] * (cell_width - 3 * halfspace_width)
buf[cell_width * i + 3 * halfspace_width:cell_width * (i + 1)] = [255] * (cell_width - 3 * halfspace_width)
def render_special(
underline: int = 0,
strikethrough: bool = False,
@@ -306,14 +323,13 @@ def render_special(
try:
f(ans, cell_width, *a)
except Exception as e:
log_error('Failed to render {} at cell_width={} and cell_height={} with error: {}'.format(
f.__name__, cell_width, cell_height, e))
log_error(f'Failed to render {f.__name__} at cell_width={cell_width} and cell_height={cell_height} with error: {e}')
if underline:
t = underline_thickness
if underline > 1:
t = max(1, min(cell_height - underline_position - 1, t))
dl([add_line, add_line, add_dline, add_curl][underline], underline_position, t, cell_height)
dl([add_line, add_line, add_dline, add_curl, add_dots, add_dashes][underline], underline_position, t, cell_height)
if strikethrough:
dl(add_line, strikethrough_position, strikethrough_thickness, cell_height)
@@ -384,7 +400,7 @@ def prerender_function(
render_cursor, cursor_beam_thickness=cursor_beam_thickness,
cursor_underline_thickness=cursor_underline_thickness, cell_width=cell_width,
cell_height=cell_height, dpi_x=dpi_x, dpi_y=dpi_y)
cells = f(1), f(2), f(3), f(0, True), f(missing=True), c(1), c(2), c(3)
cells = f(1), f(2), f(3), f(4), f(5), f(0, strikethrough=True), f(missing=True), c(1), c(2), c(3)
return tuple(map(ctypes.addressof, cells)), cells
@@ -499,7 +515,7 @@ def test_fallback_font(qtext: Optional[str] = None, bold: bool = False, italic:
try:
print(text, f)
except UnicodeEncodeError:
sys.stdout.buffer.write((text + ' %s\n' % f).encode('utf-8'))
sys.stdout.buffer.write(f'{text} {f}\n'.encode('utf-8'))
def showcase() -> None:

6
kitty/glfw-wrapper.c generated
View File

@@ -249,6 +249,12 @@ load_glfw(const char* path) {
*(void **) (&glfwPostEmptyEvent_impl) = dlsym(handle, "glfwPostEmptyEvent");
if (glfwPostEmptyEvent_impl == NULL) fail("Failed to load glfw function glfwPostEmptyEvent with error: %s", dlerror());
*(void **) (&glfwGetIgnoreOSKeyboardProcessing_impl) = dlsym(handle, "glfwGetIgnoreOSKeyboardProcessing");
if (glfwGetIgnoreOSKeyboardProcessing_impl == NULL) fail("Failed to load glfw function glfwGetIgnoreOSKeyboardProcessing with error: %s", dlerror());
*(void **) (&glfwSetIgnoreOSKeyboardProcessing_impl) = dlsym(handle, "glfwSetIgnoreOSKeyboardProcessing");
if (glfwSetIgnoreOSKeyboardProcessing_impl == NULL) fail("Failed to load glfw function glfwSetIgnoreOSKeyboardProcessing with error: %s", dlerror());
*(void **) (&glfwGetInputMode_impl) = dlsym(handle, "glfwGetInputMode");
if (glfwGetInputMode_impl == NULL) fail("Failed to load glfw function glfwGetInputMode with error: %s", dlerror());

8
kitty/glfw-wrapper.h generated
View File

@@ -1916,6 +1916,14 @@ typedef void (*glfwPostEmptyEvent_func)(void);
GFW_EXTERN glfwPostEmptyEvent_func glfwPostEmptyEvent_impl;
#define glfwPostEmptyEvent glfwPostEmptyEvent_impl
typedef bool (*glfwGetIgnoreOSKeyboardProcessing_func)(void);
GFW_EXTERN glfwGetIgnoreOSKeyboardProcessing_func glfwGetIgnoreOSKeyboardProcessing_impl;
#define glfwGetIgnoreOSKeyboardProcessing glfwGetIgnoreOSKeyboardProcessing_impl
typedef void (*glfwSetIgnoreOSKeyboardProcessing_func)(bool);
GFW_EXTERN glfwSetIgnoreOSKeyboardProcessing_func glfwSetIgnoreOSKeyboardProcessing_impl;
#define glfwSetIgnoreOSKeyboardProcessing glfwSetIgnoreOSKeyboardProcessing_impl
typedef int (*glfwGetInputMode_func)(GLFWwindow*, int);
GFW_EXTERN glfwGetInputMode_func glfwGetInputMode_impl;
#define glfwGetInputMode glfwGetInputMode_impl

View File

@@ -24,6 +24,7 @@ extern void cocoa_set_activation_policy(bool);
extern void cocoa_set_titlebar_appearance(void *w, unsigned int theme);
extern void cocoa_set_titlebar_color(void *w, color_type color);
extern bool cocoa_alt_option_key_pressed(unsigned long);
extern void cocoa_toggle_secure_keyboard_entry(void);
extern size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz);
extern monotonic_t cocoa_cursor_blink_interval(void);
@@ -1181,6 +1182,14 @@ glfw_window_hint(PyObject UNUSED *self, PyObject *args) {
// }}}
static PyObject*
toggle_secure_input(PYNOARG) {
#ifdef __APPLE__
cocoa_toggle_secure_keyboard_entry();
#endif
Py_RETURN_NONE;
}
static PyObject*
get_clipboard_string(PYNOARG) {
OSWindow *w = current_os_window();
@@ -1555,6 +1564,11 @@ strip_csi(PyObject *self UNUSED, PyObject *src) {
return PyUnicode_FromString(buf);
}
void
set_ignore_os_keyboard_processing(bool enabled) {
glfwSetIgnoreOSKeyboardProcessing(enabled);
}
// Boilerplate {{{
static PyMethodDef module_methods[] = {
@@ -1562,6 +1576,7 @@ static PyMethodDef module_methods[] = {
{"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL},
METHODB(set_default_window_icon, METH_VARARGS),
METHODB(get_clipboard_string, METH_NOARGS),
METHODB(toggle_secure_input, METH_NOARGS),
METHODB(get_content_scale_for_window, METH_NOARGS),
METHODB(ring_bell, METH_NOARGS),
METHODB(set_clipboard_string, METH_VARARGS),

View File

@@ -58,7 +58,7 @@ def guess_type(path: str, allow_filesystem_access: bool = False) -> Optional[str
ext = path.rpartition('.')[-1].lower()
mt = known_extensions.get(ext)
if mt in text_mimes:
mt = 'text/' + mt.split('/', 1)[-1]
mt = f'text/{mt.split("/", 1)[-1]}'
if not mt and is_rc_file(path):
mt = 'text/plain'
if not mt and is_folder(path):

View File

@@ -219,7 +219,6 @@ encode_function_key(const KeyEvent *ev, char *output) {
case GLFW_FKEY_PAGE_UP: S(5, '~');
case GLFW_FKEY_PAGE_DOWN: S(6, '~');
case GLFW_FKEY_HOME: S(1, 'H');
case GLFW_FKEY_KP_BEGIN: S(1, 'E');
case GLFW_FKEY_END: S(1, 'F');
case GLFW_FKEY_F1: S(1, 'P');
case GLFW_FKEY_F2: S(1, 'Q');
@@ -233,7 +232,12 @@ encode_function_key(const KeyEvent *ev, char *output) {
case GLFW_FKEY_F10: S(21, '~');
case GLFW_FKEY_F11: S(23, '~');
case GLFW_FKEY_F12: S(24, '~');
case GLFW_FKEY_KP_BEGIN: S(1, 'E');
/* end special numbers */
case GLFW_FKEY_MENU:
// use the same encoding as xterm for this key in legacy mode (F16)
if (legacy_mode) { S(29, '~'); }
break;
default: break;
}
#undef S

2
kitty/key_encoding.py generated
View File

@@ -181,7 +181,7 @@ class EventType(IntEnum):
@lru_cache(maxsize=128)
def parse_shortcut(spec: str) -> ParsedShortcut:
if spec.endswith('+'):
spec = spec[:-1] + 'plus'
spec = f'{spec[:-1]}plus'
parts = spec.split('+')
key_name = parts[-1]
key_name = functional_key_name_aliases.get(key_name.upper(), key_name)

View File

@@ -63,7 +63,7 @@ else:
import ctypes
for suffix in ('.0', ''):
with suppress(Exception):
lib = ctypes.CDLL('libxkbcommon.so' + suffix)
lib = ctypes.CDLL(f'libxkbcommon.so{suffix}')
break
else:
from ctypes.util import find_library

View File

@@ -79,6 +79,13 @@ active_window(void) {
return NULL;
}
void
update_ime_focus(OSWindow *osw, bool focused) {
if (!osw || !osw->handle) return;
GLFWIMEUpdateEvent ev = { .focused = focused, .type = GLFW_IME_UPDATE_FOCUS };
glfwUpdateIMEState(osw->handle, &ev);
}
void
update_ime_position(Window* w, Screen *screen) {
unsigned int cell_width = global_state.callback_os_window->fonts_data->cell_width, cell_height = global_state.callback_os_window->fonts_data->cell_height;

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