Compare commits

..

157 Commits

Author SHA1 Message Date
Kovid Goyal
644ee646a2 version 0.16.0 2020-01-28 08:48:00 +05:30
Kovid Goyal
8f1ec053ef ... 2020-01-27 08:50:29 +05:30
Kovid Goyal
2e3037ce3a Wayland: Fix window titles being set to very long strings on the order of 8KB causing a crash
Fixes #1526
2020-01-27 08:49:25 +05:30
Kovid Goyal
109a856b91 Fix #2313 2020-01-26 08:00:06 +05:30
Kovid Goyal
eaec854bfb ... 2020-01-25 11:51:17 +05:30
Kovid Goyal
2c6cc6f047 Remove unused slot 2020-01-25 11:22:22 +05:30
Kovid Goyal
f32ae10339 Use a global cache instead of a per window cache for grid size calculation 2020-01-25 11:21:05 +05:30
Kovid Goyal
c46317f96f Use typed layout data 2020-01-25 11:11:55 +05:30
Kovid Goyal
a67e4e550e Merge branch 'issue2310' of https://github.com/ctrlcctrlv/kitty 2020-01-24 13:43:15 +05:30
Fredrick Brennan
65a7b19f40 Make each OS window have its own framebuffer
This commit fixes graphics rendering when more than one OS window is
open, all OS windows are semi-transparent, and multiple windows contain
graphics.

This commit closes #2310.
2020-01-24 16:06:57 +08:00
Kovid Goyal
1b5daf930a Make the comment explaining wayland polling more detailed 2020-01-23 15:08:54 +05:30
Kovid Goyal
748ca81d4b Wayland: Fix a freeze in rare circumstances when having multiple OS Windows
Fixes #2307
Fixes #1722
2020-01-23 15:07:22 +05:30
Kovid Goyal
0ea7d24ba4 Fix incorrect offset when rendering images and there are +z-index, -z-index and --z-index images 2020-01-21 08:09:04 +05:30
Kovid Goyal
6c9861cb76 Fix incorrect blending of default bg color when using draw_cells_interleaved_premult with images but no below images 2020-01-21 07:59:44 +05:30
Kovid Goyal
a1125b5cc8 Move draw_bg multiplication into vertex shader 2020-01-21 07:58:34 +05:30
Kovid Goyal
b4e189d2bf Merge branch 'simplify' of https://github.com/Luflosi/kitty 2020-01-21 06:54:28 +05:30
Luflosi
dbc763fca0 Remove useless code from shader 2020-01-21 00:28:55 +01:00
Kovid Goyal
4c03ffeae2 Merge branch 'patch-1' of https://github.com/ctrlcctrlv/kitty 2020-01-18 15:11:03 +05:30
Fredrick Brennan
2719ac7d01 Add :pull:2248 to changelog.rst
It was missing
2020-01-18 17:34:27 +08:00
Kovid Goyal
219b81b5ee typo 2020-01-18 08:19:42 +05:30
Kovid Goyal
8e1443360f Merge branch 'force_ltr' of https://github.com/ctrlcctrlv/kitty 2020-01-18 08:07:56 +05:30
Kovid Goyal
d55242aa82 Merge branch 'master' of https://github.com/mfilenko/kitty 2020-01-17 21:40:10 +05:30
Max Filenko
ba531969ec List layouts in their default order 2020-01-17 16:28:44 +01:00
Max Filenko
c12e7587cb Document default configuration 2020-01-17 16:28:30 +01:00
Kovid Goyal
3aac0860a2 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-01-17 20:28:26 +05:30
Luflosi
e4fd12001c Cocoa: Add fully dynamic loading of Vulkan loader
From upstream: 7da87aaae7.
2020-01-17 14:18:28 +01:00
Luflosi
3581ffe04b Cocoa: Select Vulkan surface extension at runtime
From upstream: 15d91801b7.
2020-01-17 14:18:21 +01:00
Luflosi
f6901e4a3d Cocoa: Add support for VK_EXT_metal_surface
From upstream: c5cb4a253a.
2020-01-17 14:18:05 +01:00
Fredrick Brennan
5a2cc4578e force_ltr also makes FriBidi usable
Add above fact to documentation.
2020-01-17 17:57:26 +08:00
Fredrick Brennan
6b5acc4d3d Add force_ltr
Close #2252
2020-01-17 16:22:40 +08:00
Kovid Goyal
43326c9bd0 Fix URL detection not working for urls of the form scheme:///url
Fixes #2292
2020-01-17 07:49:01 +05:30
Kovid Goyal
96f3253e6d goto_tab now maps numbers larger than the last tab to the last tab
Fixes #2291
2020-01-16 20:17:17 +05:30
Kovid Goyal
f5090c951a Merge branch 'with_open' of https://github.com/Luflosi/kitty 2020-01-16 19:41:55 +05:30
Luflosi
edacdc5e25 Use python context manager to open file 2020-01-16 14:11:54 +01:00
Kovid Goyal
c67661af61 oops 2020-01-15 21:39:13 +05:30
Kovid Goyal
5e60329adf Changelog entry to titlebar-only 2020-01-15 21:37:20 +05:30
Kovid Goyal
6b4f8408de Merge branch 'macos_hide_titlebar' of https://github.com/Luflosi/kitty 2020-01-15 21:36:03 +05:30
Luflosi
ea1ef709c6 macOS: Allow hiding the titlebar only instead of all window decorations
Thanks to @ntruong for writing cb8279ec54 and  6807411365, the code was very helpful.
2020-01-15 16:35:36 +01:00
Kovid Goyal
bbf247c5c0 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-01-15 19:32:12 +05:30
Kovid Goyal
8243fd505f Make mark colors per window 2020-01-15 18:42:37 +05:30
Luflosi
ed9f9db1b0 X11: Fix parent window handle initialization
From upstream: 9372ba95fa.
2020-01-15 13:19:33 +01:00
Luflosi
74bed658e1 X11: Fix no window position events during resize
From upstream: fe57e3c292.
2020-01-15 13:19:32 +01:00
Kovid Goyal
2f7bdc274d Fix failing test 2020-01-15 11:48:13 +05:30
Kovid Goyal
1347ff27d9 Changelog entry for triple click change 2020-01-15 11:46:50 +05:30
Kovid Goyal
5bf4d520f5 Merge branch 'master' of https://github.com/copy/kitty 2020-01-15 11:43:20 +05:30
Kovid Goyal
cf8d665eb7 Allow having more than one full height window in the :code:tall layout
Fixes #2276
2020-01-15 11:28:58 +05:30
Fabian
b2eee6df5c Multiline triple-click selections: Don't ignore leading spaces in first line 2020-01-14 21:04:59 -06:00
Kovid Goyal
e8121b39ec Add newer layout classes to tests 2020-01-15 08:14:45 +05:30
Kovid Goyal
5359abab76 next version should be 0.16.0 2020-01-15 08:05:45 +05:30
Kovid Goyal
01644ac3f0 Changelog entry for marks 2020-01-15 08:05:12 +05:30
Kovid Goyal
8fbf552494 Allow opening new tabs/windows before the current tab/window as well as after it with the :option:launch --location option. 2020-01-15 08:02:05 +05:30
Kovid Goyal
9c3390c5e6 Allow specifying a marker when launching windows 2020-01-15 07:22:32 +05:30
Kovid Goyal
24d45b99e3 typo 2020-01-15 07:13:32 +05:30
Kovid Goyal
936f2186cc Allow scrolling to marks 2020-01-15 07:11:34 +05:30
Kovid Goyal
c68b302fa2 Merge branch 'wayland' of https://github.com/kennylevinsen/kitty 2020-01-15 06:51:24 +05:30
Kenny Levinsen
ca7cab1a2d wayland: Consistently use errno for errors 2020-01-14 20:06:13 +01:00
Kenny Levinsen
083b294659 wayland: Simplify dispatch_pending error handling
wl_display_dispatch_pending does not return EAGAIN, and always sets an
error when it fails. This allows us to handle errors in a simpler
manner.
2020-01-14 20:04:23 +01:00
Kenny Levinsen
bc24716476 wayland: errno does not have to be cleared
As long as errno is only read on error from an errno-setting function,
then there is no need to reset errno between uses.
2020-01-14 20:04:23 +01:00
Kenny Levinsen
4acab65016 wayland: Only cancel display read after prepare success
The display reader count only increments when wl_display_prepare_read
succeeds.  Calling wl_display_cancel_read when wl_display_prepare_read
has not succeeded results in a negative reader count, which does not
have well-defined behavior.
2020-01-14 20:00:47 +01:00
Kenny Levinsen
f6b03f106c wayland: Cancel display read before abortOnFatalError
Calling wl_display_cancel_read immediately ensures that other readers
waiting on us will have a chance to wake up and discover the error in a
timely manner.
2020-01-14 19:59:02 +01:00
Kovid Goyal
8f9bbeabd7 Fix reference to --debug-config 2020-01-14 19:50:18 +05:30
Kovid Goyal
ac72ac7af2 Wayland: On dispatch error cancel read before running fatal error handlers
Might help #1722
2020-01-14 19:48:28 +05:30
Kovid Goyal
68e00bc8e1 Proper fix for #2270
Fixes #2270
2020-01-14 18:50:06 +05:30
Kovid Goyal
922d6b3b32 Fix #2270 2020-01-14 17:20:35 +05:30
Kovid Goyal
4dde98305e Allow setting markers using remote control 2020-01-14 15:06:52 +05:30
Kovid Goyal
3593189a75 Add a link to the marks docs 2020-01-14 13:45:37 +05:30
Kovid Goyal
4f135be86c Allow creating markers dynamically 2020-01-13 20:05:50 +05:30
Kovid Goyal
605b32f78a Merge branch 'dryer' of https://github.com/Luflosi/kitty 2020-01-13 19:42:56 +05:30
Luflosi
476a8b1429 DRYer
Inspired by e0c66ea5ba.
2020-01-13 14:35:17 +01:00
Kovid Goyal
da9165aebf More marker docs 2020-01-13 12:54:29 +05:30
Kovid Goyal
455a1e9532 Basic docs for marking 2020-01-13 12:38:58 +05:30
Kovid Goyal
f12031e284 Make the run-program example a little fancier 2020-01-13 12:16:43 +05:30
Kovid Goyal
df551bc992 Change marker 1 color 2020-01-13 11:58:18 +05:30
Kovid Goyal
35fb702833 Use only a single marker function
Multipe colors/expressions can instead be combined at definition time
2020-01-13 11:57:19 +05:30
Kovid Goyal
072cd29e3c Make add_marker easier to use with combine 2020-01-13 09:56:39 +05:30
Kovid Goyal
624dd78460 Actually apply mark colors when rendering 2020-01-13 09:51:03 +05:30
Kovid Goyal
51c4737a29 Test function marking 2020-01-13 06:50:37 +05:30
Kovid Goyal
d63ea11cac Marking now works 2020-01-13 06:50:37 +05:30
Kovid Goyal
3d307794be Function to get marks for testing 2020-01-13 06:50:36 +05:30
Kovid Goyal
d32a05925f Code to add and remove markers using maps 2020-01-13 06:50:36 +05:30
Kovid Goyal
04006ea81e Wrapper functions to handle passing matching values to C code 2020-01-13 06:50:36 +05:30
Kovid Goyal
1723616968 Code to apply match colors based on match results 2020-01-13 06:50:36 +05:30
Kovid Goyal
23bc2171c9 Avoid a double call for mark functions 2020-01-13 06:50:36 +05:30
Kovid Goyal
0b3602f764 Code to add markers to Screen 2020-01-13 06:50:36 +05:30
Kovid Goyal
0e448347cd Fix #2279 2020-01-13 06:50:14 +05:30
Kovid Goyal
e0c66ea5ba DRYer 2020-01-12 21:42:12 +05:30
Kovid Goyal
268de9a9d5 Merge branch 'add_CIRCUMFLEX_to_control_codes' of https://github.com/Luflosi/kitty 2020-01-12 21:03:27 +05:30
Luflosi
74eb523178 Add GLFW_KEY_CIRCUMFLEX to control_codes
See https://github.com/kovidgoyal/kitty/issues/1990.
2020-01-12 16:27:40 +01:00
Kovid Goyal
8c540d5a46 Fix #2277 2020-01-12 20:00:08 +05:30
Kovid Goyal
2045055ae3 Use actual color value comparison when detecting default background color
This is very slightly more GPU intensive but means that even if
an application explicitly sets the color to be the same as the default
color, it will be transparent.
2020-01-12 18:14:03 +05:30
Kovid Goyal
d8ed135b46 Clean up rendering of cell backgrounds 2020-01-12 13:20:23 +05:30
Kovid Goyal
3a4ba7e834 Remove un-needed variable 2020-01-12 10:55:14 +05:30
Kovid Goyal
fab57fbdf1 Micro-optimization 2020-01-12 10:53:27 +05:30
Kovid Goyal
76a93b6cd6 DRYer 2020-01-12 10:52:42 +05:30
Kovid Goyal
f44c3a4606 Forgot to update comment describing render pipeline 2020-01-12 10:26:25 +05:30
Kovid Goyal
714bee7f45 Only use an extra draw call for the background when drawing interleaved cells if it is actually required 2020-01-12 09:37:16 +05:30
Kovid Goyal
91673642b3 icat: Allow easily specifying z-index values below background threshold 2020-01-12 09:36:11 +05:30
Kovid Goyal
fee08d746c Merge branch 'replace_exception_with_alias' of https://github.com/Luflosi/kitty 2020-01-11 21:21:52 +05:30
Luflosi
527ff0238a Replace all instances of EnvironmentError with OSError
According to the text just above https://docs.python.org/3/library/exceptions.html#EnvironmentError, `EnvironmentError` has been an alias of `OSError` since Python 3.3. Replacing it makes the code more consistent since `OSError` is used in other places in the code too.
2020-01-11 16:47:36 +01:00
Kovid Goyal
789d649b5c defaultbg should be a local variable 2020-01-11 16:59:50 +05:30
Fredrick Brennan
8020657d8c Allow drawing images below cells w/background 2020-01-11 16:56:59 +05:30
Kovid Goyal
a15d3ff12d ... 2020-01-10 10:19:02 +05:30
Kovid Goyal
fbce95aeda Add an example for open file hints 2020-01-10 10:18:18 +05:30
Kovid Goyal
9bc1b5a2d9 hints kitten: Allow pressing :sc:goto_file_line to quickly open the selected file at the selected line in vim
Fixes #2268
2020-01-10 10:01:03 +05:30
Kovid Goyal
6a8b7bf92f Merge branch 'with_suppress' of https://github.com/Luflosi/kitty 2020-01-09 20:58:37 +05:30
Luflosi
48728bdfdc Use "with suppress()" to suppress a python exception 2020-01-09 16:21:56 +01:00
Kovid Goyal
b6416cf8a9 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-01-09 06:58:30 +05:30
Luflosi
053de5763a X11: Fix BadMatch focusing a window on non-EWMH WM
From upstream: aa5e313561.
2020-01-08 19:50:17 +01:00
Kovid Goyal
fdb915d0c8 Merge branch 'zindex' of https://github.com/ctrlcctrlv/kitty 2020-01-08 18:20:34 +05:30
Fredrick Brennan
c2769c9aed Add --z-index to icat kitten
Meow.
2020-01-08 20:08:01 +08:00
Kovid Goyal
57deb19d0b typo 2020-01-07 10:45:05 +05:30
Kovid Goyal
321771b150 Merge branch 'resize-by-increments' of https://github.com/aes/kitty 2020-01-07 10:44:55 +05:30
Kovid Goyal
61dd2011f5 Dont rely on fontconfig to get postscript names 2020-01-07 09:30:41 +05:30
Kovid Goyal
8a5f189213 Ignore invalid font features when parsing config 2020-01-07 09:20:04 +05:30
Kovid Goyal
14560b008a Clean up font features merge 2020-01-07 08:54:32 +05:30
Kovid Goyal
db85e07d41 Merge branch 'master' of https://github.com/ctrlcctrlv/kitty 2020-01-07 07:18:31 +05:30
Kovid Goyal
07c838fe24 Merge branch 'round_printed_timeout' of https://github.com/Luflosi/kitty 2020-01-06 07:14:53 +05:30
Luflosi
1583b1b0c6 Print warning message timeout rounded to two digits after the dot
The timeout is 0.25 seconds. Printing 0.25 instead of 0.250000 looks a lot nicer and a resolution of 10 milliseconds should be enough for this warning message anyways.
2020-01-05 16:10:10 +01:00
Fredrick Brennan
d250555cd0 Make font_feature_settings respect disable_ligatures 2020-01-04 20:11:34 +08:00
Fredrick Brennan
dbd0dab154 Revert addition of ffs_set to Font struct
It was from an earlier stage of development and is not needed
2020-01-04 15:37:05 +08:00
Fredrick Brennan
b479ea410d Prevent leaks
Thanks @martinetd
2020-01-04 15:34:47 +08:00
Fredrick Brennan
1db613e95b Add font_feature_settings
Close #2247
2020-01-04 14:36:20 +08:00
Kovid Goyal
70071fe1f6 Merge branch 'simplify' of https://github.com/Luflosi/kitty 2020-01-03 07:46:58 +05:30
Luflosi
58688dda38 Remove duplicate code
The hints, once set with `glfwWindowHint()`, retain their values until changed again. This means, that the `GLFW_DECORATED` hint only needs to be set once, even on non-macOS.
2020-01-03 03:01:53 +01:00
Kovid Goyal
756d65c41e Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-01-03 07:18:25 +05:30
Luflosi
b9a08eacb0 X11: Cleanup
From upstream: 8149a5fc00.
2020-01-03 02:37:19 +01:00
Kovid Goyal
b653711446 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-01-03 06:48:30 +05:30
Luflosi
e725c647ef Cocoa: Only create per-monitor display link once
From upstream: b4a8eb9b19.
2020-01-02 18:05:38 +01:00
Kovid Goyal
003f77b257 Merge branch 'add_macos_help_menu_item' of https://github.com/Luflosi/kitty 2019-12-29 22:03:08 +05:30
Luflosi
10b578e6d2 Add help menu to macOS menu bar
Most macOS Apps have a Help menu as the last menu item in the menu bar. They usually have a menu item with the keyboard shortcut <kbd>⌘</kbd>+<kbd>?</kbd>, which shows some kind of help or documentation. I named the menu item "Visit kitty website" and let it open the kitty website as documentation.
macOS also magically adds a search feature to the help menu.
2019-12-29 17:24:12 +01:00
Kovid Goyal
151c6c2c70 Merge branch 'always_show_menu_item' of https://github.com/Luflosi/kitty 2019-12-29 20:53:22 +05:30
Luflosi
53e6de9bd9 Always show the macOS New OS Window menu item
When there is no keyboard shortcut for a new OS window, the macOS menu item should still exist, it should just not have a keyboard shortcut.
2019-12-29 15:27:52 +01:00
Kovid Goyal
66a455a079 Merge branch 'simplify' of https://github.com/Luflosi/kitty 2019-12-29 19:45:07 +05:30
Luflosi
f8855410e0 Simplify some code in kitty/cocoa_window.m
`addItemWithTitle:` can be used instead of `initWithTitle:`. It returns an `NSMenuItem`, which removes the need for allocating an `NSMenuItem` manually and releasing it again.
2019-12-29 14:33:19 +01:00
Kovid Goyal
93e8074325 ... 2019-12-28 08:49:53 +05:30
Kovid Goyal
296f512bfb Forgot you cant use sizeof on arrays passed as function parameters 2019-12-28 08:44:32 +05:30
Kovid Goyal
c8f20d0edf Ensure key name is null terminated 2019-12-28 08:33:13 +05:30
Kovid Goyal
286f3630d7 Merge branch 'macos_fix_keyboard_shortcut_encoding' of https://github.com/Luflosi/kitty 2019-12-28 08:26:03 +05:30
Luflosi
38423505b1 Remove numpad keys from macOS keyboard shortcuts
See previous commit message for the reason.
2019-12-27 15:12:26 +01:00
Luflosi
9a3c621eb6 Fix macOS keyboard shortcut encoding
`glfwGetCocoaKeyEquivalent()` in `glfw/cocoa_window.m` expects the returned characters to be of type `unichar`, which won't work for all unicode characters because it is defined as `unsigned short` according to https://developer.apple.com/documentation/foundation/unichar?language=objc, which is only guaranteed to be at least 16 bits in size. The code calling this function also expects the encoding to be UTF-16.
When I added the various keys in https://github.com/kovidgoyal/kitty/pull/1928, I missed these facts. This means, that `glfwGetCocoaKeyEquivalent()` will behave unexpectedly when called with any of the new-ish keys. Luckily this function is currently only used for determining the macOS shortcut for `new_os_window` but I plan on using it more in the future.
Some of the constants, e.g. `NSBackspaceCharacter` are UTF-16 constants, so we can't just use UTF-8 everywhere.
I fixed the problem by using either UTF-8 characters packed into a `uint32_t` or UTF-16 characters in a `unichar` and then converting them to a UTF-8 encoded char string.

`NSEventModifierFlagNumericPad` isn't guaranteed to fit in a `unichar`, which made this undefined behaviour. It also didn't work. I tried to make it work using `NSEventModifierFlagNumericPad` as a modifier instead, as can be seen in this commit, but couldn't get it to work either because the constants used are native key codes and not unicode characters. Therefore the numpad keys will be removed in the next commit.
2019-12-27 12:42:51 +01:00
Kovid Goyal
bcf847fe98 ... 2019-12-27 15:26:31 +05:30
Kovid Goyal
ee7a5eef99 Have read_shell_environment() not fail when no shell is present 2019-12-27 12:25:01 +05:30
Kovid Goyal
7bed66a458 Ensure ans is set in read_shell_environment
Fixes #2232
2019-12-27 12:20:06 +05:30
Kovid Goyal
b26229c44e Merge branch 'remove_release' of https://github.com/Luflosi/kitty 2019-12-26 08:48:20 +05:30
Luflosi
c497c0f96a Remove call to release on an automatically memory managed object
This call even reliably caused a crash for code I was trying to develop.
2019-12-25 20:29:07 +01:00
Kovid Goyal
6df64613f2 Merge branch 'replace_void_pointer' of https://github.com/Luflosi/kitty 2019-12-25 16:35:08 +05:30
Luflosi
46dd648380 Use the correct pointer type instead of void* 2019-12-25 11:51:40 +01:00
Kovid Goyal
4cd5de78c7 Merge branch 'generate_key_table' of https://github.com/Luflosi/kitty 2019-12-25 08:34:27 +05:30
Luflosi
32fa46d6eb Regenerate key table
Since `CIRCUMFLEX` and `^` were removed from `UN_SHIFTED_PRINTABLE` in b2d428618c by 3a2a16f54c and b5229ec73c, `generate_key_table()` wasn't yet run again.
2019-12-25 02:16:37 +01:00
Kovid Goyal
902c2f6c88 Merge branch 'update_comment' of https://github.com/Luflosi/kitty 2019-12-24 19:31:37 +05:30
Luflosi
2a5196873a Update comment to remove legacy way of starting kitty
Since 9135387cfa kitty should not be started with `python3 .` anymore.
2019-12-24 14:22:37 +01:00
Kovid Goyal
d2067141d3 Fix #2226 2019-12-24 17:10:33 +05:30
Kovid Goyal
b5229ec73c oops 2019-12-23 10:19:02 +05:30
Kovid Goyal
3a2a16f54c Merge branch 'add_circumflex_key' of https://github.com/Luflosi/kitty 2019-12-23 10:08:25 +05:30
Luflosi
b2d428618c Add circumflex (^) key 2019-12-22 18:41:07 +01:00
Luflosi
c28ff5259a Finish implementing resize by increment 2019-12-09 08:38:41 +01:00
Anders Eurenius
4619259e26 Implement resize by increment 2019-12-09 08:33:32 +01:00
82 changed files with 10236 additions and 8719 deletions

View File

@@ -4,6 +4,53 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.16.0 [2020-01-28]
--------------------
- A new :doc:`marks` feature that allows highlighting and scrolling to arbitrary
text in the terminal window.
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
the selected file at the selected line in vim or a configurable editor (:iss:`2268`)
- Allow having more than one full height window in the :code:`tall` layout
(:iss:`2276`)
- Allow choosing OpenType features for individual fonts via the
:opt:`font_features` option. (:pull:`2248`)
- Wayland: Fix a freeze in rare circumstances when having multiple OS Windows
(:iss:`2307` and :iss:`1722`)
- Wayland: Fix window titles being set to very long strings on the order of 8KB
causing a crash (:iss:`1526`)
- Add an option :opt:`force_ltr` to turn off the display of text in RTL scripts
in right-to-left order (:pull:`2293`)
- Allow opening new tabs/windows before the current tab/window as well as after
it with the :option:`launch --location` option.
- Add a :opt:`resize_in_steps` option that can be used to resize the OS window
in steps as large as character cells (:pull:`2131`)
- When triple-click+dragging to select multiple lines, extend the selection
of the first line to match the rest on the left (:pull:`2284`)
- macOS: Add a :code:`titlebar-only` setting to
:opt:`hide_window_decorations` to only hide the title bar (:pull:`2286`)
- Fix a segfault when using :option:`kitty --debug-config` with maps (:iss:`2270`)
- ``goto_tab`` now maps numbers larger than the last tab to the last tab
(:iss:`2291`)
- Fix URL detection not working for urls of the form scheme:///url
(:iss:`2292`)
- When windows are semi-transparent and all contain graphics, correctly render
them. (:iss:`2310`)
0.15.1 [2019-12-21]
--------------------

View File

@@ -334,7 +334,9 @@ Finally, you can specify the image *z-index*, i.e. the vertical stacking order.
placed in the same location with different z-index values will be blended if
they are semi-transparent. You can specify z-index values using the ``z`` key.
Negative z-index values mean that the images will be drawn under the text. This
allows rendering of text on top of images.
allows rendering of text on top of images. Negative z-index values below
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
colors.
Deleting images
---------------------
@@ -417,7 +419,7 @@ Key Value Default Description
``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image
``c`` Positive integer ``0`` The number of columns to display the image over
``r`` Positive integer ``0`` The number of rows to display the image over
``z`` Integer ``0`` The *z-index* vertical stacking order of the image
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
**Keys for deleting images**
-----------------------------------------------------------
``d`` Single character. ``a`` What to delete.

View File

@@ -53,7 +53,7 @@ Quickstart
--------------
Pre-built binaries of |kitty| are available for both macOS and Linux.
See the :doc:`binary install instructions </binary>`. You can also
See the :doc:`binary install instructions </binary>`. You can
:doc:`build from source </build>`.
You can also use your favorite package manager to install the |kitty| package.
@@ -224,11 +224,11 @@ using the :sc:`new_window` key combination.
Currently, there are five layouts available,
* **Stack** -- Only a single maximized window is shown at a time
* **Tall** -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
* **Fat** -- One window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom
* **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom
* **Grid** -- All windows are shown in a grid
* **Horizontal** -- All windows are shown side-by-side
* **Stack** -- Only a single maximized window is shown at a time
* **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right
* **Vertical** -- All windows are shown one below the other
You can switch between layouts using the :sc:`next_layout` key combination. You can
@@ -256,13 +256,15 @@ the resizing increment (a positive integer that defaults to 1).
Some layouts take options to control their behavior. For example, the ``fat``
and ``tall`` layouts accept the ``bias`` option to control how the available
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
how the available space is split up.
To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
enabled_layouts tall:bias=70
enabled_layouts tall:bias=70;full_size=2
This will make the tall window occupy ``70%`` of available width. ``bias`` can be
any number between 10 and 90.
This will have ``2`` instead of a single tall window, that occupy ``70%``
instead of ``50%`` of available width. ``bias`` can be any number between 10
and 90.
Writing a new layout only requires about a hundred lines of code, so if there
is some layout you want, take a look at `layout.py
@@ -441,6 +443,14 @@ internal buffer named ``a`` and :kbd:`F2` to paste from that buffer. The buffer
names are arbitrary strings, so you can define as many such buffers as you
need.
Marks
-------------
kitty has the ability to mark text on the screen based on regular expressions.
This can be useful to highlight words or phrases when browsing output from long
running programs or similar. To learn how this feature works, see :doc:`marks`.
Frequently Asked Questions
---------------------------------

View File

@@ -18,6 +18,16 @@ select anything that looks like a path or filename and then insert it into the
terminal, very useful for picking files from the output of a ``git`` or ``ls`` command and
adding them to the command line for the next command.
You can also press :sc:`goto_file_line` to select anything that looks
like a path or filename followed by a colon and a line number and open
the file in vim at the specified line number. The patterns and editor
to be used can be modified using options passed to the kitten. For example::
map ctrl+g kitten hints --type=linenum --linenum-action=tab nvim +{line} +{path}
will open the selected file in a new tab inside neovim when you press
:kbd:`ctrl+g`.
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

122
docs/marks.rst Normal file
View File

@@ -0,0 +1,122 @@
Marks
=================
kitty has the ability to mark text on the screen based on regular expressions.
This can be useful to highlight words or phrases when browsing output from long
running programs or similar. Lets start with a few examples:
Suppose we want to be able to highlight the word ERROR in the current window.
Add the following to :file:`kitty.conf`::
map f1 toggle_marker text 1 ERROR
Now when you press :kbd:`F1` all instances of the word :code:`ERROR` will be
highlighted. To turn off the highlighting, press :kbd:`F1` again.
If you want to make it case-insensitive, use::
map f1 toggle_marker itext 1 ERROR
To make it match only complete words, use::
map f1 toggle_marker regex 1 \bERROR\b
Suppose you want to highlight both :code:`ERROR` and :code:`WARNING`, case
insensitively::
map f1 toggle_marker iregex 1 \bERROR\b 2 \bWARNING\b
kitty supports up to 3 mark groups (the numbers in the commands above). You
can control the colors used for these groups in :file:`kitty.conf` with::
mark1_foreground red
mark1_background gray
mark2_foreground green
...
.. note::
For performance reasons, matching is done per line only, and only when that line is
altered in any way. So you cannot match text that stretches across multiple
lines.
Creating markers dynamically
---------------------------------
If you want to create markers dynamically rather than pre-defining them in
:file:`kitty.conf` you can do so as follows::
map f1 create_marker
map f2 remove_marker
Then pressing :kbd:`F1` will allow you to enter the marker definition and set
it and pressing :kbd:`F2` will remove the marker.
You can also use the facilities for :doc:`remote-control` to dynamically
add/remove markers.
Scrolling to marks
--------------------
kitty has an action to scroll to the next line that contains a mark. You can
use it by mapping it to some shortcut in :file:`kitty.conf`::
map ctrl+p scroll_to_mark prev
map ctrl+n scroll_to_mark next
Then pressing :kbd:`ctrl+p` will scroll to the first line in the scrollback
buffer above the current top line that contains a mark. Pressing :kbd:`ctrl+n`
will scroll to show the first line below the current last line that contains
a mark. If you wish to jump to a mark of a specific type, you can add that to
the mapping::
map ctrl+1 scroll_to_mark prev 1
Which will scroll only to marks of type 1.
The full syntax for creating marks
-------------------------------------
The syntax of the :code:`toggle_marker` command is::
toggle_marker <marker-type> <specification>
Here :code:`marker-type` is one of:
* :code:`text` - simple substring matching
* :code:`itext` - case-insensitive substring matching
* :code:`regex` - A python regular expression
* :code:`iregex` - A case-insensitive python regular expression
* :code:`function` - An arbitrary function defined in a python file, see
:ref:`marker_funcs`.
.. _marker_funcs:
Arbitrary marker functions
-----------------------------
You can create your own marker functions. Create a python file named
:file:`mymarker.py` and in it create a :code:`marker` function. This
function receives the text of the line as input and must yield three numbers,
the starting character position, the ending character position and the mark
group (1-3). For example:
.. code-block::
def marker(text):
# Function to highlight the letter X
for i, ch in enumerate(text):
if ch.lower() == 'x':
yield i, i, 3
Save this file somewhere and in :file:`kitty.conf`, use::
map f1 toggle_marker function /path/to/mymarker.py
If you save the file in the kitty config directory, you can use::
map f1 toggle_marker function mymarker.py

View File

@@ -292,7 +292,7 @@ finalizePollData(EventLoopData *eld) {
}
int
pollForEvents(EventLoopData *eld, monotonic_t timeout) {
pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func display_callback) {
int read_ok = 0;
timeout = prepareForPoll(eld, timeout);
EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout));
@@ -305,6 +305,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
errno = 0;
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
int saved_errno = errno;
if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL);
dispatchTimers(eld);
if (result > 0) {
dispatchEvents(eld);
@@ -319,6 +320,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
errno = 0;
result = poll(eld->fds, eld->watches_count, -1);
int saved_errno = errno;
if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL);
dispatchTimers(eld);
if (result > 0) {
dispatchEvents(eld);

View File

@@ -90,7 +90,7 @@ void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
void changeTimerInterval(EventLoopData *eld, id_type timer_id, monotonic_t interval);
monotonic_t prepareForPoll(EventLoopData *eld, monotonic_t timeout);
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout);
int pollForEvents(EventLoopData *eld, monotonic_t timeout);
int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func);
unsigned dispatchTimers(EventLoopData *eld);
void finalizePollData(EventLoopData *eld);
bool initPollData(EventLoopData *eld, int display_fd);

View File

@@ -529,6 +529,32 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
@end
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
void* _glfwLoadLocalVulkanLoaderNS(void)
{
CFBundleRef bundle = CFBundleGetMainBundle();
if (!bundle)
return NULL;
CFURLRef url =
CFBundleCopyAuxiliaryExecutableURL(bundle, CFSTR("libvulkan.1.dylib"));
if (!url)
return NULL;
char path[PATH_MAX];
void* handle = NULL;
if (CFURLGetFileSystemRepresentation(url, true, (UInt8*) path, sizeof(path) - 1))
handle = _glfw_dlopen(path);
CFRelease(url);
return handle;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////

View File

@@ -146,7 +146,7 @@ static bool modeIsGood(CGDisplayModeRef mode)
// Convert Core Graphics display mode to GLFW video mode
//
static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
CVDisplayLinkRef link)
double fallbackRefreshRate)
{
GLFWvidmode result;
result.width = (int) CGDisplayModeGetWidth(mode);
@@ -154,11 +154,7 @@ static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
if (result.refreshRate == 0)
{
const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
if (!(time.flags & kCVTimeIsIndefinite))
result.refreshRate = (int) (time.timeScale / (double) time.timeValue);
}
result.refreshRate = (int) round(fallbackRefreshRate);
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
@@ -240,6 +236,30 @@ static bool refreshMonitorScreen(_GLFWmonitor* monitor)
return false;
}
// Returns a fallback refresh rate for when Core Graphics says it is zero
//
static double getFallbackRefreshRate(_GLFWmonitor* monitor)
{
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(monitor->ns.displayID);
double refreshRate = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode);
if (refreshRate == 0.0)
{
CVDisplayLinkRef link = NULL;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
if (!(time.flags & kCVTimeIsIndefinite))
refreshRate = (int) (time.timeScale / (double) time.timeValue);
CVDisplayLinkRelease(link);
}
return refreshRate;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
@@ -331,6 +351,7 @@ void _glfwPollMonitorsNS(void)
_GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height);
monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber;
monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(monitor);
createDisplayLink(monitor->ns.displayID);
free(name);
@@ -360,9 +381,6 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
if (_glfwCompareVideoModes(&current, best) == 0)
return;
CVDisplayLinkRef link;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
const CFIndex count = CFArrayGetCount(modes);
CGDisplayModeRef native = NULL;
@@ -373,7 +391,8 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
if (!modeIsGood(dm))
continue;
const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link);
const GLFWvidmode mode =
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
if (_glfwCompareVideoModes(best, &mode) == 0)
{
native = dm;
@@ -392,7 +411,6 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
}
CFRelease(modes);
CVDisplayLinkRelease(link);
}
// Restore the previously saved (original) video mode
@@ -469,9 +487,6 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
{
*count = 0;
CVDisplayLinkRef link;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
const CFIndex found = CFArrayGetCount(modes);
GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
@@ -482,7 +497,8 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
if (!modeIsGood(dm))
continue;
const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link);
const GLFWvidmode mode =
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
for (CFIndex j = 0; j < *count; j++)
{
@@ -499,20 +515,14 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
}
CFRelease(modes);
CVDisplayLinkRelease(link);
return result;
}
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
{
CVDisplayLinkRef link;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
*mode = vidmodeFromCGDisplayMode(native, link);
*mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
CGDisplayModeRelease(native);
CVDisplayLinkRelease(link);
}
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)

16
glfw/cocoa_platform.h vendored
View File

@@ -65,13 +65,15 @@ typedef void* CVDisplayLinkRef;
#endif
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef VkFlags VkMetalSurfaceCreateFlagsEXT;
typedef struct VkMacOSSurfaceCreateInfoMVK
{
VkStructureType sType;
@@ -80,7 +82,16 @@ typedef struct VkMacOSSurfaceCreateInfoMVK
const void* pView;
} VkMacOSSurfaceCreateInfoMVK;
typedef struct VkMetalSurfaceCreateInfoEXT
{
VkStructureType sType;
const void* pNext;
VkMetalSurfaceCreateFlagsEXT flags;
const void* pLayer;
} VkMetalSurfaceCreateInfoEXT;
typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacOSSurfaceCreateInfoMVK*,const VkAllocationCallbacks*,VkSurfaceKHR*);
typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMetalSurfaceCreateInfoEXT*,const VkAllocationCallbacks*,VkSurfaceKHR*);
#include "posix_thread.h"
#include "cocoa_joystick.h"
@@ -201,6 +212,7 @@ typedef struct _GLFWmonitorNS
CGDisplayModeRef previousMode;
uint32_t unitNumber;
id screen;
double fallbackRefreshRate;
} _GLFWmonitorNS;
@@ -227,6 +239,8 @@ void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
float _glfwTransformYNS(float y);
void* _glfwLoadLocalVulkanLoaderNS(void);
void _glfwClearDisplayLinks(void);
void _glfwRestartDisplayLinks(void);
void _glfwDispatchTickCallback(void);

View File

@@ -410,6 +410,7 @@ static int translateKey(unsigned int key, bool apply_keymap)
K('[', LEFT_BRACKET);
K('\\', BACKSLASH);
K(']', RIGHT_BRACKET);
K('^', CIRCUMFLEX);
K('_', UNDERSCORE);
K('`', GRAVE_ACCENT);
K(PARAGRAPH_UTF_8, PARAGRAPH);
@@ -1577,10 +1578,18 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
{
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
else
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE)
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
else
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
{
if (widthincr != GLFW_DONT_CARE && heightincr != GLFW_DONT_CARE)
[window->ns.object setResizeIncrements:NSMakeSize(widthincr, heightincr)];
else
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
}
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
@@ -2070,11 +2079,16 @@ const char* _glfwPlatformGetClipboardString(void)
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface || !_glfw.vk.MVK_macos_surface)
return;
extensions[0] = "VK_KHR_surface";
extensions[1] = "VK_MVK_macos_surface";
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
{
extensions[0] = "VK_KHR_surface";
extensions[1] = "VK_EXT_metal_surface";
}
else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface)
{
extensions[0] = "VK_KHR_surface";
extensions[1] = "VK_MVK_macos_surface";
}
}
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED,
@@ -2090,19 +2104,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
VkSurfaceKHR* surface)
{
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
VkResult err;
VkMacOSSurfaceCreateInfoMVK sci;
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
if (!vkCreateMacOSSurfaceMVK)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
// HACK: Dynamically load Core Animation to avoid adding an extra
// dependency for the majority who don't use MoltenVK
NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"];
@@ -2128,11 +2129,49 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
[window->ns.view setLayer:window->ns.layer];
[window->ns.view setWantsLayer:YES];
memset(&sci, 0, sizeof(sci));
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
sci.pView = window->ns.view;
VkResult err;
if (_glfw.vk.EXT_metal_surface)
{
VkMetalSurfaceCreateInfoEXT sci;
PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT;
vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT)
vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT");
if (!vkCreateMetalSurfaceEXT)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"Cocoa: Vulkan instance missing VK_EXT_metal_surface extension");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
memset(&sci, 0, sizeof(sci));
sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
sci.pLayer = window->ns.layer;
err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface);
}
else
{
VkMacOSSurfaceCreateInfoMVK sci;
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
if (!vkCreateMacOSSurfaceMVK)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
memset(&sci, 0, sizeof(sci));
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
sci.pView = window->ns.view;
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
}
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
if (err)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -2178,9 +2217,9 @@ GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun
requestRenderFrame((_GLFWwindow*)w, callback);
}
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned short *cocoa_key, int *cocoa_mods) {
*cocoa_key = 0;
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char *cocoa_key, size_t key_sz, int *cocoa_mods) {
*cocoa_mods = 0;
memset(cocoa_key, 0, key_sz);
if (glfw_mods & GLFW_MOD_SHIFT)
*cocoa_mods |= NSEventModifierFlagShift;
@@ -2193,147 +2232,156 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
if (glfw_mods & GLFW_MOD_CAPS_LOCK)
*cocoa_mods |= NSEventModifierFlagCapsLock;
uint32_t utf_8_key = 0;
unichar utf_16_key = 0;
START_ALLOW_CASE_RANGE
switch(glfw_key) {
#define K(ch, name) case GLFW_KEY_##name: *cocoa_key = ch; break;
K('!', EXCLAM);
K('"', DOUBLE_QUOTE);
K('#', NUMBER_SIGN);
K('$', DOLLAR);
K('&', AMPERSAND);
K('\'', APOSTROPHE);
K('(', PARENTHESIS_LEFT);
K(')', PARENTHESIS_RIGHT);
K('+', PLUS);
K(',', COMMA);
K('-', MINUS);
K('.', PERIOD);
K('/', SLASH);
K('0', 0);
K('1', 1);
K('2', 2);
K('3', 3);
K('5', 5);
K('6', 6);
K('7', 7);
K('8', 8);
K('9', 9);
K(':', COLON);
K(';', SEMICOLON);
K('<', LESS);
K('=', EQUAL);
K('>', GREATER);
K('@', AT);
K('[', LEFT_BRACKET);
K('\\', BACKSLASH);
K(']', RIGHT_BRACKET);
K('_', UNDERSCORE);
K('`', GRAVE_ACCENT);
K('a', A);
K('b', B);
K('c', C);
K('d', D);
K('e', E);
K('f', F);
K('g', G);
K('h', H);
K('i', I);
K('j', J);
K('k', K);
K('l', L);
K('m', M);
K('n', N);
K('o', O);
K('p', P);
K('q', Q);
K('r', R);
K('s', S);
K('t', T);
K('u', U);
K('v', V);
K('w', W);
K('x', X);
K('y', Y);
K('z', Z);
K(PARAGRAPH_UTF_8, PARAGRAPH);
K(MASCULINE_UTF_8, MASCULINE);
K(S_SHARP_UTF_8, S_SHARP);
K(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
K(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
K(A_RING_LOWER_CASE_UTF_8, A_RING);
K(AE_LOWER_CASE_UTF_8, AE);
K(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
K(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
K(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
K(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
K(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
K(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
K(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
K(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
K(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
K(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
K(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
K(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
K(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
K(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
K(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
K(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
K(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
K(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
K(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
K(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
K(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
K(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
K(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
K(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
K(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
K(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
K(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
K(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
K(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
K(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
K(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
K(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
K(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
K(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
K(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
K(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
K(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
K(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
K(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
K(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
K(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
K(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
K(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
#define K8(ch, name) case GLFW_KEY_##name: utf_8_key = ch; break;
#define K16(ch, name) case GLFW_KEY_##name: utf_16_key = ch; break;
K8('!', EXCLAM);
K8('"', DOUBLE_QUOTE);
K8('#', NUMBER_SIGN);
K8('$', DOLLAR);
K8('&', AMPERSAND);
K8('\'', APOSTROPHE);
K8('(', PARENTHESIS_LEFT);
K8(')', PARENTHESIS_RIGHT);
K8('+', PLUS);
K8(',', COMMA);
K8('-', MINUS);
K8('.', PERIOD);
K8('/', SLASH);
K8('0', 0);
K8('1', 1);
K8('2', 2);
K8('3', 3);
K8('5', 5);
K8('6', 6);
K8('7', 7);
K8('8', 8);
K8('9', 9);
K8(':', COLON);
K8(';', SEMICOLON);
K8('<', LESS);
K8('=', EQUAL);
K8('>', GREATER);
K8('@', AT);
K8('[', LEFT_BRACKET);
K8('\\', BACKSLASH);
K8(']', RIGHT_BRACKET);
K8('^', CIRCUMFLEX);
K8('_', UNDERSCORE);
K8('`', GRAVE_ACCENT);
K8('a', A);
K8('b', B);
K8('c', C);
K8('d', D);
K8('e', E);
K8('f', F);
K8('g', G);
K8('h', H);
K8('i', I);
K8('j', J);
K8('k', K);
K8('l', L);
K8('m', M);
K8('n', N);
K8('o', O);
K8('p', P);
K8('q', Q);
K8('r', R);
K8('s', S);
K8('t', T);
K8('u', U);
K8('v', V);
K8('w', W);
K8('x', X);
K8('y', Y);
K8('z', Z);
K8(PARAGRAPH_UTF_8, PARAGRAPH);
K8(MASCULINE_UTF_8, MASCULINE);
K8(S_SHARP_UTF_8, S_SHARP);
K8(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
K8(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
K8(A_RING_LOWER_CASE_UTF_8, A_RING);
K8(AE_LOWER_CASE_UTF_8, AE);
K8(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
K8(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
K8(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
K8(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
K8(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
K8(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
K8(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
K8(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
K8(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
K8(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
K8(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
K8(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
K8(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
K8(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
K8(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
K8(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
K8(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
K8(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
K8(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
K8(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
K8(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
K8(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
K8(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
K8(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
K8(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
K8(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
K8(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
K8(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
K8(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
K8(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
K8(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
K8(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
K8(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
K8(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
K8(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
K8(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
K8(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
K8(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
K8(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
K8(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
K8(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
K8(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
K8(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
K(0x35, ESCAPE);
K('\r', ENTER);
K('\t', TAB);
K(NSBackspaceCharacter, BACKSPACE);
K(NSInsertFunctionKey, INSERT);
K(NSDeleteCharacter, DELETE);
K(NSLeftArrowFunctionKey, LEFT);
K(NSRightArrowFunctionKey, RIGHT);
K(NSUpArrowFunctionKey, UP);
K(NSDownArrowFunctionKey, DOWN);
K(NSPageUpFunctionKey, PAGE_UP);
K(NSPageDownFunctionKey, PAGE_DOWN);
K(NSHomeFunctionKey, HOME);
K(NSEndFunctionKey, END);
K(NSPrintFunctionKey, PRINT_SCREEN);
K8(0x35, ESCAPE);
K8('\r', ENTER);
K8('\t', TAB);
K16(NSBackspaceCharacter, BACKSPACE);
K16(NSInsertFunctionKey, INSERT);
K16(NSDeleteCharacter, DELETE);
K16(NSLeftArrowFunctionKey, LEFT);
K16(NSRightArrowFunctionKey, RIGHT);
K16(NSUpArrowFunctionKey, UP);
K16(NSDownArrowFunctionKey, DOWN);
K16(NSPageUpFunctionKey, PAGE_UP);
K16(NSPageDownFunctionKey, PAGE_DOWN);
K16(NSHomeFunctionKey, HOME);
K16(NSEndFunctionKey, END);
K16(NSPrintFunctionKey, PRINT_SCREEN);
case GLFW_KEY_F1 ... GLFW_KEY_F24:
*cocoa_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
case GLFW_KEY_KP_0 ... GLFW_KEY_KP_9:
*cocoa_key = NSEventModifierFlagNumericPad | (0x52 + (glfw_key - GLFW_KEY_KP_0)); break;
K((unichar)(0x41|NSEventModifierFlagNumericPad), KP_DECIMAL);
K((unichar)(0x43|NSEventModifierFlagNumericPad), KP_MULTIPLY);
K((unichar)(0x45|NSEventModifierFlagNumericPad), KP_ADD);
K((unichar)(0x4B|NSEventModifierFlagNumericPad), KP_DIVIDE);
K((unichar)(0x4E|NSEventModifierFlagNumericPad), KP_SUBTRACT);
K((unichar)(0x51|NSEventModifierFlagNumericPad), KP_EQUAL);
#undef K
utf_16_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
#undef K8
#undef K16
END_ALLOW_CASE_RANGE
}
if (utf_16_key != 0) {
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
} else {
unsigned str_pos = 0;
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
uint8_t byte = (utf_8_key >> 24) & 0xff;
utf_8_key <<= 8;
if (byte != 0) cocoa_key[str_pos++] = byte;
}
cocoa_key[str_pos] = 0;
}
}

View File

@@ -166,7 +166,7 @@ def generate_wrappers(glfw_header):
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char* cocoa_key, size_t key_sz, int* cocoa_mods)
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)

47
glfw/glfw3.h vendored
View File

@@ -405,6 +405,7 @@ extern "C" {
#define GLFW_KEY_LEFT_BRACKET 91 /* [ */
#define GLFW_KEY_BACKSLASH 92 /* \ */
#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */
#define GLFW_KEY_CIRCUMFLEX 94 /* ^ */
#define GLFW_KEY_UNDERSCORE 95 /* _ */
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
@@ -3035,6 +3036,46 @@ GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* window, int minwidth, int minhe
*
* @ingroup window
*/
GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* window, int widthincr, int heightincr);
/*! @brief Sets the size increments of the specified window.
*
* This function sets the size increments of the content area of the specified
* window. If the window is full screen, the size limits only take effect
* once it is made windowed. If the window is not resizable, this function
* does nothing.
*
* The size increments are applied immediately to a windowed mode window and
* may cause it to be resized.
*
* The dimension increments must be greater than zero.
*
* @param[in] window The window to set limits for.
* @param[in] widthincr The width increments, in screen coordinates, of the
* content area, or `GLFW_DONT_CARE`.
* @param[in] heightincr The height increments, in screen coordinates, of the
* content area, or `GLFW_DONT_CARE`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR.
*
* @remark If you set size limits and an aspect ratio that conflict, the
* results are undefined.
*
* @remark @wayland The size limits will not be applied until the window is
* actually resized, either by the user or by the compositor.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_sizelimits
* @sa @ref glfwSetWindowSizeLimits
*
* @since Added in version 3.2.
*
* @ingroup window
*/
GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* window, int numer, int denom);
/*! @brief Sets the size of the content area of the specified window.
@@ -4148,6 +4189,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
* - `GLFW_KEY_LEFT_BRACKET`
* - `GLFW_KEY_BACKSLASH`
* - `GLFW_KEY_RIGHT_BRACKET`
* - `GLFW_KEY_CIRCUMFLEX`
* - `GLFW_KEY_UNDERSCORE`
* - `GLFW_KEY_GRAVE_ACCENT`
* - `GLFW_KEY_WORLD_1`
@@ -5524,8 +5566,9 @@ GLFWAPI int glfwVulkanSupported(void);
* returned array, as it is an error to specify an extension more than once in
* the `VkInstanceCreateInfo` struct.
*
* @remark @macos This function currently only supports the
* `VK_MVK_macos_surface` extension from MoltenVK.
* @remark @macos This function currently supports either the
* `VK_MVK_macos_surface` extension from MoltenVK or `VK_EXT_metal_surface`
* extension.
*
* @pointer_lifetime The returned array is allocated and freed by GLFW. You
* should not free it yourself. It is guaranteed to be valid only until the

1
glfw/input.c vendored
View File

@@ -510,6 +510,7 @@ const char* _glfwGetKeyName(int key)
case GLFW_KEY_LEFT_BRACKET: return "LEFT_BRACKET";
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT_BRACKET";
case GLFW_KEY_CIRCUMFLEX: return "CIRCUMFLEX";
case GLFW_KEY_UNDERSCORE: return "UNDERSCORE";
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE_ACCENT";
case GLFW_KEY_WORLD_1: return "WORLD_1";

4
glfw/internal.h vendored
View File

@@ -132,6 +132,7 @@ typedef enum VkStructureType
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT = 1000217000,
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkStructureType;
@@ -418,6 +419,7 @@ struct _GLFWwindow
int minwidth, minheight;
int maxwidth, maxheight;
int numer, denom;
int widthincr, heightincr;
bool stickyKeys;
bool stickyMouseButtons;
@@ -587,6 +589,7 @@ struct _GLFWlibrary
bool KHR_win32_surface;
#elif defined(_GLFW_COCOA)
bool MVK_macos_surface;
bool EXT_metal_surface;
#elif defined(_GLFW_X11)
bool KHR_xlib_surface;
bool KHR_xcb_surface;
@@ -673,6 +676,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
int minwidth, int minheight,
int maxwidth, int maxheight);
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr);
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
void _glfwInputLiveResize(_GLFWwindow* window, bool started);
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,

4
glfw/null_window.c vendored
View File

@@ -128,6 +128,10 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED, int n UNUSED,
{
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED, int widthincr UNUSED, int heightincr UNUSED)
{
}
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
{
if (width)

4
glfw/vulkan.c vendored
View File

@@ -57,6 +57,8 @@ bool _glfwInitVulkan(int mode)
_glfw.vk.handle = _glfw_dlopen("vulkan-1.dll");
#elif defined(_GLFW_COCOA)
_glfw.vk.handle = _glfw_dlopen("libvulkan.1.dylib");
if (!_glfw.vk.handle)
_glfw.vk.handle = _glfwLoadLocalVulkanLoaderNS();
#else
_glfw.vk.handle = _glfw_dlopen("libvulkan.so.1");
#endif
@@ -130,6 +132,8 @@ bool _glfwInitVulkan(int mode)
#elif defined(_GLFW_COCOA)
else if (strcmp(ep[i].extensionName, "VK_MVK_macos_surface") == 0)
_glfw.vk.MVK_macos_surface = true;
else if (strcmp(ep[i].extensionName, "VK_EXT_metal_surface") == 0)
_glfw.vk.EXT_metal_surface = true;
#elif defined(_GLFW_X11)
else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0)
_glfw.vk.KHR_xlib_surface = true;

16
glfw/window.c vendored
View File

@@ -254,6 +254,8 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->maxheight = GLFW_DONT_CARE;
window->numer = GLFW_DONT_CARE;
window->denom = GLFW_DONT_CARE;
window->widthincr = GLFW_DONT_CARE;
window->heightincr = GLFW_DONT_CARE;
// Open the actual window and create its context
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
@@ -690,6 +692,20 @@ GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* handle, int numer, int denom)
_glfwPlatformSetWindowAspectRatio(window, numer, denom);
}
GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* handle, int widthincr, int heightincr)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
assert(widthincr >= 0);
assert(heightincr >= 0);
_GLFW_REQUIRE_INIT();
window->widthincr = widthincr;
window->heightincr = heightincr;
_glfwPlatformSetWindowSizeIncrements(window, window->widthincr, window->heightincr);
}
GLFWAPI void glfwGetFramebufferSize(GLFWwindow* handle, int* width, int* height)
{
_GLFWwindow* window = (_GLFWwindow*) handle;

48
glfw/wl_window.c vendored
View File

@@ -754,6 +754,13 @@ abortOnFatalError(int last_error) {
_glfw.wl.eventLoopData.wakeup_data_read = true;
}
static void
wayland_read_events(int poll_result, int events, void *data UNUSED) {
EVDBG("wayland_read_events poll_result: %d events: %d", poll_result, events);
if (poll_result > 0 && events) wl_display_read_events(_glfw.wl.display);
else wl_display_cancel_read(_glfw.wl.display);
}
static void
handleEvents(monotonic_t timeout)
{
@@ -762,17 +769,9 @@ handleEvents(monotonic_t timeout)
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
while (wl_display_prepare_read(display) != 0) {
while(1) {
errno = 0;
int num_dispatched = wl_display_dispatch_pending(display);
if (num_dispatched < 0) {
if (errno == EAGAIN) continue;
int last_error = wl_display_get_error(display);
if (last_error) abortOnFatalError(last_error);
wl_display_cancel_read(display);
return;
}
break;
if (wl_display_dispatch_pending(display) == -1) {
abortOnFatalError(errno);
return;
}
}
@@ -782,23 +781,22 @@ handleEvents(monotonic_t timeout)
errno = 0;
if (wl_display_flush(display) < 0 && errno != EAGAIN)
{
abortOnFatalError(errno);
wl_display_cancel_read(display);
abortOnFatalError(errno);
return;
}
bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout);
// we pass in wayland_read_events to ensure that the above wl_display_prepare_read call
// is followed by either wl_display_cancel_read or wl_display_read_events
// before any events/timers are dispatched. This allows other wayland functions
// to be called in the event/timer handlers without causing a deadlock
bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout, wayland_read_events);
EVDBG("display_read_ok: %d", display_read_ok);
if (display_read_ok) {
wl_display_read_events(display);
int num = wl_display_dispatch_pending(display);
(void)num;
EVDBG("dispatched %d Wayland events", num);
}
else
{
wl_display_cancel_read(display);
}
glfw_ibus_dispatch(&_glfw.wl.xkb.ibus);
glfw_dbus_session_bus_dispatch();
EVDBG("other dispatch done");
@@ -960,8 +958,13 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
if (window->wl.title)
free(window->wl.title);
window->wl.title = _glfw_strdup(title);
// Wayland cannot handle requests larger than ~8200 bytes. Sending
// on causes an abort(). Since titles this large are meaningless anyway
// ensure they do not happen. One should really truncate ensuring valid UTF-8
// but I cant be bothered.
if (title && strnlen(title, 2048) >= 2048) window->wl.title[2048] = 0;
if (window->wl.xdg.toplevel)
xdg_toplevel_set_title(window->wl.xdg.toplevel, title);
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
}
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
@@ -1028,6 +1031,13 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED,
// The actual limits are checked in the xdg_toplevel::configure handler.
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED,
int widthincr UNUSED, int heightincr UNUSED)
{
// TODO: find out how to trigger a resize.
// The actual limits are checked in the xdg_toplevel::configure handler.
}
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
int* width, int* height)
{

1
glfw/x11_platform.h vendored
View File

@@ -180,6 +180,7 @@ typedef struct _GLFWwindowX11
{
Colormap colormap;
Window handle;
Window parent;
bool iconified;
bool maximized;

127
glfw/x11_window.c vendored
View File

@@ -68,7 +68,7 @@ static unsigned _glfwDispatchX11Events(void);
static void
handleEvents(monotonic_t timeout) {
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout, NULL);
EVDBG("display_read_ok: %d", display_read_ok);
if (display_read_ok) {
unsigned dispatched = _glfwDispatchX11Events();
@@ -255,6 +255,14 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
hints->min_aspect.x = hints->max_aspect.x = window->numer;
hints->min_aspect.y = hints->max_aspect.y = window->denom;
}
if (window->widthincr != GLFW_DONT_CARE &&
window->heightincr != GLFW_DONT_CARE)
{
hints->flags |= PResizeInc;
hints->width_inc = window->widthincr;
hints->height_inc = window->heightincr;
}
}
else
{
@@ -525,46 +533,41 @@ static bool createNativeWindow(_GLFWwindow* window,
window->x11.transparent = _glfwIsVisualTransparentX11(visual);
// Create the actual window
XSetWindowAttributes wa = { 0 };
wa.colormap = window->x11.colormap;
wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
ExposureMask | FocusChangeMask | VisibilityChangeMask |
EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
_glfwGrabErrorHandlerX11();
window->x11.parent = _glfw.x11.root;
window->x11.handle = XCreateWindow(_glfw.x11.display,
_glfw.x11.root,
0, 0, // Position
width, height,
0, // Border width
depth, // Color depth
InputOutput,
visual,
CWBorderPixel | CWColormap | CWEventMask,
&wa);
_glfwReleaseErrorHandlerX11();
if (!window->x11.handle)
{
XSetWindowAttributes wa;
const unsigned long wamask = CWBorderPixel | CWColormap | CWEventMask;
wa.colormap = window->x11.colormap;
wa.border_pixel = 0;
wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
ExposureMask | FocusChangeMask | VisibilityChangeMask |
EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
_glfwGrabErrorHandlerX11();
window->x11.handle = XCreateWindow(_glfw.x11.display,
_glfw.x11.root,
0, 0,
width, height,
0, // Border width
depth, // Color depth
InputOutput,
visual,
wamask,
&wa);
_glfwReleaseErrorHandlerX11();
if (!window->x11.handle)
{
_glfwInputErrorX11(GLFW_PLATFORM_ERROR,
"X11: Failed to create window");
return false;
}
XSaveContext(_glfw.x11.display,
window->x11.handle,
_glfw.x11.context,
(XPointer) window);
_glfwInputErrorX11(GLFW_PLATFORM_ERROR,
"X11: Failed to create window");
return false;
}
XSaveContext(_glfw.x11.display,
window->x11.handle,
_glfw.x11.context,
(XPointer) window);
if (!wndconfig->decorated)
_glfwPlatformSetWindowDecorated(window, false);
@@ -1182,6 +1185,12 @@ static void processEvent(XEvent *event)
switch (event->type)
{
case ReparentNotify:
{
window->x11.parent = event->xreparent.parent;
return;
}
case KeyPress:
{
UPDATE_KEYMAP_IF_NEEDED;
@@ -1375,18 +1384,28 @@ static void processEvent(XEvent *event)
window->x11.height = event->xconfigure.height;
}
if (event->xconfigure.x != window->x11.xpos ||
event->xconfigure.y != window->x11.ypos)
{
if (event->xany.send_event)
{
_glfwInputWindowPos(window,
event->xconfigure.x,
event->xconfigure.y);
int xpos = event->xconfigure.x;
int ypos = event->xconfigure.y;
window->x11.xpos = event->xconfigure.x;
window->x11.ypos = event->xconfigure.y;
}
// NOTE: ConfigureNotify events from the server are in local
// coordinates, so if we are reparented we need to translate
// the position into root (screen) coordinates
if (!event->xany.send_event && window->x11.parent != _glfw.x11.root)
{
Window dummy;
XTranslateCoordinates(_glfw.x11.display,
window->x11.parent,
_glfw.x11.root,
xpos, ypos,
&xpos, &ypos,
&dummy);
}
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
{
_glfwInputWindowPos(window, xpos, ypos);
window->x11.xpos = xpos;
window->x11.ypos = ypos;
}
return;
@@ -2034,6 +2053,14 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer UNUSED, in
XFlush(_glfw.x11.display);
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr UNUSED, int heightincr UNUSED)
{
int width, height;
_glfwPlatformGetWindowSize(window, &width, &height);
updateNormalHints(window, width, height);
XFlush(_glfw.x11.display);
}
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
{
_glfwPlatformGetWindowSize(window, width, height);
@@ -2245,7 +2272,7 @@ void _glfwPlatformFocusWindow(_GLFWwindow* window)
{
if (_glfw.x11.NET_ACTIVE_WINDOW)
sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0);
else
else if (_glfwPlatformWindowVisible(window))
{
XRaiseWindow(_glfw.x11.display, window->x11.handle);
XSetInputFocus(_glfw.x11.display, window->x11.handle,

1
glfw/xkb_glfw.c vendored
View File

@@ -61,6 +61,7 @@ START_ALLOW_CASE_RANGE
S(bracketleft, LEFT_BRACKET); \
S(backslash, BACKSLASH); \
S(bracketright, RIGHT_BRACKET); \
S(asciicircum, CIRCUMFLEX); \
S(underscore, UNDERSCORE); \
S(grave, GRAVE_ACCENT); \
R(a, z, A, Z); \

View File

@@ -21,6 +21,7 @@ from ..tui.operations import faint, styled
URL_PREFIXES = 'http https file ftp'.split()
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
screen_size = screen_size_function()
@@ -265,7 +266,7 @@ def run_loop(args, text, all_marks, index_map, extra_cli_args=()):
return {
'match': handler.text_matches, 'programs': args.program,
'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing,
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args, 'linenum_action': args.linenum_action
}
raise SystemExit(loop.return_code)
@@ -323,11 +324,20 @@ def parse_input(text):
return convert_text(text, cols)
def linenum_marks(text, args, Mark, extra_cli_args, *a):
regex = args.regex
if regex == DEFAULT_REGEX:
regex = r'(?P<path>(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
yield from mark(regex, [brackets, quotes], text, args)
def load_custom_processor(customize_processing):
if customize_processing.startswith('::import::'):
import importlib
m = importlib.import_module(customize_processing[len('::import::'):])
return {k: getattr(m, k) for k in dir(m)}
if customize_processing == '::linenum::':
return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
from kitty.constants import config_dir
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
if os.path.isabs(customize_processing):
@@ -342,6 +352,8 @@ def run(args, text, extra_cli_args=()):
try:
text = parse_input(text)
pattern, post_processors = functions_for(args)
if args.type == 'linenum':
args.customize_processing = '::linenum::'
if args.customize_processing:
m = load_custom_processor(args.customize_processing)
if 'mark' in m:
@@ -386,12 +398,15 @@ multiple times to run multiple programs.
--type
default=url
choices=url,regex,path,line,hash,word
The type of text to search for.
choices=url,regex,path,line,hash,word,linenum
The type of text to search for. A value of :code:`linenum` looks for error messages
using the pattern specified with :option:`--regex`, which must have the named groups,
path and line. If not specified, will look for :code:`path:line`.
The :option:`--linenum-action` option controls what to do with the selected error message.
--regex
default=(?m)^\s*(.+)\s*$
default={default_regex}
The regular expression to use when :option:`kitty +kitten hints --type`=regex.
The regular expression is in python syntax. If you specify a numbered group in
the regular expression only the group will be matched. This allow you to match
@@ -403,8 +418,21 @@ the program will be passed arguments corresponding to each named group of
the form key=value.
--linenum-action
default=self
type=choice
choices=self,window,tab,os_window,background
The action to perform on the matched errors. The actual action is whatever
arguments are provided to the kitten, for example:
:code:`kitty + kitten hints --type=linenum vim +{line} {path}`
will open the matched path at the matched line number in vim. This option
controls where the action is executed: :code:`self` means the current window,
:code:`window` a new kitty window, :code:`tab` a new tab, :code:`os_window`
a new OS window and :code:`background` run in the background.
--url-prefixes
default={0}
default={url_prefixes}
Comma separated list of recognized URL prefixes.
@@ -470,7 +498,10 @@ on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html
for details. You can also specify absolute paths to load the script from elsewhere.
'''.format(','.join(sorted(URL_PREFIXES))).format
'''.format(
default_regex=DEFAULT_REGEX, url_prefixes=','.join(sorted(URL_PREFIXES)),
line='{{line}}', path='{{path}}'
).format
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
usage = ''
@@ -496,12 +527,39 @@ def main(args):
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
if items and not args.customize_processing:
if items and not (args.customize_processing or args.type == 'linenum'):
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
input(_('Press Enter to quit'))
return run(args, text, items)
def linenum_handle_result(args, data, target_window_id, boss, extra_cli_args, *a):
for m, g in zip(data['match'], data['groupdicts']):
if m:
path, line = g['path'], g['line']
path = path.split(':')[-1]
line = int(line)
break
else:
return
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
w = boss.window_id_map.get(target_window_id)
action = data['linenum_action']
if action == 'self':
if w is not None:
import shlex
w.paste_bytes(shlex.join(cmd) + '\r')
elif action == 'background':
import subprocess
subprocess.Popen(cmd)
else:
getattr(boss, {
'window': 'new_window_with_cwd', 'tab': 'new_tab_with_cwd', 'os_window': 'new_os_window_with_cwd'
}[action])(*cmd)
def handle_result(args, data, target_window_id, boss):
if data['customize_processing']:
m = load_custom_processor(data['customize_processing'])

View File

@@ -93,6 +93,13 @@ but you can turn it off or on explicitly, if needed.
--silent
type=bool-set
Do not print out anything to stdout during operation.
--z-index -z
default=0
Z-index of the image. When negative, text will be displayed on top of the image. Use
a double minus for values under the threshold for drawing images under cell background
colors. For example, --1 evaluates as -1,073,741,825.
'''
@@ -182,8 +189,8 @@ def write_chunked(cmd, data):
cmd.clear()
def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=None):
cmd = {'a': 'T', 'f': fmt, 's': width, 'v': height}
def show(outfile, width, height, zindex, fmt, transmit_mode='t', align='center', place=None):
cmd = {'a': 'T', 'f': fmt, 's': width, 'v': height, 'z': zindex}
if place:
set_cursor_for_place(place, cmd, width, height, align)
else:
@@ -201,6 +208,14 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
write_chunked(cmd, data)
def parse_z_index(val):
origin = 0
if val.startswith('--'):
val = val[1:]
origin = -1073741824
return origin + int(val)
def process(path, args, is_tempfile):
m = identify(path)
ss = get_screen_size()
@@ -219,7 +234,7 @@ def process(path, args, is_tempfile):
fmt = 24 if m.mode == 'rgb' else 32
transmit_mode = 't'
outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place)
show(outfile, width, height, args.z_index, fmt, transmit_mode, align=args.align, place=args.place)
if not args.place:
print() # ensure cursor is on a new line
return file_removed
@@ -360,6 +375,11 @@ def main(args=sys.argv):
except Exception:
raise SystemExit('Not a valid place specification: {}'.format(args.place))
try:
args.z_index = parse_z_index(args.z_index)
except Exception:
raise SystemExit('Not a valid z-index specification: {}'.format(args.z_index))
if args.detect_support:
if not detect_support(wait_for=args.detection_timeout, silent=True):
raise SystemExit(1)

View File

@@ -7,5 +7,4 @@ out vec4 color;
void main() {
color = texture(image, texcoord);
color = vec4(color.rgb, color.a);
}

View File

@@ -657,7 +657,7 @@ class Boss:
with suppress(Exception):
s.connect(address)
s.sendall(b'c')
with suppress(EnvironmentError):
with suppress(OSError):
s.shutdown(socket.SHUT_RDWR)
s.close()
@@ -761,9 +761,6 @@ class Boss:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
self._run_kitten('ask', args)
def show_error(self, title, msg):
self._run_kitten('show_error', args=['--title', title], input_data=msg)
def do_set_tab_title(self, title, tab_id):
tm = self.active_tab_manager
if tm is not None and title:
@@ -773,6 +770,32 @@ class Boss:
tab.set_title(title)
break
def show_error(self, title, msg):
self._run_kitten('show_error', args=['--title', title], input_data=msg)
def create_marker(self):
w = self.active_window
if w:
spec = None
def done(data, target_window_id, self):
nonlocal spec
spec = data['response']
def done2(target_window_id, self):
w = self.window_id_map.get(target_window_id)
if w is not None and spec:
try:
w.set_marker(spec)
except Exception as err:
self.show_error(_('Invalid marker specification'), str(err))
self._run_kitten('ask', [
'--name=create-marker', '--message',
_('Create marker, for example:\ntext 1 ERROR\nSee https://sw.kovidgoyal.net/kitty/marks.html\n')
],
custom_callback=done, action_on_removal=done2)
def kitty_shell(self, window_type):
cmd = ['@', kitty_exe(), '@']
if window_type == 'tab':

View File

@@ -12,6 +12,7 @@
#ifdef NEEDS_BACKROUND
in vec3 background;
in float draw_bg;
#if defined(TRANSPARENT) || defined(SPECIAL)
in float bg_alpha;
#endif
@@ -76,11 +77,13 @@ vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) {
*
* 2a) Opaque bg with images under text
* There are multiple passes, each pass is blended onto the previous using the opaque blend func (alpha, 1- alpha):
* 1) Draw only the background -- expected output is color with alpha 1
* 2) Draw the images that are supposed to be below text. This happens in the graphics shader
* 3) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise
* 4) Draw the foreground -- expected output is color with alpha which is blended using the opaque blend func
* 5) Draw the images that are supposed to be above text again in the graphics shader
* 1) Draw background for all cells
* 2) Draw the images that are supposed to be below both the background and text, if any. This happens in the graphics shader
* 3) Draw the background of cells that don't have the default background if any images were drawn in 2 above
4) Draw the images that are supposed to be below text but not background, again in graphics shader.
* 5) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise
* 6) Draw the foreground -- expected output is color with alpha which is blended using the opaque blend func
* 7) Draw the images that are supposed to be above text again in the graphics shader
*
* 2b) Transparent bg with images
* First everything is rendered into a framebuffer, and then the framebauffer is blended onto
@@ -126,10 +129,10 @@ void main() {
#endif
#ifdef BACKGROUND
#ifdef TRANSPARENT
#if defined(TRANSPARENT)
final_color = vec4(background.rgb * bg_alpha, bg_alpha);
#else
final_color = vec4(background.rgb, 1.0f);
final_color = vec4(background.rgb, draw_bg);
#endif
#endif

View File

@@ -5,7 +5,10 @@
#define REVERSE_SHIFT {REVERSE_SHIFT}
#define STRIKE_SHIFT {STRIKE_SHIFT}
#define DIM_SHIFT {DIM_SHIFT}
#define MARK_SHIFT {MARK_SHIFT}
#define MARK_MASK {MARK_MASK}
#define USE_SELECTION_FG
#define NUM_COLORS 256
// Inputs {{{
layout(std140) uniform CellRenderData {
@@ -16,8 +19,11 @@ layout(std140) uniform CellRenderData {
uint xnum, ynum, cursor_fg_sprite_idx;
float cursor_x, cursor_y, cursor_w;
uint color_table[256];
uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2];
};
#ifdef BACKGROUND
uniform uint draw_bg_bitfield;
#endif
// Have to use fixed locations here as all variants of the cell program share the same VAO
layout(location=0) in uvec3 colors;
@@ -45,6 +51,7 @@ const uvec2 cell_pos_map[] = uvec2[4](
#ifdef NEEDS_BACKROUND
out vec3 background;
out float draw_bg;
#if defined(TRANSPARENT) || defined(SPECIAL)
out float bg_alpha;
#endif
@@ -158,7 +165,11 @@ void main() {
float cell_has_cursor = is_cursor(c, r);
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
int mark = int(text_attrs >> MARK_SHIFT) & MARK_MASK;
uint has_mark = uint(step(1, float(mark)));
uint bg_as_uint = resolve_color(colors[bg_index], default_colors[bg_index]);
bg_as_uint = has_mark * color_table[NUM_COLORS + mark] + (ONE - has_mark) * bg_as_uint;
vec3 bg = color_to_vec(bg_as_uint);
// }}}
// Foreground {{{
@@ -169,12 +180,13 @@ void main() {
colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14);
// Foreground
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
foreground = color_to_vec(resolved_fg);
uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]);
fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + 1 + mark] + (ONE - has_mark) * fg_as_uint;
foreground = color_to_vec(fg_as_uint);
float has_dim = float((text_attrs >> DIM_SHIFT) & ONE);
effective_text_alpha = inactive_text_alpha * mix(1.0, dim_opacity, has_dim);
float in_url = float((is_selected & TWO) >> 1);
decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg));
decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint));
#ifdef USE_SELECTION_FG
// Selection
vec3 selection_color = color_to_vec(highlight_fg);
@@ -196,23 +208,29 @@ void main() {
// Background {{{
#ifdef NEEDS_BACKROUND
float cell_has_non_default_bg = step(1, float(abs(bg_as_uint - default_colors[bg_index])));
draw_bg = 1;
#if defined(BACKGROUND)
background = bg;
// draw_bg_bitfield has bit 0 set to draw default bg cells and bit 1 set to draw non-default bg cells
uint draw_bg_mask = uint(2 * cell_has_non_default_bg + (1 - cell_has_non_default_bg));
draw_bg = step(1, float(draw_bg_bitfield & draw_bg_mask));
#endif
#if defined(TRANSPARENT)
#ifdef TRANSPARENT
// Set bg_alpha to background_opacity on cells that have the default background color
// Which means they must not have a block cursor or a selection or reverse video
// On other cells it should be 1. For the SPECIAL program it should be 1 on cells with
// selections/block cursor and 0 everywhere else.
float is_special_cell = cell_has_block_cursor + float(is_selected & ONE);
#ifndef SPECIAL
is_special_cell += float(colors[bg_index] & BYTE_MASK) + float(is_reversed);
is_special_cell += cell_has_non_default_bg + float(is_reversed);
#endif
bg_alpha = step(0.5, is_special_cell);
#ifndef SPECIAL
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
bg_alpha *= draw_bg;
#endif
#endif

View File

@@ -633,7 +633,7 @@ draw_resizing_text(OSWindow *w) {
snprintf(text, sizeof(text), "%u x %u cells", width / w->fonts_data->cell_width, height / w->fonts_data->cell_height);
StringCanvas rendered = render_simple_text(w->fonts_data, text);
if (rendered.canvas) {
draw_centered_alpha_mask(w->gvao_idx, width, height, rendered.width, rendered.height, rendered.canvas);
draw_centered_alpha_mask(w, width, height, rendered.width, rendered.height, rendered.canvas);
free(rendered.canvas);
}
}
@@ -641,7 +641,7 @@ draw_resizing_text(OSWindow *w) {
static inline bool
no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_wait) {
bool ans = now - w->last_render_frame_received_at > max_wait;
if (ans) log_error("No render frame received in %f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
if (ans) log_error("No render frame received in %.2f seconds, re-requesting at: %f", monotonic_t_to_s_double(max_wait), monotonic_t_to_s_double(now));
return ans;
}

View File

@@ -51,7 +51,7 @@ else:
try:
with open('/proc/' + x + '/stat', 'rb') as f:
raw = f.read().decode('utf-8')
except EnvironmentError:
except OSError:
continue
try:
q = int(raw.split(' ', 5)[4])

View File

@@ -223,7 +223,7 @@ def get_defaults_from_seq(seq):
default_msg = ('''\
Run the :italic:`{appname}` terminal emulator. You can also specify the :italic:`program`
to run inside :italic:`{appname}` as normal arguments following the :italic:`options`.
For example: {appname} /bin/sh
For example: {appname} sh -c "echo hello, world. Press ENTER to quit; read"
For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname)
@@ -233,7 +233,7 @@ def print_help_for_seq(seq, usage, message, appname):
screen_size = screen_size_function()
try:
linesz = min(screen_size().cols, 76)
except EnvironmentError:
except OSError:
linesz = 76
blocks = []
a = blocks.append

View File

@@ -381,6 +381,82 @@ def set_tab_title(boss, window, payload):
# }}}
# create_marker {{{
@cmd(
'Create a marker that highlights specified text',
'Create a marker which can highlight text in the specified window. For example: '
'create_marker text 1 ERROR. For full details see: https://sw.kovidgoyal.net/kitty/marks.html',
options_spec=MATCH_WINDOW_OPTION + '''\n
--self
type=bool-set
If specified apply marker to the window this command is run in, rather than the active window.
''',
argspec='MARKER SPECIFICATION'
)
def cmd_create_marker(global_opts, opts, args):
'''
match: Which window to detach
self: Boolean indicating whether to detach the window the command is run in
marker_spec: A list or arguments that define the marker specification, for example: ['text', '1', 'ERROR']
'''
from .config import parse_marker_spec
if len(args) < 2:
raise ValueError('Invalid marker specification: {}'.format(' '.join(args)))
parse_marker_spec(args[0], args[1:])
return {'match': opts.match, 'self': opts.self, 'marker_spec': args}
def create_marker(boss, window, payload):
pg = cmd_create_marker.payload_get
match = pg(payload, 'match')
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
else:
windows = [window if window and pg(payload, 'self') else boss.active_window]
args = pg(payload, 'marker_spec')
for window in windows:
window.set_marker(args)
# }}}
# remove_marker {{{
@cmd(
'Remove the currently set marker, if any.',
options_spec=MATCH_WINDOW_OPTION + '''\n
--self
type=bool-set
If specified apply marker to the window this command is run in, rather than the active window.
''',
argspec=''
)
def cmd_remove_marker(global_opts, opts, args):
'''
match: Which window to detach
self: Boolean indicating whether to detach the window the command is run in
'''
return {'match': opts.match, 'self': opts.self}
def remove_marker(boss, window, payload):
pg = cmd_create_marker.payload_get
match = pg(payload, 'match')
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
else:
windows = [window if window and pg(payload, 'self') else boss.active_window]
for window in windows:
window.remove_marker()
# }}}
# detach_window {{{
@cmd(
'Detach a window and place it in a different/new tab',
@@ -814,6 +890,7 @@ def cmd_launch(global_opts, opts, args):
stdin_add_formatting: Boolean indicating whether to add formatting codes to stdin
stdin_add_line_wrap_markers: Boolean indicating whether to add line wrap markers to stdin
no_response: Boolean indicating whether to send back the window id
marker: Specification for marker for new window, for example: "text 1 ERROR"
'''
if opts.no_response:
global_opts.no_command_response = True

View File

@@ -83,6 +83,11 @@ find_app_name(void) {
set_cocoa_pending_action(NEW_OS_WINDOW, NULL);
}
- (void)open_kitty_website_url:(id)sender {
(void)sender;
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]];
}
+ (GlobalMenuTarget *) shared_instance
{
@@ -97,7 +102,7 @@ find_app_name(void) {
@end
static unichar new_window_key = 0;
static char new_window_key[32] = {0};
static NSEventModifierFlags new_window_mods = 0;
static PyObject*
@@ -105,9 +110,9 @@ cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
int mods, key;
if (!PyArg_ParseTuple(args, "ii", &mods, &key)) return NULL;
int nwm;
get_cocoa_key_equivalent(key, mods, &new_window_key, &nwm);
get_cocoa_key_equivalent(key, mods, new_window_key, sizeof(new_window_key), &nwm);
new_window_mods = nwm;
if (new_window_key) Py_RETURN_TRUE;
if (new_window_key[0]) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
@@ -118,11 +123,10 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
if (!dockMenu) {
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
dockMenu = [[NSMenu alloc] init];
NSMenuItem *newWindowItem =
[dockMenu addItemWithTitle:@"New OS window"
action:@selector(new_os_window:)
keyEquivalent:@""];
[newWindowItem setTarget:global_menu_target];
[[dockMenu addItemWithTitle:@"New OS window"
action:@selector(new_os_window:)
keyEquivalent:@""]
setTarget:global_menu_target];
}
return dockMenu;
}
@@ -249,17 +253,17 @@ cocoa_create_global_menu(void) {
action:@selector(orderFrontStandardAboutPanel:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
NSMenuItem* preferences_menu_item = [[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(show_preferences:) keyEquivalent:@","], *new_os_window_menu_item = NULL;
[preferences_menu_item setTarget:global_menu_target];
[appMenu addItem:preferences_menu_item];
if (new_window_key) {
NSString *s = [NSString stringWithCharacters:&new_window_key length:1];
new_os_window_menu_item = [[NSMenuItem alloc] initWithTitle:@"New OS window" action:@selector(new_os_window:) keyEquivalent:s];
[new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods];
[new_os_window_menu_item setTarget:global_menu_target];
[appMenu addItem:new_os_window_menu_item];
[s release];
}
[[appMenu addItemWithTitle:@"Preferences..."
action:@selector(show_preferences:)
keyEquivalent:@","]
setTarget:global_menu_target];
NSMenuItem* new_os_window_menu_item =
[appMenu addItemWithTitle:@"New OS window"
action:@selector(new_os_window:)
keyEquivalent:@(new_window_key)];
[new_os_window_menu_item setKeyEquivalentModifierMask:new_window_mods];
[new_os_window_menu_item setTarget:global_menu_target];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
@@ -313,10 +317,18 @@ cocoa_create_global_menu(void) {
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
[NSApp setWindowsMenu:windowMenu];
[windowMenu release];
[preferences_menu_item release];
if (new_os_window_menu_item) {
[new_os_window_menu_item release];
}
NSMenuItem* helpMenuItem =
[bar addItemWithTitle:@"Help"
action:NULL
keyEquivalent:@""];
NSMenu* helpMenu = [[NSMenu alloc] initWithTitle:@"Help"];
[helpMenuItem setSubmenu:helpMenu];
[[helpMenu addItemWithTitle:[NSString stringWithFormat:@"Visit %@ website", app_name]
action:@selector(open_kitty_website_url:)
keyEquivalent:@"?"]
setTarget:global_menu_target];
[helpMenu release];
[bar release];
@@ -474,7 +486,6 @@ cleanup() {
dockMenu = nil;
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
notification_activated_callback = NULL;
} // autoreleasepool
}
@@ -489,6 +500,30 @@ cocoa_hide_window_title(void *w)
} // autoreleasepool
}
void
cocoa_hide_titlebar(void *w)
{
@autoreleasepool {
cocoa_hide_window_title(w);
NSWindow *window = (NSWindow*)w;
NSButton *button;
button = [window standardWindowButton: NSWindowCloseButton];
if (button) button.hidden = true;
button = [window standardWindowButton: NSWindowMiniaturizeButton];
if (button) button.hidden = true;
button = [window standardWindowButton: NSWindowZoomButton];
if (button) button.hidden = true;
[window setTitlebarAppearsTransparent:YES];
[window setStyleMask:
[window styleMask] | NSWindowStyleMaskFullSizeContentView];
} // autoreleasepool
}
static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},

View File

@@ -6,7 +6,7 @@
*/
#define EXTRA_INIT if (PyModule_AddFunctions(module, module_methods) != 0) return false;
#include "data-types.h"
#include "state.h"
#include <structmember.h>
PyTypeObject ColorProfile_Type;
@@ -69,6 +69,9 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
init_FG_BG_table();
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256));
#define S(which) self->mark_foregrounds[which] = OPT(mark##which##_foreground); self->mark_backgrounds[which] = OPT(mark##which##_background)
S(1); S(2); S(3);
#undef S
self->dirty = true;
}
return (PyObject*) self;
@@ -107,6 +110,32 @@ copy_color_profile(ColorProfile *dest, ColorProfile *src) {
dest->dirty = true;
}
static inline void
patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t which, int change_configured) {
PyObject *v = PyDict_GetItemString(spec, key);
if (v) {
color_type color = PyLong_AsUnsignedLong(v);
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) {
ColorProfile *self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j);
self->color_table[which] = color;
if (change_configured) self->orig_color_table[which] = color;
self->dirty = true;
}
}
}
#define patch_mark_color(key, profiles, spec, array, i) { \
PyObject *v = PyDict_GetItemString(spec, key); \
if (v) { \
color_type color = PyLong_AsUnsignedLong(v); \
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) { \
ColorProfile *self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j); \
self->array[i] = color; \
self->dirty = true; \
} } }
static PyObject*
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured; PyObject *cursor_text_color;
@@ -114,16 +143,12 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
char key[32] = {0};
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
snprintf(key, sizeof(key) - 1, "color%zu", i);
v = PyDict_GetItemString(spec, key);
if (v) {
color_type color = PyLong_AsUnsignedLong(v);
for (Py_ssize_t j = 0; j < PyTuple_GET_SIZE(profiles); j++) {
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, j);
self->color_table[i] = color;
if (change_configured) self->orig_color_table[i] = color;
self->dirty = true;
}
}
patch_color_table(key, profiles, spec, i, change_configured);
}
for (size_t i = 1; i <= MARK_MASK; i++) {
#define S(which, i) snprintf(key, sizeof(key) - 1, "mark%zu_" #which, i); patch_mark_color(key, profiles, spec, mark_##which##s, i)
S(background, i); S(foreground, i);
#undef S
}
#define S(config_name, profile_name) { \
v = PyDict_GetItemString(spec, #config_name); \
@@ -282,8 +307,13 @@ void
copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size_t stride) {
size_t i;
stride = MAX(1u, stride);
for (i = 0, buf = buf + offset; i < sizeof(self->color_table)/sizeof(self->color_table[0]); i++, buf += stride) {
*buf = self->color_table[i];
for (i = 0, buf = buf + offset; i < arraysz(self->color_table); i++, buf += stride) *buf = self->color_table[i];
// Copy the mark colors
for (i = 0; i < arraysz(self->mark_backgrounds); i++) {
*buf = self->mark_backgrounds[i]; buf += stride;
}
for (i = 0; i < arraysz(self->mark_foregrounds); i++) {
*buf = self->mark_foregrounds[i]; buf += stride;
}
self->dirty = false;
}

View File

@@ -80,7 +80,7 @@ def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_in
_parse(include, type_map, special_handling, ans, all_keys)
except FileNotFoundError:
log_error('Could not find included config file: {}, ignoring'.format(val))
except EnvironmentError:
except OSError:
log_error('Could not read from included config file: {}, ignoring'.format(val))
return
if all_keys is not None and key not in all_keys:

View File

@@ -241,6 +241,58 @@ def disable_ligatures_in(func, rest):
return func, [where, strategy]
def parse_marker_spec(ftype, parts):
flags = re.UNICODE
if ftype in ('text', 'itext', 'regex', 'iregex'):
if ftype.startswith('i'):
flags |= re.IGNORECASE
if not parts or len(parts) % 2 != 0:
raise ValueError('No color specified in marker: {}'.format(' '.join(parts)))
ans = []
for i in range(0, len(parts), 2):
try:
color = max(1, min(int(parts[i]), 3))
except Exception:
raise ValueError('color {} in marker specification is not an integer'.format(parts[i]))
spec = parts[i + 1]
if 'regex' not in ftype:
spec = re.escape(spec)
ans.append((color, spec))
ftype = 'regex'
spec = tuple(ans)
elif ftype == 'function':
spec = ' '.join(parts)
else:
raise ValueError('Unknown marker type: {}'.format(ftype))
return ftype, spec, flags
@func_with_args('toggle_marker')
def toggle_marker(func, rest):
parts = rest.split(maxsplit=1)
if len(parts) != 2:
raise ValueError('{} if not a valid marker specification'.format(rest))
ftype, spec = parts
parts = spec.split()
return func, list(parse_marker_spec(ftype, parts))
@func_with_args('scroll_to_mark')
def scroll_to_mark(func, rest):
parts = rest.split()
if not parts or not rest:
return func, [True, 0]
if len(parts) == 1:
q = parts[0].lower()
if q in ('prev', 'previous', 'next'):
return func, [q != 'next', 0]
try:
return func, [True, max(0, min(int(q), 3))]
except Exception:
raise ValueError('{} is not a valid scroll_to_mark destination'.format(rest))
return func, [parts[0] != 'next', max(0, min(int(parts[1]), 3))]
def parse_key_action(action):
parts = action.strip().split(maxsplit=1)
func = parts[0]
@@ -251,8 +303,8 @@ def parse_key_action(action):
if parser is not None:
try:
func, args = parser(func, rest)
except Exception:
log_error('Ignoring invalid key action: {}'.format(action))
except Exception as err:
log_error('Ignoring invalid key action: {} with err: {}'.format(action, err))
else:
return KeyAction(func, args)
@@ -410,6 +462,33 @@ def handle_symbol_map(key, val, ans):
ans['symbol_map'].update(parse_symbol_map(val))
class FontFeature(str):
def __new__(cls, name, parsed):
ans = str.__new__(cls, name)
ans.parsed = parsed
return ans
@special_handler
def handle_font_features(key, val, ans):
if val != 'none':
parts = val.split()
if len(parts) < 2:
log_error("Ignoring invalid font_features {}".format(val))
else:
features = []
for feat in parts[1:]:
try:
parsed = defines.parse_font_feature(feat)
except ValueError:
log_error('Ignoring invalid font feature: {}'.format(feat))
else:
features.append(FontFeature(feat, parsed))
if features:
ans['font_features'][parts[0]] = tuple(features)
@special_handler
def handle_kitten_alias(key, val, ans):
parts = val.split(maxsplit=2)
@@ -495,7 +574,7 @@ def option_names_for_completion():
def parse_config(lines, check_keys=True, accumulate_bad_lines=None):
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}}
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_features': {}}
parse_config_base(
lines,
defaults,

View File

@@ -144,7 +144,8 @@ For example::
_('Tab management'), '',
_('''\
You can also create shortcuts to go to specific tabs, with 1 being the first
tab, 2 the second tab and -1 being the previously active tab::
tab, 2 the second tab and -1 being the previously active tab, and any number
larger than the last tab being the last tab::
map ctrl+alt+1 goto_tab 1
map ctrl+alt+2 goto_tab 2
@@ -223,6 +224,23 @@ def to_font_size(x):
o('font_size', 11.0, long_text=_('Font size (in pts)'), option_type=to_font_size)
o('force_ltr', False, long_text=_("""
kitty does not support BIDI (bidirectional text), however, for RTL scripts,
words are automatically displayed in RTL. That is
to say, in an RTL script, the words "HELLO WORLD" display in kitty as "WORLD
HELLO", and if you try to select a substring of an RTL-shaped string, you will
get the character that would be there had the the string been LTR. For example,
assuming the Hebrew word ירושלים, selecting the character that on the screen
appears to be ם actually writes into the selection buffer the character י.
kitty's default behavior is useful in conjunction with a filter to reverse the
word order, however, if you wish to manipulate RTL glyphs, it can be very
challenging to work with, so this option is provided to turn it off.
Furthermore, this option can be used with the command line program
:link:`GNU FriBidi <https://github.com/fribidi/fribidi#executable>` to get BIDI
support, because it will force kitty to always treat the text as LTR, which
FriBidi expects for terminals."""))
def adjust_line_height(x):
if x.endswith('%'):
@@ -276,6 +294,51 @@ or by defining shortcuts for it in kitty.conf, for example::
map alt+1 disable_ligatures_in active always
map alt+2 disable_ligatures_in all never
map alt+3 disable_ligatures_in tab cursor
'''))
o('font_features', 'none', long_text=_('''
Choose exactly which OpenType features to enable or disable. This is useful as
some fonts might have features worthwhile in a terminal. For example, Fira
Code Retina includes a discretionary feature, :code:`zero`, which in that font
changes the appearance of the zero (0), to make it more easily distinguishable
from Ø. Fira Code Retina also includes other discretionary features known as
Stylistic Sets which have the tags :code:`ss01` through :code:`ss20`.
Note that this code is indexed by PostScript name, and not the font
family. This allows you to define very precise feature settings; e.g. you can
disable a feature in the italic font but not in the regular font.
To get the PostScript name for a font, use :code:`kitty + list-fonts --psnames`::
$ kitty + list-fonts --psnames | grep Fira
Fira Code
Fira Code Bold (FiraCode-Bold)
Fira Code Light (FiraCode-Light)
Fira Code Medium (FiraCode-Medium)
Fira Code Regular (FiraCode-Regular)
Fira Code Retina (FiraCode-Retina)
The part in brackets is the PostScript name.
Enable alternate zero and oldstyle numerals::
font_features FiraCode-Retina +zero +onum
Enable only alternate zero::
font_features FiraCode-Retina +zero
Disable the normal ligatures, but keep the :code:`calt` feature which (in this
font) breaks up monotony::
font_features TT2020StyleB-Regular -liga +calt
In conjunction with :opt:`force_ltr`, you may want to disable Arabic shaping
entirely, and only look at their isolated forms if they show up in a document.
You can do this with e.g.::
font_features UnifontMedium +isol -medi -fina -init
'''))
@@ -569,7 +632,8 @@ def to_layout_names(raw):
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
The enabled window layouts. A comma separated list of layout names. The special
value :code:`all` means all layouts. The first listed layout will be used as the
startup layout. For a list of available layouts, see the :ref:`layouts`.
startup layout. Default configuration is all layouts in alphabetical order.
For a list of available layouts, see the :ref:`layouts`.
'''))
o('window_resize_step_cells', 2, option_type=positive_int, long_text=_('''
@@ -623,8 +687,18 @@ Fade the text in inactive windows by the specified amount (a number between
zero and one, with zero being fully faded).
'''))
o('hide_window_decorations', False, long_text=_('''
Hide the window decorations (title-bar and window borders).
def hide_window_decorations(x):
if x == 'titlebar-only':
return 0b10
if to_bool(x):
return 0b01
return 0b00
o('hide_window_decorations', 'no', option_type=hide_window_decorations, long_text=_('''
Hide the window decorations (title-bar and window borders) with :code:`yes`.
On macOS, :code:`titlebar-only` can be used to only hide the titlebar.
Whether this works and exactly what effect it has depends on the
window manager/operating system.
'''))
@@ -649,6 +723,13 @@ A value of :code:`blank` means draw a blank window.
A value of :code:`size` means show the window size in cells.
'''))
o('resize_in_steps', False, long_text=_('''
Resize the OS window in steps as large as the cells, instead of with the usual pixel accuracy.
Combined with an :opt:`initial_window_width` and :opt:`initial_window_height` in number of cells,
this option can be used to keep the margins as small as possible when resizing the OS window.
Note that this does not currently work on Wayland.
'''))
# }}}
g('tabbar') # {{{
@@ -811,6 +892,12 @@ o('color14', '#14ffff', option_type=to_color)
o('color7', '#dddddd', long_text=_('white'), option_type=to_color)
o('color15', '#ffffff', option_type=to_color)
o('mark1_foreground', 'black', long_text=_('Color for marks of type 1'), option_type=to_color)
o('mark1_background', '#98d3cb', long_text=_('Color for marks of type 1 (light steel blue)'), option_type=to_color)
o('mark2_foreground', 'black', long_text=_('Color for marks of type 2'), option_type=to_color)
o('mark2_background', '#f2dcd3', long_text=_('Color for marks of type 1 (beige)'), option_type=to_color)
o('mark3_foreground', 'black', long_text=_('Color for marks of type 3'), option_type=to_color)
o('mark3_background', '#f274bc', long_text=_('Color for marks of type 1 (violet)'), option_type=to_color)
dfctl = defines.default_color_table()
for i in range(16, 256):
o('color{}'.format(i), color_as_sharp(color_from_int(dfctl[i])), option_type=to_color, add_to_docs=False)
@@ -1228,6 +1315,11 @@ k('insert_selected_hash', 'kitty_mod+p>h', 'kitten hints --type hash --program -
Select something that looks like a hash and insert it into the terminal.
Useful with git, which uses sha1 hashes to identify commits'''))
k('goto_file_line', 'kitty_mod+p>n', 'kitten hints --type linenum', _('Open the selected file at the selected line'), long_text=_('''
Select something that looks like :code:`filename:linenum` and open it in vim at
the specified line number.'''))
# }}}
g('shortcuts.misc') # {{{

View File

@@ -10,7 +10,7 @@ from collections import namedtuple
from contextlib import suppress
appname = 'kitty'
version = (0, 15, 1)
version = (0, 16, 0)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat

View File

@@ -457,11 +457,12 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
CTFontGetGlyphsForCharacters(font, chars, glyphs, num_chars);
CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, advances, num_chars);
CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, glyphs, boxes, num_chars);
StringCanvas ans = { .width = 0, .height = (size_t)(2 * bounding_box.size.height) };
for (size_t i = 0, y = 0; i < num_chars; i++) {
positions[i] = CGPointMake(ans.width, y);
ans.width += advances[i].width; y += advances[i].height;
CGFloat x = 0, y = 0;
for (size_t i = 0; i < num_chars; i++) {
positions[i] = CGPointMake(x, y);
x += advances[i].width; y += advances[i].height;
}
StringCanvas ans = { .width = (size_t)ceil(x), .height = (size_t)(2 * bounding_box.size.height) };
ensure_render_space(ans.width, ans.height);
render_glyphs(font, ans.width, ans.height, baseline, num_chars);
ans.canvas = malloc(ans.width * ans.height);

View File

@@ -58,9 +58,9 @@ static PyObject*
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
char *devnull = NULL;
if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_OSError);
Py_RETURN_NONE;
}
@@ -251,6 +251,8 @@ PyInit_fast_data_types(void) {
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
PyModule_AddIntConstant(m, "DIM", DIM_SHIFT);
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
PyModule_AddIntConstant(m, "MARK", MARK_SHIFT);
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
PyModule_AddStringMacro(m, ERROR_PREFIX);
#ifdef KITTY_VCS_REV
PyModule_AddStringMacro(m, KITTY_VCS_REV);

View File

@@ -58,7 +58,7 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
#define MAX_CHILDREN 512
#define BLANK_CHAR 0
#define ATTRS_MASK_WITHOUT_WIDTH 0xFFC
#define ATTRS_MASK_WITHOUT_WIDTH 0xFFFC
#define WIDTH_MASK 3
#define DECORATION_SHIFT 2
#define DECORATION_MASK 3
@@ -68,6 +68,9 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
#define REVERSE_SHIFT 6
#define STRIKE_SHIFT 7
#define DIM_SHIFT 8
#define MARK_SHIFT 9
#define ATTRS_MASK_WITHOUT_MARK 0xf9ff
#define MARK_MASK 3
#define COL_MASK 0xFFFFFFFF
#define UTF8_ACCEPT 0
#define UTF8_REJECT 1
@@ -141,6 +144,7 @@ typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
#define END_ALLOW_UNUSED_RESULT _Pragma("GCC diagnostic pop")
#endif
typedef struct {
uint32_t left, top, right, bottom;
} Region;
@@ -231,6 +235,7 @@ typedef struct {
DynamicColor dynamic_color_stack[10];
size_t dynamic_color_stack_idx;
DynamicColor configured, overridden;
color_type mark_foregrounds[MARK_MASK+1], mark_backgrounds[MARK_MASK+1];
} ColorProfile;
typedef struct {

View File

@@ -62,6 +62,7 @@ static hb_feature_t hb_features[3] = {{0}};
static char_type shape_buffer[4096] = {0};
static size_t max_texture_size = 1024, max_array_len = 1024;
typedef enum { LIGA_FEATURE, DLIG_FEATURE, CALT_FEATURE } HBFeature;
static PyObject* font_feature_settings = NULL;
typedef struct {
char_type left, right;
@@ -71,14 +72,12 @@ typedef struct {
static SymbolMap *symbol_maps = NULL;
static size_t num_symbol_maps = 0;
typedef struct {
PyObject *face;
// Map glyphs to sprite map co-ords
SpritePosition sprite_map[1024];
hb_feature_t hb_features[8];
size_t num_hb_features;
hb_feature_t* ffs_hb_features;
size_t num_ffs_hb_features;
SpecialGlyphCache special_glyph_cache[SPECIAL_GLYPH_CACHE_SIZE];
bool bold, italic, emoji_presentation;
} Font;
@@ -348,27 +347,47 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
return ans;
}
static inline void
copy_hb_feature(Font *f, HBFeature which) {
memcpy(f->hb_features + f->num_hb_features++, hb_features + which, sizeof(hb_features[0]));
}
static inline bool
init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) {
f->face = face; Py_INCREF(f->face);
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
f->num_hb_features = 0;
f->num_ffs_hb_features = 0;
const char *psname = postscript_name_for_face(face);
if (strstr(psname, "NimbusMonoPS-") == psname) {
copy_hb_feature(f, LIGA_FEATURE); copy_hb_feature(f, DLIG_FEATURE);
if (font_feature_settings != NULL){
PyObject* o = PyDict_GetItemString(font_feature_settings, psname);
if (o != NULL && PyTuple_Check(o)) {
Py_ssize_t len = PyTuple_GET_SIZE(o);
if (len > 0) {
f->num_ffs_hb_features = len + 1;
f->ffs_hb_features = calloc(f->num_ffs_hb_features, sizeof(hb_feature_t));
if (!f->ffs_hb_features) return false;
for (Py_ssize_t i = 0; i < len; i++) {
PyObject* parsed = PyObject_GetAttrString(PyTuple_GET_ITEM(o, i), "parsed");
if (parsed) {
memcpy(f->ffs_hb_features + i, PyBytes_AS_STRING(parsed), sizeof(hb_feature_t));
Py_DECREF(parsed);
}
}
memcpy(f->ffs_hb_features + len, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
}
}
}
if (!f->num_ffs_hb_features) {
f->ffs_hb_features = calloc(4, sizeof(hb_feature_t));
if (!f->ffs_hb_features) return false;
if (strstr(psname, "NimbusMonoPS-") == psname) {
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[LIGA_FEATURE], sizeof(hb_feature_t));
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[DLIG_FEATURE], sizeof(hb_feature_t));
}
memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t));
}
copy_hb_feature(f, CALT_FEATURE);
return true;
}
static inline void
del_font(Font *f) {
Py_CLEAR(f->face);
free(f->ffs_hb_features); f->ffs_hb_features = NULL;
free_maps(f);
f->bold = false; f->italic = false;
}
@@ -670,6 +689,7 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
}
hb_buffer_guess_segment_properties(harfbuzz_buffer);
if (OPT(force_ltr)) hb_buffer_set_direction(harfbuzz_buffer, HB_DIRECTION_LTR);
}
@@ -776,7 +796,9 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb
group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0);
load_hb_buffer(first_cpu_cell, first_gpu_cell, num_cells);
hb_shape(font, harfbuzz_buffer, fobj->hb_features, fobj->num_hb_features - (disable_ligature ? 0 : 1));
size_t num_features = fobj->num_ffs_hb_features;
if (num_features && !disable_ligature) num_features--; // the last feature is always -calt
hb_shape(font, harfbuzz_buffer, fobj->ffs_hb_features, num_features);
unsigned int info_length, positions_length;
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
@@ -1027,8 +1049,6 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
if (face == NULL) return NULL;
font = calloc(1, sizeof(Font));
font->face = face;
font->hb_features[0] = hb_features[CALT_FEATURE];
font->num_hb_features = 1;
} else {
FontGroup *fg = font_groups;
font = fg->fonts + fg->medium_font_idx;
@@ -1059,7 +1079,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx], disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS);
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
else if (cursor_offset > -1) {
else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER
index_type left, right;
split_run_at_offset(cursor_offset, &left, &right);
if (right > left) {
@@ -1183,12 +1203,12 @@ DescriptorIndices descriptor_indices = {0};
static PyObject*
set_font_data(PyObject UNUSED *m, PyObject *args) {
PyObject *sm;
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx);
if (!PyArg_ParseTuple(args, "OOOIIIIO!d",
Py_CLEAR(box_drawing_function); Py_CLEAR(prerender_function); Py_CLEAR(descriptor_for_idx); Py_CLEAR(font_feature_settings);
if (!PyArg_ParseTuple(args, "OOOIIIIO!dO",
&box_drawing_function, &prerender_function, &descriptor_for_idx,
&descriptor_indices.bold, &descriptor_indices.italic, &descriptor_indices.bi, &descriptor_indices.num_symbol_fonts,
&PyTuple_Type, &sm, &global_state.font_sz_in_pts)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx);
&PyTuple_Type, &sm, &global_state.font_sz_in_pts, &font_feature_settings)) return NULL;
Py_INCREF(box_drawing_function); Py_INCREF(prerender_function); Py_INCREF(descriptor_for_idx); Py_INCREF(font_feature_settings);
free_font_groups();
clear_symbol_maps();
num_symbol_maps = PyTuple_GET_SIZE(sm);
@@ -1290,6 +1310,7 @@ finalize(void) {
Py_CLEAR(box_drawing_function);
Py_CLEAR(prerender_function);
Py_CLEAR(descriptor_for_idx);
Py_CLEAR(font_feature_settings);
free_font_groups();
if (harfbuzz_buffer) { hb_buffer_destroy(harfbuzz_buffer); harfbuzz_buffer = NULL; }
free(group_state.groups); group_state.groups = NULL; group_state.groups_capacity = 0;
@@ -1435,9 +1456,26 @@ free_font_data(PyObject *self UNUSED, PyObject *args UNUSED) {
Py_RETURN_NONE;
}
static PyObject*
parse_font_feature(PyObject *self UNUSED, PyObject *feature) {
if (!PyUnicode_Check(feature)) {
PyErr_SetString(PyExc_TypeError, "feature must be a unicode object");
return NULL;
}
PyObject *ans = PyBytes_FromStringAndSize(NULL, sizeof(hb_feature_t));
if (!ans) return NULL;
if (!hb_feature_from_string(PyUnicode_AsUTF8(feature), -1, (hb_feature_t*)PyBytes_AS_STRING(ans))) {
Py_CLEAR(ans);
PyErr_Format(PyExc_ValueError, "%U is not a valid font feature", feature);
return NULL;
}
return ans;
}
static PyMethodDef module_methods[] = {
METHODB(set_font_data, METH_VARARGS),
METHODB(free_font_data, METH_NOARGS),
METHODB(parse_font_feature, METH_O),
METHODB(create_test_font_group, METH_VARARGS),
METHODB(sprite_map_set_layout, METH_VARARGS),
METHODB(test_sprite_position_for, METH_VARARGS),

View File

@@ -38,7 +38,7 @@ def list_fonts():
if f:
fn = (f + ' ' + (fd['style'] or '')).strip()
is_mono = bool(fd['monospace'])
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}
def find_best_match(family, bold=False, italic=False):

View File

@@ -45,7 +45,7 @@ def list_fonts():
if f:
fn = fd.get('full_name') or (f + ' ' + fd.get('style', '')).strip()
is_mono = fd.get('spacing') in ('MONO', 'DUAL')
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
yield {'family': f, 'full_name': fn, 'postscript_name': fd.get('postscript_name', ''), 'is_monospace': is_mono}
def family_name_to_key(family):

View File

@@ -20,6 +20,7 @@ def create_family_groups(monospaced=True):
def main(argv):
psnames = '--psnames' in argv
isatty = sys.stdout.isatty()
groups = create_family_groups()
for k in sorted(groups, key=lambda x: x.lower()):
@@ -31,5 +32,7 @@ def main(argv):
p = f['full_name']
if isatty:
p = '\033[3m' + p + '\033[m'
if psnames:
p += ' ({})'.format(f['postscript_name'])
print(' ', p)
print()

View File

@@ -103,7 +103,7 @@ def set_font_family(opts=None, override_font_size=None, debug_font_matching=Fals
set_font_data(
render_box_drawing, prerender_function, descriptor_for_idx,
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
sm, sz
sm, sz, opts.font_features
)

View File

@@ -81,6 +81,11 @@ free_texture(GLuint *tex_id) {
*tex_id = 0;
}
void
free_framebuffer(GLuint *fb_id) {
glDeleteFramebuffers(1, fb_id);
*fb_id = 0;
}
// }}}
@@ -126,6 +131,12 @@ init_uniforms(int program) {
}
}
GLint
get_uniform_location(int program, const char *name) {
Program *p = programs + program;
return glGetUniformLocation(p->id, name);
}
GLint
get_uniform_information(int program, const char *name, GLenum information_type) {
GLint q; GLuint t;

View File

@@ -35,12 +35,14 @@ typedef struct {
void gl_init(void);
void update_surface_size(int w, int h, GLuint offscreen_texture_id);
void free_texture(GLuint *tex_id);
void free_framebuffer(GLuint *fb_id);
void remove_vao(ssize_t vao_idx);
void init_uniforms(int program);
GLuint program_id(int program);
Program* program_ptr(int program);
GLuint block_index(int program, const char *name);
GLint block_size(int program, GLuint block_index);
GLint get_uniform_location(int program, const char *name);
GLint get_uniform_information(int program, const char *name, GLenum information_type);
GLint attrib_location(int program, const char *name);
ssize_t create_vao(void);

3
kitty/glfw-wrapper.c generated
View File

@@ -140,6 +140,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetWindowAspectRatio_impl) = dlsym(handle, "glfwSetWindowAspectRatio");
if (glfwSetWindowAspectRatio_impl == NULL) fail("Failed to load glfw function glfwSetWindowAspectRatio with error: %s", dlerror());
*(void **) (&glfwSetWindowSizeIncrements_impl) = dlsym(handle, "glfwSetWindowSizeIncrements");
if (glfwSetWindowSizeIncrements_impl == NULL) fail("Failed to load glfw function glfwSetWindowSizeIncrements with error: %s", dlerror());
*(void **) (&glfwSetWindowSize_impl) = dlsym(handle, "glfwSetWindowSize");
if (glfwSetWindowSize_impl == NULL) fail("Failed to load glfw function glfwSetWindowSize with error: %s", dlerror());

7
kitty/glfw-wrapper.h generated
View File

@@ -165,6 +165,7 @@
#define GLFW_KEY_LEFT_BRACKET 91 /* [ */
#define GLFW_KEY_BACKSLASH 92 /* \ */
#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */
#define GLFW_KEY_CIRCUMFLEX 94 /* ^ */
#define GLFW_KEY_UNDERSCORE 95 /* _ */
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
@@ -1750,6 +1751,10 @@ typedef void (*glfwSetWindowAspectRatio_func)(GLFWwindow*, int, int);
glfwSetWindowAspectRatio_func glfwSetWindowAspectRatio_impl;
#define glfwSetWindowAspectRatio glfwSetWindowAspectRatio_impl
typedef void (*glfwSetWindowSizeIncrements_func)(GLFWwindow*, int, int);
glfwSetWindowSizeIncrements_func glfwSetWindowSizeIncrements_impl;
#define glfwSetWindowSizeIncrements glfwSetWindowSizeIncrements_impl
typedef void (*glfwSetWindowSize_func)(GLFWwindow*, int, int);
glfwSetWindowSize_func glfwSetWindowSize_impl;
#define glfwSetWindowSize glfwSetWindowSize_impl
@@ -2082,7 +2087,7 @@ typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaun
glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl;
#define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, void*);
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, char*, size_t, int*);
glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl;
#define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl

View File

@@ -14,6 +14,7 @@ extern bool cocoa_make_window_resizable(void *w, bool);
extern void cocoa_focus_window(void *w);
extern void cocoa_create_global_menu(void);
extern void cocoa_hide_window_title(void *w);
extern void cocoa_hide_titlebar(void *w);
extern void cocoa_set_activation_policy(bool);
extern void cocoa_set_titlebar_color(void *w, color_type color);
extern bool cocoa_alt_option_key_pressed(unsigned long);
@@ -513,12 +514,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
// We don't use depth and stencil buffers
glfwWindowHint(GLFW_DEPTH_BITS, 0);
glfwWindowHint(GLFW_STENCIL_BITS, 0);
if (OPT(hide_window_decorations) & 1) glfwWindowHint(GLFW_DECORATED, false);
#ifdef __APPLE__
cocoa_set_activation_policy(OPT(macos_hide_from_tasks));
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);
glfwSetApplicationShouldHandleReopen(on_application_reopen);
glfwSetApplicationWillFinishLaunching(cocoa_create_global_menu);
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
#endif
}
@@ -527,7 +528,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
#endif
if (global_state.num_os_windows >= MAX_CHILDREN) {
@@ -625,6 +625,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
w->fonts_data = fonts_data;
w->shown_once = true;
w->last_focused_counter = ++focus_counter;
if (OPT(resize_in_steps)) os_window_update_size_increments(w);
#ifdef __APPLE__
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
@@ -652,7 +653,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwSetDropCallback(glfw_window, drop_callback);
#ifdef __APPLE__
if (glfwGetCocoaWindow) {
if (!(OPT(macos_show_window_title_in) & WINDOW)) {
if (OPT(hide_window_decorations) & 2) {
cocoa_hide_titlebar(glfwGetCocoaWindow(glfw_window));
} else if (!(OPT(macos_show_window_title_in) & WINDOW)) {
cocoa_hide_window_title(glfwGetCocoaWindow(glfw_window));
}
cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window), OPT(macos_window_resizable));
@@ -673,6 +676,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
return PyLong_FromUnsignedLongLong(w->id);
}
void
os_window_update_size_increments(OSWindow *window) {
if (window->handle && window->fonts_data) glfwSetWindowSizeIncrements(
window->handle, window->fonts_data->cell_width, window->fonts_data->cell_height);
}
#ifdef __APPLE__
static inline bool
window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) {
@@ -834,7 +843,10 @@ static PyObject*
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
int key, native_key;
if (!PyArg_ParseTuple(args, "ii", &key, &native_key)) return NULL;
return Py_BuildValue("s", glfwGetKeyName(key, native_key));
if (!glfwGetKeyName) {
return PyUnicode_FromFormat("key: %d native_key: %d", key, native_key);
}
return Py_BuildValue("z", glfwGetKeyName(key, native_key));
}
static PyObject*
@@ -1079,8 +1091,8 @@ set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
#ifdef __APPLE__
void
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
get_cocoa_key_equivalent(int key, int mods, char *cocoa_key, size_t key_sz, int *cocoa_mods) {
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, key_sz, cocoa_mods);
}
static void
@@ -1296,6 +1308,7 @@ init_glfw(PyObject *m) {
ADDC(GLFW_KEY_LEFT_BRACKET);
ADDC(GLFW_KEY_BACKSLASH);
ADDC(GLFW_KEY_RIGHT_BRACKET);
ADDC(GLFW_KEY_CIRCUMFLEX);
ADDC(GLFW_KEY_UNDERSCORE);
ADDC(GLFW_KEY_GRAVE_ACCENT);
ADDC(GLFW_KEY_WORLD_1);

View File

@@ -544,7 +544,9 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
if (!self->layers_dirty) return false;
self->layers_dirty = false;
size_t i, j;
self->num_of_negative_refs = 0; self->num_of_positive_refs = 0;
self->num_of_below_refs = 0;
self->num_of_negative_refs = 0;
self->num_of_positive_refs = 0;
Image *img; ImageRef *ref;
ImageRect r;
float screen_width = dx * num_cols, screen_height = dy * num_rows;
@@ -565,7 +567,12 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
if (ref->num_cols > 0) r.right = screen_left + (ref->start_column + (int32_t)ref->num_cols) * dx;
else r.right = r.left + screen_width * (float)ref->src_width / screen_width_px;
if (ref->z_index < 0) self->num_of_negative_refs++; else self->num_of_positive_refs++;
if (ref->z_index < ((int32_t)INT32_MIN/2))
self->num_of_below_refs++;
else if (ref->z_index < 0)
self->num_of_negative_refs++;
else
self->num_of_positive_refs++;
ensure_space_for(self, render_data, ImageRenderData, self->count + 1, capacity, 64, true);
ImageRenderData *rd = self->render_data + self->count;
zero_at_ptr(rd);

View File

@@ -72,7 +72,8 @@ typedef struct {
size_t count, capacity;
ImageRenderData *render_data;
bool layers_dirty;
size_t num_of_negative_refs, num_of_positive_refs;
// The number of images below MIN_ZINDEX / 2, then the number of refs between MIN_ZINDEX / 2 and -1 inclusive, then the number of refs above 0 inclusive.
size_t num_of_below_refs, num_of_negative_refs, num_of_positive_refs;
unsigned int last_scrolled_by;
size_t used_storage;
} GraphicsManager;

View File

@@ -4,6 +4,7 @@
uniform sampler2D image;
#ifdef ALPHA_MASK
uniform uint fg;
uniform float alpha_mask_premult;
#else
uniform float inactive_text_alpha;
#endif
@@ -28,6 +29,7 @@ void main() {
color = texture(image, texcoord);
#ifdef ALPHA_MASK
color = vec4(color_to_vec(fg), color.r);
color = mix(color, vec4(color.rgb * color.a, color.a), alpha_mask_premult);
#else
color.a *= inactive_text_alpha;
#ifdef PREMULT

4
kitty/key_encoding.py generated
View File

@@ -192,7 +192,8 @@ ENCODING = {
'CYRILLIC E': 'CJ',
'CYRILLIC YU': 'CK',
'CYRILLIC YA': 'CL',
'CYRILLIC IO': 'CM'
'CYRILLIC IO': 'CM',
'CIRCUMFLEX': 'CN'
}
KEY_MAP = {
32: 'A',
@@ -254,6 +255,7 @@ KEY_MAP = {
91: 's',
92: 't',
93: 'u',
94: 'CN',
95: 'Bj',
96: 'v',
161: 'w',

View File

@@ -30,6 +30,7 @@ key_name_aliases = {
'[': 'LEFT_BRACKET',
'\\': 'BACKSLASH',
']': 'RIGHT_BRACKET',
'^': 'CIRCUMFLEX',
'_': 'UNDERSCORE',
'`': 'GRAVE_ACCENT',
'§': 'PARAGRAPH',

16395
kitty/keys.h generated

File diff suppressed because it is too large Load Diff

View File

@@ -120,16 +120,13 @@ control_codes.update({
for i, k in
enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))
})
control_codes[defines.GLFW_KEY_GRAVE_ACCENT] = (0,)
control_codes[defines.GLFW_KEY_UNDERSCORE] = (0,)
control_codes[defines.GLFW_KEY_SPACE] = (0,)
control_codes[defines.GLFW_KEY_2] = (0,)
control_codes[defines.GLFW_KEY_GRAVE_ACCENT] = control_codes[defines.GLFW_KEY_UNDERSCORE] = \
control_codes[defines.GLFW_KEY_SPACE] = control_codes[defines.GLFW_KEY_2] = (0,)
control_codes[defines.GLFW_KEY_3] = (27,)
control_codes[defines.GLFW_KEY_4] = (28,)
control_codes[defines.GLFW_KEY_5] = (29,)
control_codes[defines.GLFW_KEY_6] = (30,)
control_codes[defines.GLFW_KEY_7] = (31,)
control_codes[defines.GLFW_KEY_SLASH] = (31,)
control_codes[defines.GLFW_KEY_6] = control_codes[defines.GLFW_KEY_CIRCUMFLEX] = (30,)
control_codes[defines.GLFW_KEY_7] = control_codes[defines.GLFW_KEY_SLASH] = (31,)
control_codes[defines.GLFW_KEY_8] = (127,)
rmkx_key_map = smkx_key_map.copy()
@@ -387,7 +384,7 @@ def generate_key_table_impl(w):
def generate_key_table():
# To run this, use: python3 . +runpy "from kitty.keys import *; generate_key_table()"
# To run this, use: ./kitty/launcher/kitty +runpy "from kitty.keys import *; generate_key_table()"
import os
from functools import partial
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'keys.h'), 'w') as f:

View File

@@ -18,7 +18,7 @@ child process.
--tab-title
The title for the new tab if launching in a new tab. By default, the title
of the actie window in the tab is used as the tab title.
of the active window in the tab is used as the tab title.
--type
@@ -73,11 +73,12 @@ newly launched child process.
--location
type=choices
default=last
choices=first,neighbor,last
choices=first,after,before,neighbor,last
Where to place the newly created window when it is added to a tab which
already has existing windows in it. Also applies to creating a new tab,
where the value of neighbor will cause the new tab to be placed next to
the current tab instead of at the end.
already has existing windows in it. :code:`after` and :code:`before` place the new
window before or after the active window. :code:`neighbor` is a synonym for :code:`after`.
Also applies to creating a new tab, where the value of :code:`after`
will cause the new tab to be placed next to the current tab instead of at the end.
--allow-remote-control
@@ -116,6 +117,9 @@ want to pipe to program that wants to duplicate the screen layout of the
screen.
--marker
Create a marker that highlights text in the newly created window. The syntax is
the same as for the :code:`toggle_marker` map action (see :doc:`/marks`).
'''
options_spec.ans = OPTIONS
return options_spec.ans
@@ -144,7 +148,7 @@ def get_env(opts, active_child):
def tab_for_window(boss, opts, target_tab=None):
if opts.type == 'tab':
tm = boss.active_tab_manager
tab = tm.new_tab(empty_tab=True, as_neighbor=opts.location == 'neighbor')
tab = tm.new_tab(empty_tab=True, location=opts.location)
if opts.tab_title:
tab.set_title(opts.tab_title)
elif opts.type == 'os-window':
@@ -178,6 +182,8 @@ def launch(boss, opts, args, target_tab=None):
kw['override_title'] = opts.window_title
if opts.copy_colors and active:
kw['copy_colors_from'] = active
if opts.marker:
kw['marker'] = opts.marker
cmd = args or None
if opts.copy_cmdline and active_child:
cmd = active_child.foreground_cmdline

View File

@@ -3,7 +3,7 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from collections import namedtuple
from functools import partial
from functools import lru_cache, partial
from itertools import islice, repeat
from .constants import WindowGeometry
@@ -279,8 +279,12 @@ class Layout: # {{{
all_windows[i] = window
active_window_idx = i
elif location is not None:
if location == 'neighbor' and current_active_window_idx is not None and len(all_windows) > 1:
if location == 'neighbor':
location = 'after'
if location == 'after' and current_active_window_idx is not None and len(all_windows) > 1:
active_window_idx = min(current_active_window_idx + 1, len(all_windows))
elif location == 'before' and current_active_window_idx is not None and len(all_windows) > 1:
active_window_idx = current_active_window_idx
elif location == 'first':
active_window_idx = 0
if active_window_idx is not None:
@@ -391,8 +395,11 @@ class Layout: # {{{
br = self.blank_rects
left_blank_rect(first_window, br), top_blank_rect(first_window, br), right_blank_rect(last_window, br)
def between_blank_rect(self, left_window, right_window):
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
def between_blank_rect(self, left_window, right_window, vertical=True):
if vertical:
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
else:
self.blank_rects.append(Rect(central.left, left_window.geometry.top, central.right + 1, right_window.geometry.bottom))
def bottom_blank_rect(self, window):
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
@@ -445,28 +452,34 @@ class Tall(Layout): # {{{
only_between_border = False, False, False, True
only_main_border = False, False, True, False
@property
def num_full_size_windows(self):
return self.layout_opts['full_size']
def remove_all_biases(self):
self.main_bias = list(self.layout_opts['bias'])
self.biased_map = {}
return True
def variable_layout(self, num_windows, biased_map):
num_windows -= 1
num_windows -= self.num_full_size_windows
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
def apply_bias(self, idx, increment, num_windows, is_horizontal):
if self.main_is_horizontal == is_horizontal:
before = self.main_bias
if idx == 0:
self.main_bias = [safe_increment_bias(self.main_bias[0], increment), safe_increment_bias(self.main_bias[1], -increment)]
else:
self.main_bias = [safe_increment_bias(self.main_bias[0], -increment), safe_increment_bias(self.main_bias[1], increment)]
ncols = self.num_full_size_windows + 1
biased_col = idx if idx < self.num_full_size_windows else (ncols - 1)
self.main_bias = [
safe_increment_bias(self.main_bias[i], increment * (1 if i == biased_col else -1)) for i in range(ncols)
]
self.main_bias = normalize_biases(self.main_bias)
after = self.main_bias
else:
if idx == 0 or num_windows < 3:
num_of_short_windows = num_windows - self.num_full_size_windows
if idx < self.num_full_size_windows or num_of_short_windows < 2:
return False
idx -= 1
idx -= self.num_full_size_windows
before_layout = list(self.variable_layout(num_windows, self.biased_map))
candidate = self.biased_map.copy()
before = candidate.get(idx, 0)
@@ -480,40 +493,70 @@ class Tall(Layout): # {{{
def parse_layout_opts(self, layout_opts):
ans = Layout.parse_layout_opts(self, layout_opts)
try:
ans['bias'] = int(ans.get('bias', 50)) / 100
ans['full_size'] = int(ans.get('full_size', 1))
except Exception:
ans['bias'] = 0.5
ans['bias'] = max(0.1, min(ans['bias'], 0.9))
ans['bias'] = ans['bias'], 1.0 - ans['bias']
ans['full_size'] = 1
ans['full_size'] = fs = max(1, min(ans['full_size'], 100))
try:
b = int(ans.get('bias', 50)) / 100
except Exception:
b = 0.5
b = max(0.1, min(b, 0.9))
ans['bias'] = tuple(repeat(b / fs, fs)) + (1.0 - b,)
return ans
def do_layout(self, windows, active_window_idx):
if len(windows) == 1:
return self.layout_single_window(windows[0])
xlayout = self.xlayout(2, bias=self.main_bias)
xstart, xnum = next(xlayout)
ystart, ynum = next(self.vlayout(1))
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
xstart, xnum = next(xlayout)
ylayout = self.variable_layout(len(windows), self.biased_map)
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, 1, None), ylayout)):
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
# right bottom blank rect
self.bottom_blank_rect(windows[i + 1])
y, ynum = next(self.vlayout(1))
if len(windows) <= self.num_full_size_windows:
bias = normalize_biases(self.main_bias[:-1])
xlayout = self.xlayout(self.num_full_size_windows, bias=bias)
for i, (w, (x, xnum)) in enumerate(zip(windows, xlayout)):
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
if i > 0:
self.between_blank_rect(windows[i-1], windows[i])
# bottom blank rect
self.bottom_blank_rect(windows[i])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
return
xlayout = self.xlayout(self.num_full_size_windows + 1, bias=self.main_bias)
for i in range(self.num_full_size_windows):
w = windows[i]
x, xnum = next(xlayout)
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
self.between_blank_rect(windows[i], windows[i+1])
# bottom blank rect
self.bottom_blank_rect(windows[i])
x, xnum = next(xlayout)
ylayout = self.variable_layout(len(windows), self.biased_map)
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), ylayout)):
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, ystart, ynum))
# right bottom blank rect
self.bottom_blank_rect(windows[-1])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
# between blank rect
self.between_blank_rect(windows[0], windows[1])
# left bottom blank rect
self.bottom_blank_rect(windows[0])
def neighbors_for_window(self, window, windows):
if window is windows[0]:
return {'left': [], 'right': windows[1:], 'top': [], 'bottom': []}
idx = windows.index(window)
return {'left': [windows[0]], 'right': [], 'top': [] if idx <= 1 else [windows[idx-1]],
'bottom': [] if window is windows[-1] else [windows[idx+1]]}
prev = None if idx == 0 else windows[idx-1]
nxt = None if idx == len(windows) - 1 else windows[idx+1]
ans = {'left': [prev] if prev is not None else [], 'right': [], 'top': [], 'bottom': []}
if idx < self.num_full_size_windows - 1:
if nxt is not None:
ans['right'] = [nxt]
elif idx == self.num_full_size_windows - 1:
ans['right'] = windows[idx+1:]
else:
ans['left'] = [windows[self.num_full_size_windows - 1]]
if idx > self.num_full_size_windows:
ans['top'] = [prev]
if nxt is not None:
ans['bottom'] = [nxt]
return ans
def minimal_borders(self, windows, active_window, needs_borders_map):
last_i = len(windows) - 1
@@ -521,11 +564,11 @@ class Tall(Layout): # {{{
if needs_borders_map[w.id]:
yield all_borders
continue
if i == 0:
if last_i == 1 and needs_borders_map[windows[1].id]:
if i < self.num_full_size_windows:
if (last_i == i+1 or i+1 < self.num_full_size_windows) and needs_borders_map[windows[i+1].id]:
yield no_borders
else:
yield self.only_main_border
yield no_borders if i == last_i else self.only_main_border
continue
if i == last_i:
yield no_borders
@@ -548,36 +591,73 @@ class Fat(Tall): # {{{
def do_layout(self, windows, active_window_idx):
if len(windows) == 1:
return self.layout_single_window(windows[0])
xstart, xnum = next(self.xlayout(1))
ylayout = self.ylayout(2, bias=self.main_bias)
ystart, ynum = next(ylayout)
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
x, xnum = next(self.vlayout(1))
if len(windows) <= self.num_full_size_windows:
bias = normalize_biases(self.main_bias[:-1])
ylayout = self.ylayout(self.num_full_size_windows, bias=bias)
for i, (w, (y, ynum)) in enumerate(zip(windows, ylayout)):
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
if i > 0:
self.between_blank_rect(windows[i-1], windows[i], vertical=False)
# bottom blank rect
self.bottom_blank_rect(windows[-1])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
return
ylayout = self.ylayout(self.num_full_size_windows + 1, bias=self.main_bias)
for i in range(self.num_full_size_windows):
w = windows[i]
y, ynum = next(ylayout)
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
self.between_blank_rect(windows[i], windows[i+1], vertical=False)
y, ynum = next(ylayout)
xlayout = self.variable_layout(len(windows), self.biased_map)
ystart, ynum = next(ylayout)
for i, (w, (xstart, xnum)) in enumerate(zip(islice(windows, 1, None), xlayout)):
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
if i > 0:
# bottom between blank rect
self.between_blank_rect(windows[i - 1], windows[i])
for i, (w, (x, xnum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), xlayout)):
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, y, ynum))
# bottom blank rect
self.bottom_blank_rect(windows[i])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
# top bottom blank rect
self.bottom_blank_rect(windows[0])
# bottom blank rect
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
def neighbors_for_window(self, window, windows):
if window is windows[0]:
return {'left': [], 'bottom': windows[1:], 'top': [], 'right': []}
idx = windows.index(window)
return {'top': [windows[0]], 'bottom': [], 'left': [] if idx <= 1 else [windows[idx-1]],
'right': [] if window is windows[-1] else [windows[idx+1]]}
prev = None if idx == 0 else windows[idx-1]
nxt = None if idx == len(windows) - 1 else windows[idx+1]
ans = {'left': [], 'right': [], 'top': [] if prev is None else [prev], 'bottom': []}
if idx < self.num_full_size_windows - 1:
if nxt is not None:
ans['bottom'] = [nxt]
elif idx == self.num_full_size_windows - 1:
ans['bottom'] = windows[idx+1:]
else:
ans['top'] = [windows[self.num_full_size_windows - 1]]
if idx > self.num_full_size_windows:
ans['left'] = [prev]
if nxt is not None:
ans['right'] = [nxt]
return ans
# }}}
class Grid(Layout): # {{{
# Grid {{{
@lru_cache()
def calc_grid_size(n):
if n <= 5:
ncols = 1 if n == 1 else 2
else:
for ncols in range(3, (n // 2) + 1):
if ncols * ncols >= n:
break
nrows = n // ncols
special_rows = n - (nrows * (ncols - 1))
special_col = 0 if special_rows < nrows else ncols - 1
return ncols, nrows, special_rows, special_col
class Grid(Layout):
name = 'grid'
@@ -591,7 +671,7 @@ class Grid(Layout): # {{{
def apply_bias(self, idx, increment, num_windows, is_horizontal):
b = self.biased_cols if is_horizontal else self.biased_rows
ncols, nrows, special_rows, special_col = self.calc_grid_size(num_windows)
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
def position_for_window_idx(idx):
row_num = col_num = 0
@@ -633,18 +713,6 @@ class Grid(Layout): # {{{
setattr(self, attr, candidate)
return True
def calc_grid_size(self, n):
if n <= 5:
ncols = 1 if n == 1 else 2
else:
for ncols in range(3, (n // 2) + 1):
if ncols * ncols >= n:
break
nrows = n // ncols
special_rows = n - (nrows * (ncols - 1))
special_col = 0 if special_rows < nrows else ncols - 1
return ncols, nrows, special_rows, special_col
def layout_windows(self, num_windows, nrows, ncols, special_rows, special_col, on_col_done=lambda col_windows: None):
# Distribute windows top-to-bottom, left-to-right (i.e. in columns)
xlayout = self.variable_layout(self.xlayout, ncols, self.biased_cols)
@@ -667,10 +735,7 @@ class Grid(Layout): # {{{
n = len(windows)
if n == 1:
return self.layout_single_window(windows[0])
ncols, nrows, special_rows, special_col = self.calc_grid_size(n)
layout_data = n, ncols, nrows, special_rows, special_col
for w in windows:
w.layout_data = layout_data
ncols, nrows, special_rows, special_col = calc_grid_size(n)
win_col_map = []
@@ -693,14 +758,8 @@ class Grid(Layout): # {{{
self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0])
def minimal_borders(self, windows, active_window, needs_borders_map):
try:
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
except Exception:
n = -1
if n != len(windows):
# Something bad happened
yield from Layout.minimal_borders(self, windows, active_window, needs_borders_map)
return
n = len(windows)
ncols, nrows, special_rows, special_col = calc_grid_size(n)
blank_row = [None for i in range(ncols)]
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
wi = iter(windows)
@@ -739,14 +798,7 @@ class Grid(Layout): # {{{
n = len(windows)
if n < 4:
return Tall.neighbors_for_window(self, window, windows)
try:
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
except Exception:
n = -1
if n != len(windows):
# Something bad happened
return Layout.neighbors_for_window(self, window, windows)
ncols, nrows, special_rows, special_col = calc_grid_size(n)
blank_row = [None for i in range(ncols)]
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
wi = iter(windows)

View File

@@ -50,7 +50,7 @@ cell_text(CPUCell *cell) {
// URL detection {{{
static const char* url_prefixes[4] = {"https", "http", "file", "ftp"};
static size_t url_prefix_lengths[sizeof(url_prefixes)/sizeof(url_prefixes[0])] = {0};
static size_t url_prefix_lengths[arraysz(url_prefixes)] = {0};
static inline index_type
find_colon_slash(Line *self, index_type x, index_type limit) {
@@ -79,7 +79,7 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
break;
case SECOND_SLASH:
if (ch == ':') return pos;
state = ANY;
state = ch == '/' ? SECOND_SLASH : ANY;
break;
}
pos--;
@@ -100,9 +100,9 @@ prefix_matches(Line *self, index_type at, const char* prefix, index_type prefix_
static inline bool
has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_type *ans) {
if (UNLIKELY(!url_prefix_lengths[0])) {
for (index_type i = 0; i < sizeof(url_prefixes)/sizeof(url_prefixes[0]); i++) url_prefix_lengths[i] = strlen(url_prefixes[i]);
for (index_type i = 0; i < arraysz(url_prefixes); i++) url_prefix_lengths[i] = strlen(url_prefixes[i]);
}
for (index_type i = 0; i < sizeof(url_prefixes)/sizeof(url_prefixes[0]); i++) {
for (index_type i = 0; i < arraysz(url_prefixes); i++) {
index_type prefix_len = url_prefix_lengths[i];
if (at < prefix_len || prefix_len < min_prefix_len) continue;
if (prefix_matches(self, at, url_prefixes[i], prefix_len)) { *ans = at - prefix_len; return true; }
@@ -665,6 +665,78 @@ __eq__(Line *a, Line *b) {
return a->xnum == b->xnum && memcmp(a->cpu_cells, b->cpu_cells, sizeof(CPUCell) * a->xnum) == 0 && memcmp(a->gpu_cells, b->gpu_cells, sizeof(GPUCell) * a->xnum) == 0;
}
bool
line_has_mark(Line *line, attrs_type mark) {
for (index_type x = 0; x < line->xnum; x++) {
attrs_type m = (line->gpu_cells[x].attrs >> MARK_SHIFT) & MARK_MASK;
if (m && (!mark || mark == m)) return true;
}
return false;
}
static inline void
report_marker_error(PyObject *marker) {
if (!PyObject_HasAttrString(marker, "error_reported")) {
PyErr_Print();
if (PyObject_SetAttrString(marker, "error_reported", Py_True) != 0) PyErr_Clear();
} else PyErr_Clear();
}
static inline void
apply_marker(PyObject *marker, Line *line, const PyObject *text) {
unsigned int l=0, r=0, col=0, match_pos=0;
PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col);
if (!pl || !pr || !pcol) { PyErr_Clear(); return; }
PyObject *iter = PyObject_CallFunctionObjArgs(marker, text, pl, pr, pcol, NULL);
Py_DECREF(pl); Py_DECREF(pr); Py_DECREF(pcol);
if (iter == NULL) { report_marker_error(marker); return; }
PyObject *match;
index_type x = 0;
#define INCREMENT_MATCH_POS { \
if (line->cpu_cells[x].ch) { \
match_pos++; \
for (index_type i = 0; i < arraysz(line->cpu_cells[x].cc_idx); i++) { \
if (line->cpu_cells[x].cc_idx[i]) match_pos++; \
}}}
while ((match = PyIter_Next(iter)) && x < line->xnum) {
Py_DECREF(match);
while (match_pos < l && x < line->xnum) {
line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK;
INCREMENT_MATCH_POS;
x++;
}
attrs_type am = (col & MARK_MASK) << MARK_SHIFT;
while(x < line->xnum && match_pos <= r) {
line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK;
line->gpu_cells[x].attrs |= am;
INCREMENT_MATCH_POS;
x++;
}
}
while(x < line->xnum) line->gpu_cells[x++].attrs &= ATTRS_MASK_WITHOUT_MARK;
Py_DECREF(iter);
if (PyErr_Occurred()) report_marker_error(marker);
#undef INCREMENT_MATCH_POS
}
void
mark_text_in_line(PyObject *marker, Line *line) {
if (!marker) {
for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK;
return;
}
PyObject *text = line_as_unicode(line);
if (PyUnicode_GET_LENGTH(text) > 0) {
apply_marker(marker, line, text);
} else {
for (index_type i = 0; i < line->xnum; i++) line->gpu_cells[i].attrs &= ATTRS_MASK_WITHOUT_MARK;
}
Py_DECREF(text);
}
// Boilerplate {{{
static PyObject*
copy_char(Line* self, PyObject *args);

View File

@@ -90,6 +90,8 @@ void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
void historybuf_refresh_sprite_positions(HistoryBuf *self);
void historybuf_clear(HistoryBuf *self);
void mark_text_in_line(PyObject *marker, Line *line);
bool line_has_mark(Line *, attrs_type mark);
#define as_text_generic(args, container, get_line, lines, columns) { \

View File

@@ -67,14 +67,14 @@ def talk_to_instance(args):
data = json.dumps(data, ensure_ascii=False).encode('utf-8')
single_instance.socket.sendall(data)
with suppress(EnvironmentError):
with suppress(OSError):
single_instance.socket.shutdown(socket.SHUT_RDWR)
single_instance.socket.close()
if args.wait_for_single_instance_window_close:
conn = notify_socket.accept()[0]
conn.recv(1)
with suppress(EnvironmentError):
with suppress(OSError):
conn.shutdown(socket.SHUT_RDWR)
conn.close()
@@ -177,7 +177,8 @@ def setup_profiling(args):
exe = kitty_exe()
cg = '/tmp/kitty-profile.callgrind'
print('Post processing profile data for', exe, '...')
subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=open(cg, 'wb'))
with open(cg, 'wb') as f:
subprocess.call(['pprof', '--callgrind', exe, '/tmp/kitty-profile.log'], stdout=f)
try:
subprocess.Popen(['kcachegrind', cg])
except FileNotFoundError:
@@ -201,12 +202,17 @@ def macos_cmdline(argv_args):
def read_shell_environment(opts=None):
if not hasattr(read_shell_environment, 'ans'):
ans = read_shell_environment.ans = {}
import subprocess
from .session import resolved_shell
shell = resolved_shell(opts)
master, slave = openpty()
remove_blocking(master)
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
try:
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
except FileNotFoundError:
log_error('Could not find shell to read environment')
return ans
with os.fdopen(master, 'rb') as stdout, os.fdopen(slave, 'wb'):
raw = b''
from subprocess import TimeoutExpired
@@ -234,14 +240,12 @@ def read_shell_environment(opts=None):
break
raw += x
raw = raw.decode('utf-8', 'replace')
ans = read_shell_environment.ans = {}
for line in raw.splitlines():
k, v = line.partition('=')[::2]
if k and v:
ans[k] = v
else:
log_error('Failed to run shell to read its environment')
read_shell_environment.ans = {}
return read_shell_environment.ans

93
kitty/marks.py Normal file
View File

@@ -0,0 +1,93 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from ctypes import POINTER, c_uint, c_void_p, cast
from .constants import config_dir
pointer_to_uint = POINTER(c_uint)
def null_marker(*a):
return iter(())
def get_output_variables(left_address, right_address, color_address):
return (
cast(c_void_p(left_address), pointer_to_uint).contents,
cast(c_void_p(right_address), pointer_to_uint).contents,
cast(c_void_p(color_address), pointer_to_uint).contents,
)
def marker_from_regex(expression, color, flags=re.UNICODE):
color = max(1, min(color, 3))
if isinstance(expression, str):
pat = re.compile(expression, flags=flags)
else:
pat = expression
def marker(text, left_address, right_address, color_address):
left, right, colorv = get_output_variables(left_address, right_address, color_address)
colorv.value = color
for match in pat.finditer(text):
left.value = match.start()
right.value = match.end() - 1
yield
return marker
def marker_from_multiple_regex(regexes, flags=re.UNICODE):
expr = ''
color_map = {}
for i, (color, spec) in enumerate(regexes):
grp = 'mcg{}'.format(i)
expr += '|(?P<{}>{})'.format(grp, spec)
color_map[grp] = color
expr = expr[1:]
pat = re.compile(expr, flags=flags)
def marker(text, left_address, right_address, color_address):
left, right, color = get_output_variables(left_address, right_address, color_address)
for match in pat.finditer(text):
left.value = match.start()
right.value = match.end() - 1
grp = next(k for k, v in match.groupdict().items() if v is not None)
color.value = color_map[grp]
yield
return marker
def marker_from_text(expression, color):
return marker_from_regex(re.escape(expression), color)
def marker_from_function(func):
def marker(text, left_address, right_address, color_address):
left, right, colorv = get_output_variables(left_address, right_address, color_address)
for (l, r, c) in func(text):
left.value = l
right.value = r
colorv.value = c
yield
return marker
def marker_from_spec(ftype, spec, flags):
if ftype == 'regex':
if len(spec) == 1:
return marker_from_regex(spec[0][1], spec[0][0], flags=flags)
return marker_from_multiple_regex(spec, flags=flags)
if ftype == 'function':
import runpy
path = spec
if not os.path.isabs(path):
path = os.path.join(config_dir, path)
return marker_from_function(runpy.run_path(path, run_name='__marker__').marker)
raise ValueError('Unknown marker type: {}'.format(ftype))

View File

@@ -68,7 +68,7 @@ class SocketIO:
def __exit__(self, *a):
import socket
with suppress(EnvironmentError): # on some OSes such as macOS the socket is already closed at this point
with suppress(OSError): # on some OSes such as macOS the socket is already closed at this point
self.socket.shutdown(socket.SHUT_RDWR)
self.socket.close()

View File

@@ -115,7 +115,11 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->pending_mode.wait_time = 2.0;
self->disable_ligatures = OPT(disable_ligatures);
self->main_tabstops = PyMem_Calloc(2 * self->columns, sizeof(bool));
if (self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL || self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL || self->alt_grman == NULL || self->color_profile == NULL) {
if (
self->cursor == NULL || self->main_linebuf == NULL || self->alt_linebuf == NULL ||
self->main_tabstops == NULL || self->historybuf == NULL || self->main_grman == NULL ||
self->alt_grman == NULL || self->color_profile == NULL
) {
Py_CLEAR(self); return NULL;
}
self->alt_tabstops = self->main_tabstops + self->columns * sizeof(bool);
@@ -278,6 +282,7 @@ dealloc(Screen* self) {
Py_CLEAR(self->alt_linebuf);
Py_CLEAR(self->historybuf);
Py_CLEAR(self->color_profile);
Py_CLEAR(self->marker);
PyMem_Free(self->overlay_line.cpu_cells);
PyMem_Free(self->overlay_line.gpu_cells);
PyMem_Free(self->main_tabstops);
@@ -1503,6 +1508,12 @@ screen_reset_dirty(Screen *self) {
self->history_line_added_count = 0;
}
static inline bool
screen_has_marker(Screen *self) {
return self->marker != NULL;
}
void
screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_data, bool cursor_has_moved) {
unsigned int history_line_added_count = self->history_line_added_count;
@@ -1516,6 +1527,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
historybuf_init_line(self->historybuf, lnum, self->historybuf->line);
if (self->historybuf->line->has_dirty_text) {
render_line(fonts_data, self->historybuf->line, lnum, self->cursor, self->disable_ligatures);
if (screen_has_marker(self)) mark_text_in_line(self->marker, self->historybuf->line);
historybuf_mark_line_clean(self->historybuf, lnum);
}
update_line_data(self->historybuf->line, y, address);
@@ -1526,6 +1538,8 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
if (self->linebuf->line->has_dirty_text ||
(cursor_has_moved && (self->cursor->y == lnum || self->last_rendered_cursor_y == lnum))) {
render_line(fonts_data, self->linebuf->line, lnum, self->cursor, self->disable_ligatures);
if (self->linebuf->line->has_dirty_text && screen_has_marker(self)) mark_text_in_line(self->marker, self->linebuf->line);
linebuf_mark_line_clean(self->linebuf, lnum);
}
update_line_data(self->linebuf->line, y, address);
@@ -2183,12 +2197,13 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
index_type bottom_line = extending_leftwards ? self->selection.start_y : self->selection.end_y;
while(top_line > 0 && visual_line_(self, top_line)->continued) top_line--;
while(bottom_line < self->lines - 1 && visual_line_(self, bottom_line + 1)->continued) bottom_line++;
bool multiline = self->selection.end_y != self->selection.start_y;
found = screen_selection_range_for_line(self, top_line, &start, &end);
if (found) {
if (extending_leftwards) {
self->selection.end_x = start; self->selection.end_y = top_line;
self->selection.end_x = multiline ? 0 : start; self->selection.end_y = top_line;
} else {
self->selection.start_x = start; self->selection.start_y = top_line;
self->selection.start_x = multiline ? 0 : start; self->selection.start_y = top_line;
}
}
found = screen_selection_range_for_line(self, bottom_line, &start, &end);
@@ -2241,14 +2256,115 @@ send_escape_code_to_child(Screen *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline void
screen_mark_all(Screen *self) {
for (index_type y = 0; y < self->main_linebuf->ynum; y++) {
linebuf_init_line(self->main_linebuf, y);
mark_text_in_line(self->marker, self->main_linebuf->line);
}
for (index_type y = 0; y < self->alt_linebuf->ynum; y++) {
linebuf_init_line(self->alt_linebuf, y);
mark_text_in_line(self->marker, self->alt_linebuf->line);
}
for (index_type y = 0; y < self->historybuf->count; y++) {
historybuf_init_line(self->historybuf, y, self->historybuf->line);
mark_text_in_line(self->marker, self->historybuf->line);
}
self->is_dirty = true;
}
static PyObject*
set_marker(Screen *self, PyObject *args) {
PyObject *marker = NULL;
if (!PyArg_ParseTuple(args, "|O", &marker)) return NULL;
if (!marker) {
if (self->marker) {
Py_CLEAR(self->marker);
screen_mark_all(self);
}
Py_RETURN_NONE;
}
if (!PyCallable_Check(marker)) {
PyErr_SetString(PyExc_TypeError, "marker must be a callable");
return NULL;
}
self->marker = marker;
Py_INCREF(marker);
screen_mark_all(self);
Py_RETURN_NONE;
}
static PyObject*
scroll_to_next_mark(Screen *self, PyObject *args) {
int backwards = 1;
unsigned int mark = 0;
if (!PyArg_ParseTuple(args, "|Ip", &mark, &backwards)) return NULL;
if (!screen_has_marker(self) || self->linebuf == self->alt_linebuf) Py_RETURN_FALSE;
if (backwards) {
for (unsigned int y = self->scrolled_by; y < self->historybuf->count; y++) {
historybuf_init_line(self->historybuf, y, self->historybuf->line);
if (line_has_mark(self->historybuf->line, mark)) {
screen_history_scroll(self, y - self->scrolled_by + 1, true);
Py_RETURN_TRUE;
}
}
} else {
Line *line;
for (unsigned int y = self->scrolled_by; y > 0; y--) {
if (y > self->lines) {
historybuf_init_line(self->historybuf, y - self->lines, self->historybuf->line);
line = self->historybuf->line;
} else {
linebuf_init_line(self->linebuf, self->lines - y);
line = self->linebuf->line;
}
if (line_has_mark(line, mark)) {
screen_history_scroll(self, self->scrolled_by - y + 1, false);
Py_RETURN_TRUE;
}
}
}
Py_RETURN_FALSE;
}
static PyObject*
marked_cells(Screen *self, PyObject *o UNUSED) {
PyObject *ans = PyList_New(0);
if (!ans) return ans;
for (index_type y = 0; y < self->lines; y++) {
linebuf_init_line(self->linebuf, y);
for (index_type x = 0; x < self->columns; x++) {
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + x;
unsigned int mark = (gpu_cell->attrs >> MARK_SHIFT) & MARK_MASK;
if (mark) {
PyObject *t = Py_BuildValue("III", x, y, mark);
if (!t) { Py_DECREF(ans); return NULL; }
if (PyList_Append(ans, t) != 0) { Py_DECREF(t); Py_DECREF(ans); return NULL; }
Py_DECREF(t);
}
}
}
return ans;
}
static PyObject*
paste(Screen *self, PyObject *bytes) {
if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_START);
write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_END);
Py_RETURN_NONE;
}
static PyObject*
paste_bytes(Screen *self, PyObject *bytes) {
if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
Py_RETURN_NONE;
}
WRAP2(cursor_position, 1, 1)
#define COUNT_WRAP(name) WRAP1(name, 1)
@@ -2329,7 +2445,11 @@ static PyMethodDef methods[] = {
MND(toggle_alt_screen, METH_NOARGS)
MND(reset_callbacks, METH_NOARGS)
MND(paste, METH_O)
MND(paste_bytes, METH_O)
MND(copy_colors_from, METH_O)
MND(set_marker, METH_VARARGS)
MND(marked_cells, METH_NOARGS)
MND(scroll_to_next_mark, METH_VARARGS)
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},
{NULL} /* Sentinel */

View File

@@ -61,7 +61,6 @@ typedef struct {
index_type xstart, ynum, xnum;
} OverlayLine;
typedef struct {
PyObject_HEAD
@@ -108,7 +107,7 @@ typedef struct {
uint8_t stop_buf[32];
} pending_mode;
DisableLigature disable_ligatures;
PyObject *marker;
} Screen;

View File

@@ -143,7 +143,7 @@ def create_sessions(opts, args=None, special_window=None, cwd_from=None, respect
try:
with open(default_session) as f:
session_data = f.read()
except EnvironmentError:
except OSError:
log_error('Failed to read from session file, ignoring: {}'.format(default_session))
else:
yield from parse_session(session_data, opts, getattr(args, 'title', None))

View File

@@ -150,10 +150,10 @@ send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei heigh
typedef struct {
UniformBlock render_data;
ArrayInformation color_table;
GLint draw_bg_bitfield_location;
} CellProgramLayout;
static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
static GLuint offscreen_framebuffer = 0;
static ssize_t blit_vertex_array;
static void
@@ -165,13 +165,13 @@ init_cell_program(void) {
cell_program_layouts[i].color_table.offset = get_uniform_information(i, "color_table[0]", GL_UNIFORM_OFFSET);
cell_program_layouts[i].color_table.stride = get_uniform_information(i, "color_table[0]", GL_UNIFORM_ARRAY_STRIDE);
}
cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location = get_uniform_location(CELL_BG_PROGRAM, "draw_bg_bitfield");
// Sanity check to ensure the attribute location binding worked
#define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); }
for (int p = CELL_PROGRAM; p < BORDERS_PROGRAM; p++) {
C(p, colors, 0); C(p, sprite_coords, 1); C(p, is_selected, 2);
}
#undef C
glGenFramebuffers(1, &offscreen_framebuffer);
blit_vertex_array = create_vao();
}
@@ -211,7 +211,7 @@ create_graphics_vao() {
struct CellUniformData {
bool constants_set;
bool alpha_mask_fg_set;
GLint gploc, gpploc, cploc, cfploc, fg_loc;
GLint gploc, gpploc, cploc, cfploc, fg_loc, amask_premult_loc;
GLfloat prev_inactive_text_alpha;
};
@@ -347,7 +347,7 @@ draw_graphics(int program, ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *d
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
void
draw_centered_alpha_mask(ssize_t gvao_idx, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas) {
draw_centered_alpha_mask(OSWindow *os_window, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas) {
static ImageRenderData data = {.group_count=1};
gpu_data_for_centered_image(&data, screen_width, screen_height, width, height);
if (!data.texture_id) { glGenTextures(1, &data.texture_id); }
@@ -364,11 +364,15 @@ draw_centered_alpha_mask(ssize_t gvao_idx, size_t screen_width, size_t screen_he
glUniform1i(glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "image"), GRAPHICS_UNIT);
glUniform1ui(glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "fg"), OPT(foreground));
}
glScissor(0, 0, screen_width, screen_height);
send_graphics_data_to_gpu(1, gvao_idx, &data);
glUniform1f(cell_uniform_data.amask_premult_loc, os_window->is_semi_transparent ? 1.f : 0.f);
send_graphics_data_to_gpu(1, os_window->gvao_idx, &data);
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, gvao_idx, &data, 0, 1);
if (os_window->is_semi_transparent) {
BLEND_PREMULT;
} else {
BLEND_ONTO_OPAQUE;
}
draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, os_window->gvao_idx, &data, 0, 1);
glDisable(GL_BLEND);
}
@@ -386,12 +390,23 @@ draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
static void
draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
bind_program(CELL_BG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
glEnable(GL_BLEND);
BLEND_ONTO_OPAQUE;
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs);
bind_program(CELL_BG_PROGRAM);
// draw background for all cells
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
if (screen->grman->num_of_below_refs) {
draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs);
bind_program(CELL_BG_PROGRAM);
// draw background for non-default bg cells
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 2);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
}
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_below_refs, screen->grman->num_of_negative_refs);
bind_program(CELL_SPECIAL_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
@@ -399,7 +414,7 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
bind_program(CELL_FG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs);
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs);
glDisable(GL_BLEND);
}
@@ -407,6 +422,7 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
static void
draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *os_window) {
if (!os_window->offscreen_texture_id) {
glGenFramebuffers(1, &os_window->offscreen_framebuffer);
glGenTextures(1, &os_window->offscreen_texture_id);
glBindTexture(GL_TEXTURE_2D, os_window->offscreen_texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, os_window->viewport_width, os_window->viewport_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
@@ -416,16 +432,34 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, offscreen_framebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, os_window->offscreen_framebuffer);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, os_window->offscreen_texture_id, 0);
/* if (glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) fatal("Offscreen framebuffer not complete"); */
bind_program(CELL_BG_PROGRAM);
// draw background for all cells
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
glEnable(GL_BLEND);
BLEND_PREMULT;
if (screen->grman->num_of_negative_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs);
if (screen->grman->num_of_below_refs) {
draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_below_refs);
bind_program(CELL_BG_PROGRAM);
// Draw background for non-default bg cells
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 2);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
} else {
// Apply background_opacity
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 0);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
}
bind_program(CELL_BG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
if (screen->grman->num_of_negative_refs) {
draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_below_refs, screen->grman->num_of_negative_refs);
}
bind_program(CELL_SPECIAL_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
@@ -433,7 +467,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
bind_program(CELL_FG_PROGRAM);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs);
if (screen->grman->num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs + screen->grman->num_of_below_refs, screen->grman->num_of_positive_refs);
glDisable(GL_BLEND);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
@@ -459,11 +493,13 @@ set_cell_uniforms(float current_inactive_text_alpha, bool force) {
cell_uniform_data.gpploc = glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "inactive_text_alpha");
cell_uniform_data.cploc = glGetUniformLocation(program_id(CELL_PROGRAM), "inactive_text_alpha");
cell_uniform_data.cfploc = glGetUniformLocation(program_id(CELL_FG_PROGRAM), "inactive_text_alpha");
cell_uniform_data.amask_premult_loc = glGetUniformLocation(program_id(GRAPHICS_ALPHA_MASK_PROGRAM), "alpha_mask_premult");
#define S(prog, name, val, type) { bind_program(prog); glUniform##type(glGetUniformLocation(program_id(prog), #name), val); }
S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT, 1i);
S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT, 1i);
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT, 1i);
S(CELL_PROGRAM, dim_opacity, OPT(dim_opacity), 1f); S(CELL_FG_PROGRAM, dim_opacity, OPT(dim_opacity), 1f);
S(CELL_BG_PROGRAM, defaultbg, OPT(background), 1f);
#undef S
cell_uniform_data.constants_set = true;
}
@@ -525,7 +561,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
if (screen->grman->count) draw_cells_interleaved_premult(vao_idx, gvao_idx, screen, os_window);
else draw_cells_simple(vao_idx, gvao_idx, screen);
} else {
if (screen->grman->num_of_negative_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
if (screen->grman->num_of_negative_refs || screen->grman->num_of_below_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen);
else draw_cells_simple(vao_idx, gvao_idx, screen);
}
}

View File

@@ -23,7 +23,7 @@ match_commands = tuple(sorted(all_commands + ('exit', 'help', 'quit')))
def init_readline(readline):
try:
readline.read_init_file()
except EnvironmentError:
except OSError:
if not is_macos:
raise
if 'libedit' in readline.__doc__:

View File

@@ -255,6 +255,7 @@ destroy_os_window_item(OSWindow *w) {
}
Py_CLEAR(w->window_title); Py_CLEAR(w->tab_bar_render_data.screen);
if (w->offscreen_texture_id) free_texture(&w->offscreen_texture_id);
if (w->offscreen_framebuffer) free_framebuffer(&w->offscreen_framebuffer);
remove_vao(w->tab_bar_render_data.vao_idx);
remove_vao(w->gvao_idx);
free(w->tabs); w->tabs = NULL;
@@ -476,7 +477,7 @@ PYWRAP1(set_options) {
#define SS(name, dest, convert) { GA(name); dest = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
#define S(name, convert) SS(name, OPT(name), convert)
SS(kitty_mod, kitty_mod, PyLong_AsLong);
S(hide_window_decorations, PyObject_IsTrue);
S(hide_window_decorations, PyLong_AsUnsignedLong);
S(visual_bell_duration, parse_s_double_to_monotonic_t);
S(enable_audio_bell, PyObject_IsTrue);
S(focus_follows_mouse, PyObject_IsTrue);
@@ -499,6 +500,12 @@ PYWRAP1(set_options) {
S(terminal_select_modifiers, convert_mods);
S(click_interval, parse_s_double_to_monotonic_t);
S(resize_debounce_time, parse_s_double_to_monotonic_t);
S(mark1_foreground, color_as_int);
S(mark1_background, color_as_int);
S(mark2_foreground, color_as_int);
S(mark2_background, color_as_int);
S(mark3_foreground, color_as_int);
S(mark3_background, color_as_int);
S(url_color, color_as_int);
S(background, color_as_int);
S(foreground, color_as_int);
@@ -521,7 +528,9 @@ PYWRAP1(set_options) {
S(macos_thicken_font, PyFloat_AsFloat);
S(tab_bar_min_tabs, PyLong_AsUnsignedLong);
S(disable_ligatures, PyLong_AsLong);
S(force_ltr, PyObject_IsTrue);
S(resize_draw_strategy, PyLong_AsLong);
S(resize_in_steps, PyObject_IsTrue);
S(pointer_shape_when_grabbed, pointer_shape);
GA(tab_bar_style);
@@ -781,6 +790,7 @@ PYWRAP1(os_window_font_size) {
resize_screen(os_window, w->render_data.screen, true);
}
}
if (OPT(resize_in_steps)) os_window_update_size_increments(os_window);
}
return Py_BuildValue("d", os_window->font_sz_in_pts);
END_WITH_OS_WINDOW
@@ -807,6 +817,8 @@ PYWRAP1(patch_global_colors) {
P(active_border_color); P(inactive_border_color); P(bell_border_color);
if (configured) {
P(background); P(url_color);
P(mark1_background); P(mark1_foreground); P(mark2_background); P(mark2_foreground);
P(mark3_background); P(mark3_foreground);
}
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;

View File

@@ -26,8 +26,10 @@ typedef struct {
unsigned int scrollback_pager_history_size;
char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
color_type url_color, background, foreground, active_border_color, inactive_border_color, bell_border_color;
color_type mark1_foreground, mark1_background, mark2_foreground, mark2_background, mark3_foreground, mark3_background;
monotonic_t repaint_delay, input_delay;
bool focus_follows_mouse, hide_window_decorations;
bool focus_follows_mouse;
unsigned int hide_window_decorations;
bool macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
unsigned int macos_option_as_alt;
float macos_thicken_font;
@@ -41,7 +43,9 @@ typedef struct {
Edge tab_bar_edge;
unsigned long tab_bar_min_tabs;
DisableLigature disable_ligatures;
bool force_ltr;
ResizeDrawStrategy resize_draw_strategy;
bool resize_in_steps;
bool sync_to_monitor;
bool close_on_child_death;
bool window_alert_on_bell;
@@ -127,6 +131,7 @@ typedef struct {
typedef struct {
void *handle;
id_type id;
uint32_t offscreen_framebuffer;
OSWindowGeometry before_fullscreen;
int viewport_width, viewport_height, window_width, window_height;
double viewport_x_ratio, viewport_y_ratio;
@@ -214,9 +219,10 @@ ssize_t create_graphics_vao(void);
ssize_t create_border_vao(void);
bool send_cell_data_to_gpu(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *);
void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *, bool, bool);
void draw_centered_alpha_mask(ssize_t gvao_idx, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas);
void draw_centered_alpha_mask(OSWindow *w, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas);
void update_surface_size(int, int, uint32_t);
void free_texture(uint32_t*);
void free_framebuffer(uint32_t*);
void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool);
void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
void blank_canvas(float, color_type);
@@ -225,7 +231,7 @@ void set_titlebar_color(OSWindow *w, color_type color);
FONTS_DATA_HANDLE load_fonts_data(double, double, double);
void send_prerendered_sprites_for_window(OSWindow *w);
#ifdef __APPLE__
void get_cocoa_key_equivalent(int, int, unsigned short*, int*);
void get_cocoa_key_equivalent(int, int, char *key, size_t key_sz, int*);
typedef enum {
PREFERENCES_WINDOW = 1,
NEW_OS_WINDOW = 2,
@@ -245,3 +251,4 @@ void remove_main_loop_timer(id_type timer_id);
void update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled);
void run_main_loop(tick_callback_fun, void*);
void stop_main_loop(void);
void os_window_update_size_increments(OSWindow *window);

View File

@@ -263,7 +263,7 @@ class Tab: # {{{
def new_window(
self, use_shell=True, cmd=None, stdin=None, override_title=None,
cwd_from=None, cwd=None, overlay_for=None, env=None, location=None,
copy_colors_from=None, allow_remote_control=False
copy_colors_from=None, allow_remote_control=False, marker=None
):
child = self.launch_child(
use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env, allow_remote_control=allow_remote_control)
@@ -275,6 +275,12 @@ class Tab: # {{{
# Must add child before laying out so that resize_pty succeeds
get_boss().add_child(window)
self._add_window(window, location=location)
if marker:
try:
window.set_marker(marker)
except Exception:
import traceback
traceback.print_exc()
return window
def new_special_window(self, special_window, location=None, copy_colors_from=None, allow_remote_control=False):
@@ -554,9 +560,11 @@ class TabManager: # {{{
self.set_active_tab_idx((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs))
def goto_tab(self, tab_num):
if 0 <= tab_num < len(self.tabs):
if tab_num >= len(self.tabs):
tab_num = max(0, len(self.tabs) - 1)
if tab_num >= 0:
self.set_active_tab_idx(tab_num)
elif tab_num < 0:
else:
try:
old_active_tab_id = self.active_tab_history[tab_num]
except IndexError:
@@ -609,17 +617,26 @@ class TabManager: # {{{
self._set_active_tab(nidx)
self.mark_tab_bar_dirty()
def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False, empty_tab=False):
nidx = self.active_tab_idx + 1
def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False, empty_tab=False, location='last'):
idx = len(self.tabs)
orig_active_tab_idx = self.active_tab_idx
self._add_tab(Tab(self, no_initial_window=True) if empty_tab else Tab(self, special_window=special_window, cwd_from=cwd_from))
self._set_active_tab(idx)
if len(self.tabs) > 2 and as_neighbor and idx != nidx:
for i in range(idx, nidx, -1):
self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]
swap_tabs(self.os_window_id, i, i-1)
self._set_active_tab(nidx)
idx = nidx
if as_neighbor:
location = 'after'
if location == 'neighbor':
location = 'after'
if len(self.tabs) > 1 and location != 'last':
if location == 'first':
desired_idx = 0
else:
desired_idx = orig_active_tab_idx + (0 if location == 'before' else 1)
if idx != desired_idx:
for i in range(idx, desired_idx, -1):
self.tabs[i], self.tabs[i-1] = self.tabs[i-1], self.tabs[i]
swap_tabs(self.os_window_id, i, i-1)
self._set_active_tab(desired_idx)
idx = desired_idx
self.mark_tab_bar_dirty()
return self.tabs[idx]

View File

@@ -109,7 +109,7 @@ def update_check(timer_id=None):
kitty_exe(), '+runpy',
'from kitty.update_check import run_worker; run_worker()'
], stdout=subprocess.PIPE)
except EnvironmentError as e:
except OSError as e:
log_error('Failed to run kitty for update check, with error: {}'.format(e))
return False
monitor_pid(p.pid)

View File

@@ -260,10 +260,10 @@ class startup_notification_handler:
def remove_socket_file(s, path=None):
with suppress(EnvironmentError):
with suppress(OSError):
s.close()
if path:
with suppress(EnvironmentError):
with suppress(OSError):
os.unlink(path)
@@ -287,7 +287,7 @@ def single_instance_unix(name):
fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_CLOEXEC)
try:
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except EnvironmentError as err:
except OSError as err:
if err.errno in (errno.EAGAIN, errno.EACCES):
# Client
s = socket.socket(family=socket.AF_UNIX)
@@ -298,7 +298,7 @@ def single_instance_unix(name):
s = socket.socket(family=socket.AF_UNIX)
try:
s.bind(socket_path)
except EnvironmentError as err:
except OSError as err:
if err.errno in (errno.EADDRINUSE, errno.EEXIST):
os.unlink(socket_path)
s.bind(socket_path)
@@ -322,7 +322,7 @@ def single_instance(group_id=None):
addr = '\0' + name
try:
s.bind(addr)
except EnvironmentError as err:
except OSError as err:
if err.errno == errno.ENOENT:
return single_instance_unix(name)
if err.errno == errno.EADDRINUSE:

View File

@@ -18,11 +18,11 @@ from .fast_data_types import (
BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM,
CELL_SPECIAL_PROGRAM, CSI, DCS, DECORATION, DIM,
GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM,
OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, Screen,
add_window, cell_size_for_window, compile_program, get_clipboard_string,
init_cell_program, set_clipboard_string, set_titlebar_color,
set_window_render_data, update_window_title, update_window_visibility,
viewport_for_window
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, Screen, add_window, cell_size_for_window, compile_program,
get_clipboard_string, init_cell_program, set_clipboard_string,
set_titlebar_color, set_window_render_data, update_window_title,
update_window_visibility, viewport_for_window
)
from .keys import defines, extended_key_event, keyboard_mode_name
from .rgb import to_color
@@ -72,6 +72,8 @@ def load_shader_programs(semi_transparent=False):
'STRIKE_SHIFT': STRIKETHROUGH,
'DIM_SHIFT': DIM,
'DECORATION_SHIFT': DECORATION,
'MARK_SHIFT': MARK,
'MARK_MASK': MARK_MASK,
}.items():
vv = vv.replace('{{{}}}'.format(gln), str(pyn), 1)
if semi_transparent:
@@ -133,7 +135,7 @@ class Window:
def __init__(self, tab, child, opts, args, override_title=None, copy_colors_from=None):
self.action_on_close = self.action_on_removal = None
self.layout_data = None
self.current_marker_spec = None
self.pty_resized_once = False
self.needs_attention = False
self.override_title = override_title
@@ -535,6 +537,12 @@ class Window:
cmd = [x.replace('INPUT_LINE_NUMBER', str(data['input_line_number'])) for x in self.opts.scrollback_pager]
get_boss().display_scrollback(self, data['text'], cmd)
def paste_bytes(self, text):
# paste raw bytes without any processing
if isinstance(text, str):
text = text.encode('utf-8')
self.screen.paste_bytes(text)
def paste(self, text):
if text and not self.destroyed:
if isinstance(text, str):
@@ -597,4 +605,32 @@ class Window:
def scroll_end(self):
if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_FULL, False)
def toggle_marker(self, ftype, spec, flags):
from .marks import marker_from_spec
key = ftype, spec
if key == self.current_marker_spec:
self.remove_marker()
return
self.screen.set_marker(marker_from_spec(ftype, spec, flags))
self.current_marker_spec = key
def set_marker(self, spec):
from .config import toggle_marker, parse_marker_spec
from .marks import marker_from_spec
if isinstance(spec, str):
func, (ftype, spec, flags) = toggle_marker('toggle_marker', spec)
else:
ftype, spec, flags = parse_marker_spec(spec[0], spec[1:])
key = ftype, spec
self.screen.set_marker(marker_from_spec(ftype, spec, flags))
self.current_marker_spec = key
def remove_marker(self):
if self.current_marker_spec is not None:
self.screen.set_marker()
self.current_marker_spec = None
def scroll_to_mark(self, prev=True, mark=0):
self.screen.scroll_to_next_mark(mark, prev)
# }}}

View File

@@ -238,6 +238,9 @@ class TestDataTypes(BaseTest):
lf.set_text(t, 0, len(t), C())
return lf
l0 = create('file:///etc/test')
self.ae(l0.url_start_at(0), 0)
for trail in '.,]>)\\':
lx = create("http://xyz.com" + trail)
self.ae(lx.url_end_at(0), len(lx) - 2)

View File

@@ -2,10 +2,12 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from . import BaseTest
from kitty.config import defaults
from kitty.layout import Stack, Horizontal, idx_for_id
from kitty.fast_data_types import pt_to_px
from kitty.layout import Horizontal, Stack, Tall, Grid, idx_for_id
from kitty.constants import WindowGeometry
from . import BaseTest
class Window:
@@ -15,6 +17,7 @@ class Window:
self.overlay_for = overlay_for
self.overlay_window_id = overlay_window_id
self.is_visible_in_layout = True
self.geometry = WindowGeometry(0, 0, 0, 0, 0, 0)
def set_visible_in_layout(self, idx, val):
self.is_visible_in_layout = bool(val)
@@ -172,11 +175,11 @@ class TestLayout(BaseTest):
check_visible()
def test_layout_operations(self):
for layout_class in Stack, Horizontal:
for layout_class in (Stack, Horizontal, Tall, Grid):
q = create_layout(layout_class)
self.do_ops_test(q)
def test_overlay_layout_operations(self):
for layout_class in Stack, Horizontal:
for layout_class in (Stack, Horizontal, Tall, Grid):
q = create_layout(layout_class)
self.do_overlay_test(q)

View File

@@ -4,6 +4,7 @@
from . import BaseTest
from kitty.fast_data_types import DECAWM, IRM, Cursor, DECCOLM, DECOM
from kitty.marks import marker_from_regex, marker_from_function
class TestScreen(BaseTest):
@@ -453,3 +454,37 @@ class TestScreen(BaseTest):
self.ae(as_text(), 'ababababab\nc\n\n')
self.ae(as_text(True), 'ababababab\nc\n\n')
def test_user_marking(self):
s = self.create_screen()
s.draw('abaa')
s.carriage_return(), s.linefeed()
s.draw('xyxyx')
s.set_marker(marker_from_regex('a', 3))
self.ae(s.marked_cells(), [(0, 0, 3), (2, 0, 3), (3, 0, 3)])
s.set_marker()
self.ae(s.marked_cells(), [])
def mark_x(text):
col = 0
for i, c in enumerate(text):
if c == 'x':
col += 1
yield i, i, col
s.set_marker(marker_from_function(mark_x))
self.ae(s.marked_cells(), [(0, 1, 1), (2, 1, 2), (4, 1, 3)])
s = self.create_screen(lines=5, scrollback=10)
for i in range(15):
s.draw(str(i))
if i != 14:
s.carriage_return(), s.linefeed()
s.set_marker(marker_from_regex(r'\d+', 3))
for i in range(10):
self.assertTrue(s.scroll_to_next_mark())
self.ae(s.scrolled_by, i + 1)
self.ae(s.scrolled_by, 10)
for i in range(10):
self.assertTrue(s.scroll_to_next_mark(0, False))
self.ae(s.scrolled_by, 10 - i - 1)
self.ae(s.scrolled_by, 0)

View File

@@ -17,7 +17,7 @@ def abspath(x):
def run(*args):
try:
subprocess.check_call(args)
except EnvironmentError:
except OSError:
raise SystemExit('You are missing the {} program needed to generate the kitty logo'.format(args[0]))

View File

@@ -333,14 +333,12 @@ SPECIAL_SOURCES = {
def newer(dest, *sources):
try:
dtime = os.path.getmtime(dest)
except EnvironmentError:
except OSError:
return True
for s in sources:
try:
with suppress(FileNotFoundError):
if os.path.getmtime(s) >= dtime:
return True
except FileNotFoundError:
pass
return False

View File

@@ -17,9 +17,9 @@ if False:
os.makedirs(dest)
os.chdir(dest)
dest = os.path.expanduser('~/.local/bin/kitty')
with suppress(EnvironmentError):
with suppress(OSError):
os.remove(dest)
with suppress(EnvironmentError):
with suppress(OSError):
os.makedirs(os.path.dirname(dest))
subprocess.check_call(['tar', 'xJf', tarball])
os.symlink(os.path.abspath('bin/kitty'), dest)