Compare commits

..

216 Commits

Author SHA1 Message Date
Kovid Goyal
804f72b31a Get monotonic.h to build on older linux distros 2019-11-27 09:55:00 +05:30
Kovid Goyal
e106dcc83b Dont use -Wfloat-conversion on old compilers 2019-11-27 09:27:23 +05:30
Kovid Goyal
9347be639b version 0.15.0 2019-11-27 09:23:01 +05:30
Kovid Goyal
ffc0c42a63 Merge branch 'faq-font-clarification' of https://github.com/ricci/kitty 2019-11-27 07:31:51 +05:30
Robert Ricci
f5e855b3ad Direct user to rebuild fc-cache in font FAQ
The configuration file that's suggested to force a font to
show up as monospace is very helpful, but it appears you may
need to force fontconfig to rebuild the cache in order for the
change to take effect.
2019-11-26 12:02:23 -07:00
Kovid Goyal
9af4dc4bff Merge branch 'redirect' of https://github.com/Luflosi/kitty 2019-11-26 22:13:51 +05:30
Luflosi
6f6180c34a Change URL to avoid redirect
Since `https://sw.kovidgoyal.net/kitty` redirects to `https://sw.kovidgoyal.net/kitty/`, it would be better to directly use the second URL.
2019-11-26 17:23:21 +01:00
Kovid Goyal
6ddd751713 Merge branch 'simplify' of https://github.com/Luflosi/kitty 2019-11-26 21:09:40 +05:30
Luflosi
2c72b06012 Simplify conditions 2019-11-26 16:06:16 +01:00
Kovid Goyal
88854c1aa4 Merge branch 'fix_stdin_source' of https://github.com/miseran/kitty 2019-11-26 06:47:21 +05:30
Reto Schnyder
5ad704469f Fix condition in stdin selection for launch 2019-11-25 20:10:18 +01:00
Kovid Goyal
c140e17a6f Merge branch 'undo_commenting_out' of https://github.com/Luflosi/kitty 2019-11-24 18:08:14 +05:30
Luflosi
4fc733a9c8 Comment commented out code back in
This piece of code is not run when the `GLFW_COCOA_MENUBAR` init hint is set to `0`, which kitty does.
This reduces the difference to GLFW upstream a little.
2019-11-24 13:29:06 +01:00
Kovid Goyal
c5c59e4c55 Merge branch 'return_type' of https://github.com/Luflosi/kitty 2019-11-24 17:56:26 +05:30
Luflosi
e0edef6719 Change return type of two functions
`on_application_reopen()` and `intercept_cocoa_fullscreen()` both only have two possible return values and can therefore have a return type of `bool`.
2019-11-24 13:06:46 +01:00
Kovid Goyal
0f46ce49e2 Merge branch 'create_menu_bar_in_callback' of https://github.com/Luflosi/kitty 2019-11-24 13:27:35 +05:30
Luflosi
f9fd39b8c1 Create macOS menu bar where GLFW creates it
GLFW creates the menu bar in the applicationWillFinishLaunching method, while kitty creates it in `create_os_window()`. This patch changes the behaviour to match GLFW.
In practice, without this change, there can be a short time where the menu bar is not fully populated.
2019-11-24 08:43:01 +01:00
Kovid Goyal
bd75157428 macOS: Disable mouse hiding by default as getting it to work robustly on Cocoa is too much effort
See #2158
2019-11-24 11:29:23 +05:30
Kovid Goyal
f987f3ba77 Merge branch 'remove_obsolete_comment' of https://github.com/Luflosi/kitty 2019-11-24 08:09:14 +05:30
Luflosi
1c16806efd Remove obsolete comment
In bef9490fa8, `unjam_event_loop()` was removed but not the corresponding comment.
2019-11-23 17:51:38 +01:00
Kovid Goyal
29155fef23 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-23 18:59:07 +05:30
Luflosi
c05cbed5b2 Reduce difference to GLFW upstream 2019-11-23 13:19:06 +01:00
Kovid Goyal
4f85d4db71 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-22 20:16:22 +05:30
Luflosi
468468ab9f Wayland: Don’t update cursor position in the frame
From upstream: a80788c17f.
2019-11-22 12:38:56 +01:00
Kovid Goyal
1b4a6da577 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-22 07:11:14 +05:30
Luflosi
aed7a197c7 Wayland: Don’t reload the cursor on every pointer motion
From upstream: a9f674e719.
2019-11-22 02:35:14 +01:00
Kovid Goyal
c257b7ecfd Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-22 07:00:04 +05:30
Luflosi
d6072f6218 X11: Assume 96 DPI if RandR monitor size is zero
From upstream: e96dc5d219.
2019-11-22 02:07:34 +01:00
Kovid Goyal
4ea72a2892 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-22 06:34:42 +05:30
Luflosi
89b07a1c6b Cocoa: Fix full screen window iconification
From upstream: 2c519709be.
2019-11-22 01:56:55 +01:00
Kovid Goyal
db0cd61807 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-22 06:17:47 +05:30
Luflosi
6feb906953 Cocoa: Cleanup
From upstream: bd452016be.
2019-11-21 18:30:03 +01:00
Kovid Goyal
7cbf8c63bc Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-11-21 20:00:30 +05:30
Luflosi
fae51d4e00 Cocoa: Add comments for Cocoa symbol macros
From upstream: 94cb0347ab.
2019-11-21 14:40:34 +01:00
Kovid Goyal
c510584afd Dont hang trying to read shell environment if the shell never quits. Fixes #2143 2019-11-20 11:53:28 +05:30
Kovid Goyal
738878c2ff unicode input kitten: Add an option :option:kitty +kitten unicode_input --emoji-variation to control the presentation variant of selected emojis
Fixes #2139
2019-11-17 13:13:37 +05:30
Kovid Goyal
d5682fe49a Fix #2137 2019-11-17 08:35:23 +05:30
Kovid Goyal
d8dd22048a Wayland: Ensure that glfw window content scale and kitty window content scale are in sync after the window is placed on a monitor 2019-11-16 10:47:05 +05:30
Kovid Goyal
737d8b96b6 Wayland: Dont bother with creating a temporary window anyway, as it is useless 2019-11-16 10:27:01 +05:30
Kovid Goyal
c8627c9713 Expand user/env vars in csutomize processing path and note that absolute paths can be used in the docs for it. 2019-11-16 10:05:25 +05:30
Kovid Goyal
65fa7da24a Wayland: Use the primary monitor scale for windows on creation
This is needed because on creation the window may not have been assigned
to any monitors, so we fallback to using the scale of the primary
monitor. Fixes #2133. Fixes #2135
2019-11-16 10:00:41 +05:30
Kovid Goyal
c4c6ef799f Dont shadow data 2019-11-13 19:47:08 +05:30
Kovid Goyal
0d18b91895 Grammar 2019-11-13 19:45:48 +05:30
Kovid Goyal
42089aaadc typo 2019-11-13 19:45:28 +05:30
Kovid Goyal
432d0f3318 hints kitten: Allow passing extra CLI args when using --customize-procesing 2019-11-13 17:27:55 +05:30
Kovid Goyal
4ade6587e8 spelling 2019-11-13 16:57:11 +05:30
Kovid Goyal
1724f11429 Deprecate pipe in favor of launch 2019-11-13 16:08:48 +05:30
Kovid Goyal
75d66d1048 Changelog entry for launch 2019-11-13 15:44:57 +05:30
Kovid Goyal
feccef67b9 Document the new launch action 2019-11-13 15:43:38 +05:30
Kovid Goyal
a7d30a199d location should also apply to tab creation 2019-11-13 15:40:46 +05:30
Kovid Goyal
37bbfb11d3 Fix location=neighbor causing a hang on a new tab 2019-11-13 15:39:38 +05:30
Kovid Goyal
79c80b4043 Fix doc generation 2019-11-13 15:03:02 +05:30
Kovid Goyal
18a1402ae2 Remote control interface for launch 2019-11-13 15:00:13 +05:30
Kovid Goyal
5d639b91b0 Switch OS windows when focusing on platforms where it is supported 2019-11-13 14:59:49 +05:30
Kovid Goyal
080c09d22e DRYer 2019-11-13 14:41:45 +05:30
Kovid Goyal
00c2670039 Fix handing of --stdin-source and getting of active window 2019-11-13 14:39:44 +05:30
Kovid Goyal
f113184165 Micro-optimization 2019-11-13 14:39:06 +05:30
Kovid Goyal
d69f8af107 Add a --keep-focus option for launch 2019-11-13 11:48:30 +05:30
Kovid Goyal
ad2bd1bf63 Allow using @selection in the launch cmdline 2019-11-13 10:46:04 +05:30
Kovid Goyal
059786ab66 Implement piping for launch 2019-11-13 10:38:58 +05:30
Kovid Goyal
72dfa5e5e6 Allow creating overlays via launch 2019-11-13 10:14:32 +05:30
Kovid Goyal
d84da6b7b8 Documentation placeholder for the new launch interface 2019-11-13 10:09:43 +05:30
Kovid Goyal
6aa82d82ad Start work on a unified interface for launching processes 2019-11-13 09:58:28 +05:30
Kovid Goyal
a649eb2a48 Coalesce neighboring symbol maps for better lookup performance 2019-11-12 20:05:48 +05:30
Kovid Goyal
83932c8099 Add a remote control command for detach_tab 2019-11-12 15:03:58 +05:30
Kovid Goyal
95e36cece1 Add a detach_window remote control command 2019-11-12 13:04:28 +05:30
Kovid Goyal
7075a7f1c0 Only detach window after the ask overlay is removed 2019-11-12 10:59:36 +05:30
Kovid Goyal
3093610d26 Allow choosing new os window/tab during detach ask 2019-11-12 10:42:07 +05:30
Kovid Goyal
86eed7f895 Speed up taking over of tab 2019-11-12 10:38:57 +05:30
Kovid Goyal
5dc14c5861 Copy cwd when taking over tab 2019-11-12 10:24:37 +05:30
Kovid Goyal
cab87f6fb2 Ensure Tab has cwd even with no initial window 2019-11-12 10:18:42 +05:30
Kovid Goyal
fb3acd1f4a Add a detach_tab command 2019-11-12 10:07:44 +05:30
Kovid Goyal
b247cef85c ... 2019-11-12 08:46:41 +05:30
Kovid Goyal
70445f33e9 Another typo 2019-11-12 06:30:21 +05:30
Kovid Goyal
cadb4f1389 ... 2019-11-12 06:29:55 +05:30
Kovid Goyal
bf75a4de61 Also document debug() 2019-11-12 06:25:27 +05:30
Kovid Goyal
400ab584ac hints kitten: Allow completely customizing the matching and actions performed by the kitten using your own script
Fixes #2124
2019-11-11 21:26:42 +05:30
Kovid Goyal
9edad62144 hints kitten: Allow using named groups in the regular expression. The named groups are passed to the invoked program for further processing. 2019-11-11 17:34:19 +05:30
Kovid Goyal
e94205098f Fix link 2019-11-11 16:00:35 +05:30
Kovid Goyal
35ab7eb472 Add a new action :ref:detach_window that can be used to move the current window into a different tab
Fixes #1310
2019-11-11 15:47:48 +05:30
Kovid Goyal
18619b14ac Merge branch 'simplify' of https://github.com/Luflosi/kitty 2019-11-10 23:35:27 +05:30
Luflosi
97e0b9b65a Simplify code 2019-11-10 16:51:25 +01:00
Kovid Goyal
5175d9f4a6 Properly re-initialize GPU resources when detaching/attaching a window 2019-11-09 13:14:50 +05:30
Kovid Goyal
a5aed0f4ab Forgot to change tab_ref when attaching window 2019-11-09 11:23:04 +05:30
Kovid Goyal
14d693a8ff When attaching window make its parent tab active 2019-11-09 11:09:29 +05:30
Kovid Goyal
645963cc24 Cleanup tab after detaching window 2019-11-09 09:42:17 +05:30
Kovid Goyal
20751c2807 Start work on a detach_window command 2019-11-09 09:20:38 +05:30
Kovid Goyal
dda886b97c Merge branch 'caps_lock_modifier' of https://github.com/Luflosi/kitty 2019-11-09 08:31:59 +05:30
Luflosi
b796ceecdd Add Caps Lock to the list of modifier keys
When a key is pressed and `mouse_hide_wait` is less than zero, kitty will hide the mouse cursor. When a key is pressed, kitty will also scroll the history to the bottom. Both of these things don't happen if the key being pressed was a modifier key. Both of these things should not happen when Caps Lock is pressed, so this key should be added to the list of modifier keys.
`is_modifier_key()` is not used for anything else.
2019-11-09 00:41:12 +01:00
Kovid Goyal
4def89ebb5 Add a note on debugging custom kittens 2019-11-08 16:53:26 +05:30
Kovid Goyal
bd1c1839dd Add API to boss.py to move a window into a different tab 2019-11-08 16:18:52 +05:30
Kovid Goyal
8739f698f9 When attaching window and font sizes changes, resize Screen 2019-11-08 14:58:08 +05:30
Kovid Goyal
b8c8482f20 Better fix for Powerline lacking a space glyph
This does not require increasing the size of the cache key, thereby
improving cache lookup performance
2019-11-08 14:41:31 +05:30
Kovid Goyal
4348ca8cc5 Remove unused function 2019-11-08 14:29:41 +05:30
Kovid Goyal
d1aa59080f Add an API to attach and detach windows from tabs 2019-11-08 14:22:02 +05:30
Kovid Goyal
bf0ffa80be Merge branch 'feature-window-to-tab' of https://github.com/0x17de/kitty
Currently only merged the function to move internal window state.
Want to refactor the changes to tabs.py and boss.py to make them more
orthogonal.
2019-11-08 09:55:51 +05:30
Kovid Goyal
2a89a0c87e Merge branch 'master' of https://github.com/martinpelikan/kitty 2019-11-08 07:57:47 +05:30
Kovid Goyal
de84489a20 Fix a regression in 0.14.5 that caused rendering of private use glyphs with and without spaces to be identical
Fixes #2117
2019-11-08 07:55:41 +05:30
Martin Pelikan
40901778ad Update fish completion docs for fish 3.0.0
As of 3.0.0, fish sources `kitty +complete fish` by default.
2019-11-07 18:11:47 -08:00
Kovid Goyal
c1febea659 Merge branch 'restore_error_reporting' of https://github.com/Luflosi/kitty 2019-11-05 07:07:03 +05:30
Luflosi
b42f4ac288 Restore proper error reporting functionality
In 841c907efc, the `%m` format specifier was replaced by `%%m`, which just prints `%m`. To keep the functionality of `%m` without using `%m`, the error string can be printed manually.
2019-11-05 00:42:29 +01:00
Kovid Goyal
fae57a5868 Website: Add a scrollbar to the sidebar if needed 2019-11-04 18:05:08 +05:30
Kovid Goyal
7a02d09508 Merge branch 'master' of https://github.com/CD3/kitty 2019-11-01 23:23:22 +05:30
C.D. Clark III
8ec6fe960d docs: added '@selection' to pipe documentation. 2019-11-01 08:46:49 -05:00
Kovid Goyal
61a77e19ef X11: Fix content scale fallback value on KDE
From upstream: 75294462b3
2019-10-31 09:10:01 +05:30
Kovid Goyal
82e88b54c7 X11: Fix decoration enabling after window creation
From upstream: 5fc4c01302
2019-10-31 09:04:45 +05:30
Kovid Goyal
ab8975f4da Merge branch 'with_suppress' of https://github.com/Luflosi/kitty 2019-10-29 10:17:19 +05:30
Luflosi
5e7fd21c8d Use "with suppress()" to suppress a python exception 2019-10-28 18:35:21 +01:00
Kovid Goyal
30cd9b2ae4 Merge branch 'fix_two_linker_error' of https://github.com/Luflosi/kitty 2019-10-28 07:31:49 +05:30
Luflosi
4f7d12f3fd Fix error when two linker processes fail
When a linker process fails, `failed` will be set to a value other than `None`. When a second linker process fails, the `else` case will be taken because `failed is None`, which executes `compile_cmd.on_success()`. This function tries to rename or move the file generated by the linker but since the linker process failed, the file will most likely not exist. This will throw an error, which will prevent printing the actual error message from the linker.
2019-10-27 21:50:22 +01:00
Kovid Goyal
62da158823 Merge branch 'fix_potential_error' of https://github.com/Luflosi/kitty 2019-10-27 07:42:21 +05:30
Luflosi
2d19b87208 Fix potential error when $PATH is not defined
When `PATH` is not in `os.environ`, kitty could potentially throw a `KeyError`.
2019-10-27 00:10:55 +02:00
Kovid Goyal
54ca775006 Merge branch 'visual' of https://github.com/toonn/kitty 2019-10-26 09:10:15 +05:30
toonn
a64a0d64cb Prefer VISUAL to EDITOR
Fix #2096
2019-10-25 18:52:17 +02:00
Kovid Goyal
6634dc81fe Merge branch 'simplify' of https://github.com/Luflosi/kitty 2019-10-25 07:45:30 +05:30
Luflosi
a2d5eef398 Simplify access to os.environ
The `if` can be removed by using `get()` with a default parameter to access `os.environ`. This also reduces the number of accesses to `os.environ`.
2019-10-24 20:27:27 +02:00
Kovid Goyal
8184ba246a Load libcanberra dynamically at runtime, as needed
Fixes #2089
2019-10-24 09:03:52 +05:30
Kovid Goyal
54ad1d103a Fix #2087 2019-10-23 18:21:34 +05:30
Kovid Goyal
d92f8a90ef Allow changing colors by mapping a keyboard shortcut to read a kitty config file with color definitions.
Fixes #2083
2019-10-21 14:43:35 +05:30
Kovid Goyal
d33c287344 typo 2019-10-21 14:23:07 +05:30
Kovid Goyal
782eebf976 typo 2019-10-21 14:21:09 +05:30
Kovid Goyal
0fbf75b95a Allow specifying that remote control only works over a socket 2019-10-21 14:20:30 +05:30
Kovid Goyal
5781facf4b Merge branch 'fix_icat' of https://github.com/Luflosi/kitty 2019-10-21 07:32:40 +05:30
Luflosi
b710ffc403 Fix icat kitten
Fixes https://github.com/kovidgoyal/kitty/issues/2081.
This bug was introduced in 10e5fcc375.

One place in the code requires the actual `screen_size_function`, not just the result of it.
2019-10-20 21:37:14 +02:00
Kovid Goyal
7d8edccb04 Merge branch 'pending_scroll_pixels_per_screen' of https://github.com/Luflosi/kitty 2019-10-20 16:58:28 +05:30
Luflosi
3750d70173 Store pending_scroll_pixels for every screen
Scrolling in one screen shouldn't affect the `pending_scroll_pixels` for another screen in the same OS window.
2019-10-20 13:22:46 +02:00
Kovid Goyal
025e0bb546 Merge branch 'var-names-consistency-wrt-scancode' of https://github.com/bew/kitty 2019-10-20 08:08:48 +05:30
Kovid Goyal
033367de91 Merge branch 'fix_line_drawing' of https://github.com/Luflosi/kitty 2019-10-19 06:26:57 +05:30
Luflosi
f93b605b75 Fix line drawing algorithm 2019-10-18 21:26:11 +02:00
Kovid Goyal
7b4141b0c8 Add a changelog entry for powerline tabbar 2019-10-18 19:38:08 +05:30
Kovid Goyal
90acef72dc Merge branch 'add_powerline_style_tabs' of https://github.com/brujoand/kitty 2019-10-18 19:36:31 +05:30
Kovid Goyal
43f200be77 Add an entry for the new box character rendering functions 2019-10-18 17:24:33 +05:30
Kovid Goyal
2fbb167803 Merge branch 'fix-triangle-symbol-scaling' of https://github.com/bew/kitty 2019-10-18 17:13:50 +05:30
Benoit de Chezelles
2b396262f0 Update glyph ids 2019-10-18 11:48:55 +02:00
Benoit de Chezelles
74deca52b9 Remove trailing whitespace 2019-10-18 11:39:51 +02:00
Benoit de Chezelles
058d956464 Merge branch 'master' into fix-triangle-symbol-scaling 2019-10-18 11:30:46 +02:00
brujoand
0f38dc1bd1 Adding powerline style tabs 2019-10-18 11:18:28 +02:00
Kovid Goyal
576b03c047 Implement drawing thick anti-aliased lines
And actually use it for the unicode diagonal box drawing chars and also
the powerline angle separators
2019-10-18 13:19:04 +05:30
Kovid Goyal
59cb1ad1e0 Implement special rendering for the diagonal line box drawing chars 2019-10-18 11:01:51 +05:30
Benoit de Chezelles
f77acdce14 remove useless import in help msg for box drawing debug 2019-10-18 06:08:45 +02:00
Benoit de Chezelles
43a98a1df1 Add missing association between char & glyph id 2019-10-18 06:08:20 +02:00
Benoit de Chezelles
d1cf771c14 Add other corner triangles & enable in C side 2019-10-18 05:45:13 +02:00
Benoit de Chezelles
10e5fcc375 Fix usage of icat function show
The `screen_size` function was not initialized yet
2019-10-18 04:54:14 +02:00
Benoit de Chezelles
2112932d04 Add corner triangle glyphs drawing 2019-10-18 04:48:03 +02:00
Benoit de Chezelles
be505ce6e0 Fix test functions for setup_for_testing's new call syntax 2019-10-18 04:47:04 +02:00
Benoit de Chezelles
4d6472128c Rename scancode to keycode for cocoa implementation 2019-10-17 20:41:48 +02:00
Kovid Goyal
26b7b1ec63 Merge branch 'typo' of https://github.com/Luflosi/kitty 2019-10-17 18:32:42 +05:30
Luflosi
9a8f0a9b5a Fix typo 2019-10-17 13:56:46 +02:00
Kovid Goyal
16dbbf3a1e Make count-lines-of-code a little nicer 2019-10-17 15:19:13 +05:30
Kovid Goyal
8492d0a264 Merge branch 'dont_cloc_parse-graphics-command.h' of https://github.com/Luflosi/kitty 2019-10-17 14:36:53 +05:30
Luflosi
d8f8f1f590 Exclude kitty/parse-graphics-command.h from the line count
This file is automatically generated and should not count towards the total line count.
2019-10-17 10:38:55 +02:00
Kovid Goyal
b762e81da0 Merge branch 'python_encoding_header' of https://github.com/Luflosi/kitty 2019-10-17 06:28:16 +05:30
Luflosi
8b3b68ea69 Unify python encoding headers
All the other python encoding headers are of the form `vim:fileencoding=utf-8`.
2019-10-16 20:34:56 +02:00
Benoit de Chezelles
28525766a5 Final changes before 'ready for review' 2019-10-16 02:47:14 +02:00
Benoit de Chezelles
d8fc23c38f remove outdated 'fixme' 2019-10-16 02:41:26 +02:00
Benoit de Chezelles
8cae70fa36 Update doc 2019-10-16 02:36:55 +02:00
Benoit de Chezelles
7f2b98fad7 Rename all uses of 'scancode' to 'native_key' where relevant 2019-10-16 02:25:20 +02:00
Benoit de Chezelles
2b6dde2ac5 Wish: rename scancode → native_key in glfw codebase 2019-10-16 02:00:24 +02:00
Benoit de Chezelles
0c254fa7c7 GLFWkeyevent: rename scancode → native_key 2019-10-16 02:00:24 +02:00
Kovid Goyal
fe7375becc Merge branch 'grammar' of https://github.com/Luflosi/kitty 2019-10-16 04:19:54 +05:30
Luflosi
b6ee88056e Minor grammar correction 2019-10-16 00:46:52 +02:00
Kovid Goyal
96f691ad64 Merge branch 'mark_parse-graphics-command.h_as_generated' of https://github.com/Luflosi/kitty 2019-10-16 03:31:32 +05:30
Luflosi
8cf3380a77 Mark kitty/parse-graphics-command.h as being generated 2019-10-15 18:16:28 +02:00
Kovid Goyal
e8d7e3e98b Merge branch 'remove_khrplatform.h_references' of https://github.com/Luflosi/kitty 2019-10-15 20:58:16 +05:30
Luflosi
8af0a8476f Remove remaining references to kitty/khrplatform.h
`kitty/khrplatform.h` was removed in f872f955b0.
2019-10-15 17:25:47 +02:00
Kovid Goyal
2de4248f76 Merge branch 'regenerate_glfw-wrapper' of https://github.com/Luflosi/kitty 2019-10-15 20:42:49 +05:30
Luflosi
8c73ab6afb Regenerate kitty/glfw-wrapper.h 2019-10-15 17:10:02 +02:00
Kovid Goyal
ee9bd77dc4 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-15 19:10:50 +05:30
Luflosi
81f31ca215 Reduce difference of glfw/glfw3.h to GLFW upstream 2019-10-15 15:37:35 +02:00
Kovid Goyal
51eff372f8 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-15 18:16:22 +05:30
Luflosi
087376b44d GLFW: Documentation work
From upstream: a43d1a4937.
2019-10-15 14:37:41 +02:00
Kovid Goyal
e49047ef7d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-15 18:02:17 +05:30
Luflosi
c117f36009 GLFW: Cleanup
From upstream: d5ab3e919a.
2019-10-15 14:25:46 +02:00
Kovid Goyal
5ec83ee759 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-15 16:17:49 +05:30
Luflosi
410165c14d GLFW: Improve (?) reference documentation for callbacks
From upstream: 51bb76c7c3.
2019-10-15 12:11:01 +02:00
Kovid Goyal
973ef9fcda Merge branch 'fix_nix_link' of https://github.com/Luflosi/kitty 2019-10-15 04:14:04 +05:30
Luflosi
396bfb8540 Fix link to Nix kitty package
Instead of a fragment identifier, the URL now needs a query parameter.
2019-10-14 18:48:21 +02:00
Kovid Goyal
aaf597bffd Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-13 09:08:15 +05:30
Luflosi
63c4f64bf5 Reduce difference to GLFW upstream
From upstream: 773f4495f0.
2019-10-12 15:19:44 +02:00
Kovid Goyal
5745dc34f7 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-12 17:58:50 +05:30
Luflosi
114b8cff93 Cleanup
From upstream: 62b7fe8311.
2019-10-11 20:26:27 +02:00
Kovid Goyal
261571bb69 Merge branch 'modern_objective-c' of https://github.com/Luflosi/kitty 2019-10-10 22:01:30 +05:30
Manuel Herrmann
497e2c9163 #1310 move windows into separate tabs 2019-10-10 17:13:34 +02:00
Luflosi
ac01ffaa54 Cocoa: Move slightly towards modern Objective-C 2019-10-10 14:36:52 +02:00
Kovid Goyal
eb9fe06d10 Merge branch 'correct_space' of https://github.com/Luflosi/kitty 2019-10-09 22:15:38 +05:30
Luflosi
506f2f7db7 Replace weird character with space
In 4c4c6ab0e6 you added a weird unicode character, which is apparently called an Object Replacement Character, instead of a space.
2019-10-09 18:32:49 +02:00
Kovid Goyal
f476c56f91 Drop support for the gtk_primary_selection wayland protocol
sway recently decided to start complaining when binding
wp_primary_selection_device_manager interface to gtk_primary_selection
name, which means we either have to maintain an in-tree version
of the gtk_primary_selection protocol or drop support for sway.

Sway has not quite annoyed me enough yet for the latter.

See #1977
2019-10-09 07:37:03 +05:30
Kovid Goyal
474688888d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-08 17:16:51 +05:30
Luflosi
7ddbb613fe Cocoa: Fix window creation blocking after re-init
From 2fbb560eb7.
2019-10-08 13:41:10 +02:00
Kovid Goyal
adc1c06089 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-08 12:12:00 +05:30
Luflosi
1f8631cc99 Cocoa: Move app delegate and menu creation to init
From ea7eb2ddab.
2019-10-07 21:24:58 +02:00
Kovid Goyal
f9e4e71285 Slightly better error message when opening controlling terminal fails 2019-10-07 20:48:31 +05:30
Kovid Goyal
5529194c8d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2019-10-06 20:39:25 +05:30
Luflosi
5b008163af Cocoa: Add NSApplicationDelegate protocol
From c3ed70a4b7.
2019-10-06 16:58:06 +02:00
Kovid Goyal
802da7087c diff kitten: Allow diffing remote files easily via ssh
See #727
2019-10-04 09:08:22 +05:30
Kovid Goyal
53ab7617ca Dont use space ligatures for symbol_map fonts
space ligatures breaks in font ligatures when there is a trailing space.
So only use them for PUA and cells using a fallback font. Fixes #2019
2019-10-02 07:10:51 +05:30
Kovid Goyal
4369255a25 When drawing unicode symbols that are followed by spaces, use multiple cells to avoid resized or cut-off glyphs
Fixes #1452
2019-10-01 19:56:07 +05:30
Kovid Goyal
b709ee6842 Add a function to check if a codepoint is a symbol 2019-10-01 18:57:06 +05:30
Kovid Goyal
4a8f9efe79 Wayland: Resume key repeat on focus gained if the previously repeated key is pressed 2019-09-30 18:47:43 +05:30
Kovid Goyal
19d36fbe69 Wayland: Fix key repeat not being stopped when focus leaves window. This is expected behavior on Wayland, apparently
Fixes #2014
2019-09-30 16:45:39 +05:30
Kovid Goyal
d37e73db54 Merge branch 'fix_broken_paste' of https://github.com/Luflosi/kitty
Fixes #2008
2019-09-29 08:02:31 +05:30
Luflosi
a8f6615380 Fix broken paste on X11
Fixes https://github.com/kovidgoyal/kitty/issues/2008.
2019-09-29 02:48:09 +02:00
Kovid Goyal
97fe38fbbc monotonic_start_time needs to be a global symbol, not local to each translation unit 2019-09-28 08:18:21 +05:30
Kovid Goyal
3378175450 Hopefully the last macOS float conversion fix 2019-09-27 20:14:05 +05:30
Kovid Goyal
9f60d2aea3 More macOS float conversion fixes 2019-09-27 20:11:32 +05:30
Kovid Goyal
46d49e18c0 More macOS float fixes 2019-09-27 20:07:09 +05:30
Kovid Goyal
43fc335be2 more macOS float fixes 2019-09-27 20:05:11 +05:30
Kovid Goyal
f68339c571 float conversion fixes for macOS 2019-09-27 19:56:48 +05:30
Kovid Goyal
0d68b7078c Check for float conversion issues on build
Useful to catch any errors left over from the migration of times from
double to int64_t
2019-09-27 19:47:25 +05:30
Kovid Goyal
2e71dde780 Merge branch 'monotonic_t' of https://github.com/Luflosi/kitty 2019-09-27 19:23:27 +05:30
Kovid Goyal
51ddd584d6 Merge branch 'mark_unicode_data_as_generated' of https://github.com/Luflosi/kitty 2019-09-27 09:21:29 +05:30
Luflosi
4b090259cd Mark kitty/unicode-data.c as being generated 2019-09-26 21:54:42 +02:00
Luflosi
f3b9ff5f9f Use datatype monotonic_t instead of double to keep track of time
The time is stored in a signed 64 bit integer with nanosecond accuracy. This eliminates the possibility of floating-point inaccuracies.
`monotonic_t` can currently hold values large enough to work correctly for more than 200 years into the future.
Using a typedef instead of directly using `int64_t` everywhere will also allow easily changing the datatype in the future should the need arise for more precise or bigger time values.
2019-09-25 17:43:11 +02:00
Kovid Goyal
4ec1a8d9c3 Merge branch 'extract-glfw-key-event-data' of https://github.com/bew/kitty 2019-09-25 15:44:56 +05:30
Benoit de Chezelles
aadab38487 Fix compilation for macos 2019-09-24 22:03:55 +02:00
Benoit de Chezelles
53275c9137 Extract key event data to struct 2019-09-24 19:15:35 +02:00
100 changed files with 3896 additions and 1405 deletions

4
.gitattributes vendored
View File

@@ -3,11 +3,11 @@ kitty/emoji.h linguist-generated=true
kitty/keys.h linguist-generated=true
kitty/charsets.c linguist-generated=true
kitty/key_encoding.py linguist-generated=true
kitty/unicode-data.c
kitty/unicode-data.c linguist-generated=true
kitty/rgb.py linguist-generated=true
kitty/gl-wrapper.* linguist-generated=true
kitty/khrplatform.h linguist-generated=true
kitty/glfw-wrapper.* linguist-generated=true
kitty/parse-graphics-command.h linguist-generated=true
glfw/*.c linguist-vendored=true
glfw/*.h linguist-vendored=true
kittens/unicode_input/names.h linguist-generated=true

View File

@@ -1,5 +1,5 @@
= kitty - the fast, featureful, GPU based, terminal emulator
See https://sw.kovidgoyal.net/kitty
See https://sw.kovidgoyal.net/kitty/
image:https://circleci.com/gh/kovidgoyal/kitty.svg?style=svg["Build status", link="https://circleci.com/gh/kovidgoyal/kitty"]

View File

@@ -92,8 +92,10 @@ def setup_openssl_environment():
if 'SSL_CERT_FILE' not in os.environ and 'SSL_CERT_DIR' not in os.environ:
if os.access('/etc/pki/tls/certs/ca-bundle.crt', os.R_OK):
os.environ['SSL_CERT_FILE'] = '/etc/pki/tls/certs/ca-bundle.crt'
sys.kitty_ssl_env_var = 'SSL_CERT_FILE'
elif os.path.isdir('/etc/ssl/certs'):
os.environ['SSL_CERT_DIR'] = '/etc/ssl/certs'
sys.kitty_ssl_env_var = 'SSL_CERT_DIR'
def main():

View File

@@ -1,2 +1,27 @@
#!/usr/bin/env bash
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h\nkittens/unicode_input/names.h') kitty kittens
#!/usr/bin/env python
import subprocess
files_to_exclude = '''\
kitty/wcwidth-std.h
kitty/glfw.c
kitty/keys.h
kitty/charsets.c
kitty/unicode-data.c
kitty/key_encoding.py
kitty/rgb.py
kitty/gl.h
kitty/gl-wrapper.h
kitty/gl-wrapper.c
kitty/glfw-wrapper.h
kitty/glfw-wrapper.c
kitty/emoji.h
kittens/unicode_input/names.h
kitty/parse-graphics-command.h
'''
p = subprocess.Popen([
'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens'
], stdin=subprocess.PIPE)
p.communicate(files_to_exclude.encode('utf-8'))
raise SystemExit(p.wait())

View File

@@ -78,6 +78,8 @@ body div.document {
div.sphinxsidebar {
font-size: inherit;
line-height: inherit;
max-height: 100%;
overflow-y: auto;
}
#sidebartoc li {

View File

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

View File

@@ -4,6 +4,56 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.15.0 [2019-11-27]
--------------------
- Add a new action :ref:`detach_window <detach_window>` that can be used to move the current
window into a different tab (:iss:`1310`)
- Add a new action :doc:`launch <launch>` that unifies launching of processes
in new kitty windows/tabs.
- Add a new style ``powerline`` for tab bar rendering, see :opt:`tab_bar_style` (:pull:`2021`)
- Allow changing colors by mapping a keyboard shortcut to read a kitty config
file with color definitions. See the :doc:`FAQ <faq>` for details
(:iss:`2083`)
- hints kitten: Allow completely customizing the matching and actions performed
by the kitten using your own script (:iss:`2124`)
- Wayland: Fix key repeat not being stopped when focus leaves window. This is
expected behavior on Wayland, apparently (:iss:`2014`)
- When drawing unicode symbols that are followed by spaces, use multiple cells
to avoid resized or cut-off glyphs (:iss:`1452`)
- diff kitten: Allow diffing remote files easily via ssh (:iss:`727`)
- unicode input kitten: Add an option :option:`kitty +kitten unicode_input
--emoji-variation` to control the presentation variant of selected emojis
(:iss:`2139`)
- Add specialised rendering for a few more box powerline and unicode symbols
(:pull:`2074` and :pull:`2021`)
- Add a new socket only mode for :opt:`allow_remote_control`. This makes
it possible for programs running on the local machine to control kitty
but not programs running over ssh.
- hints kitten: Allow using named groups in the regular expression. The named
groups are passed to the invoked program for further processing.
- Fix a regression in 0.14.5 that caused rendering of private use glyphs
with and without spaces to be identical (:iss:`2117`)
- Wayland: Fix incorrect scale used when first creating an OS window
(:iss:`2133`)
- macOS: Disable mouse hiding by default as getting it to work robustly
on Cocoa is too much effort (:iss:`2158`)
0.14.6 [2019-09-25]
---------------------

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim:fileencoding=utf-8
#
# Configuration file for the Sphinx documentation builder.
#
@@ -243,7 +243,17 @@ def add_html_context(app, pagename, templatename, context, *args):
# CLI docs {{{
def write_cli_docs(all_kitten_names):
from kitty.launch import options_spec as launch_options_spec
from kitty.cli import option_spec_as_rst
with open('generated/launch.rst', 'w') as f:
f.write(option_spec_as_rst(
appname='launch', ospec=launch_options_spec, heading_char='_',
message='''\
Launch an arbitrary program in a new kitty window/tab. Note that
if you specify a program-to-run you can use the special placeholder
:code:`@selection` which will be replaced by the current selection.
'''
))
with open('generated/cli-kitty.rst', 'w') as f:
f.write(option_spec_as_rst(appname='kitty').replace(
'kitty --to', 'kitty @ --to'))

View File

@@ -109,8 +109,13 @@ How do I change the colors in a running kitty instance?
You can either use the
`OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands>`_
to set colors or you can enable :doc:`remote control <remote-control>`
for |kitty| and use :ref:`at_set-colors`.
to set colors or you can define keyboard shortcuts to set colors, for example::
map f1 set_colors --configured /path/to/some/config/file/colors.conf
Or you can enable :doc:`remote control <remote-control>` for |kitty| and use :ref:`at_set-colors`.
The shortcut mapping technique has the same syntax as the remote control
command, for details, see :ref:`at_set-colors`.
A list of pre-made color themes for kitty is available at:
`kitty-themes <https://github.com/dexpota/kitty-themes>`_
@@ -184,6 +189,11 @@ following :file:`~/.config/fontconfig/fonts.conf`::
</match>
</fontconfig>
After creating (or modifying) this file, you may need to run the following
command to rebuild your fontconfig cache::
fc-cache -r
Then, the font will be available in ``kitty list-fonts``.

View File

@@ -59,7 +59,7 @@ See the :doc:`binary install instructions </binary>`. You can also
You can also use your favorite package manager to install the |kitty| package.
|kitty| packages are available for:
`macOS with Homebrew (Cask) <https://formulae.brew.sh/cask/kitty>`_,
`macOS and Linux with Nix <https://nixos.org/nixos/packages.html#kitty>`_,
`macOS and Linux with Nix <https://nixos.org/nixos/packages.html?channel=nixpkgs-unstable&query=kitty>`_,
`Ubuntu <https://launchpad.net/ubuntu/+source/kitty>`_,
`Debian <https://packages.debian.org/buster/kitty>`_,
`openSUSE <https://build.opensuse.org/package/show/X11:terminals/kitty>`_,
@@ -173,6 +173,19 @@ You can also define a shortcut to switch to the previously active window::
``nth_window`` will focus the nth window for positive numbers and the
previously active windows for negative numbers.
.. _detach_window:
You can define shortcuts to detach the current window and
move it to another tab or another OS window::
map ctrl+f2 detach_window # moves the window into a new OS window
map ctrl+f3 detach_window new-tab # moves the window into a new Tab
map ctrl+f4 detach_window ask # asks which tab to move the window into
Similarly, you can detach the current tab, with::
map ctrl+f2 detach_tab # moves the tab into a new OS window
map ctrl+f4 detach_tab ask # asks which OS Window to move the tab into
Other keyboard shortcuts
----------------------------------
@@ -402,7 +415,7 @@ comfortably within the pager.
Additionally, you can pipe the contents of the scrollback buffer to an
arbitrary, command running in a new window, tab or overlay, for example::
map f1 pipe @ansi window less +G -R
map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting less +G -R
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
key. See :sc:`show_scrollback` for details.
@@ -478,7 +491,8 @@ completions happens after the call to :file:`compinit`.
fish
~~~~~~~~
Add the following to your :file:`~/.config/fish/config.fish`
For versions of fish earlier than 3.0.0, add the following to your
:file:`~/.config/fish/config.fish`. Later versions source completions by default.
.. code-block:: sh

View File

@@ -128,7 +128,27 @@ layout, by simply adding the line::
boss.toggle_fullscreen()
to the ``handle_result()`` function, above.
To the ``handle_result()`` function, above.
Debugging kittens
--------------------
The part of the kitten that runs in ``main()`` is just a normal program and
the output of print statements will be visible in the kitten window. Or
alternately, you can use::
from kittens.tui.loop import debug
debug('whatever')
The ``debug()`` function is just like ``print()`` except that the output
will appear in the ``STDOUT`` of the kitty process inside which the kitten is
running.
The ``handle_result()`` part of the kitten runs inside the kitty process.
The output of print statements will go to the ``STDOUT`` of the kitty process.
So if you run kitty from another kitty instance, the output will be visible
in the first kitty instance.
.. _external_kittens:

View File

@@ -23,6 +23,56 @@ options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text
snippets. See :sc:`insert_selected_path` for examples.
Completely customizing the matching and actions of the kitten
---------------------------------------------------------------
The hints kitten supports writing simple python scripts that can be used to
completely customize how it finds matches and what happens when a match is
selected. This allows the hints kitten to provide the user interface, while
you can provide the logic for finding matches and performing actions on them.
This is best illustrated with an example. Create the file
:file:`custom-hints.py` in the kitty config directory with the following
contents:
.. code-block:: python
import re
def mark(text, args, Mark, extra_cli_args, *a):
# This function is responsible for finding all
# matching text. extra_cli_args are any extra arguments
# passed on the command line when invoking the kitten.
# We mark all individual word for potential selection
for idx, m in enumerate(re.finditer(r'\w+', text)):
start, end = m.span()
mark_text = text[start:end].replace('\n', '').replace('\0', '')
# The empty dictionary below will be available as groupdicts
# in handle_result() and can contain arbitrary data.
yield Mark(idx, start, end, mark_text, {})
def handle_result(args, data, target_window_id, boss, extra_cli_args, *a):
# This function is responsible for performing some
# action on the selected text.
# matches is a list of the selected entries and groupdicts contains
# the arbitrary data associated with each entry in mark() above
matches, groupdicts = [], []
for m, g in zip(data['match'], data['groupdicts']):
if m:
matches.append(m), groupdicts.append(g)
for word, match_data in zip(matches, groupdicts):
# Lookup the word in a dictionary, the open_url function
# will open the provided url in the system browser
boss.open_url(f'https://www.google.com/search?q=define:{word}')
Now run kitty with::
kitty -o 'map f1 kitten hints --customize-processing custom-hints.py'
When you press the :kbd:`F1` key you will be able to select a word to
look it up in the Google dictionary.
Command Line Interface
-------------------------

View File

@@ -22,3 +22,8 @@ In :guilabel:`Name` mode you instead type words from the character name and use
keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you don't like to use arrow
keys.
Command Line Interface
-------------------------
.. include:: ../generated/cli-kitten-unicode_input.rst

56
docs/launch.rst Normal file
View File

@@ -0,0 +1,56 @@
Launching programs in new windows/tabs
========================================
.. program:: launch
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
in news windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
It is very powerful and allows sending the contents of
the current window to the launched program, as well as many other options.
In the simplest form, you can use it to open a new kitty window running the
shell, as shown below::
map f1 launch
To run a different program simply pass the command line as arguments to
launch::
map f1 launch vim path/to/some/file
To open a new window with the same working directory as the currently
active window::
map f1 launch --cwd=current
To open the new window in a new tab::
map f1 launch --type=tab
To pass the contents of the current screen and scrollback to the started process::
map f1 launch --stdin-source=@screen_scrollback less
There are many more powerful options, refer to the complete list below.
The piping environment
--------------------------
When using :option:`launch --stdin-source`, the program to which the data is
piped has a special environment variable declared, ``KITTY_PIPE_DATA`` whose
contents are::
KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}
where ``scrolled_by`` is the number of lines kitty is currently scrolled by,
``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)``
being the top left corner and ``{lines},{columns}`` being the number of rows
and columns of the screen.
Syntax reference
------------------
.. include:: /generated/launch.rst

View File

@@ -1,6 +1,9 @@
Working with the screen and history buffer contents
======================================================
.. warning::
The pipe action has been deprecated in favor of the
:doc:`launch <launch>` action which is more powerful.
You can pipe the contents of the current screen and history buffer as
:file:`STDIN` to an arbitrary program using the ``pipe`` function. The program
@@ -56,6 +59,9 @@ Input placeholders
There are various different kinds of placeholders
``@selection``
Plain text, currently selected text
``@text``
Plain text, current screen + scrollback buffer

View File

@@ -126,7 +126,7 @@ If you do not want to allow all programs running in |kitty| to control it, you c
enable remote control for only some |kitty| windows. Simply create a shortcut
such as::
map ctrl+k new_window @ some_program
map ctrl+k launch --allow-remote-control some_program
Then programs running in windows created with that shortcut can use ``kitty @``
to control kitty. Note that any program with the right level of permissions can

View File

@@ -43,6 +43,7 @@ def get_data(fname, folder='UCD'):
# Map of class names to set of codepoints in class
class_maps = {}
all_symbols = set()
name_map = {}
word_search_map = defaultdict(set)
zwj = 0x200d
@@ -89,6 +90,8 @@ def parse_ucd():
not_assigned.discard(codepoint)
if category.startswith('M'):
marks.add(codepoint)
elif category.startswith('S'):
all_symbols.add(codepoint)
# Some common synonyms
word_search_map['bee'] |= word_search_map['honeybee']
@@ -194,6 +197,7 @@ def gen_emoji():
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
p('static inline bool\nis_emoji_modifier(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])):
@@ -203,6 +207,15 @@ def gen_emoji():
p('\t}')
p('\treturn false;\n}')
p('static inline bool\nis_symbol(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(all_symbols)):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()):
static = 'static inline ' if static else ''

45
glfw/backend_utils.c vendored
View File

@@ -22,19 +22,6 @@
#define ppoll pollts
#endif
static inline double
monotonic(void) {
struct timespec ts = {0};
#ifdef CLOCK_HIGHRES
clock_gettime(CLOCK_HIGHRES, &ts);
#elif CLOCK_MONOTONIC_RAW
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
#else
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
return (((double)ts.tv_nsec) / 1e9) + (double)ts.tv_sec;
}
void
update_fds(EventLoopData *eld) {
for (nfds_t i = 0; i < eld->watches_count; i++) {
@@ -109,7 +96,7 @@ update_timers(EventLoopData *eld) {
}
id_type
addTimer(EventLoopData *eld, const char *name, double interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free) {
addTimer(EventLoopData *eld, const char *name, monotonic_t interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free) {
if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
return 0;
@@ -117,7 +104,7 @@ addTimer(EventLoopData *eld, const char *name, double interval, int enabled, boo
Timer *t = eld->timers + eld->timers_count++;
t->interval = interval;
t->name = name;
t->trigger_at = enabled ? monotonic() + interval : DBL_MAX;
t->trigger_at = enabled ? monotonic() + interval : MONOTONIC_T_MAX;
t->repeats = repeats;
t->callback = cb;
t->callback_data = cb_data;
@@ -144,7 +131,7 @@ void
toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) {
for (nfds_t i = 0; i < eld->timers_count; i++) {
if (eld->timers[i].id == timer_id) {
double trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : DBL_MAX;
monotonic_t trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : MONOTONIC_T_MAX;
if (trigger_at != eld->timers[i].trigger_at) {
eld->timers[i].trigger_at = trigger_at;
update_timers(eld);
@@ -155,7 +142,7 @@ toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) {
}
void
changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) {
changeTimerInterval(EventLoopData *eld, id_type timer_id, monotonic_t interval) {
for (nfds_t i = 0; i < eld->timers_count; i++) {
if (eld->timers[i].id == timer_id) {
eld->timers[i].interval = interval;
@@ -165,11 +152,11 @@ changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) {
}
double
prepareForPoll(EventLoopData *eld, double timeout) {
monotonic_t
prepareForPoll(EventLoopData *eld, monotonic_t timeout) {
for (nfds_t i = 0; i < eld->watches_count; i++) eld->fds[i].revents = 0;
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return timeout;
double now = monotonic(), next_repeat_at = eld->timers[0].trigger_at;
if (!eld->timers_count || eld->timers[0].trigger_at == MONOTONIC_T_MAX) return timeout;
monotonic_t now = monotonic(), next_repeat_at = eld->timers[0].trigger_at;
if (timeout < 0 || now + timeout > next_repeat_at) {
timeout = next_repeat_at <= now ? 0 : next_repeat_at - now;
}
@@ -177,10 +164,8 @@ prepareForPoll(EventLoopData *eld, double timeout) {
}
int
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
const long seconds = (long) timeout;
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
struct timespec tv = { seconds, nanoseconds };
pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout) {
struct timespec tv = calc_time(timeout);
return ppoll(fds, nfds, &tv, NULL);
}
@@ -198,10 +183,10 @@ dispatchEvents(EventLoopData *eld) {
unsigned
dispatchTimers(EventLoopData *eld) {
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0;
if (!eld->timers_count || eld->timers[0].trigger_at == MONOTONIC_T_MAX) return 0;
static struct { timer_callback_func func; id_type id; void* data; bool repeats; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])];
unsigned num_dispatches = 0;
double now = monotonic();
monotonic_t now = monotonic();
for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) {
eld->timers[i].trigger_at = now + eld->timers[i].interval;
dispatches[num_dispatches].func = eld->timers[i].callback;
@@ -299,12 +284,12 @@ finalizePollData(EventLoopData *eld) {
}
int
pollForEvents(EventLoopData *eld, double timeout) {
pollForEvents(EventLoopData *eld, monotonic_t timeout) {
int read_ok = 0;
timeout = prepareForPoll(eld, timeout);
EVDBG("pollForEvents final timeout: %.3f", timeout);
EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout));
int result;
double end_time = monotonic() + timeout;
monotonic_t end_time = monotonic() + timeout;
eld->wakeup_fd_ready = false;
while(1) {

13
glfw/backend_utils.h vendored
View File

@@ -25,6 +25,7 @@
//========================================================================
#pragma once
#include "../kitty/monotonic.h"
#include <poll.h>
#include <unistd.h>
#include <stdbool.h>
@@ -55,7 +56,7 @@ typedef struct {
typedef struct {
id_type id;
double interval, trigger_at;
monotonic_t interval, trigger_at;
timer_callback_func callback;
void *callback_data;
GLFWuserdatafreefun free;
@@ -82,14 +83,14 @@ void check_for_wakeup_events(EventLoopData *eld);
id_type addWatch(EventLoopData *eld, const char *name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data);
void removeWatch(EventLoopData *eld, id_type watch_id);
void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled);
id_type addTimer(EventLoopData *eld, const char *name, double interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free);
id_type addTimer(EventLoopData *eld, const char *name, monotonic_t interval, int enabled, bool repeats, timer_callback_func cb, void *cb_data, GLFWuserdatafreefun free);
void removeTimer(EventLoopData *eld, id_type timer_id);
void removeAllTimers(EventLoopData *eld);
void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
void changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval);
double prepareForPoll(EventLoopData *eld, double timeout);
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout);
int pollForEvents(EventLoopData *eld, double timeout);
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);
unsigned dispatchTimers(EventLoopData *eld);
void finalizePollData(EventLoopData *eld);
bool initPollData(EventLoopData *eld, int display_fd);

View File

@@ -27,9 +27,13 @@
//========================================================================
#include "internal.h"
#include "../kitty/monotonic.h"
#include <sys/param.h> // For MAXPATHLEN
#include <pthread.h>
// Needed for _NSGetProgname
#include <crt_externs.h>
// Change to our application bundle's resources directory, if present
//
static void changeToResourcesDirectory(void)
@@ -66,14 +70,119 @@ static void changeToResourcesDirectory(void)
chdir(resourcesPath);
}
// Set up the menu bar (manually)
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
// could go away at any moment, lots of stuff that really should be
// localize(d|able), etc. Add a nib to save us this horror.
//
static void createMenuBar(void)
{
size_t i;
NSString* appName = nil;
NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString* nameKeys[] =
{
@"CFBundleDisplayName",
@"CFBundleName",
@"CFBundleExecutable",
};
// Try to figure out what the calling application is called
for (i = 0; i < sizeof(nameKeys) / sizeof(nameKeys[0]); i++)
{
id name = bundleInfo[nameKeys[i]];
if (name &&
[name isKindOfClass:[NSString class]] &&
![name isEqualToString:@""])
{
appName = name;
break;
}
}
if (!appName)
{
char** progname = _NSGetProgname();
if (progname && *progname)
appName = @(*progname);
else
appName = @"GLFW Application";
}
NSMenu* bar = [[NSMenu alloc] init];
[NSApp setMainMenu:bar];
NSMenuItem* appMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
NSMenu* appMenu = [[NSMenu alloc] init];
[appMenuItem setSubmenu:appMenu];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName]
action:@selector(orderFrontStandardAboutPanel:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
NSMenu* servicesMenu = [[NSMenu alloc] init];
[NSApp setServicesMenu:servicesMenu];
[[appMenu addItemWithTitle:@"Services"
action:NULL
keyEquivalent:@""] setSubmenu:servicesMenu];
[servicesMenu release];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
action:@selector(hide:)
keyEquivalent:@"h"];
[[appMenu addItemWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"]
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
[appMenu addItemWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
action:@selector(terminate:)
keyEquivalent:@"q"];
NSMenuItem* windowMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
[bar release];
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
[NSApp setWindowsMenu:windowMenu];
[windowMenuItem setSubmenu:windowMenu];
[windowMenu addItemWithTitle:@"Minimize"
action:@selector(performMiniaturize:)
keyEquivalent:@"m"];
[windowMenu addItemWithTitle:@"Zoom"
action:@selector(performZoom:)
keyEquivalent:@""];
[windowMenu addItem:[NSMenuItem separatorItem]];
[windowMenu addItemWithTitle:@"Bring All to Front"
action:@selector(arrangeInFront:)
keyEquivalent:@""];
// TODO: Make this appear at the bottom of the menu (for consistency)
[windowMenu addItem:[NSMenuItem separatorItem]];
[[windowMenu addItemWithTitle:@"Enter Full Screen"
action:@selector(toggleFullScreen:)
keyEquivalent:@"f"]
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
// Prior to Snow Leopard, we need to use this oddly-named semi-private API
// to get the application menu working properly.
SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:");
[NSApp performSelector:setAppleMenuSelector withObject:appMenu];
}
// Create key code translation tables
//
static void createKeyTables(void)
{
int scancode;
int keycode;
memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes));
memset(_glfw.ns.key_to_keycode, -1, sizeof(_glfw.ns.key_to_keycode));
_glfw.ns.keycodes[0x1D] = GLFW_KEY_0;
_glfw.ns.keycodes[0x12] = GLFW_KEY_1;
@@ -190,11 +299,11 @@ static void createKeyTables(void)
_glfw.ns.keycodes[0x43] = GLFW_KEY_KP_MULTIPLY;
_glfw.ns.keycodes[0x4E] = GLFW_KEY_KP_SUBTRACT;
for (scancode = 0; scancode < 256; scancode++)
for (keycode = 0; keycode < 256; keycode++)
{
// Store the reverse translation for faster key name lookup
if (_glfw.ns.keycodes[scancode] >= 0)
_glfw.ns.scancodes[_glfw.ns.keycodes[scancode]] = scancode;
if (_glfw.ns.keycodes[keycode] >= 0)
_glfw.ns.key_to_keycode[_glfw.ns.keycodes[keycode]] = keycode;
}
}
@@ -276,6 +385,17 @@ static bool initializeTIS(void)
return updateUnicodeDataNS();
}
static void
display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFlags flags, void *userInfo UNUSED)
{
if (flags & kCGDisplayBeginConfigurationFlag) {
return;
}
if (flags & kCGDisplaySetModeFlag) {
// GPU possibly changed
}
}
@interface GLFWHelper : NSObject
@end
@@ -294,6 +414,103 @@ static bool initializeTIS(void)
@end // GLFWHelper
// Delegate for application related notifications {{{
@interface GLFWApplicationDelegate : NSObject <NSApplicationDelegate>
@end
@implementation GLFWApplicationDelegate
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
(void)sender;
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
_glfwInputWindowCloseRequest(window);
return NSTerminateCancel;
}
static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
{
(void)sender;
if (!handle_reopen_callback) return YES;
if (handle_reopen_callback(flag)) return YES;
return NO;
}
- (void)applicationDidChangeScreenParameters:(NSNotification *) notification
{
(void)notification;
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
{
if (window->context.client != GLFW_NO_API)
[window->context.nsgl.object update];
}
_glfwPollMonitorsNS();
}
static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
(void)notification;
if (_glfw.hints.init.ns.menubar)
{
// In case we are unbundled, make us a proper UI application
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// Menu bar setup must go between sharedApplication above and
// finishLaunching below, in order to properly emulate the behavior
// of NSApplicationMain
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
{
[[NSBundle mainBundle] loadNibNamed:@"MainMenu"
owner:NSApp
topLevelObjects:&_glfw.ns.nibObjects];
}
else
createMenuBar();
}
if (finish_launching_callback)
finish_launching_callback();
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
(void)notification;
[NSApp stop:nil];
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
_glfwCocoaPostEmptyEvent();
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
(void)aNotification;
CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL);
}
- (void)applicationDidHide:(NSNotification *)notification
{
(void)notification;
int i;
for (i = 0; i < _glfw.monitorCount; i++)
_glfwRestoreVideoModeNS(_glfw.monitors[i]);
}
@end // GLFWApplicationDelegate
// }}}
@interface GLFWApplication : NSApplication
- (void)tick_callback;
- (void)render_frame_received:(id)displayIDAsID;
@@ -335,6 +552,18 @@ is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) {
return false;
}
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
handle_reopen_callback = callback;
return previous;
}
GLFWAPI GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback) {
GLFWapplicationwillfinishlaunchingfun previous = finish_launching_callback;
finish_launching_callback = callback;
return previous;
}
int _glfwPlatformInit(void)
{
@autoreleasepool {
@@ -345,8 +574,21 @@ int _glfwPlatformInit(void)
toTarget:_glfw.ns.helper
withObject:nil];
if (NSApp)
_glfw.ns.finishedLaunching = true;
[GLFWApplication sharedApplication];
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
if (_glfw.ns.delegate == nil)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create application delegate");
return false;
}
[NSApp setDelegate:_glfw.ns.delegate];
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
NSEventModifierFlags modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
@@ -385,6 +627,10 @@ int _glfwPlatformInit(void)
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
[[NSNotificationCenter defaultCenter]
addObserver:_glfw.ns.helper
selector:@selector(selectedKeyboardInputSourceChanged:)
@@ -531,7 +777,7 @@ typedef struct {
NSTimer *os_timer;
unsigned long long id;
bool repeats;
double interval;
monotonic_t interval;
GLFWuserdatafun callback;
void *callback_data;
GLFWuserdatafun free_callback_data;
@@ -551,7 +797,7 @@ remove_timer_at(size_t idx) {
}
static void schedule_timer(Timer *t) {
t->os_timer = [NSTimer scheduledTimerWithTimeInterval:t->interval repeats:(t->repeats ? YES: NO) block:^(NSTimer *os_timer) {
t->os_timer = [NSTimer scheduledTimerWithTimeInterval:monotonic_t_to_s_double(t->interval) repeats:(t->repeats ? YES: NO) block:^(NSTimer *os_timer) {
for (size_t i = 0; i < num_timers; i++) {
if (timers[i].os_timer == os_timer) {
timers[i].callback(timers[i].id, timers[i].callback_data);
@@ -562,7 +808,7 @@ static void schedule_timer(Timer *t) {
}];
}
unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) {
unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback) {
static unsigned long long timer_counter = 0;
if (num_timers >= sizeof(timers)/sizeof(timers[0]) - 1) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
@@ -588,7 +834,7 @@ void _glfwPlatformRemoveTimer(unsigned long long timer_id) {
}
}
void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled) {
void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) {
for (size_t i = 0; i < num_timers; i++) {
if (timers[i].id == timer_id) {
Timer *t = timers + i;

View File

@@ -328,7 +328,7 @@ void _glfwPollMonitorsNS(void)
if (!name)
name = _glfw_strdup("Unknown");
_GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height);
_GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height);
monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber;
createDisplayLink(monitor->ns.displayID);
@@ -354,8 +354,9 @@ void _glfwPollMonitorsNS(void)
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
{
GLFWvidmode current;
const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
_glfwPlatformGetVideoMode(monitor, &current);
const GLFWvidmode* best = _glfwChooseVideoMode(monitor, desired);
if (_glfwCompareVideoModes(&current, best) == 0)
return;
@@ -454,13 +455,13 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
const NSRect frameRect = [monitor->ns.screen visibleFrame];
if (xpos)
*xpos = frameRect.origin.x;
*xpos = (int)frameRect.origin.x;
if (ypos)
*ypos = _glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
*ypos = (int)_glfwTransformYNS(frameRect.origin.y + frameRect.size.height - 1);
if (width)
*width = frameRect.size.width;
*width = (int)frameRect.size.width;
if (height)
*height = frameRect.size.height;
*height = (int)frameRect.size.height;
}
@@ -505,7 +506,6 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
{
CVDisplayLinkRef link;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);

14
glfw/cocoa_platform.h vendored
View File

@@ -36,6 +36,9 @@ typedef void* id;
typedef void* CVDisplayLinkRef;
#endif
// NOTE: Many Cocoa enum values have been renamed and we need to build across
// SDK versions where one is unavailable or the other deprecated
// We use the newer names in code and these macros to handle compatibility
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
#define NSEventMaskAny NSAnyEventMask
@@ -64,8 +67,9 @@ typedef void* CVDisplayLinkRef;
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
typedef struct VkMacOSSurfaceCreateInfoMVK
@@ -144,7 +148,7 @@ typedef struct _GLFWDisplayLinkNS
{
CVDisplayLinkRef displayLink;
CGDirectDisplayID displayID;
double lastRenderFrameRequestedAt;
monotonic_t lastRenderFrameRequestedAt;
} _GLFWDisplayLinkNS;
// Cocoa-specific global data
@@ -153,6 +157,7 @@ typedef struct _GLFWlibraryNS
{
CGEventSourceRef eventSource;
id delegate;
bool finishedLaunching;
bool cursorHidden;
TISInputSourceRef inputSource;
IOHIDManagerRef hidManager;
@@ -160,11 +165,12 @@ typedef struct _GLFWlibraryNS
id helper;
id keyUpMonitor;
id keyDownMonitor;
id nibObjects;
char keyName[64];
char text[256];
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
short int key_to_keycode[GLFW_KEY_LAST + 1];
char* clipboardString;
CGPoint cascadePoint;
// Where to place the cursor when re-enabled

3
glfw/cocoa_time.c vendored
View File

@@ -42,7 +42,7 @@ void _glfwInitTimerNS(void)
mach_timebase_info_data_t info;
mach_timebase_info(&info);
_glfw.timer.ns.frequency = (info.denom * 1e9) / info.numer;
_glfw.timer.ns.frequency = (unsigned long long)((info.denom * 1e9) / info.numer);
}
@@ -59,4 +59,3 @@ uint64_t _glfwPlatformGetTimerFrequency(void)
{
return _glfw.timer.ns.frequency;
}

View File

@@ -27,13 +27,11 @@
//========================================================================
#include "internal.h"
#include "../kitty/monotonic.h"
#include <float.h>
#include <string.h>
// Needed for _NSGetProgname
#include <crt_externs.h>
#define PARAGRAPH_UTF_8 0xc2a7 // §
#define MASCULINE_UTF_8 0xc2ba // º
@@ -93,15 +91,14 @@
//
static NSUInteger getStyleMask(_GLFWwindow* window)
{
NSUInteger styleMask = 0;
NSUInteger styleMask = NSWindowStyleMaskMiniaturizable;
if (window->monitor || !window->decorated)
styleMask |= NSWindowStyleMaskBorderless;
else
{
styleMask |= NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable;
NSWindowStyleMaskClosable;
if (window->resizable)
styleMask |= NSWindowStyleMaskResizable;
@@ -120,7 +117,7 @@ CGDirectDisplayID displayIDForWindow(_GLFWwindow *w) {
}
static unsigned long long display_link_shutdown_timer = 0;
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL 30
#define DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL s_to_monotonic_t(30ll)
void
_glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data UNUSED) {
@@ -147,7 +144,7 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
} else {
display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
}
double now = glfwGetTime();
monotonic_t now = glfwGetTime();
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
_GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i];
if (dl->displayID == displayID) {
@@ -329,8 +326,8 @@ format_text(const char *src) {
}
static const char*
safe_name_for_scancode(unsigned int scancode) {
const char *ans = _glfwPlatformGetScancodeName(scancode);
safe_name_for_keycode(unsigned int keycode) {
const char *ans = _glfwPlatformGetNativeKeyName(keycode);
if (!ans) return "<noname>";
if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>";
return ans;
@@ -343,7 +340,7 @@ static int translateKey(unsigned int key, bool apply_keymap)
{
if (apply_keymap) {
// Look for the effective key name after applying any keyboard layouts/mappings
const char *name_chars = _glfwPlatformGetScancodeName(key);
const char *name_chars = _glfwPlatformGetNativeKeyName(key);
uint32_t name = 0;
if (name_chars) {
for (int i = 0; i < 4; i++) {
@@ -480,17 +477,6 @@ static int translateKey(unsigned int key, bool apply_keymap)
return _glfw.ns.keycodes[key];
}
static void
display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFlags flags, void *userInfo UNUSED)
{
if (flags & kCGDisplayBeginConfigurationFlag) {
return;
}
if (flags & kCGDisplaySetModeFlag) {
// GPU possibly changed
}
}
// Translate a GLFW keycode to a Cocoa modifier flag
//
static NSUInteger translateKeyToModifierFlag(int key)
@@ -572,17 +558,17 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
if (fbRect.size.width != window->ns.fbWidth ||
fbRect.size.height != window->ns.fbHeight)
{
window->ns.fbWidth = fbRect.size.width;
window->ns.fbHeight = fbRect.size.height;
_glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
window->ns.fbWidth = (int)fbRect.size.width;
window->ns.fbHeight = (int)fbRect.size.height;
_glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height);
}
if (contentRect.size.width != window->ns.width ||
contentRect.size.height != window->ns.height)
{
window->ns.width = contentRect.size.width;
window->ns.height = contentRect.size.height;
_glfwInputWindowSize(window, contentRect.size.width, contentRect.size.height);
window->ns.width = (int)contentRect.size.width;
window->ns.height = (int)contentRect.size.height;
_glfwInputWindowSize(window, (int)contentRect.size.width, (int)contentRect.size.height);
}
}
@@ -661,77 +647,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
}
}
@end // }}}
// Delegate for application related notifications {{{
@interface GLFWApplicationDelegate : NSObject
@end
@implementation GLFWApplicationDelegate
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
(void)sender;
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
_glfwInputWindowCloseRequest(window);
return NSTerminateCancel;
}
static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
{
(void)sender;
if (!handle_reopen_callback) return YES;
if (handle_reopen_callback(flag)) return YES;
return NO;
}
- (void)applicationDidChangeScreenParameters:(NSNotification *) notification
{
(void)notification;
_GLFWwindow* window;
for (window = _glfw.windowListHead; window; window = window->next)
{
if (window->context.client != GLFW_NO_API)
[window->context.nsgl.object update];
}
_glfwPollMonitorsNS();
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
(void)notification;
[NSApp stop:nil];
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
_glfwCocoaPostEmptyEvent();
}
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
(void)aNotification;
CGDisplayRemoveReconfigurationCallback(display_reconfigured, NULL);
}
- (void)applicationDidHide:(NSNotification *)notification
{
(void)notification;
int i;
for (i = 0; i < _glfw.monitorCount; i++)
_glfwRestoreVideoModeNS(_glfw.monitors[i]);
}
@end
// }}}
// Content view class for the GLFW window {{{
@@ -960,9 +877,9 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
if (fbRect.size.width != window->ns.fbWidth ||
fbRect.size.height != window->ns.fbHeight)
{
window->ns.fbWidth = fbRect.size.width;
window->ns.fbHeight = fbRect.size.height;
_glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
window->ns.fbWidth = (int)fbRect.size.width;
window->ns.fbHeight = (int)fbRect.size.height;
_glfwInputFramebufferSize(window, (int)fbRect.size.width, (int)fbRect.size.height);
}
const float xscale = fbRect.size.width / contentRect.size.width;
@@ -1047,14 +964,16 @@ is_ascii_control_char(char x) {
- (void)keyDown:(NSEvent *)event
{
const unsigned int scancode = [event keyCode];
const unsigned int keycode = [event keyCode];
const NSUInteger flags = [event modifierFlags];
const int mods = translateFlags(flags);
const int key = translateKey(scancode, true);
const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode, flags) != 1;
const int key = translateKey(keycode, true);
const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1;
const bool previous_has_marked_text = [self hasMarkedText];
[self unmarkText];
_glfw.ns.text[0] = 0;
GLFWkeyevent glfw_keyevent;
_glfwInitializeKeyEvent(&glfw_keyevent, key, keycode, GLFW_PRESS, mods);
if (!_glfw.ns.unicodeData) {
// Using the cocoa API for key handling is disabled, as there is no
// reliable way to handle dead keys using it. Only use it if the
@@ -1069,7 +988,7 @@ is_ascii_control_char(char x) {
const bool in_compose_sequence = window->ns.deadKeyState != 0;
if (UCKeyTranslate(
[(NSData*) _glfw.ns.unicodeData bytes],
scancode,
keycode,
kUCKeyActionDown,
convert_cocoa_to_carbon_modifiers(flags),
LMGetKbdType(),
@@ -1079,30 +998,32 @@ is_ascii_control_char(char x) {
&char_count,
text
) != noErr) {
debug_key(@"UCKeyTranslate failed for scancode: 0x%x (%@) %@\n",
scancode, @(safe_name_for_scancode(scancode)), @(format_mods(mods)));
debug_key(@"UCKeyTranslate failed for keycode: 0x%x (%@) %@\n",
keycode, @(safe_name_for_keycode(keycode)), @(format_mods(mods)));
window->ns.deadKeyState = 0;
return;
}
debug_key(@"scancode: 0x%x (%@) %@char_count: %lu deadKeyState: %u repeat: %d",
scancode, @(safe_name_for_scancode(scancode)), @(format_mods(mods)), char_count, window->ns.deadKeyState, event.ARepeat);
debug_key(@"keycode: 0x%x (%@) %@char_count: %lu deadKeyState: %u repeat: %d",
keycode, @(safe_name_for_keycode(keycode)), @(format_mods(mods)), char_count, window->ns.deadKeyState, event.ARepeat);
if (process_text) {
// this will call insertText which will fill up _glfw.ns.text
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
} else {
window->ns.deadKeyState = 0;
}
if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) {
if (window->ns.deadKeyState && (char_count == 0 || keycode == 0x75)) {
// 0x75 is the delete key which needs to be ignored during a compose sequence
debug_key(@"Sending pre-edit text for dead key (text: %@ markedText: %@).\n", @(format_text(_glfw.ns.text)), markedText);
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods,
[[markedText string] UTF8String], 1); // update pre-edit text
glfw_keyevent.text = [[markedText string] UTF8String];
glfw_keyevent.ime_state = 1;
_glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text
return;
}
if (in_compose_sequence) {
debug_key(@"Clearing pre-edit text at end of compose sequence\n");
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods,
NULL, 1); // clear pre-edit text
glfw_keyevent.text = NULL;
glfw_keyevent.ime_state = 1;
_glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text
}
}
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
@@ -1110,18 +1031,22 @@ is_ascii_control_char(char x) {
@(format_text(_glfw.ns.text)), @(_glfwGetKeyName(key)), markedText);
if (!window->ns.deadKeyState) {
if ([self hasMarkedText]) {
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods,
[[markedText string] UTF8String], 1); // update pre-edit text
glfw_keyevent.text = [[markedText string] UTF8String];
glfw_keyevent.ime_state = 1;
_glfwInputKeyboard(window, &glfw_keyevent); // update pre-edit text
} else if (previous_has_marked_text) {
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods,
NULL, 1); // clear pre-edit text
glfw_keyevent.text = NULL;
glfw_keyevent.ime_state = 1;
_glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text
}
if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) {
// do not pass keys like BACKSPACE while there's pre-edit text, let IME handle it
return;
}
}
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
glfw_keyevent.text = _glfw.ns.text;
glfw_keyevent.ime_state = 0;
_glfwInputKeyboard(window, &glfw_keyevent);
}
- (void)flagsChanged:(NSEvent *)event
@@ -1143,14 +1068,19 @@ is_ascii_control_char(char x) {
else
action = GLFW_RELEASE;
_glfwInputKeyboard(window, key, [event keyCode], action, mods, "", 0);
GLFWkeyevent glfw_keyevent;
_glfwInitializeKeyEvent(&glfw_keyevent, key, [event keyCode], action, mods);
_glfwInputKeyboard(window, &glfw_keyevent);
}
- (void)keyUp:(NSEvent *)event
{
const int key = translateKey([event keyCode], true);
const int mods = translateFlags([event modifierFlags]);
_glfwInputKeyboard(window, key, [event keyCode], GLFW_RELEASE, mods, "", 0);
GLFWkeyevent glfw_keyevent;
_glfwInitializeKeyEvent(&glfw_keyevent, key, [event keyCode], GLFW_RELEASE, mods);
_glfwInputKeyboard(window, &glfw_keyevent);
}
- (void)scrollWheel:(NSEvent *)event
@@ -1341,7 +1271,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c,
@end
// }}}
// GLFW Window class {{{
// GLFW window class {{{
@interface GLFWWindow : NSWindow {
_GLFWwindow* glfw_window;
@@ -1394,154 +1324,6 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c,
@end
// }}}
// Set up the menu bar (manually)
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
// could go away at any moment, lots of stuff that really should be
// localize(d|able), etc. Add a nib to save us this horror.
//
static void createMenuBar(void)
{
size_t i;
NSString* appName = nil;
NSDictionary* bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString* nameKeys[] =
{
@"CFBundleDisplayName",
@"CFBundleName",
@"CFBundleExecutable",
};
// Try to figure out what the calling application is called
for (i = 0; i < sizeof(nameKeys) / sizeof(nameKeys[0]); i++)
{
id name = bundleInfo[nameKeys[i]];
if (name &&
[name isKindOfClass:[NSString class]] &&
![name isEqualToString:@""])
{
appName = name;
break;
}
}
if (!appName)
{
char** progname = _NSGetProgname();
if (progname && *progname)
appName = @(*progname);
else
appName = @"GLFW Application";
}
NSMenu* bar = [[NSMenu alloc] init];
[NSApp setMainMenu:bar];
NSMenuItem* appMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
NSMenu* appMenu = [[NSMenu alloc] init];
[appMenuItem setSubmenu:appMenu];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", appName]
action:@selector(orderFrontStandardAboutPanel:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
NSMenu* servicesMenu = [[NSMenu alloc] init];
[NSApp setServicesMenu:servicesMenu];
[[appMenu addItemWithTitle:@"Services"
action:NULL
keyEquivalent:@""] setSubmenu:servicesMenu];
[servicesMenu release];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", appName]
action:@selector(hide:)
keyEquivalent:@"h"];
[[appMenu addItemWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"]
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
[appMenu addItemWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:[NSMenuItem separatorItem]];
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", appName]
action:@selector(terminate:)
keyEquivalent:@"q"];
NSMenuItem* windowMenuItem =
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
[bar release];
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
[NSApp setWindowsMenu:windowMenu];
[windowMenuItem setSubmenu:windowMenu];
[windowMenu addItemWithTitle:@"Minimize"
action:@selector(performMiniaturize:)
keyEquivalent:@"m"];
[windowMenu addItemWithTitle:@"Zoom"
action:@selector(performZoom:)
keyEquivalent:@""];
[windowMenu addItem:[NSMenuItem separatorItem]];
[windowMenu addItemWithTitle:@"Bring All to Front"
action:@selector(arrangeInFront:)
keyEquivalent:@""];
// TODO: Make this appear at the bottom of the menu (for consistency)
[windowMenu addItem:[NSMenuItem separatorItem]];
[[windowMenu addItemWithTitle:@"Enter Full Screen"
action:@selector(toggleFullScreen:)
keyEquivalent:@"f"]
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
// Prior to Snow Leopard, we need to use this oddly-named semi-private API
// to get the application menu working properly.
SEL setAppleMenuSelector = NSSelectorFromString(@"setAppleMenu:");
[NSApp performSelector:setAppleMenuSelector withObject:appMenu];
}
// Initialize the Cocoa Application Kit
//
static bool initializeAppKit(void)
{
if (_glfw.ns.delegate)
return true;
// There can only be one application delegate, but we allocate it the
// first time a window is created to keep all window code in this file
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
if (_glfw.ns.delegate == nil)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to create application delegate");
return false;
}
[NSApp setDelegate:_glfw.ns.delegate];
if (_glfw.hints.init.ns.menubar)
{
// In case we are unbundled, make us a proper UI application
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// Menu bar setup must go between sharedApplication above and
// finishLaunching below, in order to properly emulate the behavior
// of NSApplicationMain
// disabled by Kovid
/* if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) */
/* [NSApp loadMainMenu]; */
/* else */
createMenuBar();
}
[NSApp run];
// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
return true;
}
// Create the Cocoa window
//
@@ -1589,7 +1371,7 @@ static bool createNativeWindow(_GLFWwindow* window,
[window->ns.object setLevel:NSMainMenuWindowLevel + 1];
else
{
[(NSWindow*)window->ns.object center];
[(NSWindow*) window->ns.object center];
_glfw.ns.cascadePoint =
NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
@@ -1646,8 +1428,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWfbconfig* fbconfig)
{
window->ns.deadKeyState = 0;
if (!initializeAppKit())
return false;
if (!_glfw.ns.finishedLaunching)
{
[NSApp run];
_glfw.ns.finishedLaunching = true;
}
if (!createNativeWindow(window, wndconfig, fbconfig))
return false;
@@ -1734,9 +1519,9 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
[window->ns.object contentRectForFrameRect:[window->ns.object frame]];
if (xpos)
*xpos = contentRect.origin.x;
*xpos = (int)contentRect.origin.x;
if (ypos)
*ypos = _glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1);
*ypos = (int)_glfwTransformYNS(contentRect.origin.y + contentRect.size.height - 1);
}
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int x, int y)
@@ -1752,9 +1537,9 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
const NSRect contentRect = [window->ns.view frame];
if (width)
*width = contentRect.size.width;
*width = (int)contentRect.size.width;
if (height)
*height = contentRect.size.height;
*height = (int)contentRect.size.height;
}
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
@@ -1817,15 +1602,15 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
const NSRect frameRect = [window->ns.object frameRectForContentRect:contentRect];
if (left)
*left = contentRect.origin.x - frameRect.origin.x;
*left = (int)(contentRect.origin.x - frameRect.origin.x);
if (top)
*top = frameRect.origin.y + frameRect.size.height -
contentRect.origin.y - contentRect.size.height;
*top = (int)(frameRect.origin.y + frameRect.size.height -
contentRect.origin.y - contentRect.size.height);
if (right)
*right = frameRect.origin.x + frameRect.size.width -
contentRect.origin.x - contentRect.size.width;
*right = (int)(frameRect.origin.x + frameRect.size.width -
contentRect.origin.x - contentRect.size.width);
if (bottom)
*bottom = contentRect.origin.y - frameRect.origin.y;
*bottom = (int)(contentRect.origin.y - frameRect.origin.y);
}
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
@@ -1840,9 +1625,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = (float) (pixels.size.height / points.size.height);
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
{
return [NSEvent doubleClickInterval];
return s_double_to_monotonic_t([NSEvent doubleClickInterval]);
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
@@ -2110,14 +1895,14 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED)
updateCursorMode(window);
}
const char* _glfwPlatformGetScancodeName(int scancode)
const char* _glfwPlatformGetNativeKeyName(int keycode)
{
UInt32 deadKeyState = 0;
UniChar characters[8];
UniCharCount characterCount = 0;
if (UCKeyTranslate([(NSData*) _glfw.ns.unicodeData bytes],
scancode,
keycode,
kUCKeyActionDisplay,
0,
LMGetKbdType(),
@@ -2137,9 +1922,9 @@ const char* _glfwPlatformGetScancodeName(int scancode)
return _glfw.ns.keyName;
}
int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformGetNativeKeyForKey(int key)
{
return _glfw.ns.scancodes[key];
return _glfw.ns.key_to_keycode[key];
}
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
@@ -2389,12 +2174,6 @@ GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWw
return previous;
}
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
handle_reopen_callback = callback;
return previous;
}
GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback) {
requestRenderFrame((_GLFWwindow*)w, callback);
}

3
glfw/dbus_glfw.c vendored
View File

@@ -27,6 +27,7 @@
#include "internal.h"
#include "dbus_glfw.h"
#include "../kitty/monotonic.h"
#include <stdlib.h>
#include <string.h>
@@ -107,7 +108,7 @@ on_dbus_timer_ready(id_type timer_id UNUSED, void *data) {
static dbus_bool_t
add_dbus_timeout(DBusTimeout *timeout, void *data) {
int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
double interval = ((double)dbus_timeout_get_interval(timeout)) / 1000.0;
monotonic_t interval = ms_to_monotonic_t(dbus_timeout_get_interval(timeout));
if (interval < 0) return FALSE;
id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, true, on_dbus_timer_ready, timeout, NULL);
if (!timer_id) return FALSE;

View File

@@ -165,13 +165,14 @@ def generate_wrappers(glfw_header):
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
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 glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
int glfwGetXKBScancode(const char* key_name, int case_sensitive)
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
@@ -185,15 +186,23 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac
p = src.find('*/', p)
preamble = src[p + 2:first]
header = '''\
//
// THIS FILE IS GENERATED BY glfw.py
//
// SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT
//
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "monotonic.h"
{}
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*);

646
glfw/glfw3.h vendored

File diff suppressed because it is too large Load Diff

29
glfw/ibus_glfw.c vendored
View File

@@ -107,10 +107,14 @@ get_ibus_text_from_message(DBusMessage *msg) {
}
static inline void
send_text(const char *text, int state) {
send_text(const char *text, int ime_state) {
_GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) {
w->callbacks.keyboard((GLFWwindow*) w, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, text, state);
GLFWkeyevent fake_ev;
_glfwInitializeKeyEvent(&fake_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0);
fake_ev.text = text;
fake_ev.ime_state = ime_state;
w->callbacks.keyboard((GLFWwindow*) w, &fake_ev);
}
}
@@ -393,31 +397,36 @@ ibus_key_state(unsigned int glfw_modifiers, int action) {
void
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
uint32_t handled = 0;
KeyEvent *ev = (KeyEvent*)data;
bool is_release = ev->action == GLFW_RELEASE;
_GLFWIBUSKeyEvent *ev = (_GLFWIBUSKeyEvent*)data;
// Restore key's text from the text embedded in the structure.
ev->glfw_ev.text = ev->__embedded_text;
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
bool failed = false;
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
failed = true;
} else {
glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID);
debug("IBUS processed scancode: 0x%x release: %d handled: %u\n", ev->keycode, is_release, handled);
debug("IBUS processed native_key: 0x%x release: %d handled: %u\n", ev->glfw_ev.native_key, is_release, handled);
}
glfw_xkb_key_from_ime(ev, handled ? true : false, failed);
free(ev);
}
bool
ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus) {
ibus_process_key(const _GLFWIBUSKeyEvent *ev_, _GLFWIBUSData *ibus) {
if (!check_connection(ibus)) return false;
KeyEvent *ev = malloc(sizeof(KeyEvent));
_GLFWIBUSKeyEvent *ev = calloc(1, sizeof(_GLFWIBUSKeyEvent));
if (!ev) return false;
memcpy(ev, ev_, sizeof(KeyEvent));
uint32_t state = ibus_key_state(ev->glfw_modifiers, ev->action);
memcpy(ev, ev_, sizeof(_GLFWIBUSKeyEvent));
// Put the key's text in a field IN the structure, for proper serialization.
if (ev->glfw_ev.text) strncpy(ev->__embedded_text, ev->glfw_ev.text, sizeof(ev->__embedded_text) - 1);
ev->glfw_ev.text = NULL;
uint32_t state = ibus_key_state(ev->glfw_ev.mods, ev->glfw_ev.action);
if (!glfw_dbus_call_method_with_reply(
ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
3000, key_event_processed, ev,
DBUS_TYPE_UINT32, &ev->ibus_sym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32,
DBUS_TYPE_UINT32, &ev->ibus_keysym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32,
&state, DBUS_TYPE_INVALID)) {
free(ev);
return false;

15
glfw/ibus_glfw.h vendored
View File

@@ -27,6 +27,7 @@
#pragma once
#include "internal.h"
#include "dbus_glfw.h"
#include <xkbcommon/xkbcommon.h>
@@ -38,18 +39,16 @@ typedef struct {
} _GLFWIBUSData;
typedef struct {
xkb_keycode_t keycode, ibus_keycode;
xkb_keysym_t keysym, ibus_sym;
unsigned int glfw_modifiers;
int action;
xkb_keycode_t ibus_keycode;
xkb_keysym_t ibus_keysym;
GLFWid window_id;
int glfw_keycode;
char text[64];
} KeyEvent;
GLFWkeyevent glfw_ev;
char __embedded_text[64];
} _GLFWIBUSKeyEvent;
void glfw_connect_to_ibus(_GLFWIBUSData *ibus);
void glfw_ibus_terminate(_GLFWIBUSData *ibus);
void glfw_ibus_set_focused(_GLFWIBUSData *ibus, bool focused);
void glfw_ibus_dispatch(_GLFWIBUSData *ibus);
bool ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus);
bool ibus_process_key(const _GLFWIBUSKeyEvent *ev_, _GLFWIBUSData *ibus);
void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h);

10
glfw/init.c vendored
View File

@@ -27,6 +27,7 @@
// Please use C89 style variable declarations in this file because VS 2010
//========================================================================
#define MONOTONIC_START_MODULE
#include "internal.h"
#include "mappings.h"
@@ -201,7 +202,7 @@ _glfwDebug(const char *format, ...) {
{
va_list vl;
fprintf(stderr, "[%.4f] ", glfwGetTime());
fprintf(stderr, "[%.4f] ", monotonic_t_to_s_double(glfwGetTime()));
va_start(vl, format);
vfprintf(stderr, format, vl);
va_end(vl);
@@ -214,10 +215,11 @@ _glfwDebug(const char *format, ...) {
////// GLFW public API //////
//////////////////////////////////////////////////////////////////////////
GLFWAPI int glfwInit(void)
GLFWAPI int glfwInit(monotonic_t start_time)
{
if (_glfw.initialized)
return true;
monotonic_start_time = start_time;
memset(&_glfw, 0, sizeof(_glfw));
_glfw.hints.init = _glfwInitHints;
@@ -350,13 +352,13 @@ GLFWAPI void glfwStopMainLoop(void) {
}
GLFWAPI unsigned long long glfwAddTimer(
double interval, bool repeats, GLFWuserdatafun callback,
monotonic_t interval, bool repeats, GLFWuserdatafun callback,
void *callback_data, GLFWuserdatafun free_callback)
{
return _glfwPlatformAddTimer(interval, repeats, callback, callback_data, free_callback);
}
GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, double interval, bool enabled) {
GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) {
_glfwPlatformUpdateTimer(timer_id, interval, enabled);
}

58
glfw/input.c vendored
View File

@@ -28,6 +28,7 @@
//========================================================================
#include "internal.h"
#include "../kitty/monotonic.h"
#include <assert.h>
#include <float.h>
@@ -256,33 +257,44 @@ static bool parseMapping(_GLFWmapping* mapping, const char* string)
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods)
{
ev->key = key;
ev->native_key = native_key;
ev->action = action;
ev->mods = mods;
ev->text = NULL;
ev->ime_state = 0;
}
// Notifies shared code of a physical key event
//
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state)
void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev)
{
if (key >= 0 && key <= GLFW_KEY_LAST)
if (ev->key >= 0 && ev->key <= GLFW_KEY_LAST)
{
bool repeated = false;
if (action == GLFW_RELEASE && window->keys[key] == GLFW_RELEASE)
if (ev->action == GLFW_RELEASE && window->keys[ev->key] == GLFW_RELEASE)
return;
if (action == GLFW_PRESS && window->keys[key] == GLFW_PRESS)
if (ev->action == GLFW_PRESS && window->keys[ev->key] == GLFW_PRESS)
repeated = true;
if (action == GLFW_RELEASE && window->stickyKeys)
window->keys[key] = _GLFW_STICK;
if (ev->action == GLFW_RELEASE && window->stickyKeys)
window->keys[ev->key] = _GLFW_STICK;
else
window->keys[key] = (char) action;
window->keys[ev->key] = (char) ev->action;
if (repeated)
action = GLFW_REPEAT;
ev->action = GLFW_REPEAT;
}
// FIXME: will need to update ev->virtual_mods here too?
if (window->callbacks.keyboard) {
if (!window->lockKeyMods) mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
window->callbacks.keyboard((GLFWwindow*) window, key, scancode, action, mods, text, state);
if (!window->lockKeyMods) ev->mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
window->callbacks.keyboard((GLFWwindow*) window, ev);
}
}
@@ -744,7 +756,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
}
GLFWAPI const char* glfwGetKeyName(int key, int scancode)
GLFWAPI const char* glfwGetKeyName(int key, int native_key)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -757,13 +769,13 @@ GLFWAPI const char* glfwGetKeyName(int key, int scancode)
return NULL;
}
scancode = _glfwPlatformGetKeyScancode(key);
native_key = _glfwPlatformGetNativeKeyForKey(key);
}
return _glfwPlatformGetScancodeName(scancode);
return _glfwPlatformGetNativeKeyName(native_key);
}
GLFWAPI int glfwGetKeyScancode(int key)
GLFWAPI int glfwGetNativeKeyForKey(int key)
{
_GLFW_REQUIRE_INIT_OR_RETURN(-1);
@@ -773,7 +785,7 @@ GLFWAPI int glfwGetKeyScancode(int key)
return GLFW_RELEASE;
}
return _glfwPlatformGetKeyScancode(key);
return _glfwPlatformGetNativeKeyForKey(key);
}
GLFWAPI int glfwGetKey(GLFWwindow* handle, int key)
@@ -1466,25 +1478,23 @@ GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle UNUSED)
}
#endif
GLFWAPI double glfwGetTime(void)
GLFWAPI monotonic_t glfwGetTime(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);
return (double) (_glfwPlatformGetTimerValue() - _glfw.timer.offset) /
_glfwPlatformGetTimerFrequency();
_GLFW_REQUIRE_INIT_OR_RETURN(0);
return monotonic();
}
GLFWAPI void glfwSetTime(double time)
GLFWAPI void glfwSetTime(monotonic_t time)
{
_GLFW_REQUIRE_INIT();
if (time != time || time < 0.0 || time > 18446744073.0)
if (time < 0)
{
_glfwInputError(GLFW_INVALID_VALUE, "Invalid time %f", time);
_glfwInputError(GLFW_INVALID_VALUE, "Invalid time %f", monotonic_t_to_s_double(time));
return;
}
_glfw.timer.offset = _glfwPlatformGetTimerValue() -
(uint64_t) (time * _glfwPlatformGetTimerFrequency());
// Do nothing
}
GLFWAPI uint64_t glfwGetTimerValue(void)

17
glfw/internal.h vendored
View File

@@ -27,6 +27,8 @@
#pragma once
#include "../kitty/monotonic.h"
#if defined(_GLFW_USE_CONFIG_H)
#include "glfw_config.h"
#endif
@@ -638,8 +640,8 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
const char* _glfwPlatformGetScancodeName(int scancode);
int _glfwPlatformGetKeyScancode(int key);
const char* _glfwPlatformGetNativeKeyName(int native_key);
int _glfwPlatformGetNativeKeyForKey(int key);
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor);
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
@@ -687,7 +689,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* right, int* bottom);
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale);
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
@@ -716,7 +718,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c,
void _glfwPlatformPollEvents(void);
void _glfwPlatformWaitEvents(void);
void _glfwPlatformWaitEventsTimeout(double timeout);
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
void _glfwPlatformPostEmptyEvent(void);
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
@@ -756,7 +758,8 @@ void _glfwInputWindowDamage(_GLFWwindow* window);
void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods);
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
@@ -823,8 +826,8 @@ _GLFWwindow* _glfwFocusedWindow(void);
_GLFWwindow* _glfwWindowForId(GLFWid id);
void _glfwPlatformRunMainLoop(GLFWtickcallback, void*);
void _glfwPlatformStopMainLoop(void);
unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback);
void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled);
unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafun callback, void *callback_data, GLFWuserdatafun free_callback);
void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled);
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
char* _glfw_strdup(const char* source);

View File

@@ -222,7 +222,8 @@ static bool openJoystickDevice(const char* path)
}
}
_GLFWjoystick* js = _glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount);
_GLFWjoystick* js =
_glfwAllocJoystick(name, guid, axisCount, buttonCount, hatCount);
if (!js)
{
close(linjs.fd);

5
glfw/main_loop.h vendored
View File

@@ -7,6 +7,7 @@
#pragma once
#include "internal.h"
#include "../kitty/monotonic.h"
#ifndef GLFW_LOOP_BACKEND
#define GLFW_LOOP_BACKEND x11
@@ -36,7 +37,7 @@ void _glfwPlatformRunMainLoop(GLFWtickcallback tick_callback, void* data) {
EVDBG("main loop exiting");
}
unsigned long long _glfwPlatformAddTimer(double interval, bool repeats, GLFWuserdatafreefun callback, void *callback_data, GLFWuserdatafreefun free_callback) {
unsigned long long _glfwPlatformAddTimer(monotonic_t interval, bool repeats, GLFWuserdatafreefun callback, void *callback_data, GLFWuserdatafreefun free_callback) {
return addTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, "user timer", interval, 1, repeats, callback, callback_data, free_callback);
}
@@ -44,7 +45,7 @@ void _glfwPlatformRemoveTimer(unsigned long long timer_id) {
removeTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id);
}
void _glfwPlatformUpdateTimer(unsigned long long timer_id, double interval, bool enabled) {
void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled) {
changeTimerInterval(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id, interval);
toggleTimer(&_glfw.GLFW_LOOP_BACKEND.eventLoopData, timer_id, enabled);
}

4
glfw/nsgl_context.h vendored
View File

@@ -32,8 +32,8 @@
//
typedef struct _GLFWcontextNSGL
{
id pixelFormat;
id object;
id pixelFormat;
id object;
} _GLFWcontextNSGL;

View File

@@ -271,7 +271,7 @@ bool _glfwCreateContextNSGL(_GLFWwindow* window,
return false;
}
NSOpenGLContext* share = NULL;
NSOpenGLContext* share = nil;
if (ctxconfig->share)
share = ctxconfig->share->context.nsgl.object;
@@ -325,7 +325,7 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
if (window->context.client == GLFW_NO_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return NULL;
return nil;
}
return window->context.nsgl.object;

11
glfw/null_window.c vendored
View File

@@ -28,6 +28,7 @@
//========================================================================
#include "internal.h"
#include "../kitty/monotonic.h"
static int createNativeWindow(_GLFWwindow* window,
@@ -150,9 +151,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED,
*yscale = 1.f;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
{
return 0.5;
return ms_to_monotonic_t(500ll);
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window UNUSED)
@@ -257,7 +258,7 @@ void _glfwPlatformWaitEvents(void)
{
}
void _glfwPlatformWaitEventsTimeout(double timeout UNUSED)
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout UNUSED)
{
}
@@ -306,12 +307,12 @@ const char* _glfwPlatformGetClipboardString(void)
return NULL;
}
const char* _glfwPlatformGetScancodeName(int scancode UNUSED)
const char* _glfwPlatformGetNativeKeyName(int native_key UNUSED)
{
return "";
}
int _glfwPlatformGetKeyScancode(int key UNUSED)
int _glfwPlatformGetNativeKeyForKey(int key UNUSED)
{
return -1;
}

11
glfw/window.c vendored
View File

@@ -29,6 +29,7 @@
//========================================================================
#include "internal.h"
#include "../kitty/monotonic.h"
#include <assert.h>
#include <string.h>
@@ -56,8 +57,10 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused)
{
if (window->keys[key] == GLFW_PRESS)
{
const int scancode = _glfwPlatformGetKeyScancode(key);
_glfwInputKeyboard(window, key, scancode, GLFW_RELEASE, 0, "", 0);
const int native_key = _glfwPlatformGetNativeKeyForKey(key);
GLFWkeyevent ev;
_glfwInitializeKeyEvent(&ev, key, native_key, GLFW_RELEASE, 0);
_glfwInputKeyboard(window, &ev);
}
}
@@ -736,12 +739,12 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle,
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
}
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle)
GLFWAPI monotonic_t glfwGetDoubleClickInterval(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(0.5f);
_GLFW_REQUIRE_INIT_OR_RETURN(ms_to_monotonic_t(500ll));
return _glfwPlatformGetDoubleClickInterval(window);
}

55
glfw/wl_init.c vendored
View File

@@ -29,6 +29,7 @@
#define _GNU_SOURCE
#include "internal.h"
#include "backend_utils.h"
#include "../kitty/monotonic.h"
#include <assert.h>
#include <errno.h>
@@ -164,6 +165,7 @@ static void setCursor(GLFWCursorShape shape)
wl_surface_damage(surface, 0, 0,
image->width, image->height);
wl_surface_commit(surface);
_glfw.wl.cursorPreviousShape = shape;
}
static void pointerHandleMotion(void* data UNUSED,
@@ -174,43 +176,45 @@ static void pointerHandleMotion(void* data UNUSED,
{
_GLFWwindow* window = _glfw.wl.pointerFocus;
GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR;
double x, y;
if (!window)
return;
if (window->cursorMode == GLFW_CURSOR_DISABLED)
return;
window->wl.cursorPosX = wl_fixed_to_double(sx);
window->wl.cursorPosY = wl_fixed_to_double(sy);
x = wl_fixed_to_double(sx);
y = wl_fixed_to_double(sy);
switch (window->wl.decorations.focus)
{
case mainWindow:
_glfwInputCursorPos(window,
window->wl.cursorPosX, window->wl.cursorPosY);
window->wl.cursorPosX = x;
window->wl.cursorPosY = y;
_glfwInputCursorPos(window, x, y);
return;
case topDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
if (y < _GLFW_DECORATION_WIDTH)
cursorShape = GLFW_VRESIZE_CURSOR;
else
cursorShape = GLFW_ARROW_CURSOR;
break;
case leftDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
if (y < _GLFW_DECORATION_WIDTH)
cursorShape = GLFW_NW_RESIZE_CURSOR;
else
cursorShape = GLFW_HRESIZE_CURSOR;
break;
case rightDecoration:
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
if (y < _GLFW_DECORATION_WIDTH)
cursorShape = GLFW_NE_RESIZE_CURSOR;
else
cursorShape = GLFW_HRESIZE_CURSOR;
break;
case bottomDecoration:
if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH)
if (x < _GLFW_DECORATION_WIDTH)
cursorShape = GLFW_SW_RESIZE_CURSOR;
else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH)
else if (x > window->wl.width + _GLFW_DECORATION_WIDTH)
cursorShape = GLFW_SE_RESIZE_CURSOR;
else
cursorShape = GLFW_VRESIZE_CURSOR;
@@ -218,7 +222,8 @@ static void pointerHandleMotion(void* data UNUSED,
default:
assert(0);
}
setCursor(cursorShape);
if (_glfw.wl.cursorPreviousShape != cursorShape)
setCursor(cursorShape);
}
static void pointerHandleButton(void* data UNUSED,
@@ -284,8 +289,8 @@ static void pointerHandleButton(void* data UNUSED,
{
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
_glfw.wl.seat, serial,
window->wl.cursorPosX,
window->wl.cursorPosY);
(int32_t)window->wl.cursorPosX,
(int32_t)window->wl.cursorPosY);
return;
}
}
@@ -367,7 +372,7 @@ static void keyboardHandleEnter(void* data UNUSED,
struct wl_keyboard* keyboard UNUSED,
uint32_t serial UNUSED,
struct wl_surface* surface,
struct wl_array* keys UNUSED)
struct wl_array* keys)
{
// Happens in the case we just destroyed the surface.
if (!surface)
@@ -383,6 +388,15 @@ static void keyboardHandleEnter(void* data UNUSED,
_glfw.wl.keyboardFocus = window;
_glfwInputWindowFocus(window, true);
uint32_t* key;
if (keys && _glfw.wl.keyRepeatInfo.key) {
wl_array_for_each(key, keys) {
if (*key == _glfw.wl.keyRepeatInfo.key) {
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
break;
}
}
}
}
static void keyboardHandleLeave(void* data UNUSED,
@@ -397,13 +411,14 @@ static void keyboardHandleLeave(void* data UNUSED,
_glfw.wl.keyboardFocus = NULL;
_glfwInputWindowFocus(window, false);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
}
static void
dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) {
if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (s_to_monotonic_t(1ll) / (monotonic_t)_glfw.wl.keyboardRepeatRate));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
}
@@ -421,6 +436,7 @@ static void keyboardHandleKey(void* data UNUSED,
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
bool repeatable = false;
_glfw.wl.keyRepeatInfo.key = 0;
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
{
@@ -429,7 +445,7 @@ static void keyboardHandleKey(void* data UNUSED,
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
}
if (repeatable) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay);
}
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0);
}
@@ -454,7 +470,7 @@ static void keyboardHandleRepeatInfo(void* data UNUSED,
return;
_glfw.wl.keyboardRepeatRate = rate;
_glfw.wl.keyboardRepeatDelay = delay;
_glfw.wl.keyboardRepeatDelay = ms_to_monotonic_t(delay);
}
static const struct wl_keyboard_listener keyboardListener = {
@@ -607,8 +623,7 @@ static void registryHandleGlobal(void* data UNUSED,
_glfwSetupWaylandDataDevice();
}
}
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 ||
strcmp(interface, "gtk_primary_selection_device_manager") == 0)
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0)
{
_glfw.wl.primarySelectionDeviceManager =
wl_registry_bind(registry, name,
@@ -728,8 +743,8 @@ int _glfwPlatformInit(void)
"Wayland: Failed to initialize event loop data");
}
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", 0.5, 0, true, dispatchPendingKeyRepeats, NULL, NULL);
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", 0.5, 0, true, animateCursorImage, NULL, NULL);
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", ms_to_monotonic_t(500ll), 0, true, dispatchPendingKeyRepeats, NULL, NULL);
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", ms_to_monotonic_t(500ll), 0, true, animateCursorImage, NULL, NULL);
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
wl_registry_add_listener(_glfw.wl.registry, &registryListener, NULL);

4
glfw/wl_platform.h vendored
View File

@@ -156,6 +156,7 @@ typedef struct _GLFWwindowWayland
// We need to track the monitors the window spans on to calculate the
// optimal scaling factor.
int scale;
bool initial_scale_notified;
_GLFWmonitor** monitors;
int monitorsCount;
int monitorsSize;
@@ -247,10 +248,11 @@ typedef struct _GLFWlibraryWayland
struct wl_cursor_theme* cursorTheme;
struct wl_surface* cursorSurface;
GLFWCursorShape cursorPreviousShape;
uint32_t pointerSerial;
int32_t keyboardRepeatRate;
int32_t keyboardRepeatDelay;
monotonic_t keyboardRepeatDelay;
struct {
uint32_t key;
id_type keyRepeatTimer;

52
glfw/wl_window.c vendored
View File

@@ -32,6 +32,7 @@
#include "backend_utils.h"
#include "memfd.h"
#include "linux_notify.h"
#include "../kitty/monotonic.h"
#include <stdio.h>
#include <stdlib.h>
@@ -54,13 +55,18 @@ static bool checkScaleChange(_GLFWwindow* window)
if (_glfw.wl.compositorVersion < 3)
return false;
// Get the scale factor from the highest scale monitor.
// Get the scale factor from the highest scale monitor that this window is on
for (i = 0; i < window->wl.monitorsCount; ++i)
{
monitorScale = window->wl.monitors[i]->wl.scale;
if (scale < monitorScale)
scale = monitorScale;
}
if (window->wl.monitorsCount < 1 && _glfw.monitorCount > 0) {
// The window has not yet been assigned to any monitors, use the primary monitor
_GLFWmonitor *m = _glfw.monitors[0];
if (m && m->wl.scale > scale) scale = m->wl.scale;
}
// Only change the framebuffer size if the scale changed.
if (scale != window->wl.scale)
@@ -69,6 +75,10 @@ static bool checkScaleChange(_GLFWwindow* window)
wl_surface_set_buffer_scale(window->wl.surface, scale);
return true;
}
if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) {
window->wl.initial_scale_notified = true;
return true;
}
return false;
}
@@ -241,8 +251,8 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
if (fd < 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %%m",
length);
"Wayland: Creating a buffer file for %d B failed: %s",
length, strerror(errno));
return NULL;
}
@@ -250,7 +260,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
if (data == MAP_FAILED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: mmap failed: %%m");
"Wayland: mmap failed: %s", strerror(errno));
close(fd);
return NULL;
}
@@ -545,9 +555,9 @@ static void xdgToplevelHandleConfigure(void* data,
aspectRatio = (float)width / (float)height;
targetRatio = (float)window->numer / (float)window->denom;
if (aspectRatio < targetRatio)
height = width / targetRatio;
height = (int32_t)((float)width / targetRatio);
else if (aspectRatio > targetRatio)
width = height * targetRatio;
width = (int32_t)((float)height * targetRatio);
}
}
}
@@ -684,7 +694,7 @@ setCursorImage(_GLFWcursorWayland* cursorWayland)
image = cursorWayland->cursor->images[cursorWayland->currentImage];
buffer = wl_cursor_image_get_buffer(image);
if (image->delay) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ((double)image->delay) / 1000.0);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
} else {
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
@@ -745,11 +755,11 @@ abortOnFatalError(int last_error) {
}
static void
handleEvents(double timeout)
handleEvents(monotonic_t timeout)
{
struct wl_display* display = _glfw.wl.display;
errno = 0;
EVDBG("starting handleEvents(%.2f)", timeout);
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
while (wl_display_prepare_read(display) != 0) {
while(1) {
@@ -1052,9 +1062,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = (float) window->wl.scale;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
{
return 0.5;
return ms_to_monotonic_t(500ll);
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
@@ -1227,11 +1237,11 @@ void _glfwPlatformPollEvents(void)
void _glfwPlatformWaitEvents(void)
{
double timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1;
monotonic_t timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1;
handleEvents(timeout);
}
void _glfwPlatformWaitEventsTimeout(double timeout)
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout)
{
if (wl_display_dispatch_pending(_glfw.wl.display) > 0) timeout = 0;
handleEvents(timeout);
@@ -1268,12 +1278,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode UNUSED)
_glfwPlatformSetCursor(window, window->wl.currentCursor);
}
const char* _glfwPlatformGetScancodeName(int scancode)
const char* _glfwPlatformGetNativeKeyName(int native_key)
{
return glfw_xkb_keysym_name(scancode);
return glfw_xkb_keysym_name(native_key);
}
int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformGetNativeKeyForKey(int key)
{
return glfw_xkb_sym_for_key(key);
}
@@ -1462,8 +1472,8 @@ static void send_text(char *text, int fd)
{
if (text) {
size_t len = strlen(text), pos = 0;
double start = glfwGetTime();
while (pos < len && glfwGetTime() - start < 2.0) {
monotonic_t start = glfwGetTime();
while (pos < len && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
ssize_t ret = write(fd, text + pos, len - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
@@ -1496,7 +1506,7 @@ static char* read_offer_string(int data_pipe) {
struct pollfd fds;
fds.fd = data_pipe;
fds.events = POLLIN;
double start = glfwGetTime();
monotonic_t start = glfwGetTime();
#define bail(...) { \
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
free(buf); buf = NULL; \
@@ -1504,7 +1514,7 @@ static char* read_offer_string(int data_pipe) {
return NULL; \
}
while (glfwGetTime() - start < 2) {
while (glfwGetTime() - start < s_to_monotonic_t(2ll)) {
int ret = poll(&fds, 1, 2000);
if (ret == -1) {
if (errno == EINTR) continue;
@@ -2031,7 +2041,7 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
return window->wl.surface;
}
GLFWAPI int glfwGetXKBScancode(const char* keyName, bool caseSensitive) {
GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}

12
glfw/x11_init.c vendored
View File

@@ -433,12 +433,10 @@ static bool initExtensions(void)
//
void _glfwGetSystemContentScaleX11(float* xscale, float* yscale, bool bypass_cache)
{
// NOTE: Default to the display-wide DPI as we don't currently have a policy
// for which monitor a window is considered to be on
float xdpi = DisplayWidth(_glfw.x11.display, _glfw.x11.screen) *
25.4f / DisplayWidthMM(_glfw.x11.display, _glfw.x11.screen);
float ydpi = DisplayHeight(_glfw.x11.display, _glfw.x11.screen) *
25.4f / DisplayHeightMM(_glfw.x11.display, _glfw.x11.screen);
// Start by assuming the default X11 DPI
// NOTE: Some desktop environments (KDE) may remove the Xft.dpi field when it
// would be set to 96, so assume that is the case if we cannot find it
float xdpi = 96.f, ydpi = 96.f;
// NOTE: Basing the scale on Xft.dpi where available should provide the most
// consistent user experience (matches Qt, Gtk, etc), although not
@@ -468,7 +466,7 @@ void _glfwGetSystemContentScaleX11(float* xscale, float* yscale, bool bypass_cac
if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value))
{
if (type && strcmp(type, "String") == 0)
xdpi = ydpi = atof(value.addr);
xdpi = ydpi = (float)atof(value.addr);
}
XrmDestroyDatabase(db);

10
glfw/x11_monitor.c vendored
View File

@@ -166,6 +166,16 @@ void _glfwPollMonitorsX11(void)
heightMM = oi->mm_height;
}
if (widthMM <= 0 || heightMM <= 0)
{
// HACK: If RandR does not provide a physical size, assume the
// X11 default 96 DPI and calcuate from the CRTC viewport
// NOTE: These members are affected by rotation, unlike the mode
// info and output info members
widthMM = (int) (ci->width * 25.4f / 96.f);
heightMM = (int) (ci->height * 25.4f / 96.f);
}
_GLFWmonitor* monitor = _glfwAllocMonitor(oi->name, widthMM, heightMM);
monitor->x11.output = sr->outputs[i];
monitor->x11.crtc = oi->crtc;

90
glfw/x11_window.c vendored
View File

@@ -31,6 +31,7 @@
#include "internal.h"
#include "backend_utils.h"
#include "linux_notify.h"
#include "../kitty/monotonic.h"
#include <X11/cursorfont.h>
#include <X11/Xmd.h>
@@ -51,6 +52,10 @@
#define Button6 6
#define Button7 7
// Motif WM hints flags
#define MWM_HINTS_DECORATIONS 2
#define MWM_DECOR_ALL 1
#define _GLFW_XDND_VERSION 5
@@ -61,8 +66,8 @@
static unsigned _glfwDispatchX11Events(void);
static void
handleEvents(double timeout) {
EVDBG("starting handleEvents(%.2f)", timeout);
handleEvents(monotonic_t timeout) {
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
EVDBG("display_read_ok: %d", display_read_ok);
if (display_read_ok) {
@@ -77,9 +82,9 @@ handleEvents(double timeout) {
}
static bool
waitForX11Event(double timeout) {
waitForX11Event(monotonic_t timeout) {
// returns true if there is X11 data waiting to be read, does not run watches and timers
double end_time = glfwGetTime() + timeout;
monotonic_t end_time = glfwGetTime() + timeout;
while(true) {
if (timeout >= 0) {
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout);
@@ -109,7 +114,7 @@ static bool waitForVisibilityNotify(_GLFWwindow* window)
VisibilityNotify,
&dummy))
{
if (!waitForX11Event(0.1))
if (!waitForX11Event(ms_to_monotonic_t(100ll)))
return false;
}
@@ -508,8 +513,8 @@ static bool createNativeWindow(_GLFWwindow* window,
if (wndconfig->scaleToMonitor)
{
width *= _glfw.x11.contentScaleX;
height *= _glfw.x11.contentScaleY;
width *= (int)_glfw.x11.contentScaleX;
height *= (int)_glfw.x11.contentScaleY;
}
// Create a colormap based on the visual used by the current context
@@ -880,7 +885,7 @@ static const char* getSelectionString(Atom selection)
Atom actualType;
int actualFormat;
unsigned long itemCount, bytesAfter;
double start = glfwGetTime();
monotonic_t start = glfwGetTime();
XEvent notification, dummy;
XConvertSelection(_glfw.x11.display,
@@ -895,10 +900,10 @@ static const char* getSelectionString(Atom selection)
SelectionNotify,
&notification))
{
double time = glfwGetTime();
if (time - start > 2)
monotonic_t time = glfwGetTime();
if (time - start > s_to_monotonic_t(2ll))
return "";
waitForX11Event(2.0 - (time - start));
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
}
if (notification.xselection.property == None)
@@ -935,10 +940,10 @@ static const char* getSelectionString(Atom selection)
isSelPropNewValueNotify,
(XPointer) &notification))
{
double time = glfwGetTime();
if (time - start > 2)
monotonic_t time = glfwGetTime();
if (time - start > s_to_monotonic_t(2ll))
return "";
waitForX11Event(2.0 - (time - start));
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
}
XFree(data);
@@ -2066,7 +2071,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
isFrameExtentsEvent,
(XPointer) window))
{
if (!waitForX11Event(0.5))
if (!waitForX11Event(ms_to_monotonic_t(500ll)))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue");
@@ -2103,9 +2108,9 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED,
*yscale = _glfw.x11.contentScaleY;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
{
return 0.5;
return ms_to_monotonic_t(500ll);
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
@@ -2350,33 +2355,24 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled UNUSED)
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled)
{
if (enabled)
struct
{
XDeleteProperty(_glfw.x11.display,
window->x11.handle,
_glfw.x11.MOTIF_WM_HINTS);
}
else
{
struct
{
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long input_mode;
unsigned long status;
} hints;
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long input_mode;
unsigned long status;
} hints = {0};
hints.flags = 2; // Set decorations
hints.decorations = 0; // No decorations
hints.flags = MWM_HINTS_DECORATIONS;
hints.decorations = enabled ? MWM_DECOR_ALL : 0;
XChangeProperty(_glfw.x11.display, window->x11.handle,
_glfw.x11.MOTIF_WM_HINTS,
_glfw.x11.MOTIF_WM_HINTS, 32,
PropModeReplace,
(unsigned char*) &hints,
sizeof(hints) / sizeof(long));
}
XChangeProperty(_glfw.x11.display, window->x11.handle,
_glfw.x11.MOTIF_WM_HINTS,
_glfw.x11.MOTIF_WM_HINTS, 32,
PropModeReplace,
(unsigned char*) &hints,
sizeof(hints) / sizeof(long));
}
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
@@ -2527,11 +2523,11 @@ void _glfwPlatformPollEvents(void)
void _glfwPlatformWaitEvents(void)
{
double timeout = _glfwDispatchX11Events() ? 0 : -1;
monotonic_t timeout = _glfwDispatchX11Events() ? 0 : -1;
handleEvents(timeout);
}
void _glfwPlatformWaitEventsTimeout(double timeout)
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout)
{
if (_glfwDispatchX11Events()) timeout = 0;
handleEvents(timeout);
@@ -2585,13 +2581,13 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
XFlush(_glfw.x11.display);
}
const char* _glfwPlatformGetScancodeName(int scancode)
const char* _glfwPlatformGetNativeKeyName(int native_key)
{
return glfw_xkb_keysym_name(scancode);
return glfw_xkb_keysym_name(native_key);
}
int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformGetNativeKeyForKey(int key)
{
return glfw_xkb_sym_for_key(key);
}
@@ -2870,7 +2866,7 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
return window->x11.handle;
}
GLFWAPI int glfwGetXKBScancode(const char* keyName, bool caseSensitive) {
GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}

119
glfw/xkb_glfw.c vendored
View File

@@ -437,24 +437,22 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m
}
bool
glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode) {
#ifdef _GLFW_WAYLAND
scancode += 8;
keycode += 8;
#endif
return xkb_keymap_key_repeats(xkb->keymap, scancode);
return xkb_keymap_key_repeats(xkb->keymap, keycode);
}
static KeyEvent key_event = {0};
static inline xkb_keysym_t
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) {
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed, char *key_text, int n) {
*compose_completed = 0;
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
switch (xkb_compose_state_get_status(composeState)) {
case XKB_COMPOSE_COMPOSED:
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
xkb_compose_state_get_utf8(composeState, key_text, n);
*compose_completed = 1;
return xkb_compose_state_get_one_sym(composeState);
case XKB_COMPOSE_COMPOSING:
@@ -534,11 +532,14 @@ glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, i
}
void
glfw_xkb_key_from_ime(KeyEvent *ev, bool handled_by_ime, bool failed) {
glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) {
_GLFWwindow *window = _glfwWindowForId(ev->window_id);
if (failed && window && window->callbacks.keyboard) {
// notify application to remove any existing pre-edit text
window->callbacks.keyboard((GLFWwindow*) window, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, "", 1);
GLFWkeyevent fake_ev;
_glfwInitializeKeyEvent(&fake_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0);
fake_ev.ime_state = 1;
window->callbacks.keyboard((GLFWwindow*) window, &fake_ev);
}
static xkb_keycode_t last_handled_press_keycode = 0;
// We filter out release events that correspond to the last press event
@@ -547,53 +548,58 @@ glfw_xkb_key_from_ime(KeyEvent *ev, bool handled_by_ime, bool failed) {
// you'd need to implement a ring buffer to store pending key presses.
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
last_handled_press_keycode = 0;
bool is_release = ev->action == GLFW_RELEASE;
debug("From IBUS: scancode: 0x%x name: %s is_release: %d\n", ev->keycode, glfw_xkb_keysym_name(ev->keysym), is_release);
if (window && !handled_by_ime && !(is_release && ev->keycode == prev_handled_press)) {
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
debug("From IBUS: native_key: 0x%x name: %s is_release: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release);
if (window && !handled_by_ime && !(is_release && ev->glfw_ev.native_key == (int) prev_handled_press)) {
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
ev->glfw_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym),
(ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")),
format_mods(ev->glfw_modifiers), ev->text
ev->glfw_ev.native_key, _glfwGetKeyName(ev->glfw_ev.native_key), ev->glfw_ev.key, glfw_xkb_keysym_name(ev->glfw_ev.key),
(ev->glfw_ev.action == GLFW_RELEASE ? "RELEASE" : (ev->glfw_ev.action == GLFW_PRESS ? "PRESS" : "REPEAT")),
format_mods(ev->glfw_ev.mods), ev->glfw_ev.text
);
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, ev->text, 0);
ev->glfw_ev.ime_state = 0;
_glfwInputKeyboard(window, &ev->glfw_ev);
} else debug("↳ discarded\n");
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
if (!is_release && handled_by_ime)
last_handled_press_keycode = ev->glfw_ev.native_key;
}
void
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t xkb_keycode, int action) {
static char key_text[64] = {0};
const xkb_keysym_t *syms, *clean_syms, *default_syms;
xkb_keysym_t glfw_sym;
xkb_keycode_t code_for_sym = scancode;
key_event.ibus_keycode = scancode;
xkb_keysym_t xkb_sym;
xkb_keycode_t code_for_sym = xkb_keycode, ibus_keycode = xkb_keycode;
GLFWkeyevent glfw_ev;
_glfwInitializeKeyEvent(&glfw_ev, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0); // init with default values
#ifdef _GLFW_WAYLAND
code_for_sym += 8;
#else
key_event.ibus_keycode -= 8;
ibus_keycode -= 8;
#endif
debug("%s scancode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", scancode);
debug("%s xkb_keycode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", xkb_keycode);
XKBStateGroup *sg = &xkb->states;
int num_syms = xkb_state_key_get_syms(sg->state, code_for_sym, &syms);
int num_clean_syms = xkb_state_key_get_syms(sg->clean_state, code_for_sym, &clean_syms);
key_event.text[0] = 0;
key_text[0] = 0;
// According to the documentation of xkb_compose_state_feed it does not
// support multi-sym events, so we ignore them
if (num_syms != 1 || num_clean_syms != 1) {
debug("num_syms: %d num_clean_syms: %d ignoring event\n", num_syms, num_clean_syms);
return;
}
glfw_sym = clean_syms[0];
xkb_sym = clean_syms[0];
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
const char *text_type = "composed_text";
int compose_completed;
glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed);
if (glfw_sym == XKB_KEY_NoSymbol && !compose_completed) {
xkb_sym = compose_symbol(sg->composeState, syms[0], &compose_completed, key_text, sizeof(key_text));
if (xkb_sym == XKB_KEY_NoSymbol && !compose_completed) {
debug("compose not complete, ignoring.\n");
return;
}
debug("composed_sym: %s ", glfw_xkb_keysym_name(glfw_sym));
if (glfw_sym == syms[0]) { // composed sym is the same as non-composed sym
debug("composed_sym: %s ", glfw_xkb_keysym_name(xkb_sym));
if (xkb_sym == syms[0]) { // composed sym is the same as non-composed sym
// Only use the clean_sym if no mods other than the mods we report
// are active (for example if ISO_Shift_Level_* mods are active
// they are not reported by GLFW so the key should be the shifted
@@ -601,37 +607,58 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(sg->state, code_for_sym) & sg->activeUnknownModifiers;
if (sg->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", sg->activeUnknownModifiers));
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
else glfw_sym = clean_syms[0];
else xkb_sym = clean_syms[0];
// xkb returns text even if alt and/or super are pressed
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & sg->modifiers) == 0) xkb_state_key_get_utf8(sg->state, code_for_sym, key_event.text, sizeof(key_event.text));
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & sg->modifiers) == 0) {
xkb_state_key_get_utf8(sg->state, code_for_sym, key_text, sizeof(key_text));
}
text_type = "text";
}
if ((1 <= key_event.text[0] && key_event.text[0] <= 31) || key_event.text[0] == 127) key_event.text[0] = 0; // don't send text for ascii control codes
if (key_event.text[0]) { debug("%s: %s ", text_type, key_event.text); }
if ((1 <= key_text[0] && key_text[0] <= 31) || key_text[0] == 127) {
key_text[0] = 0; // don't send text for ascii control codes
}
if (key_text[0]) { debug("%s: %s ", text_type, key_text); }
}
int glfw_keycode = glfw_key_for_sym(glfw_sym);
int glfw_sym = glfw_key_for_sym(xkb_sym);
bool is_fallback = false;
if (glfw_keycode == GLFW_KEY_UNKNOWN && !key_event.text[0]) {
if (glfw_sym == GLFW_KEY_UNKNOWN && !key_text[0]) {
int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms);
if (num_default_syms > 0) {
glfw_sym = default_syms[0];
glfw_keycode = glfw_key_for_sym(glfw_sym);
xkb_sym = default_syms[0];
glfw_sym = glfw_key_for_sym(xkb_sym);
is_fallback = true;
}
}
debug(
"%s%s: %d (%s) xkb_key: %d (%s)\n",
format_mods(sg->modifiers),
is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_keycode, _glfwGetKeyName(glfw_keycode),
glfw_sym, glfw_xkb_keysym_name(glfw_sym)
is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_sym, _glfwGetKeyName(glfw_sym),
xkb_sym, glfw_xkb_keysym_name(xkb_sym)
);
key_event.action = action; key_event.glfw_modifiers = sg->modifiers;
key_event.keycode = scancode; key_event.keysym = glfw_sym;
key_event.window_id = window->id; key_event.glfw_keycode = glfw_keycode;
key_event.ibus_sym = syms[0];
if (ibus_process_key(&key_event, &xkb->ibus)) {
debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", key_event.ibus_keycode, key_event.ibus_sym, glfw_xkb_keysym_name(key_event.ibus_sym), format_mods(key_event.glfw_modifiers));
// NOTE: On linux, the reported native key identifier is the XKB keysym value.
// Do not confuse `native_key` with `xkb_keycode` (the native keycode reported for the
// glfw event VS the X internal code for a key).
//
// We use the XKB keysym instead of the X keycode to be able to go back-and-forth between
// the GLFW keysym and the XKB keysym when needed, which is not possible using the X keycode,
// because of the lost information when resolving the keycode to the keysym, like consumed
// mods.
glfw_ev.native_key = xkb_sym;
glfw_ev.action = action;
glfw_ev.key = glfw_sym;
glfw_ev.mods = sg->modifiers;
glfw_ev.text = key_text;
_GLFWIBUSKeyEvent ibus_ev;
ibus_ev.glfw_ev = glfw_ev;
ibus_ev.ibus_keycode = ibus_keycode;
ibus_ev.window_id = window->id;
ibus_ev.ibus_keysym = syms[0];
if (ibus_process_key(&ibus_ev, &xkb->ibus)) {
debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", ibus_ev.ibus_keycode, ibus_ev.ibus_keysym, glfw_xkb_keysym_name(ibus_ev.ibus_keysym), format_mods(ibus_ev.glfw_ev.mods));
} else {
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, sg->modifiers, key_event.text, 0);
_glfwInputKeyboard(window, &glfw_ev);
}
}

6
glfw/xkb_glfw.h vendored
View File

@@ -87,10 +87,10 @@ void glfw_xkb_release(_GLFWXKBData *xkb);
bool glfw_xkb_create_context(_GLFWXKBData *xkb);
bool glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str);
void glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group);
bool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode);
bool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode);
const char* glfw_xkb_keysym_name(xkb_keysym_t sym);
xkb_keysym_t glfw_xkb_sym_for_key(int key);
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action);
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t keycode, int action);
int glfw_xkb_keysym_from_name(const char *name, bool case_sensitive);
void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d);
void glfw_xkb_key_from_ime(KeyEvent *ev, bool handled_by_ime, bool failed);
void glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed);

View File

@@ -2,14 +2,17 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
import os
import signal
import subprocess
import sys
import tempfile
import warnings
from collections import defaultdict
from contextlib import suppress
from functools import partial
from gettext import gettext as _
from contextlib import suppress
from kitty.cli import CONFIG_HELP, parse_args
from kitty.constants import appname
@@ -506,7 +509,7 @@ def showwarning(message, category, filename, lineno, file=None, line=None):
showwarning.warnings = []
help_text = 'Show a side-by-side diff of the specified files/directories'
help_text = 'Show a side-by-side diff of the specified files/directories. You can also use ssh:hostname:remote-file-path to diff remote files.'
usage = 'file_or_directory_left file_or_directory_right'
@@ -516,6 +519,20 @@ def terminate_processes(processes):
os.kill(pid, signal.SIGKILL)
def get_remote_file(path):
if path.startswith('ssh:'):
parts = path.split(':', 2)
if len(parts) == 3:
hostname, rpath = parts[1:]
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(rpath), prefix='remote:', delete=False) as tf:
atexit.register(os.remove, tf.name)
p = subprocess.Popen(['ssh', hostname, 'cat', rpath], stdout=tf)
if p.wait() != 0:
raise SystemExit(p.returncode)
return tf.name
return path
def main(args):
warnings.showwarning = showwarning
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
@@ -528,6 +545,7 @@ def main(args):
opts = init_config(args)
set_diff_command(opts.diff_cmd)
lines_for_path.replace_tab_by = opts.replace_tab_by
left, right = map(get_remote_file, (left, right))
for f in left, right:
if not os.path.exists(f):
raise SystemExit('{} does not exist'.format(f))

View File

@@ -26,11 +26,12 @@ screen_size = screen_size_function()
class Mark:
__slots__ = ('index', 'start', 'end', 'text')
__slots__ = ('index', 'start', 'end', 'text', 'groupdict')
def __init__(self, index, start, end, text):
def __init__(self, index, start, end, text, groupdict):
self.index, self.start, self.end = index, start, end
self.text = text
self.groupdict = groupdict
@lru_cache(maxsize=2048)
@@ -94,6 +95,14 @@ class Hints(Handler):
self.chosen = []
self.reset()
@property
def text_matches(self):
return [m.text + self.match_suffix for m in self.chosen]
@property
def groupdicts(self):
return [m.groupdict for m in self.chosen]
def get_match_suffix(self, args):
if args.add_trailing_space == 'always':
return ' '
@@ -126,7 +135,7 @@ class Hints(Handler):
if encode_hint(idx, self.alphabet).startswith(self.current_input)
]
if len(matches) == 1:
self.chosen.append(matches[0].text + self.match_suffix)
self.chosen.append(matches[0])
if self.multiple:
self.ignore_mark_indices.add(matches[0].index)
self.reset()
@@ -144,7 +153,7 @@ class Hints(Handler):
elif key_event is enter_key and self.current_input:
try:
idx = decode_hint(self.current_input, self.alphabet)
self.chosen.append(self.index_map[idx].text + self.match_suffix)
self.chosen.append(self.index_map[idx])
self.ignore_mark_indices.add(idx)
except Exception:
self.current_input = ''
@@ -176,12 +185,13 @@ class Hints(Handler):
def regex_finditer(pat, minimum_match_length, text):
has_named_groups = bool(pat.groupindex)
for m in pat.finditer(text):
s, e = m.span(pat.groups)
s, e = m.span(0 if has_named_groups else pat.groups)
while e > s + 1 and text[e-1] == '\0':
e -= 1
if e - s >= minimum_match_length:
yield s, e
yield s, e, m.groupdict()
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
@@ -240,21 +250,23 @@ def quotes(text, s, e):
def mark(pattern, post_processors, text, args):
pat = re.compile(pattern)
for idx, (s, e) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
for func in post_processors:
s, e = func(text, s, e)
mark_text = text[s:e].replace('\n', '').replace('\0', '')
yield Mark(idx, s, e, mark_text)
yield Mark(idx, s, e, mark_text, groupdict)
def run_loop(args, text, all_marks, index_map):
def run_loop(args, text, all_marks, index_map, extra_cli_args=()):
loop = Loop()
handler = Hints(text, all_marks, index_map, args)
loop.loop(handler)
if handler.chosen and loop.return_code == 0:
return {'match': handler.chosen, 'programs': args.program,
'multiple_joiner': args.multiple_joiner,
'type': args.type}
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
}
raise SystemExit(loop.return_code)
@@ -311,11 +323,29 @@ def parse_input(text):
return convert_text(text, cols)
def run(args, text):
def load_custom_processor(customize_processing):
from kitty.constants import config_dir
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
if os.path.isabs(customize_processing):
custom_path = customize_processing
else:
custom_path = os.path.join(config_dir, customize_processing)
import runpy
return runpy.run_path(custom_path, run_name='__main__')
def run(args, text, extra_cli_args=()):
try:
pattern, post_processors = functions_for(args)
text = parse_input(text)
all_marks = tuple(mark(pattern, post_processors, text, args))
pattern, post_processors = functions_for(args)
if args.customize_processing:
m = load_custom_processor(args.customize_processing)
if 'mark' in m:
all_marks = tuple(m['mark'](text, args, Mark, extra_cli_args))
else:
all_marks = tuple(mark(pattern, post_processors, text, args))
else:
all_marks = tuple(mark(pattern, post_processors, text, args))
if not all_marks:
input(_('No {} found, press Enter to quit.').format(
'URLs' if args.type == 'url' else 'matches'
@@ -333,7 +363,7 @@ def run(args, text):
input('Press Enter to quit.')
raise SystemExit(1)
return run_loop(args, text, all_marks, index_map)
return run_loop(args, text, all_marks, index_map, extra_cli_args)
# CLI {{{
@@ -356,11 +386,14 @@ The type of text to search for.
--regex
default=(?m)^\s*(.+)\s*$
The regular expression to use when :option:`kitty +kitten hints --type`=regex.
The regular expression is in python syntax. If you specify a group in
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
text ignoring a prefix/suffix, as needed. The default expression matches lines.
To match text over multiple lines you should prefix the regular expression with
:code:`(?ms)`, which turns on MULTILINE and DOTALL modes for the regex engine.
If you specify named groups and a :option:`kitty +kitten hints --program` then
the program will be passed arguments corresponding to each named group of
the form key=value.
--url-prefixes
@@ -418,6 +451,13 @@ unless you specify the hints offset as zero the first match will be highlighted
the second character you specify.
--customize-processing
Name of a python file in the kitty config directory which will be imported to provide
custom implementations for pattern finding and performing actions
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
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
usage = ''
@@ -444,15 +484,23 @@ def main(args):
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
if items:
if items and not args.customize_processing:
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
input(_('Press Enter to quit'))
return run(args, text)
return run(args, text, items)
def handle_result(args, data, target_window_id, boss):
if data['customize_processing']:
m = load_custom_processor(data['customize_processing'])
if 'handle_result' in m:
return m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args'])
programs = data['programs'] or ('default',)
matches = tuple(filter(None, data['match']))
matches, groupdicts = [], []
for m, g in zip(data['match'], data['groupdicts']):
if m:
matches.append(m), groupdicts.append(g)
joiner = data['multiple_joiner']
try:
is_int = int(joiner)
@@ -489,7 +537,11 @@ def handle_result(args, data, target_window_id, boss):
if w is not None:
cwd = w.cwd_of_child
program = None if program == 'default' else program
for m in matches:
for m, groupdict in zip(matches, groupdicts):
if groupdict:
m = []
for k, v in groupdict.items():
m.append('{}={}'.format(k, v or ''))
boss.open_url(m, program, cwd=cwd)

View File

@@ -22,7 +22,6 @@ from ..tui.images import (
)
from ..tui.operations import clear_images_on_screen, serialize_gr_command
screen_size = None
OPTIONS = '''\
--align
type=choices
@@ -97,6 +96,21 @@ Do not print out anything to stdout during operation.
'''
screen_size = None
def get_screen_size_function():
global screen_size
if screen_size is None:
screen_size = screen_size_function()
return screen_size
def get_screen_size():
screen_size = get_screen_size_function()
return screen_size()
def options_spec():
if not hasattr(options_spec, 'ans'):
options_spec.ans = OPTIONS.format(
@@ -122,7 +136,7 @@ def calculate_in_cell_x_offset(width, cell_width, align):
def set_cursor(cmd, width, height, align):
ss = screen_size()
ss = get_screen_size()
cw = int(ss.width / ss.cols)
num_of_cells_needed = int(ceil(width / cw))
if num_of_cells_needed > ss.cols:
@@ -143,7 +157,7 @@ def set_cursor(cmd, width, height, align):
def set_cursor_for_place(place, cmd, width, height, align):
x = place.left + 1
ss = screen_size()
ss = get_screen_size()
cw = int(ss.width / ss.cols)
num_of_cells_needed = int(ceil(width / cw))
cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
@@ -189,7 +203,7 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
def process(path, args, is_tempfile):
m = identify(path)
ss = screen_size()
ss = get_screen_size()
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height
needs_scaling = m.width > available_width or m.height > available_height
@@ -314,7 +328,6 @@ def process_single_item(item, args, url_pat=None, maybe_dir=True):
def main(args=sys.argv):
global screen_size
args, items = parse_args(args[1:], options_spec, usage, help_text, '{} +kitten icat'.format(appname))
if args.print_window_size:
@@ -334,7 +347,7 @@ def main(args=sys.argv):
sys.stdin.close()
sys.stdin = open(os.ctermid(), 'r')
screen_size = screen_size_function()
screen_size = get_screen_size_function()
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
if screen_size().width == 0:
if args.detect_support:

View File

@@ -4,6 +4,7 @@
import os
import sys
from contextlib import suppress
from kitty.cli import parse_args
@@ -30,9 +31,8 @@ def real_main(args):
def main(args):
try:
real_main(args)
except KeyboardInterrupt:
pass
with suppress(KeyboardInterrupt):
real_main(args)
except Exception:
import traceback
traceback.print_exc()

View File

@@ -76,6 +76,9 @@ def parse_ssh_args(args):
if arg in boolean_ssh_args:
ssh_args.append(arg)
continue
if arg.startswith('-p') and arg[2:].isdigit():
ssh_args.append(arg)
continue
if arg in other_ssh_args:
if i != len(all_args) - 1:
raise SystemExit('Option {} cannot occur in the middle'.format(arg))

View File

@@ -5,25 +5,27 @@
import os
import string
import subprocess
import sys
from contextlib import suppress
from functools import lru_cache
from gettext import gettext as _
from contextlib import suppress
from kitty.cli import parse_args
from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.utils import get_editor
from kitty.fast_data_types import wcswidth
from kitty.fast_data_types import wcswidth, is_emoji_presentation_base
from kitty.key_encoding import (
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
enter_key
)
from kitty.utils import get_editor
from ..tui.line_edit import LineEdit
from ..tui.handler import Handler
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, colored, cursor, faint, set_line_wrapping,
set_window_title, sgr, styled
clear_screen, colored, cursor, faint, set_line_wrapping, set_window_title,
sgr, styled
)
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
@@ -131,7 +133,8 @@ def decode_hint(x):
class Table:
def __init__(self):
def __init__(self, emoji_variation):
self.emoji_variation = emoji_variation
self.layout_dirty = True
self.last_rows = self.last_cols = -1
self.codepoints = []
@@ -161,7 +164,10 @@ class Table:
self.layout_dirty = False
def safe_chr(codepoint):
return chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
ans = chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
if self.emoji_variation and is_emoji_presentation_base(codepoint):
ans += self.emoji_variation
return ans
if self.mode is NAME:
def as_parts(i, codepoint):
@@ -248,8 +254,13 @@ def is_index(w):
class UnicodeInput(Handler):
def __init__(self, cached_values):
def __init__(self, cached_values, emoji_variation='none'):
self.cached_values = cached_values
self.emoji_variation = ''
if emoji_variation == 'text':
self.emoji_variation = '\ufe0e'
elif emoji_variation == 'graphic':
self.emoji_variation = '\ufe0f'
self.line_edit = LineEdit()
self.recent = list(self.cached_values.get('recent', DEFAULT_SET))
self.current_char = None
@@ -257,9 +268,17 @@ class UnicodeInput(Handler):
self.last_updated_code_point_at = None
self.choice_line = ''
self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX')
self.table = Table()
self.table = Table(self.emoji_variation)
self.update_prompt()
@property
def resolved_current_char(self):
ans = self.current_char
if ans:
if self.emoji_variation and is_emoji_presentation_base(ord(ans[0])):
ans += self.emoji_variation
return ans
def update_codepoints(self):
codepoints = None
if self.mode is HEX:
@@ -320,8 +339,10 @@ class UnicodeInput(Handler):
self.choice_line = ''
else:
c, color = self.current_char, 'green'
if self.emoji_variation and is_emoji_presentation_base(ord(c[0])):
c += self.emoji_variation
self.choice_line = _('Chosen:') + ' {} U+{} {}'.format(
colored(c, 'green'), hex(ord(c))[2:], faint(styled(name(c) or '', italic=True)))
colored(c, 'green'), hex(ord(c[0]))[2:], faint(styled(name(c) or '', italic=True)))
self.prompt = self.prompt_template.format(colored(c, color))
def init_terminal_state(self):
@@ -475,17 +496,43 @@ class UnicodeInput(Handler):
self.refresh()
help_text = 'Input a unicode character'
usage = ''
OPTIONS = '''
--emoji-variation
type=choices
default=none
choices=none,graphic,text
Whether to use the textual or the graphical form for emoji. By default the
default form specified in the unicode standard for the symbol is used.
'''.format
def parse_unicode_input_args(args):
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten unicode_input')
def main(args):
try:
args, items = parse_unicode_input_args(args[1:])
except SystemExit as e:
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
loop = Loop()
with cached_values_for('unicode-input') as cached_values:
handler = UnicodeInput(cached_values)
handler = UnicodeInput(cached_values, args.emoji_variation)
loop.loop(handler)
if handler.current_char and loop.return_code == 0:
with suppress(Exception):
handler.recent.remove(ord(handler.current_char))
recent = [ord(handler.current_char)] + handler.recent
cached_values['recent'] = recent[:len(DEFAULT_SET)]
return handler.current_char
return handler.resolved_current_char
if loop.return_code != 0:
raise SystemExit(loop.return_code)
@@ -497,10 +544,10 @@ def handle_result(args, current_char, target_window_id, boss):
if __name__ == '__main__':
import sys
if '-h' in sys.argv or '--help' in sys.argv:
print('Choose a unicode character to input into the terminal')
raise SystemExit(0)
ans = main(sys.argv)
if ans:
print(ans)
elif __name__ == '__doc__':
sys.cli_docs['usage'] = usage
sys.cli_docs['options'] = OPTIONS
sys.cli_docs['help_text'] = help_text

View File

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

View File

@@ -23,10 +23,10 @@ from .constants import (
from .fast_data_types import (
ChildMonitor, background_opacity_of, change_background_opacity,
change_os_window_state, create_os_window, current_os_window,
destroy_global_data, get_clipboard_string, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
safe_pipe, set_clipboard_string, set_in_sequence_mode, thread_write,
toggle_fullscreen, toggle_maximized
destroy_global_data, focus_os_window, get_clipboard_string,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, safe_pipe, set_clipboard_string, set_in_sequence_mode,
thread_write, toggle_fullscreen, toggle_maximized
)
from .keys import get_shortcut, shortcut_matches
from .layout import set_layout_options
@@ -58,9 +58,9 @@ def data_for_at(w, arg, add_wrap_markers=False):
if arg == '@selection':
return w.text_for_selection()
if arg == '@ansi':
if arg in ('@ansi', '@ansi_screen_scrollback'):
return as_text(as_ansi=True, add_history=True)
if arg == '@text':
if arg in ('@text', '@screen_scrollback'):
return as_text(add_history=True)
if arg == '@screen':
return as_text()
@@ -121,7 +121,7 @@ class Boss:
talk_fd = getattr(single_instance, 'socket', None)
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
listen_fd = -1
if opts.allow_remote_control and args.listen_on:
if args.listen_on and (opts.allow_remote_control in ('y', 'socket-only')):
listen_fd = listen_on(args.listen_on)
self.child_monitor = ChildMonitor(
self.on_child_death,
@@ -146,9 +146,9 @@ class Boss:
from .fast_data_types import cocoa_set_notification_activated_callback
cocoa_set_notification_activated_callback(self.notification_activated)
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
def add_os_window(self, startup_session=None, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
if os_window_id is None:
opts_for_size = opts_for_size or startup_session.os_window_size or self.opts
opts_for_size = opts_for_size or getattr(startup_session, 'os_window_size', None) or self.opts
cls = wclass or self.args.cls or appname
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
os_window_id = create_os_window(
@@ -235,7 +235,7 @@ class Boss:
if tab:
yield tab
def set_active_window(self, window):
def set_active_window(self, window, switch_os_window_if_needed=False):
for os_window_id, tm in self.os_window_map.items():
for tab in tm:
for w in tab:
@@ -243,6 +243,8 @@ class Boss:
if tab is not self.active_tab:
tm.set_active_tab(tab)
tab.set_active_window(w)
if switch_os_window_if_needed and current_os_window() != os_window_id:
focus_os_window(os_window_id, True)
return os_window_id
def _new_os_window(self, args, cwd_from=None):
@@ -276,9 +278,9 @@ class Boss:
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
self.window_id_map[window.id] = window
def _handle_remote_command(self, cmd, window=None):
def _handle_remote_command(self, cmd, window=None, from_peer=False):
response = None
if self.opts.allow_remote_control or getattr(window, 'allow_remote_control', False):
if self.opts.allow_remote_control == 'y' or from_peer or getattr(window, 'allow_remote_control', False):
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
@@ -287,7 +289,7 @@ class Boss:
if not getattr(err, 'hide_traceback', False):
response['tb'] = traceback.format_exc()
else:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
return response
def peer_message_received(self, msg):
@@ -295,7 +297,7 @@ class Boss:
cmd_prefix = '\x1bP@kitty-cmd'
if msg.startswith(cmd_prefix):
cmd = msg[len(cmd_prefix):-2]
response = self._handle_remote_command(cmd)
response = self._handle_remote_command(cmd, from_peer=True)
if response is not None:
response = (cmd_prefix + json.dumps(response) + '\x1b\\').encode('utf-8')
return response
@@ -321,6 +323,16 @@ class Boss:
if window is not None:
window.send_cmd_response(response)
def _cleanup_tab_after_window_removal(self, src_tab):
if len(src_tab) < 1:
tm = src_tab.tab_manager_ref()
if tm is not None:
tm.remove(src_tab)
src_tab.destroy()
if len(tm) == 0:
if not self.shutting_down:
mark_os_window_for_close(src_tab.os_window_id)
def on_child_death(self, window_id):
window = self.window_id_map.pop(window_id, None)
if window is None:
@@ -334,20 +346,22 @@ class Boss:
os_window_id = window.os_window_id
window.destroy()
tm = self.os_window_map.get(os_window_id)
if tm is None:
return
for tab in tm:
if window in tab:
break
else:
return
tab.remove_window(window)
if len(tab) == 0:
tm.remove(tab)
tab.destroy()
if len(tm) == 0:
if not self.shutting_down:
mark_os_window_for_close(os_window_id)
tab = None
if tm is not None:
for q in tm:
if window in q:
tab = q
break
if tab is not None:
tab.remove_window(window)
self._cleanup_tab_after_window_removal(tab)
if window.action_on_removal:
try:
window.action_on_removal(window)
except Exception:
import traceback
traceback.print_exc()
window.action_on_close = window.action_on_removal = None
def close_window(self, window=None):
if window is None:
@@ -512,27 +526,27 @@ class Boss:
if t is not None:
return t.active_window
def dispatch_special_key(self, key, scancode, action, mods):
def dispatch_special_key(self, key, native_key, action, mods):
# Handles shortcuts, return True if the key was consumed
key_action = get_shortcut(self.keymap, mods, key, scancode)
key_action = get_shortcut(self.keymap, mods, key, native_key)
if key_action is None:
sequences = get_shortcut(self.opts.sequence_map, mods, key, scancode)
sequences = get_shortcut(self.opts.sequence_map, mods, key, native_key)
if sequences:
self.pending_sequences = sequences
set_in_sequence_mode(True)
return True
else:
self.current_key_press_info = key, scancode, action, mods
self.current_key_press_info = key, native_key, action, mods
return self.dispatch_action(key_action)
def process_sequence(self, key, scancode, action, mods):
def process_sequence(self, key, native_key, action, mods):
if not self.pending_sequences:
set_in_sequence_mode(False)
remaining = {}
matched_action = None
for seq, key_action in self.pending_sequences.items():
if shortcut_matches(seq[0], mods, key, scancode):
if shortcut_matches(seq[0], mods, key, native_key):
seq = seq[1:]
if seq:
remaining[seq] = key_action
@@ -670,7 +684,7 @@ class Boss:
output += str(s.linebuf.line(i))
return output
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
def _run_kitten(self, kitten, args=(), input_data=None, window=None, custom_callback=None, action_on_removal=None):
orig_args, args = list(args), list(args)
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
@@ -719,7 +733,10 @@ class Boss:
),
copy_colors_from=w
)
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
wid = w.id
overlay_window.action_on_close = partial(self.on_kitten_finish, wid, custom_callback or end_kitten)
if action_on_removal is not None:
overlay_window.action_on_removal = lambda *a: action_on_removal(wid, self)
return overlay_window
def kitten(self, kitten, *args):
@@ -881,6 +898,9 @@ class Boss:
stdin = stdin.encode('utf-8')
return env, stdin
def data_for_at(self, which, window=None, add_wrap_markers=False):
return data_for_at(window or self.active_window, which, add_wrap_markers=add_wrap_markers)
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
w = window or self.active_window
env, stdin = self.process_stdin_source(w, stdin)
@@ -894,6 +914,25 @@ class Boss:
overlay_for = w.id if as_overlay and w.overlay_for is None else None
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
def run_background_process(self, cmd, cwd=None, env=None, stdin=None, cwd_from=None):
import subprocess
if cwd_from:
with suppress(Exception):
cwd = cwd_of_process(cwd_from)
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd)
except Exception:
os.close(w)
else:
thread_write(w, stdin)
finally:
os.close(r)
else:
subprocess.Popen(cmd, env=env, cwd=cwd)
def pipe(self, source, dest, exe, *args):
cmd = [exe] + list(args)
window = self.active_window
@@ -919,24 +958,8 @@ class Boss:
func = set_clipboard_string if dest == 'clipboard' else set_primary_selection
func(stdin)
else:
import subprocess
env, stdin = self.process_stdin_source(stdin=source, window=window)
cwd = None
if cwd_from:
with suppress(Exception):
cwd = cwd_of_process(cwd_from)
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd)
except Exception:
os.close(w)
else:
thread_write(w, stdin)
finally:
os.close(r)
else:
subprocess.Popen(cmd, env=env, cwd=cwd)
self.run_background_process(cmd, cwd_from=cwd_from, stdin=stdin, env=env)
def args_to_special_window(self, args, cwd_from=None):
args = list(args)
@@ -991,14 +1014,20 @@ class Boss:
def _new_window(self, args, cwd_from=None):
tab = self.active_tab
if tab is not None:
allow_remote_control = False
location = None
if args and args[0].startswith('!'):
location = args[0][1:].lower()
args = args[1:]
if args and args[0] == '@':
args = args[1:]
allow_remote_control = True
if args:
return tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from), location=location)
return tab.new_special_window(
self.args_to_special_window(args, cwd_from=cwd_from),
location=location, allow_remote_control=allow_remote_control)
else:
return tab.new_window(cwd_from=cwd_from, location=location)
return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control)
def new_window(self, *args):
self._new_window(args)
@@ -1010,6 +1039,11 @@ class Boss:
cwd_from = w.child.pid_for_cwd if w is not None else None
self._new_window(args, cwd_from=cwd_from)
def launch(self, *args):
from kitty.launch import parse_launch_args, launch
opts, args = parse_launch_args(args)
launch(self, opts, args)
def move_tab_forward(self):
tm = self.active_tab_manager
if tm is not None:
@@ -1096,3 +1130,132 @@ class Boss:
msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip()
self.show_error(_('Errors in kitty.conf'), msg)
def set_colors(self, *args):
from .cmds import parse_subcommand_cli, cmd_set_colors, set_colors
opts, items = parse_subcommand_cli(cmd_set_colors, ['set-colors'] + list(args))
payload = cmd_set_colors(None, opts, items)
set_colors(self, self.active_window, payload)
def _move_window_to(self, window=None, target_tab_id=None, target_os_window_id=None):
window = window or self.active_window
if not window:
return
src_tab = self.tab_for_window(window)
if src_tab is None:
return
if target_os_window_id == 'new':
target_os_window_id = self.add_os_window()
tm = self.os_window_map[target_os_window_id]
target_tab = tm.new_tab(empty_tab=True)
else:
target_os_window_id = target_os_window_id or current_os_window()
if target_tab_id == 'new':
tm = self.os_window_map[target_os_window_id]
target_tab = tm.new_tab(empty_tab=True)
else:
for tab in self.all_tabs:
if tab.id == target_tab_id:
target_tab = tab
target_os_window_id = tab.os_window_id
break
else:
return
underlaid_window, overlaid_window = src_tab.detach_window(window)
if underlaid_window:
target_tab.attach_window(underlaid_window)
if overlaid_window:
target_tab.attach_window(overlaid_window)
self._cleanup_tab_after_window_removal(src_tab)
target_tab.make_active()
def _move_tab_to(self, tab=None, target_os_window_id=None):
tab = tab or self.active_tab
if tab is None:
return
if target_os_window_id is None:
target_os_window_id = self.add_os_window()
tm = self.os_window_map[target_os_window_id]
target_tab = tm.new_tab(empty_tab=True)
target_tab.take_over_from(tab)
self._cleanup_tab_after_window_removal(tab)
target_tab.make_active()
def detach_window(self, *args):
if not args or args[0] == 'new':
return self._move_window_to(target_os_window_id='new')
if args[0] == 'new-tab':
return self._move_window_to(target_tab_id='new')
lines = [
'Choose a tab to move the window to',
''
]
tab_id_map = {}
current_tab = self.active_tab
for i, tab in enumerate(self.all_tabs):
if tab is not current_tab:
tab_id_map[i + 1] = tab.id
lines.append('{} {}'.format(i + 1, tab.title))
new_idx = len(tab_id_map) + 1
tab_id_map[new_idx] = 'new'
lines.append('{} {}'.format(new_idx, 'New tab'))
new_idx = len(tab_id_map) + 1
tab_id_map[new_idx] = None
lines.append('{} {}'.format(new_idx, 'New OS Window'))
def done(data, target_window_id, self):
done.tab_id = tab_id_map[int(data['match'][0].partition(' ')[0])]
def done2(target_window_id, self):
tab_id = done.tab_id
target_window = None
for w in self.all_windows:
if w.id == target_window_id:
target_window = w
break
if tab_id is None:
self._move_window_to(window=target_window, target_os_window_id='new')
else:
self._move_window_to(window=target_window, target_tab_id=tab_id)
self._run_kitten(
'hints', args=('--type=regex', r'--regex=(?m)^\d+ .+$',),
input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2)
def detach_tab(self, *args):
if not args or args[0] == 'new':
return self._move_tab_to()
lines = [
'Choose an OS window to move the tab to',
''
]
os_window_id_map = {}
current_os_window = getattr(self.active_tab, 'os_window_id', 0)
for i, osw in enumerate(self.os_window_map):
tm = self.os_window_map[osw]
if current_os_window != osw and tm.active_tab and tm.active_tab:
os_window_id_map[i + 1] = osw
lines.append('{} {}'.format(i + 1, tm.active_tab.title))
new_idx = len(os_window_id_map) + 1
os_window_id_map[new_idx] = None
lines.append('{} {}'.format(new_idx, 'New OS Window'))
def done(data, target_window_id, self):
done.os_window_id = os_window_id_map[int(data['match'][0].partition(' ')[0])]
def done2(target_window_id, self):
os_window_id = done.os_window_id
target_tab = self.active_tab
for w in self.all_windows:
if w.id == target_window_id:
target_tab = w.tabref()
break
if target_tab and target_tab.os_window_id == os_window_id:
return
self._move_tab_to(tab=target_tab, target_os_window_id=os_window_id)
self._run_kitten(
'hints', args=('--type=regex', r'--regex=(?m)^\d+ .+$',),
input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2)

View File

@@ -11,6 +11,7 @@
#include "screen.h"
#include "fonts.h"
#include "charsets.h"
#include "monotonic.h"
#include <termios.h>
#include <unistd.h>
#include <float.h>
@@ -34,7 +35,7 @@ extern PyTypeObject Screen_Type;
#endif
#define USE_RENDER_FRAMES (global_state.has_render_frames && OPT(sync_to_monitor))
static void (*parse_func)(Screen*, PyObject*, double);
static void (*parse_func)(Screen*, PyObject*, monotonic_t);
typedef struct {
char *data;
@@ -110,10 +111,10 @@ static size_t reaped_pids_count = 0;
// The max time (in secs) to wait for events from the window system
// before ticking over the main loop. Negative values mean wait forever.
static double maximum_wait = -1.0;
static monotonic_t maximum_wait = -1;
static inline void
set_maximum_wait(double val) {
set_maximum_wait(monotonic_t val) {
if (val >= 0 && (val < maximum_wait || maximum_wait < 0)) maximum_wait = val;
}
@@ -296,11 +297,11 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
}
static inline bool
do_parse(ChildMonitor *self, Screen *screen, double now) {
do_parse(ChildMonitor *self, Screen *screen, monotonic_t now) {
bool input_read = false;
screen_mutex(lock, read);
if (screen->read_buf_sz || screen->pending_mode.used) {
double time_since_new_input = now - screen->new_input_at;
monotonic_t time_since_new_input = now - screen->new_input_at;
if (time_since_new_input >= OPT(input_delay)) {
bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ;
input_read = true;
@@ -308,7 +309,7 @@ do_parse(ChildMonitor *self, Screen *screen, double now) {
if (read_buf_full) wakeup_io_loop(self, false); // Ensure the read fd has POLLIN set
screen->new_input_at = 0;
if (screen->pending_mode.activated_at) {
double time_since_pending = MAX(0, now - screen->pending_mode.activated_at);
monotonic_t time_since_pending = MAX(0, now - screen->pending_mode.activated_at);
set_maximum_wait(screen->pending_mode.wait_time - time_since_pending);
}
} else set_maximum_wait(OPT(input_delay) - time_since_new_input);
@@ -322,7 +323,7 @@ parse_input(ChildMonitor *self) {
// Parse all available input that was read in the I/O thread.
size_t count = 0, remove_count = 0;
bool input_read = false;
double now = monotonic();
monotonic_t now = monotonic();
PyObject *msg = NULL;
children_mutex(lock);
while (remove_queue_count) {
@@ -485,22 +486,22 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
extern void cocoa_update_menu_bar_title(PyObject*);
static inline void
collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_window) {
collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) {
ScreenRenderData *rd = &w->render_data;
Cursor *cursor = rd->screen->cursor;
ans->x = cursor->x; ans->y = cursor->y;
ans->is_visible = false;
if (rd->screen->scrolled_by || !screen_is_cursor_visible(rd->screen)) return;
double time_since_start_blink = now - os_window->cursor_blink_zero_time;
monotonic_t time_since_start_blink = now - os_window->cursor_blink_zero_time;
bool cursor_blinking = OPT(cursor_blink_interval) > 0 && os_window->is_focused && (OPT(cursor_stop_blinking_after) == 0 || time_since_start_blink <= OPT(cursor_stop_blinking_after));
bool do_draw_cursor = true;
if (cursor_blinking) {
int t = (int)(time_since_start_blink * 1000);
int d = (int)(OPT(cursor_blink_interval) * 1000);
int t = monotonic_t_to_ms(time_since_start_blink);
int d = monotonic_t_to_ms(OPT(cursor_blink_interval));
int n = t / d;
do_draw_cursor = n % 2 == 0 ? true : false;
double bucket = (n + 1) * d;
double delay = (bucket / 1000.0) - time_since_start_blink;
monotonic_t bucket = ms_to_monotonic_t((n + 1) * d);
monotonic_t delay = bucket - time_since_start_blink;
set_maximum_wait(delay);
}
if (!do_draw_cursor) { ans->is_visible = false; return; }
@@ -526,7 +527,7 @@ update_window_title(Window *w, OSWindow *os_window) {
}
static inline bool
prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg) {
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg) {
#define TD os_window->tab_bar_render_data
bool needs_render = os_window->needs_render;
os_window->needs_render = false;
@@ -555,10 +556,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
if (*num_visible_windows == 1) first_window_bg = window_bg;
if (first_window_bg != window_bg) all_windows_have_same_bg = false;
if (w->last_drag_scroll_at > 0) {
if (now - w->last_drag_scroll_at >= 0.02) {
if (now - w->last_drag_scroll_at >= ms_to_monotonic_t(20ll)) {
if (drag_scroll(w, os_window)) {
w->last_drag_scroll_at = now;
set_maximum_wait(0.02);
set_maximum_wait(ms_to_monotonic_t(20ll));
needs_render = true;
} else w->last_drag_scroll_at = 0;
} else set_maximum_wait(now - w->last_drag_scroll_at);
@@ -579,7 +580,7 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
}
static inline void
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) {
render_os_window(OSWindow *os_window, monotonic_t now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) {
// ensure all pixels are cleared to background color at least once in every buffer
if (os_window->clear_count++ < 3) blank_os_window(os_window);
Tab *tab = os_window->tabs + os_window->active_tab;
@@ -587,8 +588,8 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC;
float x_ratio = 1, y_ratio = 1;
if (static_live_resize_in_progress) {
x_ratio = os_window->viewport_width / (double) os_window->live_resize.width;
y_ratio = os_window->viewport_height / (double) os_window->live_resize.height;
x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width;
y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height;
}
if (!static_live_resize_in_progress) {
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window);
@@ -601,7 +602,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
bool is_active_window = i == tab->active_window;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx * x_ratio, WD.dy * y_ratio, WD.screen, os_window, is_active_window, true);
if (WD.screen->start_visual_bell_at != 0) {
double bell_left = OPT(visual_bell_duration) - (now - WD.screen->start_visual_bell_at);
monotonic_t bell_left = OPT(visual_bell_duration) - (now - WD.screen->start_visual_bell_at);
set_maximum_wait(bell_left);
}
w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_x = WD.screen->cursor_render_info.x; w->last_cursor_y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape;
@@ -638,17 +639,17 @@ draw_resizing_text(OSWindow *w) {
}
static inline bool
no_render_frame_received_recently(OSWindow *w, double now, double max_wait) {
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", max_wait, now);
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));
return ans;
}
static inline void
render(double now, bool input_read) {
render(monotonic_t now, bool input_read) {
EVDBG("input_read: %d", input_read);
static double last_render_at = -DBL_MAX;
double time_since_last_render = now - last_render_at;
static monotonic_t last_render_at = MONOTONIC_T_MIN;
monotonic_t time_since_last_render = now - last_render_at;
if (!input_read && time_since_last_render < OPT(repaint_delay)) {
set_maximum_wait(OPT(repaint_delay) - time_since_last_render);
return;
@@ -662,7 +663,7 @@ render(double now, bool input_read) {
continue;
}
if (USE_RENDER_FRAMES && w->render_state != RENDER_FRAME_READY) {
if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, 0.25)) request_frame_render(w);
if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, ms_to_monotonic_t(250ll))) request_frame_render(w);
continue;
}
make_os_window_context_current(w);
@@ -806,7 +807,7 @@ add_python_timer(PyObject *self UNUSED, PyObject *args) {
double interval;
int repeats = 1;
if (!PyArg_ParseTuple(args, "Od|p", &callback, &interval, &repeats)) return NULL;
unsigned long long timer_id = add_main_loop_timer(interval, repeats ? true: false, python_timer_callback, callback, python_timer_cleanup);
unsigned long long timer_id = add_main_loop_timer(s_double_to_monotonic_t(interval), repeats ? true: false, python_timer_callback, callback, python_timer_cleanup);
Py_INCREF(callback);
return Py_BuildValue("K", timer_id);
}
@@ -821,7 +822,7 @@ remove_python_timer(PyObject *self UNUSED, PyObject *args) {
static inline void
process_pending_resizes(double now) {
process_pending_resizes(monotonic_t now) {
global_state.has_pending_resizes = false;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
@@ -830,11 +831,11 @@ process_pending_resizes(double now) {
if (w->live_resize.from_os_notification) {
if (w->live_resize.os_says_resize_complete || (now - w->live_resize.last_resize_event_at) > 1) update_viewport = true;
} else {
double debounce_time = OPT(resize_debounce_time);
monotonic_t debounce_time = OPT(resize_debounce_time);
// if more than one resize event has occurred, wait at least 0.2 secs
// before repainting, to avoid rapid transitions between the cells banner
// and the normal screen
if (w->live_resize.num_of_resize_events > 1 && OPT(resize_draw_strategy) == RESIZE_DRAW_SIZE) debounce_time = MAX(0.2, debounce_time);
if (w->live_resize.num_of_resize_events > 1 && OPT(resize_draw_strategy) == RESIZE_DRAW_SIZE) debounce_time = MAX(ms_to_monotonic_t(200ll), debounce_time);
if (now - w->live_resize.last_resize_event_at >= debounce_time) update_viewport = true;
else {
global_state.has_pending_resizes = true;
@@ -916,7 +917,7 @@ process_global_state(void *data) {
maximum_wait = -1;
bool state_check_timer_enabled = false;
double now = monotonic();
monotonic_t now = monotonic();
if (global_state.has_pending_resizes) process_pending_resizes(now);
bool input_read = parse_input(self);
render(now, input_read);
@@ -1167,7 +1168,7 @@ io_loop(void *data) {
size_t i;
int ret;
bool has_more, data_received, has_pending_wakeups = false;
double last_main_loop_wakeup_at = -1, now = -1;
monotonic_t last_main_loop_wakeup_at = -1, now = -1;
Screen *screen;
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyChildMon");
@@ -1188,8 +1189,8 @@ io_loop(void *data) {
}
if (has_pending_wakeups) {
now = monotonic();
double time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at);
if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, (int)ceil(1000 * time_delta));
monotonic_t time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at);
if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, monotonic_t_to_ms(time_delta));
else ret = 0;
} else {
ret = poll(fds, self->count + EXTRA_FDS, -1);

View File

@@ -4,9 +4,9 @@
import fcntl
import os
import sys
from collections import defaultdict
from contextlib import contextmanager
from contextlib import suppress
from contextlib import contextmanager, suppress
import kitty.fast_data_types as fast_data_types
@@ -125,15 +125,24 @@ def remove_blocking(fd):
os.set_blocking(fd, False)
def process_env():
ans = os.environ
ssl_env_var = getattr(sys, 'kitty_ssl_env_var', None)
if ssl_env_var is not None:
ans = ans.copy()
ans.pop(ssl_env_var, None)
return ans
def default_env():
try:
return default_env.env
except AttributeError:
return os.environ
return process_env()
def set_default_env(val=None):
env = os.environ.copy()
env = process_env().copy()
if val:
env.update(val)
default_env.env = env
@@ -151,12 +160,8 @@ class Child:
child_fd = pid = None
forked = False
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
self.allow_remote_control = False
if argv and argv[0] == '@':
self.allow_remote_control = True
if len(argv) > 1:
argv = argv[1:]
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None, allow_remote_control=False):
self.allow_remote_control = allow_remote_control
self.argv = argv
if cwd_from is not None:
try:
@@ -262,6 +267,13 @@ class Child:
except Exception:
return list(self.argv)
@property
def foreground_cmdline(self):
try:
return cmdline_of_process(self.pid_for_cwd) or self.cmdline
except Exception:
return self.cmdline
@property
def environ(self):
try:
@@ -287,3 +299,14 @@ class Child:
def foreground_cwd(self):
with suppress(Exception):
return cwd_of_process(self.pid_for_cwd) or None
@property
def foreground_environ(self):
try:
return environ_of_process(self.pid_for_cwd)
except Exception:
try:
return environ_of_process(self.pid)
except Exception:
pass
return {}

View File

@@ -225,7 +225,7 @@ Run the :italic:`{appname}` terminal emulator. You can also specify the :italic:
to run inside :italic:`{appname}` as normal arguments following the :italic:`options`.
For example: {appname} /bin/sh
For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty''').format(appname=appname)
For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname)
def print_help_for_seq(seq, usage, message, appname):
@@ -423,8 +423,8 @@ class Options:
class Namespace:
def __init__(self, kwargs):
for name in kwargs:
setattr(self, name, kwargs[name])
for name, val in kwargs.items():
setattr(self, name, val)
def parse_cmdline(oc, disabled, args=None):

View File

@@ -7,10 +7,16 @@ import os
import sys
from contextlib import suppress
from .cli import parse_args, parse_option_spec, get_defaults_from_seq
from .cli import (
Namespace, get_defaults_from_seq, parse_args, parse_option_spec
)
from .config import parse_config, parse_send_text_bytes
from .constants import appname
from .fast_data_types import focus_os_window
from .launch import (
launch as do_launch, options_spec as launch_options_spec,
parse_launch_args
)
from .tabs import SpecialWindow
from .utils import natsort_ints
@@ -375,6 +381,100 @@ def set_tab_title(boss, window, payload):
# }}}
# detach_window {{{
@cmd(
'Detach a window and place it in a different/new tab',
'Detach the specified window and either move it into a new tab, a new OS window'
' or add it to the specified tab. Use the special value :code:`new` for --target-tab'
' to move to a new tab. If no target tab is specified the window is moved to a new OS window.',
options_spec=MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n
--self
type=bool-set
If specified detach the window this command is run in, rather than the active window.
''',
argspec=''
)
def cmd_detach_window(global_opts, opts, args):
'''
match: Which window to detach
target: Which tab to move the detached window to
self: Boolean indicating whether to detach the window the command is run in
'''
return {'match': opts.match, 'target': opts.target_tab, 'self': opts.self}
def detach_window(boss, window, payload):
pg = cmd_detach_window.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]
match = pg(payload, 'target_tab')
kwargs = {}
if match:
if match == 'new':
kwargs['target_tab_id'] = 'new'
else:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
kwargs['target_tab_id'] = tabs[0].id
if not kwargs:
kwargs['target_os_window_id'] = 'new'
for window in windows:
boss._move_window_to(window=window, **kwargs)
# }}}
# detach_tab {{{
@cmd(
'Detach a tab and place it in a different/new OS Window',
'Detach the specified tab and either move it into a new OS window'
' or add it to the OS Window containing the tab specified by --target-tab',
options_spec=MATCH_TAB_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--target-tab -t') + '''\n
--self
type=bool-set
If specified detach the tab this command is run in, rather than the active tab.
''',
argspec=''
)
def cmd_detach_tab(global_opts, opts, args):
'''
match: Which tab to detach
target: Which OS Window to move the detached tab to
self: Boolean indicating whether to detach the tab the command is run in
'''
return {'match': opts.match, 'target': opts.target_tab, 'self': opts.self}
def detach_tab(boss, window, payload):
pg = cmd_detach_tab.payload_get
match = pg(payload, 'match')
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match)
else:
tabs = [window.tabref() if pg(payload, 'self') and window and window.tabref() else boss.active_tab]
match = pg(payload, 'target_tab')
kwargs = {}
if match:
targets = tuple(boss.match_tabs(match))
if not targets:
raise MatchError(match, 'tabs')
kwargs['target_os_window_id'] = targets[0].os_window_id
for tab in tabs:
boss._move_tab_to(tab=tab, **kwargs)
# }}}
# goto_layout {{{
@cmd(
'Set the window layout',
@@ -675,6 +775,76 @@ def new_window(boss, window, payload):
# }}}
# launch {{{
@cmd(
'Run an arbitrary process in a new window/tab',
' Prints out the id of the newly opened window. Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:'
' :italic:`kitty @ launch --title Email mutt`.',
options_spec=MATCH_TAB_OPTION + '\n\n' + '''\
--no-response
type=bool-set
Do not print out the id of the newly created window.
--self
type=bool-set
If specified the tab containing the window this command is run in is used
instead of the active tab
''' + '\n\n' + launch_options_spec().replace(':option:`launch', ':option:`kitty @ launch'),
argspec='[CMD ...]'
)
def cmd_launch(global_opts, opts, args):
'''
args+: The command line to run in the new window, as a list, use an empty list to run the default shell
match: The tab to open the new window in
window_title: Title for the new window
cwd: Working directory for the new window
env: List of environment varibles of the form NAME=VALUE
tab_title: Title for the new tab
type: The type of window to open
keep_focus: Boolean indicating whether the current window should retain focus or not
copy_colors: Boolean indicating whether to copy the colors from the current window
copy_cmdline: Boolean indicating whether to copy the cmdline from the current window
copy_env: Boolean indicating whether to copy the environ from the current window
location: Where in the tab to open the new window
allow_remote_control: Boolean indicating whether to allow remote control from the new window
stdin_source: Where to get stdin for thew process from
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
'''
if opts.no_response:
global_opts.no_command_response = True
ans = {'args': args or []}
for attr, val in opts.__dict__.items():
ans[attr] = val
return ans
def launch(boss, window, payload):
pg = cmd_launch.payload_get
default_opts = parse_launch_args()[0]
opts = {}
for key, default_value in default_opts.__dict__.items():
opts[key] = payload.get(key, default_value)
opts = Namespace(opts)
match = pg(payload, 'match')
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.active_tab]
if pg(payload, 'self') and window and window.tabref():
tabs = [window.tabref()]
tab = tabs[0]
w = do_launch(boss, opts, pg(payload, 'args') or None, target_tab=tab)
return None if pg(payload, 'no_response') else str(getattr(w, 'id', 0))
# }}}
# focus_window {{{
@cmd(
'Focus the specified window',
@@ -882,7 +1052,7 @@ this option, any color arguments are ignored and --configured and --all are impl
def cmd_set_colors(global_opts, opts, args):
'''
colors+: An object mapping names to colors as 24-bit RGB integers
cursor_text_color: A 24-bit clor for text under the cursor
cursor_text_color: A 24-bit color for text under the cursor
match_window: Window to change colors in
match_tab: Tab to change colors in
all: Boolean indicating change colors everywhere or not

View File

@@ -7,6 +7,7 @@
#include "state.h"
#include "monotonic.h"
#include <Cocoa/Cocoa.h>
#include <AvailabilityMacros.h>
@@ -231,8 +232,6 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
// global menu {{{
void
cocoa_create_global_menu(void) {
@autoreleasepool {
NSString* app_name = find_app_name();
NSMenu* bar = [[NSMenu alloc] init];
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
@@ -324,13 +323,11 @@ cocoa_create_global_menu(void) {
[NSApp setServicesProvider:[[[ServiceProvider alloc] init] autorelease]];
} // autoreleasepool
}
void
cocoa_update_menu_bar_title(PyObject *pytitle) {
NSString *title = [[NSString alloc] initWithUTF8String:PyUnicode_AsUTF8(pytitle)];
NSString *title = @(PyUnicode_AsUTF8(pytitle));
NSMenu *bar = [NSApp mainMenu];
if (title_menu != NULL) {
[bar removeItem:title_menu];
@@ -339,7 +336,6 @@ cocoa_update_menu_bar_title(PyObject *pytitle) {
NSMenu *m = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@" :: %@", title]];
[title_menu setSubmenu:m];
[m release];
[title release];
} // }}}
bool
@@ -411,7 +407,7 @@ cocoa_get_lang(PyObject UNUSED *self) {
} // autoreleasepool
}
double
monotonic_t
cocoa_cursor_blink_interval(void) {
@autoreleasepool {
@@ -420,12 +416,12 @@ cocoa_cursor_blink_interval(void) {
double off_period_ms = [defaults doubleForKey:@"NSTextInsertionPointBlinkPeriodOff"];
double period_ms = [defaults doubleForKey:@"NSTextInsertionPointBlinkPeriod"];
double max_value = 60 * 1000.0, ans = -1.0;
if (on_period_ms || off_period_ms) {
if (on_period_ms != 0. || off_period_ms != 0.) {
ans = on_period_ms + off_period_ms;
} else if (period_ms) {
} else if (period_ms != 0.) {
ans = period_ms;
}
return ans > max_value ? 0.0 : ans;
return ans > max_value ? 0ll : ms_double_to_monotonic_t(ans);
} // autoreleasepool
}

View File

@@ -16,7 +16,7 @@ from .shell import options_for_cmd
To add completion for a new shell, you need to:
1) Add an entry to completion scripts for your shell, this is
a simple function that call's kitty's completion code and passes the
a simple function that calls kitty's completion code and passes the
results to the shell's completion system. This can be output by
`kitty +complete setup shell_name` and its output goes into
your shell's rc file.

View File

@@ -50,7 +50,8 @@ func_with_args, args_funcs = key_func()
@func_with_args(
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd',
'launch'
)
def shlex_parse(func, rest):
return func, to_cmdline(rest)
@@ -92,6 +93,20 @@ def goto_tab_parse(func, rest):
return func, args
@func_with_args('detach_window')
def detach_window_parse(func, rest):
if rest not in ('new', 'new-tab', 'ask'):
rest = 'new'
return func, (rest,)
@func_with_args('detach_tab')
def detach_tab_parse(func, rest):
if rest not in ('new', 'ask'):
rest = 'new'
return func, (rest,)
@func_with_args('set_background_opacity', 'goto_layout', 'kitty_shell')
def simple_parse(func, rest):
return func, [rest]
@@ -193,6 +208,15 @@ def pipe(func, rest):
return func, rest
@func_with_args('set_colors')
def set_colors(func, rest):
import shlex
rest = shlex.split(rest)
if len(rest) < 1:
log_error('Too few arguments to set_colors function')
return func, rest
@func_with_args('nth_window')
def nth_window(func, rest):
try:

View File

@@ -393,10 +393,13 @@ Use negative numbers to change scroll direction.'''))
g('mouse') # {{{
o('mouse_hide_wait', 3.0, option_type=float, long_text=_('''
o('mouse_hide_wait', 0.0 if is_macos else 3.0, option_type=float, long_text=_('''
Hide mouse cursor after the specified number of seconds
of the mouse not being used. Set to zero to disable mouse cursor hiding.
Set to a negative value to hide the mouse cursor immediately when typing text.'''))
Set to a negative value to hide the mouse cursor immediately when typing text.
Disabled by default on macOS as getting it to work robustly with
the ever-changing sea of bugs that is Cocoa is too much effort.
'''))
o('url_color', '#0087bd', option_type=to_color, long_text=_('''
The color and style for highlighting URLs on mouse-over.
@@ -679,10 +682,10 @@ Which edge to show the tab bar on, top or bottom'''))
o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_('''
The margin to the left and right of the tab bar (in pts)'''))
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'hidden'), long_text=_('''
The tab bar style, can be one of: :code:`fade`, :code:`separator` or :code:`hidden`. In the fade style,
each tab's edges fade into the background color, in the separator style, tabs are
separated by a configurable separator.
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'powerline', 'hidden'), long_text=_('''
The tab bar style, can be one of: :code:`fade`, :code:`separator`, :code:`powerline`, or :code:`hidden`.
In the fade style, each tab's edges fade into the background color, in the separator style, tabs are
separated by a configurable separator, and the powerline shows the tabs as a continuous line.
'''))
o('tab_bar_min_tabs', 2, option_type=lambda x: max(1, positive_int(x)), long_text=_('''
@@ -812,9 +815,9 @@ ensure that the shell starts in interactive mode and reads its startup rc files.
o('editor', '.', long_text=_('''
The console editor to use when editing the kitty config file or similar tasks.
A value of . means to use the environment variable EDITOR. Note that this
environment variable has to be set not just in your shell startup scripts but
system-wide, otherwise kitty will not see it.
A value of . means to use the environment variables VISUAL and EDITOR in that
order. Note that this environment variable has to be set not just in your shell
startup scripts but system-wide, otherwise kitty will not see it.
'''))
o('close_on_child_death', False, long_text=_('''
@@ -826,11 +829,23 @@ Note that setting it to yes means that any background processes still using the
terminal can fail silently because their stdout/stderr/stdin no longer work.
'''))
o('allow_remote_control', False, long_text=_('''
def allow_remote_control(x):
if x != 'socket-only':
x = 'y' if to_bool(x) else 'n'
return x
o('allow_remote_control', 'no', option_type=allow_remote_control, long_text=_('''
Allow other programs to control kitty. If you turn this on other programs can
control all aspects of kitty, including sending text to kitty windows,
opening new windows, closing windows, reading the content of windows, etc.
Note that this even works over ssh connections.
control all aspects of kitty, including sending text to kitty windows, opening
new windows, closing windows, reading the content of windows, etc. Note that
this even works over ssh connections. You can chose to either allow any program
running within kitty to control it, with :code:`yes` or only programs that
connect to the socket specified with the :option:`kitty --listen-on` command
line option, if you use the value :code:`socket-only`. The latter is useful if
you want to prevent programs running on a remote computer over ssh from
controlling kitty.
'''))
o(
@@ -1061,13 +1076,13 @@ if is_macos:
k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_('''
You can pipe the contents of the current screen + history buffer as
:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example,
:file:`STDIN` to an arbitrary program using the ``launch`` function. For example,
the following opens the scrollback buffer in less in an overlay window::
map f1 pipe @ansi overlay less +G -R
map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting --type=overlay less +G -R
For more details on piping screen and buffer contents to external programs,
see :doc:`pipe`.
see :doc:`launch`.
'''))
@@ -1077,26 +1092,27 @@ g('shortcuts.window') # {{{
k('new_window', 'kitty_mod+enter', 'new_window', _(''), long_text=_('''
You can open a new window running an arbitrary program, for example::
map kitty_mod+y new_window mutt
map kitty_mod+y launch mutt
You can open a new window with the current working directory set to the
working directory of the current window using::
map ctrl+alt+enter new_window_with_cwd
map ctrl+alt+enter launch --cwd=current
You can open a new window that is allowed to control kitty via
the kitty remote control facility by prefixing the command line with @.
Any programs running in that window will be allowed to control kitty.
For example::
map ctrl+enter new_window @ some_program
map ctrl+enter launch --allow-remote-control some_program
You can open a new window next to the currently active window or as the first window,
with::
map ctrl+n new_window !neighbor some_program
map ctrl+f new_window !first some_program
map ctrl+n launch --location=neighbor some_program
map ctrl+f launch --location=first some_program
For more details, see :doc:`launch`.
'''))
if is_macos:
k('new_window', 'cmd+enter', 'new_window', _('New window'), add_to_docs=False)

View File

@@ -9,7 +9,7 @@ from collections import namedtuple
from contextlib import suppress
appname = 'kitty'
version = (0, 14, 6)
version = (0, 15, 0)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat
@@ -25,7 +25,7 @@ def kitty_exe():
if ans is None:
rpath = sys._xoptions.get('bundle_exe_dir')
if not rpath:
items = os.environ['PATH'].split(os.pathsep)
items = filter(None, os.environ.get('PATH', '').split(os.pathsep))
seen = set()
for candidate in items:
if candidate not in seen:
@@ -49,9 +49,8 @@ def _get_config_dir():
locations.append(os.path.expanduser('~/.config'))
if is_macos:
locations.append(os.path.expanduser('~/Library/Preferences'))
if 'XDG_CONFIG_DIRS' in os.environ:
for loc in os.environ['XDG_CONFIG_DIRS'].split(os.pathsep):
locations.append(os.path.abspath(os.path.expanduser(loc)))
for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)):
locations.append(os.path.abspath(os.path.expanduser(loc)))
for loc in locations:
if loc:
q = os.path.join(loc, appname)

View File

@@ -278,7 +278,7 @@ get_glyph_width(PyObject *s, glyph_index g) {
CGGlyph gg = g;
CGRect bounds;
CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1);
return bounds.size.width;
return (int)ceil(bounds.size.width);
}
static inline float
@@ -326,7 +326,7 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
}
}
*cell_width = MAX(1u, width);
*underline_position = floor(self->ascent - self->underline_position + 0.5);
*underline_position = (unsigned int)floor(self->ascent - self->underline_position + 0.5);
*underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness));
*baseline = (unsigned int)self->ascent;
// float line_height = MAX(1, floor(self->ascent + self->descent + MAX(0, self->leading) + 0.5));
@@ -457,7 +457,7 @@ 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 = 2 * bounding_box.size.height };
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;

View File

@@ -5,6 +5,7 @@
* Distributed under terms of the GPL3 license.
*/
#define MONOTONIC_START_MODULE
#ifdef __APPLE__
// Needed for _CS_DARWIN_USER_CACHE_DIR
#define _DARWIN_C_SOURCE
@@ -22,31 +23,10 @@
#ifdef WITH_PROFILER
#include <gperftools/profiler.h>
#endif
/* To millisecond (10^-3) */
#define SEC_TO_MS 1000
/* To microseconds (10^-6) */
#define MS_TO_US 1000
#define SEC_TO_US (SEC_TO_MS * MS_TO_US)
/* To nanoseconds (10^-9) */
#define US_TO_NS 1000
#define MS_TO_NS (MS_TO_US * US_TO_NS)
#define SEC_TO_NS (SEC_TO_MS * MS_TO_NS)
/* Conversion from nanoseconds */
#define NS_TO_MS (1000 * 1000)
#define NS_TO_US (1000)
#include "monotonic.h"
#ifdef __APPLE__
#include <libproc.h>
#include <mach/mach_time.h>
static mach_timebase_info_data_t timebase = {0};
static inline double monotonic_(void) {
return ((double)(mach_absolute_time() * timebase.numer) / timebase.denom)/SEC_TO_NS;
}
static PyObject*
user_cache_dir() {
@@ -73,25 +53,7 @@ process_group_map() {
free(buf);
return ans;
}
#else
#include <time.h>
static inline double monotonic_(void) {
struct timespec ts = {0};
#ifdef CLOCK_HIGHRES
clock_gettime(CLOCK_HIGHRES, &ts);
#elif CLOCK_MONOTONIC_RAW
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
#else
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
return (((double)ts.tv_nsec) / SEC_TO_NS) + (double)ts.tv_sec;
}
#endif
static double start_time = 0;
double monotonic() { return monotonic_() - start_time; }
static PyObject*
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
@@ -148,7 +110,7 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
if (!read_with_timeout) flags |= O_NONBLOCK;
static char ctty[L_ctermid+1];
int fd = open(ctermid(ctty), flags);
if (fd == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
if (fd == -1) { PyErr_Format(PyExc_OSError, "Failed to open controlling terminal: %s (identified with ctermid()) with error: %s", ctty, strerror(errno)); return NULL; }
struct termios *termios_p = calloc(1, sizeof(struct termios));
if (!termios_p) return PyErr_NoMemory();
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
@@ -253,10 +215,7 @@ PyInit_fast_data_types(void) {
m = PyModule_Create(&module);
if (m == NULL) return NULL;
#ifdef __APPLE__
mach_timebase_info(&timebase);
#endif
start_time = monotonic_();
init_monotonic();
if (m != NULL) {
if (!init_logging(m)) return NULL;

View File

@@ -13,6 +13,7 @@
#include <stdbool.h>
#include <poll.h>
#include <pthread.h>
#include "glfw-wrapper.h"
// Required minimum OpenGL version
#define OPENGL_REQUIRED_VERSION_MAJOR 3
#define OPENGL_REQUIRED_VERSION_MINOR 3
@@ -291,7 +292,6 @@ void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned i
const char* cell_as_sgr(const GPUCell *, const GPUCell *);
const char* cursor_as_sgr(const Cursor *);
double monotonic(void);
PyObject* cm_thread_write(PyObject *self, PyObject *args);
bool schedule_write_to_child(unsigned long id, unsigned int num, ...);
bool set_iutf8(int, bool);
@@ -309,7 +309,7 @@ void focus_in_event(void);
void scroll_event(double, double, int);
void fake_scroll(int, bool);
void set_special_key_combo(int glfw_key, int mods, bool is_native);
void on_key_input(int key, int scancode, int action, int mods, const char*, int);
void on_key_input(GLFWkeyevent *ev);
void request_window_attention(id_type, bool);
#ifndef __APPLE__
void play_canberra_sound(const char *which_sound, const char *event_id);

View File

@@ -7,7 +7,6 @@
#include "data-types.h"
#include <dlfcn.h>
#include <canberra.h>
#define FUNC(name, restype, ...) typedef restype (*name##_func)(__VA_ARGS__); static name##_func name = NULL
#define LOAD_FUNC(handle, name) {\
@@ -90,15 +89,56 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */
};
static ca_context *canberra_ctx = NULL;
static void* libcanberra_handle = NULL;
static void *canberra_ctx = NULL;
FUNC(ca_context_create, int, void**);
FUNC(ca_context_destroy, int, void*);
typedef int (*ca_context_play_func)(void*, uint32_t, ...); static ca_context_play_func ca_context_play = NULL;
static PyObject*
load_libcanberra_functions(void) {
LOAD_FUNC(libcanberra_handle, ca_context_create);
LOAD_FUNC(libcanberra_handle, ca_context_play);
LOAD_FUNC(libcanberra_handle, ca_context_destroy);
return NULL;
}
static void
load_libcanberra(void) {
static const char* libname = "libcanberra.so";
// some installs are missing the .so symlink, so try the full name
static const char* libname2 = "libcanberra.so.0";
static const char* libname3 = "libcanberra.so.0.2.5";
static bool done = false;
if (done) return;
done = true;
libcanberra_handle = dlopen(libname, RTLD_LAZY);
if (libcanberra_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY);
if (libcanberra_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY);
if (libcanberra_handle == NULL) {
fprintf(stderr, "Failed to load %s, cannot play beep sound, with error: %s\n", libname, dlerror());
return;
}
load_libcanberra_functions();
if (PyErr_Occurred()) {
PyErr_Print();
dlclose(libcanberra_handle); libcanberra_handle = NULL;
}
if (ca_context_create(&canberra_ctx) != 0) {
fprintf(stderr, "Failed to create libcanberra context, cannot play beep sound\n");
ca_context_destroy(canberra_ctx); canberra_ctx = NULL;
dlclose(libcanberra_handle); libcanberra_handle = NULL;
}
}
void
play_canberra_sound(const char *which_sound, const char *event_id) {
if (canberra_ctx == NULL) ca_context_create(&canberra_ctx);
load_libcanberra();
if (libcanberra_handle == NULL || canberra_ctx == NULL) return;
ca_context_play(
canberra_ctx, 0,
CA_PROP_EVENT_ID, which_sound,
CA_PROP_EVENT_DESCRIPTION, event_id,
"event.id", which_sound,
"event.description", event_id,
NULL
);
}
@@ -109,6 +149,7 @@ finalize(void) {
libsn_handle = NULL;
if (canberra_ctx) ca_context_destroy(canberra_ctx);
canberra_ctx = NULL;
if (libcanberra_handle) dlclose(libcanberra_handle);
}
bool

461
kitty/emoji.h generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2019-08-02
// unicode data, built from the unicode standard on: 2019-10-01
// see gen-wcwidth.py
#pragma once
#include "data-types.h"
@@ -187,5 +187,464 @@ is_emoji_modifier(char_type code) {
}
return false;
}
static inline bool
is_symbol(char_type code) {
switch(code) {
case 0x24:
return true;
case 0x2b:
return true;
case 0x3c ... 0x3e:
return true;
case 0x5e:
return true;
case 0x60:
return true;
case 0x7c:
return true;
case 0x7e:
return true;
case 0xa2 ... 0xa6:
return true;
case 0xa8 ... 0xa9:
return true;
case 0xac:
return true;
case 0xae ... 0xb1:
return true;
case 0xb4:
return true;
case 0xb8:
return true;
case 0xd7:
return true;
case 0xf7:
return true;
case 0x2c2 ... 0x2c5:
return true;
case 0x2d2 ... 0x2df:
return true;
case 0x2e5 ... 0x2eb:
return true;
case 0x2ed:
return true;
case 0x2ef ... 0x2ff:
return true;
case 0x375:
return true;
case 0x384 ... 0x385:
return true;
case 0x3f6:
return true;
case 0x482:
return true;
case 0x58d ... 0x58f:
return true;
case 0x606 ... 0x608:
return true;
case 0x60b:
return true;
case 0x60e ... 0x60f:
return true;
case 0x6de:
return true;
case 0x6e9:
return true;
case 0x6fd ... 0x6fe:
return true;
case 0x7f6:
return true;
case 0x7fe ... 0x7ff:
return true;
case 0x9f2 ... 0x9f3:
return true;
case 0x9fa ... 0x9fb:
return true;
case 0xaf1:
return true;
case 0xb70:
return true;
case 0xbf3 ... 0xbfa:
return true;
case 0xc7f:
return true;
case 0xd4f:
return true;
case 0xd79:
return true;
case 0xe3f:
return true;
case 0xf01 ... 0xf03:
return true;
case 0xf13:
return true;
case 0xf15 ... 0xf17:
return true;
case 0xf1a ... 0xf1f:
return true;
case 0xf34:
return true;
case 0xf36:
return true;
case 0xf38:
return true;
case 0xfbe ... 0xfc5:
return true;
case 0xfc7 ... 0xfcc:
return true;
case 0xfce ... 0xfcf:
return true;
case 0xfd5 ... 0xfd8:
return true;
case 0x109e ... 0x109f:
return true;
case 0x1390 ... 0x1399:
return true;
case 0x166d:
return true;
case 0x17db:
return true;
case 0x1940:
return true;
case 0x19de ... 0x19ff:
return true;
case 0x1b61 ... 0x1b6a:
return true;
case 0x1b74 ... 0x1b7c:
return true;
case 0x1fbd:
return true;
case 0x1fbf ... 0x1fc1:
return true;
case 0x1fcd ... 0x1fcf:
return true;
case 0x1fdd ... 0x1fdf:
return true;
case 0x1fed ... 0x1fef:
return true;
case 0x1ffd ... 0x1ffe:
return true;
case 0x2044:
return true;
case 0x2052:
return true;
case 0x207a ... 0x207c:
return true;
case 0x208a ... 0x208c:
return true;
case 0x20a0 ... 0x20bf:
return true;
case 0x2100 ... 0x2101:
return true;
case 0x2103 ... 0x2106:
return true;
case 0x2108 ... 0x2109:
return true;
case 0x2114:
return true;
case 0x2116 ... 0x2118:
return true;
case 0x211e ... 0x2123:
return true;
case 0x2125:
return true;
case 0x2127:
return true;
case 0x2129:
return true;
case 0x212e:
return true;
case 0x213a ... 0x213b:
return true;
case 0x2140 ... 0x2144:
return true;
case 0x214a ... 0x214d:
return true;
case 0x214f:
return true;
case 0x218a ... 0x218b:
return true;
case 0x2190 ... 0x2307:
return true;
case 0x230c ... 0x2328:
return true;
case 0x232b ... 0x2426:
return true;
case 0x2440 ... 0x244a:
return true;
case 0x249c ... 0x24e9:
return true;
case 0x2500 ... 0x2767:
return true;
case 0x2794 ... 0x27c4:
return true;
case 0x27c7 ... 0x27e5:
return true;
case 0x27f0 ... 0x2982:
return true;
case 0x2999 ... 0x29d7:
return true;
case 0x29dc ... 0x29fb:
return true;
case 0x29fe ... 0x2b73:
return true;
case 0x2b76 ... 0x2b95:
return true;
case 0x2b98 ... 0x2bff:
return true;
case 0x2ce5 ... 0x2cea:
return true;
case 0x2e80 ... 0x2e99:
return true;
case 0x2e9b ... 0x2ef3:
return true;
case 0x2f00 ... 0x2fd5:
return true;
case 0x2ff0 ... 0x2ffb:
return true;
case 0x3004:
return true;
case 0x3012 ... 0x3013:
return true;
case 0x3020:
return true;
case 0x3036 ... 0x3037:
return true;
case 0x303e ... 0x303f:
return true;
case 0x309b ... 0x309c:
return true;
case 0x3190 ... 0x3191:
return true;
case 0x3196 ... 0x319f:
return true;
case 0x31c0 ... 0x31e3:
return true;
case 0x3200 ... 0x321e:
return true;
case 0x322a ... 0x3247:
return true;
case 0x3250:
return true;
case 0x3260 ... 0x327f:
return true;
case 0x328a ... 0x32b0:
return true;
case 0x32c0 ... 0x33ff:
return true;
case 0x4dc0 ... 0x4dff:
return true;
case 0xa490 ... 0xa4c6:
return true;
case 0xa700 ... 0xa716:
return true;
case 0xa720 ... 0xa721:
return true;
case 0xa789 ... 0xa78a:
return true;
case 0xa828 ... 0xa82b:
return true;
case 0xa836 ... 0xa839:
return true;
case 0xaa77 ... 0xaa79:
return true;
case 0xab5b:
return true;
case 0xfb29:
return true;
case 0xfbb2 ... 0xfbc1:
return true;
case 0xfdfc ... 0xfdfd:
return true;
case 0xfe62:
return true;
case 0xfe64 ... 0xfe66:
return true;
case 0xfe69:
return true;
case 0xff04:
return true;
case 0xff0b:
return true;
case 0xff1c ... 0xff1e:
return true;
case 0xff3e:
return true;
case 0xff40:
return true;
case 0xff5c:
return true;
case 0xff5e:
return true;
case 0xffe0 ... 0xffe6:
return true;
case 0xffe8 ... 0xffee:
return true;
case 0xfffc ... 0xfffd:
return true;
case 0x10137 ... 0x1013f:
return true;
case 0x10179 ... 0x10189:
return true;
case 0x1018c ... 0x1018e:
return true;
case 0x10190 ... 0x1019b:
return true;
case 0x101a0:
return true;
case 0x101d0 ... 0x101fc:
return true;
case 0x10877 ... 0x10878:
return true;
case 0x10ac8:
return true;
case 0x1173f:
return true;
case 0x11fd5 ... 0x11ff1:
return true;
case 0x16b3c ... 0x16b3f:
return true;
case 0x16b45:
return true;
case 0x1bc9c:
return true;
case 0x1d000 ... 0x1d0f5:
return true;
case 0x1d100 ... 0x1d126:
return true;
case 0x1d129 ... 0x1d164:
return true;
case 0x1d16a ... 0x1d16c:
return true;
case 0x1d183 ... 0x1d184:
return true;
case 0x1d18c ... 0x1d1a9:
return true;
case 0x1d1ae ... 0x1d1e8:
return true;
case 0x1d200 ... 0x1d241:
return true;
case 0x1d245:
return true;
case 0x1d300 ... 0x1d356:
return true;
case 0x1d6c1:
return true;
case 0x1d6db:
return true;
case 0x1d6fb:
return true;
case 0x1d715:
return true;
case 0x1d735:
return true;
case 0x1d74f:
return true;
case 0x1d76f:
return true;
case 0x1d789:
return true;
case 0x1d7a9:
return true;
case 0x1d7c3:
return true;
case 0x1d800 ... 0x1d9ff:
return true;
case 0x1da37 ... 0x1da3a:
return true;
case 0x1da6d ... 0x1da74:
return true;
case 0x1da76 ... 0x1da83:
return true;
case 0x1da85 ... 0x1da86:
return true;
case 0x1e14f:
return true;
case 0x1e2ff:
return true;
case 0x1ecac:
return true;
case 0x1ecb0:
return true;
case 0x1ed2e:
return true;
case 0x1eef0 ... 0x1eef1:
return true;
case 0x1f000 ... 0x1f02b:
return true;
case 0x1f030 ... 0x1f093:
return true;
case 0x1f0a0 ... 0x1f0ae:
return true;
case 0x1f0b1 ... 0x1f0bf:
return true;
case 0x1f0c1 ... 0x1f0cf:
return true;
case 0x1f0d1 ... 0x1f0f5:
return true;
case 0x1f110 ... 0x1f16c:
return true;
case 0x1f170 ... 0x1f1ac:
return true;
case 0x1f1e6 ... 0x1f202:
return true;
case 0x1f210 ... 0x1f23b:
return true;
case 0x1f240 ... 0x1f248:
return true;
case 0x1f250 ... 0x1f251:
return true;
case 0x1f260 ... 0x1f265:
return true;
case 0x1f300 ... 0x1f6d5:
return true;
case 0x1f6e0 ... 0x1f6ec:
return true;
case 0x1f6f0 ... 0x1f6fa:
return true;
case 0x1f700 ... 0x1f773:
return true;
case 0x1f780 ... 0x1f7d8:
return true;
case 0x1f7e0 ... 0x1f7eb:
return true;
case 0x1f800 ... 0x1f80b:
return true;
case 0x1f810 ... 0x1f847:
return true;
case 0x1f850 ... 0x1f859:
return true;
case 0x1f860 ... 0x1f887:
return true;
case 0x1f890 ... 0x1f8ad:
return true;
case 0x1f900 ... 0x1f90b:
return true;
case 0x1f90d ... 0x1f971:
return true;
case 0x1f973 ... 0x1f976:
return true;
case 0x1f97a ... 0x1f9a2:
return true;
case 0x1f9a5 ... 0x1f9aa:
return true;
case 0x1f9ae ... 0x1f9ca:
return true;
case 0x1f9cd ... 0x1fa53:
return true;
case 0x1fa60 ... 0x1fa6d:
return true;
case 0x1fa70 ... 0x1fa73:
return true;
case 0x1fa78 ... 0x1fa7a:
return true;
case 0x1fa80 ... 0x1fa82:
return true;
case 0x1fa90 ... 0x1fa95:
return true;
default: return false;
}
return false;
}
END_ALLOW_CASE_RANGE

View File

@@ -409,9 +409,9 @@ calc_cell_metrics(FontGroup *fg) {
unsigned int before_cell_height = cell_height;
int cw = cell_width, ch = cell_height;
if (OPT(adjust_line_height_px) != 0) ch += OPT(adjust_line_height_px);
if (OPT(adjust_line_height_frac) != 0.f) ch *= OPT(adjust_line_height_frac);
if (OPT(adjust_line_height_frac) != 0.f) ch = (int)(ch * OPT(adjust_line_height_frac));
if (OPT(adjust_column_width_px != 0)) cw += OPT(adjust_column_width_px);
if (OPT(adjust_column_width_frac) != 0.f) cw *= OPT(adjust_column_width_frac);
if (OPT(adjust_column_width_frac) != 0.f) cw = (int)(cw * OPT(adjust_column_width_frac));
#define MAX_DIM 1000
#define MIN_WIDTH 2
#define MIN_HEIGHT 4
@@ -543,8 +543,18 @@ in_symbol_maps(FontGroup *fg, char_type ch) {
}
static ssize_t
font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
// Decides which 'font' to use for a given cell.
//
// Possible results:
// - NO_FONT
// - MISSING_FONT
// - BLANK_FONT
// - BOX_FONT
// - an index in the fonts list
static inline ssize_t
font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_fallback_font, bool *is_emoji_presentation) {
*is_fallback_font = false;
*is_emoji_presentation = false;
START_ALLOW_CASE_RANGE
ssize_t ans;
switch(cpu_cell->ch) {
@@ -552,12 +562,14 @@ START_ALLOW_CASE_RANGE
case ' ':
case '\t':
return BLANK_FONT;
case 0x2500 ... 0x2570:
case 0x2500 ... 0x2573:
case 0x2574 ... 0x259f:
case 0xe0b0:
case 0xe0b2:
case 0xe0b4:
case 0xe0b0 ... 0xe0b4:
case 0xe0b6:
case 0xe0b8: // 
case 0xe0ba: // 
case 0xe0bc: // 
case 0xe0be: // 
return BOX_FONT;
default:
ans = in_symbol_maps(fg, cpu_cell->ch);
@@ -573,7 +585,9 @@ START_ALLOW_CASE_RANGE
ans = fg->bi_font_idx; break;
}
if (ans < 0) ans = fg->medium_font_idx;
if (!has_emoji_presentation(cpu_cell, gpu_cell) && has_cell_text(fg->fonts + ans, cpu_cell)) return ans;
*is_emoji_presentation = has_emoji_presentation(cpu_cell, gpu_cell);
if (!*is_emoji_presentation && has_cell_text(fg->fonts + ans, cpu_cell)) return ans;
*is_fallback_font = true;
return fallback_font(fg, cpu_cell, gpu_cell);
}
END_ALLOW_CASE_RANGE
@@ -584,20 +598,15 @@ set_sprite(GPUCell *cell, sprite_index x, sprite_index y, sprite_index z) {
cell->sprite_x = x; cell->sprite_y = y; cell->sprite_z = z;
}
// Gives a unique (arbitrary) id to a box glyph
static inline glyph_index
box_glyph_id(char_type ch) {
START_ALLOW_CASE_RANGE
switch(ch) {
case 0x2500 ... 0x259f:
return ch - 0x2500;
case 0xe0b0:
return 0xfa;
case 0xe0b2:
return 0xfb;
case 0xe0b4:
return 0xfc;
case 0xe0b6:
return 0xfd;
return ch - 0x2500; // IDs from 0x00 to 0x9f
case 0xe0b0 ... 0xe0d4:
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
default:
return 0xff;
}
@@ -682,7 +691,7 @@ static inline void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, bool center_glyph) {
static SpritePosition* sprite_position[16];
int error = 0;
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
num_cells = MIN(arraysz(sprite_position), num_cells);
for (unsigned int i = 0; i < num_cells; i++) {
sprite_position[i] = sprite_position_for(fg, font, glyph, extra_glyphs, (uint8_t)i, &error);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return; }
@@ -717,7 +726,7 @@ typedef struct {
typedef struct {
unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells;
bool has_special_glyph;
bool has_special_glyph, is_space_ligature;
} Group;
typedef struct {
@@ -955,13 +964,11 @@ merge_groups_for_pua_space_ligature(void) {
while (G(group_idx) > 0) {
Group *g = G(groups), *g1 = G(groups) + 1;
g->num_cells += g1->num_cells;
// We dont want the space glyphs rendered because some stupid
// fonts like PowerLine dont have a space glyph
// https://github.com/kovidgoyal/kitty/issues/1225
/* g->num_glyphs += g1->num_glyphs; */
/* g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1); */
g->num_glyphs += g1->num_glyphs;
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
G(group_idx)--;
}
G(groups)->is_space_ligature = true;
}
static inline void
@@ -995,7 +1002,11 @@ render_groups(FontGroup *fg, Font *font, bool center_glyph) {
int last = -1;
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
// We dont want to render the spaces in a space ligature because
// there exist stupid fonts like Powerline that have no space glyph,
// so special case it: https://github.com/kovidgoyal/kitty/issues/1225
unsigned int num_glyphs = group->is_space_ligature ? 1 : group->num_glyphs;
render_group(fg, group->num_cells, num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
idx++;
}
}
@@ -1096,18 +1107,20 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line, index_type lnum, Cursor *cursor,
if (prev_width == 2) { prev_width = 0; continue; }
CPUCell *cpu_cell = line->cpu_cells + i;
GPUCell *gpu_cell = line->gpu_cells + i;
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell);
bool is_fallback_font, is_emoji_presentation;
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell, &is_fallback_font, &is_emoji_presentation);
if (is_private_use(cpu_cell->ch)
&& cell_font_idx != BOX_FONT
&& cell_font_idx != MISSING_FONT) {
if (
cell_font_idx != MISSING_FONT &&
((is_fallback_font && !is_emoji_presentation && is_symbol(cpu_cell->ch)) || (cell_font_idx != BOX_FONT && is_private_use(cpu_cell->ch)))
) {
unsigned int desired_cells = 1;
if (cell_font_idx > 0) {
Font *font = (fg->fonts + cell_font_idx);
glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch);
int width = get_glyph_width(font->face, glyph_id);
desired_cells = ceilf((float)width / fg->cell_width);
desired_cells = (unsigned int)ceilf((float)width / fg->cell_width);
}
unsigned int num_spaces = 0;

View File

@@ -2,6 +2,11 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
#
# NOTE: to add a new glyph, add an entry to the `box_chars` dict, then update
# the functions `font_for_cell` and `box_glyph_id` in `kitty/fonts.c`.
#
import math
from functools import partial as p
from itertools import repeat
@@ -172,6 +177,117 @@ def triangle(buf, width, height, left=True):
fill_region(buf, width, height, xlimits)
def corner_triangle(buf, width, height, corner):
if corner == 'top-right' or corner == 'bottom-left':
diagonal_y = line_equation(0, 0, width - 1, height - 1)
if corner == 'top-right':
xlimits = [(0, diagonal_y(x)) for x in range(width)]
elif corner == 'bottom-left':
xlimits = [(diagonal_y(x), height - 1) for x in range(width)]
else:
diagonal_y = line_equation(width - 1, 0, 0, height - 1)
if corner == 'top-left':
xlimits = [(0, diagonal_y(x)) for x in range(width)]
elif corner == 'bottom-right':
xlimits = [(diagonal_y(x), height - 1) for x in range(width)]
fill_region(buf, width, height, xlimits)
def antialiased_1px_line(buf, width, height, p1, p2):
# Draw an antialiased line using the Wu algorithm
x1, y1 = p1
x2, y2 = p2
dx, dy = x2 - x1, y2 - y1
off_limit = height * width
steep = abs(dx) < abs(dy)
if steep:
x1, y1, x2, y2, dx, dy = y1, x1, y2, x2, dy, dx
def p(x, y):
return y, x
else:
def p(x, y):
return x, y
if x2 < x1:
x1, x2, y1, y2 = x2, x1, y2, y1
def fpart(x):
return x - int(x)
def rfpart(x):
return 1 - fpart(x)
def putpixel(p, alpha):
x, y = p
off = int(x + y * width)
if 0 <= off < off_limit:
buf[off] = int(min(buf[off] + (alpha * 255), 255))
def draw_endpoint(pt):
x, y = pt
xend = round(x)
yend = y + grad * (xend - x)
xgap = rfpart(x + 0.5)
px, py = int(xend), int(yend)
putpixel(p(px, py), rfpart(yend) * xgap)
putpixel(p(px, py+1), fpart(yend) * xgap)
return px
grad = dy/dx
intery = y1 + rfpart(x1) * grad
xstart = draw_endpoint(p(*p1))
xend = draw_endpoint(p(*p2))
if xstart > xend:
xstart, xend = xend, xstart
xstart += 1
for x in range(xstart, xend):
y = int(intery)
putpixel(p(x, y), rfpart(intery))
putpixel(p(x, y+1), fpart(intery))
intery += grad
def antialiased_line(buf, width, height, p1, p2, level=1):
th = thickness(level)
if th < 2:
return antialiased_1px_line(buf, width, height, p1, p2)
(x1, y1), (x2, y2) = p1, p2
dh = th // 2
items = range(-dh, dh + (th % 2))
for delta in items:
antialiased_1px_line(buf, width, height, (x1, y1 + delta), (x2, y2 + delta))
def cross_line(buf, width, height, left=True, level=1):
if left:
p1, p2 = (0, 0), (width - 1, height - 1)
else:
p1, p2 = (width - 1, 0), (0, height - 1)
antialiased_line(buf, width, height, p1, p2, level=level)
def half_cross_line(buf, width, height, which='tl', level=1):
my = (height - 1) // 2
if which == 'tl':
p1 = 0, 0
p2 = width - 1, my
elif which == 'bl':
p2 = 0, height - 1
p1 = width - 1, my
elif which == 'tr':
p1 = width - 1, 0
p2 = 0, my
else:
p2 = width - 1, height - 1
p1 = 0, my
antialiased_line(buf, width, height, p1, p2, level=level)
def cubic_bezier(start, end, c1, c2):
def bezier_eq(p0, p1, p2, p3):
@@ -459,6 +575,12 @@ box_chars = {
'': [p(triangle, left=False)],
'': [D],
'': [p(D, left=False)],
'': [p(half_cross_line, which='tl'), p(half_cross_line, which='bl')],
'': [p(half_cross_line, which='tr'), p(half_cross_line, which='br')],
'': [p(corner_triangle, corner='bottom-left')],
'': [p(corner_triangle, corner='bottom-right')],
'': [p(corner_triangle, corner='top-left')],
'': [p(corner_triangle, corner='top-right')],
'': [dhline],
'': [dvline],
'': [vline, p(half_dhline, which='right')],
@@ -472,6 +594,9 @@ box_chars = {
'': [p(inner_corner, which='tl'), p(inner_corner, which='bl'), p(dvline, only='right')],
'': [p(inner_corner, which='bl'), p(inner_corner, which='br'), p(dhline, only='top')],
'': [p(inner_corner, which='tl'), p(inner_corner, which='tr'), p(dhline, only='bottom')],
'': [p(cross_line, left=False)],
'': [cross_line],
'': [cross_line, p(cross_line, left=False)],
'': [p(vblock, frac=1/2)],
'': [p(vblock, frac=1/8, gravity='bottom')],
'': [p(vblock, frac=1/4, gravity='bottom')],
@@ -551,60 +676,60 @@ def render_missing_glyph(buf, width, height):
def test_char(ch, sz=48):
# kitty +runpy "from kitty.fonts.box_drawing import test_char; import sys; test_char('XXX')"
# kitty +runpy "from kitty.fonts.box_drawing import test_char; test_char('XXX')"
from .render import display_bitmap, setup_for_testing
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
width, height = setup_for_testing('monospace', sz)[1:]
buf = bytearray(width * height)
try:
render_box_char(ch, buf, width, height)
with setup_for_testing('monospace', sz) as (_, width, height):
buf = bytearray(width * height)
try:
render_box_char(ch, buf, width, height)
def join_cells(*cells):
cells = tuple(bytes(x) for x in cells)
return concat_cells(width, height, False, cells)
def join_cells(*cells):
cells = tuple(bytes(x) for x in cells)
return concat_cells(width, height, False, cells)
rgb_data = join_cells(buf)
display_bitmap(rgb_data, width, height)
print()
finally:
set_send_sprite_to_gpu(None)
rgb_data = join_cells(buf)
display_bitmap(rgb_data, width, height)
print()
finally:
set_send_sprite_to_gpu(None)
def test_drawing(sz=48, family='monospace'):
from .render import display_bitmap, setup_for_testing
from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu
width, height = setup_for_testing(family, sz)[1:]
space = bytearray(width * height)
with setup_for_testing(family, sz) as (_, width, height):
space = bytearray(width * height)
def join_cells(cells):
cells = tuple(bytes(x) for x in cells)
return concat_cells(width, height, False, cells)
def join_cells(cells):
cells = tuple(bytes(x) for x in cells)
return concat_cells(width, height, False, cells)
def render_chr(ch):
if ch in box_chars:
cell = bytearray(len(space))
render_box_char(ch, cell, width, height)
return cell
return space
def render_chr(ch):
if ch in box_chars:
cell = bytearray(len(space))
render_box_char(ch, cell, width, height)
return cell
return space
pos = 0x2500
rows = []
space_row = join_cells(repeat(space, 32))
pos = 0x2500
rows = []
space_row = join_cells(repeat(space, 32))
try:
for r in range(10):
row = []
for i in range(16):
row.append(render_chr(chr(pos)))
row.append(space)
pos += 1
rows.append(join_cells(row))
rows.append(space_row)
rgb_data = b''.join(rows)
width *= 32
height *= len(rows)
assert len(rgb_data) == width * height * 4, '{} != {}'.format(len(rgb_data), width * height * 4)
display_bitmap(rgb_data, width, height)
finally:
set_send_sprite_to_gpu(None)
try:
for r in range(10):
row = []
for i in range(16):
row.append(render_chr(chr(pos)))
row.append(space)
pos += 1
rows.append(join_cells(row))
rows.append(space_row)
rgb_data = b''.join(rows)
width *= 32
height *= len(rows)
assert len(rgb_data) == width * height * 4, '{} != {}'.format(len(rgb_data), width * height * 4)
display_bitmap(rgb_data, width, height)
finally:
set_send_sprite_to_gpu(None)

View File

@@ -25,8 +25,29 @@ else:
current_faces = None
def coalesce_symbol_maps(maps):
if not maps:
return maps
items = tuple((k, maps[k]) for k in sorted(maps))
ans = [items[0]]
def merge(prev_item, item):
s, e = item[0]
pe = prev_item[0][1]
ans[-1] = ((prev_item[0][0], max(pe, e)), prev_item[1])
for item in items[1:]:
current_item = ans[-1]
if current_item[1] != item[1] or item[0][0] > current_item[0][1] + 1:
ans.append(item)
else:
merge(current_item, item)
return dict(ans)
def create_symbol_map(opts):
val = opts.symbol_map
val = coalesce_symbol_maps(opts.symbol_map)
family_map = {}
count = 0
for family in val.values():

View File

@@ -77,12 +77,12 @@ static FT_Library library;
static inline int
font_units_to_pixels_y(Face *self, int x) {
return ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0);
return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0);
}
static inline int
font_units_to_pixels_x(Face *self, int x) {
return ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0);
return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0);
}
@@ -140,7 +140,7 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
if (!error) {
unsigned int ch = calc_cell_height(self, false);
if (desired_height && ch != desired_height) {
FT_F26Dot6 h = floor((double)char_height * (double)desired_height / (double) ch);
FT_F26Dot6 h = (FT_F26Dot6)floor((double)char_height * (double)desired_height / (double) ch);
return set_font_size(self, 0, h, xdpi, ydpi, 0, cell_height);
}
self->char_width = char_width; self->char_height = char_height; self->xdpi = xdpi; self->ydpi = ydpi;
@@ -160,8 +160,8 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
int32_t min_diff = INT32_MAX;
if (desired_height == 0) desired_height = cell_height;
if (desired_height == 0) {
desired_height = ceil(((double)char_height / 64.) * (double)ydpi / 72.);
desired_height += ceil(0.2 * desired_height);
desired_height = (unsigned int)ceil(((double)char_height / 64.) * (double)ydpi / 72.);
desired_height += (unsigned int)ceil(0.2 * desired_height);
}
FT_Int strike_index = -1;
for (FT_Int i = 0; i < self->face->num_fixed_sizes; i++) {
@@ -190,7 +190,7 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA
FT_F26Dot6 w = (FT_F26Dot6)(ceil(fg->font_sz_in_pts * 64.0));
FT_UInt xdpi = (FT_UInt)fg->logical_dpi_x, ydpi = (FT_UInt)fg->logical_dpi_y;
if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true;
((Face*)self)->size_in_pts = fg->font_sz_in_pts;
((Face*)self)->size_in_pts = (float)fg->font_sz_in_pts;
return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height);
}
@@ -438,7 +438,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
// better with bi-cubic or lanczos, but at these small sizes I don't think
// it matters
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
int factor = ceilf(ratio);
int factor = (int)ceilf(ratio);
uint8_t *dest = calloc(4, width * cell_height);
if (dest == NULL) fatal("Out of memory");
uint8_t *d = dest;
@@ -499,8 +499,8 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int
ans->rows = bitmap->rows;
ans->pixel_mode = bitmap->pixel_mode;
if (ans->width > num_cells * cell_width + 2) downsample_bitmap(ans, num_cells * cell_width, cell_height);
ans->bitmap_top = (float)self->face->glyph->bitmap_top / ans->factor;
ans->bitmap_left = (float)self->face->glyph->bitmap_left / ans->factor;
ans->bitmap_top = (int)((float)self->face->glyph->bitmap_top / ans->factor);
ans->bitmap_left = (int)((float)self->face->glyph->bitmap_left / ans->factor);
detect_right_edge(ans);
return true;
}

8
kitty/glfw-wrapper.c generated
View File

@@ -245,8 +245,8 @@ load_glfw(const char* path) {
*(void **) (&glfwGetKeyName_impl) = dlsym(handle, "glfwGetKeyName");
if (glfwGetKeyName_impl == NULL) fail("Failed to load glfw function glfwGetKeyName with error: %s", dlerror());
*(void **) (&glfwGetKeyScancode_impl) = dlsym(handle, "glfwGetKeyScancode");
if (glfwGetKeyScancode_impl == NULL) fail("Failed to load glfw function glfwGetKeyScancode with error: %s", dlerror());
*(void **) (&glfwGetNativeKeyForKey_impl) = dlsym(handle, "glfwGetNativeKeyForKey");
if (glfwGetNativeKeyForKey_impl == NULL) fail("Failed to load glfw function glfwGetNativeKeyForKey with error: %s", dlerror());
*(void **) (&glfwGetKey_impl) = dlsym(handle, "glfwGetKey");
if (glfwGetKey_impl == NULL) fail("Failed to load glfw function glfwGetKey with error: %s", dlerror());
@@ -389,6 +389,8 @@ load_glfw(const char* path) {
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
*(void **) (&glfwSetApplicationWillFinishLaunching_impl) = dlsym(handle, "glfwSetApplicationWillFinishLaunching");
*(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent");
*(void **) (&glfwCocoaRequestRenderFrame_impl) = dlsym(handle, "glfwCocoaRequestRenderFrame");
@@ -401,7 +403,7 @@ load_glfw(const char* path) {
*(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString");
*(void **) (&glfwGetXKBScancode_impl) = dlsym(handle, "glfwGetXKBScancode");
*(void **) (&glfwGetNativeKeyForName_impl) = dlsym(handle, "glfwGetNativeKeyForName");
*(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent");

334
kitty/glfw-wrapper.h generated
View File

@@ -1,6 +1,13 @@
//
// THIS FILE IS GENERATED BY glfw.py
//
// SAVE YOURSELF SOME TIME, DO NOT MANUALLY EDIT
//
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "monotonic.h"
@@ -18,7 +25,7 @@
* backward-compatible.
* @ingroup init
*/
#define GLFW_VERSION_MINOR 3
#define GLFW_VERSION_MINOR 4
/*! @brief The revision number of the GLFW library.
*
* This is incremented when a bug fix release is made that does not contain any
@@ -54,6 +61,7 @@
/*! @} */
/*! @defgroup hat_state Joystick hat states
* @brief Joystick hat states.
*
* See [joystick hat input](@ref joystick_hat) for how these are used.
*
@@ -725,74 +733,87 @@
#define GLFW_CLIENT_API 0x00022001
/*! @brief Context client API major version hint and attribute.
*
* Context client API major version [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context client API major version [hint](@ref GLFW_CONTEXT_VERSION_MAJOR_hint)
* and [attribute](@ref GLFW_CONTEXT_VERSION_MAJOR_attrib).
*/
#define GLFW_CONTEXT_VERSION_MAJOR 0x00022002
/*! @brief Context client API minor version hint and attribute.
*
* Context client API minor version [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context client API minor version [hint](@ref GLFW_CONTEXT_VERSION_MINOR_hint)
* and [attribute](@ref GLFW_CONTEXT_VERSION_MINOR_attrib).
*/
#define GLFW_CONTEXT_VERSION_MINOR 0x00022003
/*! @brief Context client API revision number hint and attribute.
*
* Context client API revision number [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context client API revision number
* [attribute](@ref GLFW_CONTEXT_REVISION_attrib).
*/
#define GLFW_CONTEXT_REVISION 0x00022004
/*! @brief Context robustness hint and attribute.
*
* Context client API revision number [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context client API revision number [hint](@ref GLFW_CONTEXT_ROBUSTNESS_hint)
* and [attribute](@ref GLFW_CONTEXT_ROBUSTNESS_attrib).
*/
#define GLFW_CONTEXT_ROBUSTNESS 0x00022005
/*! @brief OpenGL forward-compatibility hint and attribute.
*
* OpenGL forward-compatibility [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* OpenGL forward-compatibility [hint](@ref GLFW_OPENGL_FORWARD_COMPAT_hint)
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
*/
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
/*! @brief OpenGL debug context hint and attribute.
*
* OpenGL debug context [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
*/
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
/*! @brief OpenGL profile hint and attribute.
*
* OpenGL profile [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
* [attribute](@ref GLFW_OPENGL_PROFILE_attrib).
*/
#define GLFW_OPENGL_PROFILE 0x00022008
/*! @brief Context flush-on-release hint and attribute.
*
* Context flush-on-release [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context flush-on-release [hint](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_hint) and
* [attribute](@ref GLFW_CONTEXT_RELEASE_BEHAVIOR_attrib).
*/
#define GLFW_CONTEXT_RELEASE_BEHAVIOR 0x00022009
/*! @brief Context error suppression hint and attribute.
*
* Context error suppression [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context error suppression [hint](@ref GLFW_CONTEXT_NO_ERROR_hint) and
* [attribute](@ref GLFW_CONTEXT_NO_ERROR_attrib).
*/
#define GLFW_CONTEXT_NO_ERROR 0x0002200A
/*! @brief Context creation API hint and attribute.
*
* Context creation API [hint](@ref GLFW_CLIENT_API_hint) and
* [attribute](@ref GLFW_CLIENT_API_attrib).
* Context creation API [hint](@ref GLFW_CONTEXT_CREATION_API_hint) and
* [attribute](@ref GLFW_CONTEXT_CREATION_API_attrib).
*/
#define GLFW_CONTEXT_CREATION_API 0x0002200B
/*! @brief Window content area scaling window
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
*/
#define GLFW_SCALE_TO_MONITOR 0x0002200C
/*! @brief macOS specific
* [window hint](@ref GLFW_COCOA_RETINA_FRAMEBUFFER_hint).
*/
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
/*! @brief macOS specific
* [window hint](@ref GLFW_COCOA_FRAME_NAME_hint).
*/
#define GLFW_COCOA_FRAME_NAME 0x00023002
/*! @brief macOS specific
* [window hint](@ref GLFW_COCOA_GRAPHICS_SWITCHING_hint).
*/
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
/*! @brief X11 specific
* [window hint](@ref GLFW_X11_CLASS_NAME_hint).
*/
#define GLFW_X11_CLASS_NAME 0x00024001
/*! @brief X11 specific
* [window hint](@ref GLFW_X11_CLASS_NAME_hint).
*/
#define GLFW_X11_INSTANCE_NAME 0x00024002
#define GLFW_WAYLAND_APP_ID 0x00025001
@@ -855,11 +876,22 @@ typedef enum {
/*! @addtogroup init
* @{ */
/*! @brief Joystick hat buttons init hint.
*
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
*/
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
#define GLFW_DEBUG_KEYBOARD 0x00050002
#define GLFW_ENABLE_JOYSTICKS 0x00050003
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
*/
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_MENUBAR_hint).
*/
#define GLFW_COCOA_MENUBAR 0x00051002
/*! @} */
@@ -930,17 +962,48 @@ typedef struct GLFWwindow GLFWwindow;
*
* @since Added in version 3.1.
*
* @ingroup cursor
* @ingroup input
*/
typedef struct GLFWcursor GLFWcursor;
/*! @brief The function signature for error callbacks.
typedef struct GLFWkeyevent
{
// The [keyboard key](@ref keys) that was pressed or released.
int key;
// The platform-specific identifier of the key.
int native_key;
// The event action. Either `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
int action;
// Bit field describing which [modifier keys](@ref mods) were held down.
int mods;
// UTF-8 encoded text generated by this key event or empty string or NULL
const char *text;
// Used for Input Method events. Zero for normal key events.
// A value of 1 means the pre-edit text for the input event has been changed.
// A value of 2 means the text should be committed.
int ime_state;
} GLFWkeyevent;
/*! @brief The function pointer type for error callbacks.
*
* This is the function signature for error callback functions.
* This is the function pointer type for error callbacks. An error callback
* function has the following signature:
* @code
* void callback_name(int error_code, const char* description)
* @endcode
*
* @param[in] error An [error code](@ref errors).
* @param[in] error_code An [error code](@ref errors). Future releases may add
* more error codes.
* @param[in] description A UTF-8 encoded string describing the error.
*
* @pointer_lifetime The error description string is valid until the callback
* function returns.
*
* @sa @ref error_handling
* @sa @ref glfwSetErrorCallback
*
@@ -950,9 +1013,13 @@ typedef struct GLFWcursor GLFWcursor;
*/
typedef void (* GLFWerrorfun)(int,const char*);
/*! @brief The function signature for window position callbacks.
/*! @brief The function pointer type for window position callbacks.
*
* This is the function signature for window position callback functions.
* This is the function pointer type for window position callbacks. A window
* position callback function has the following signature:
* @code
* void callback_name(GLFWwindow* window, int xpos, int ypos)
* @endcode
*
* @param[in] window The window that was moved.
* @param[in] xpos The new x-coordinate, in screen coordinates, of the
@@ -969,9 +1036,13 @@ typedef void (* GLFWerrorfun)(int,const char*);
*/
typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int);
/*! @brief The function signature for window resize callbacks.
/*! @brief The function pointer type for window size callbacks.
*
* This is the function signature for window size callback functions.
* This is the function pointer type for window size callbacks. A window size
* callback function has the following signature:
* @code
* void callback_name(GLFWwindow* window, int width, int height)
* @endcode
*
* @param[in] window The window that was resized.
* @param[in] width The new width, in screen coordinates, of the window.
@@ -987,9 +1058,13 @@ typedef void (* GLFWwindowposfun)(GLFWwindow*,int,int);
*/
typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int);
/*! @brief The function signature for window close callbacks.
/*! @brief The function pointer type for window close callbacks.
*
* This is the function signature for window close callback functions.
* This is the function pointer type for window close callbacks. A window
* close callback function has the following signature:
* @code
* void function_name(GLFWwindow* window)
* @endcode
*
* @param[in] window The window that the user attempted to close.
*
@@ -1003,9 +1078,13 @@ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int);
*/
typedef void (* GLFWwindowclosefun)(GLFWwindow*);
/*! @brief The function signature for window content refresh callbacks.
/*! @brief The function pointer type for window content refresh callbacks.
*
* This is the function signature for window refresh callback functions.
* This is the function pointer type for window content refresh callbacks.
* A window content refresh callback function has the following signature:
* @code
* void function_name(GLFWwindow* window);
* @endcode
*
* @param[in] window The window whose content needs to be refreshed.
*
@@ -1019,9 +1098,13 @@ typedef void (* GLFWwindowclosefun)(GLFWwindow*);
*/
typedef void (* GLFWwindowrefreshfun)(GLFWwindow*);
/*! @brief The function signature for window focus/defocus callbacks.
/*! @brief The function pointer type for window focus callbacks.
*
* This is the function signature for window focus callback functions.
* This is the function pointer type for window focus callbacks. A window
* focus callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int focused)
* @endcode
*
* @param[in] window The window that gained or lost input focus.
* @param[in] focused `true` if the window was given input focus, or
@@ -1054,10 +1137,13 @@ typedef void (* GLFWwindowfocusfun)(GLFWwindow*,int);
typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool);
/*! @brief The function signature for window iconify/restore callbacks.
/*! @brief The function pointer type for window iconify callbacks.
*
* This is the function signature for window iconify/restore callback
* functions.
* This is the function pointer type for window iconify callbacks. A window
* iconify callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int iconified)
* @endcode
*
* @param[in] window The window that was iconified or restored.
* @param[in] iconified `true` if the window was iconified, or
@@ -1072,10 +1158,13 @@ typedef void (* GLFWwindowocclusionfun)(GLFWwindow*, bool);
*/
typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int);
/*! @brief The function signature for window maximize/restore callbacks.
/*! @brief The function pointer type for window maximize callbacks.
*
* This is the function signature for window maximize/restore callback
* functions.
* This is the function pointer type for window maximize callbacks. A window
* maximize callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int maximized)
* @endcode
*
* @param[in] window The window that was maximized or restored.
* @param[in] iconified `true` if the window was maximized, or
@@ -1090,10 +1179,13 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int);
*/
typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
/*! @brief The function signature for framebuffer resize callbacks.
/*! @brief The function pointer type for framebuffer size callbacks.
*
* This is the function signature for framebuffer resize callback
* functions.
* This is the function pointer type for framebuffer size callbacks.
* A framebuffer size callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int width, int height)
* @endcode
*
* @param[in] window The window whose framebuffer was resized.
* @param[in] width The new width, in pixels, of the framebuffer.
@@ -1108,10 +1200,13 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
*/
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
/*! @brief The function signature for window content scale callbacks.
/*! @brief The function pointer type for window content scale callbacks.
*
* This is the function signature for window content scale callback
* functions.
* This is the function pointer type for window content scale callbacks.
* A window content scale callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, float xscale, float yscale)
* @endcode
*
* @param[in] window The window whose content scale changed.
* @param[in] xscale The new x-axis content scale of the window.
@@ -1126,14 +1221,19 @@ typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
*/
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
/*! @brief The function signature for mouse button callbacks.
/*! @brief The function pointer type for mouse button callbacks.
*
* This is the function signature for mouse button callback functions.
* This is the function pointer type for mouse button callback functions.
* A mouse button callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int button, int action, int mods)
* @endcode
*
* @param[in] window The window that received the event.
* @param[in] button The [mouse button](@ref buttons) that was pressed or
* released.
* @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`.
* @param[in] action One of `GLFW_PRESS` or `GLFW_RELEASE`. Future releases
* may add more actions.
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
*
@@ -1147,9 +1247,13 @@ typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
*/
typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int);
/*! @brief The function signature for cursor position callbacks.
/*! @brief The function pointer type for cursor position callbacks.
*
* This is the function signature for cursor position callback functions.
* This is the function pointer type for cursor position callbacks. A cursor
* position callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, double xpos, double ypos);
* @endcode
*
* @param[in] window The window that received the event.
* @param[in] xpos The new cursor x-coordinate, relative to the left edge of
@@ -1166,12 +1270,16 @@ typedef void (* GLFWmousebuttonfun)(GLFWwindow*,int,int,int);
*/
typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double);
/*! @brief The function signature for cursor enter/leave callbacks.
/*! @brief The function pointer type for cursor enter/leave callbacks.
*
* This is the function signature for cursor enter/leave callback functions.
* This is the function pointer type for cursor enter/leave callbacks.
* A cursor enter/leave callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int entered)
* @endcode
*
* @param[in] window The window that received the event.
* @param[in] entered `true` if the cursor entered the window's client
* @param[in] entered `true` if the cursor entered the window's content
* area, or `false` if it left it.
*
* @sa @ref cursor_enter
@@ -1183,16 +1291,20 @@ typedef void (* GLFWcursorposfun)(GLFWwindow*,double,double);
*/
typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
/*! @brief The function signature for scroll callbacks.
/*! @brief The function pointer type for scroll callbacks.
*
* This is the function signature for scroll callback functions.
* This is the function pointer type for scroll callbacks. A scroll callback
* function has the following signature:
* @code
* void function_name(GLFWwindow* window, double xoffset, double yoffset)
* @endcode
*
* @param[in] window The window that received the event.
* @param[in] xoffset The scroll offset along the x-axis.
* @param[in] yoffset The scroll offset along the y-axis.
* @param[in] flags A bit-mask providing extra data about the event.
* flags & 1 will be true if and only if the offset values are "high-precision".
* Typically pixel values. Otherwise the offset values are number of lines.
* flags & 1 will be true if and only if the offset values are "high-precision",
* typically pixel values. Otherwise the offset values are number of lines.
* (flags >> 1) & 7 will have value 1 for the start of momentum scrolling,
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
@@ -1208,9 +1320,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
*/
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
/*! @brief The function signature for key callbacks.
/*! @brief The function pointer type for key callbacks.
*
* This is the function signature for key callback functions.
* This is the function pointer type for key callbacks. A keyboard
* key callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int key, int native_key, int action, int mods)
* @endcode
* The semantics of this function are that the key that is interacted with on the
* keyboard is reported, and the text, if any generated by the key is reported.
* So, for example, if on a US-ASCII keyboard the user presses Shift+= GLFW
@@ -1219,15 +1335,8 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
* the "s" key will generate text "o" and GLFW_KEY_O.
*
* @param[in] window The window that received the event.
* @param[in] key The [keyboard key](@ref keys) that was pressed or released.
* @param[in] scancode The system-specific scancode of the key.
* @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
* @param[in] Used for Input Method events. Zero for normal key events.
* A value of 1 means the pre-edit text for the input event has been changed.
* A value of 2 means the text should be committed.
* @param[in] ev The key event, see GLFWkeyevent. The data in this event is only valid for
* the lifetime of the callback.
*
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
@@ -1241,16 +1350,23 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
*
* @ingroup input
*/
typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, int);
typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
/*! @brief The function signature for file drop callbacks.
/*! @brief The function pointer type for path drop callbacks.
*
* This is the function signature for file drop callbacks.
* This is the function pointer type for path drop callbacks. A path drop
* callback function has the following signature:
* @code
* void function_name(GLFWwindow* window, int path_count, const char* paths[])
* @endcode
*
* @param[in] window The window that received the event.
* @param[in] count The number of dropped files.
* @param[in] path_count The number of dropped paths.
* @param[in] paths The UTF-8 encoded file and/or directory path names.
*
* @pointer_lifetime The path array and its strings are valid until the
* callback function returns.
*
* @sa @ref path_drop
* @sa @ref glfwSetDropCallback
*
@@ -1258,17 +1374,21 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, i
*
* @ingroup input
*/
typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**);
typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]);
typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);
/*! @brief The function signature for monitor configuration callbacks.
/*! @brief The function pointer type for monitor configuration callbacks.
*
* This is the function signature for monitor configuration callback functions.
* This is the function pointer type for monitor configuration callbacks.
* A monitor callback function has the following signature:
* @code
* void function_name(GLFWmonitor* monitor, int event)
* @endcode
*
* @param[in] monitor The monitor that was connected or disconnected.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
* values reserved for future use.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future
* releases may add more events.
*
* @sa @ref monitor_event
* @sa @ref glfwSetMonitorCallback
@@ -1279,14 +1399,17 @@ typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);
*/
typedef void (* GLFWmonitorfun)(GLFWmonitor*,int);
/*! @brief The function signature for joystick configuration callbacks.
/*! @brief The function pointer type for joystick configuration callbacks.
*
* This is the function signature for joystick configuration callback
* functions.
* This is the function pointer type for joystick configuration callbacks.
* A joystick configuration callback function has the following signature:
* @code
* void function_name(int jid, int event)
* @endcode
*
* @param[in] jid The joystick that was connected or disconnected.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
* values reserved for future use.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Future
* releases may add more events.
*
* @sa @ref joystick_event
* @sa @ref glfwSetJoystickCallback
@@ -1373,6 +1496,8 @@ typedef struct GLFWgammaramp
*
* @since Added in version 2.1.
* @glfw3 Removed format and bytes-per-pixel members.
*
* @ingroup window
*/
typedef struct GLFWimage
{
@@ -1395,6 +1520,8 @@ typedef struct GLFWimage
* @sa @ref glfwGetGamepadState
*
* @since Added in version 3.3.
*
* @ingroup input
*/
typedef struct GLFWgamepadstate
{
@@ -1448,13 +1575,14 @@ typedef struct GLFWgamepadstate
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
typedef void (*GLFWDBusnotificationcreatedfun)(unsigned long long, uint32_t, void*);
typedef void (*GLFWDBusnotificationactivatedfun)(uint32_t, const char*);
typedef int (*glfwInit_func)(void);
typedef int (*glfwInit_func)(monotonic_t);
glfwInit_func glfwInit_impl;
#define glfwInit glfwInit_impl
@@ -1466,11 +1594,11 @@ typedef void (*glfwStopMainLoop_func)(void);
glfwStopMainLoop_func glfwStopMainLoop_impl;
#define glfwStopMainLoop glfwStopMainLoop_impl
typedef unsigned long long (*glfwAddTimer_func)(double, bool, GLFWuserdatafun, void *, GLFWuserdatafun);
typedef unsigned long long (*glfwAddTimer_func)(monotonic_t, bool, GLFWuserdatafun, void *, GLFWuserdatafun);
glfwAddTimer_func glfwAddTimer_impl;
#define glfwAddTimer glfwAddTimer_impl
typedef void (*glfwUpdateTimer_func)(unsigned long long, double, bool);
typedef void (*glfwUpdateTimer_func)(unsigned long long, monotonic_t, bool);
glfwUpdateTimer_func glfwUpdateTimer_impl;
#define glfwUpdateTimer glfwUpdateTimer_impl
@@ -1638,7 +1766,7 @@ typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*);
glfwGetWindowContentScale_func glfwGetWindowContentScale_impl;
#define glfwGetWindowContentScale glfwGetWindowContentScale_impl
typedef double (*glfwGetDoubleClickInterval_func)(GLFWwindow*);
typedef monotonic_t (*glfwGetDoubleClickInterval_func)(GLFWwindow*);
glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl;
#define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl
@@ -1762,9 +1890,9 @@ typedef const char* (*glfwGetKeyName_func)(int, int);
glfwGetKeyName_func glfwGetKeyName_impl;
#define glfwGetKeyName glfwGetKeyName_impl
typedef int (*glfwGetKeyScancode_func)(int);
glfwGetKeyScancode_func glfwGetKeyScancode_impl;
#define glfwGetKeyScancode glfwGetKeyScancode_impl
typedef int (*glfwGetNativeKeyForKey_func)(int);
glfwGetNativeKeyForKey_func glfwGetNativeKeyForKey_impl;
#define glfwGetNativeKeyForKey glfwGetNativeKeyForKey_impl
typedef int (*glfwGetKey_func)(GLFWwindow*, int);
glfwGetKey_func glfwGetKey_impl;
@@ -1890,11 +2018,11 @@ typedef const char* (*glfwGetClipboardString_func)(GLFWwindow*);
glfwGetClipboardString_func glfwGetClipboardString_impl;
#define glfwGetClipboardString glfwGetClipboardString_impl
typedef double (*glfwGetTime_func)(void);
typedef monotonic_t (*glfwGetTime_func)(void);
glfwGetTime_func glfwGetTime_impl;
#define glfwGetTime glfwGetTime_impl
typedef void (*glfwSetTime_func)(double);
typedef void (*glfwSetTime_func)(monotonic_t);
glfwSetTime_func glfwSetTime_impl;
#define glfwSetTime glfwSetTime_impl
@@ -1962,6 +2090,10 @@ typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReo
glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl;
#define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl
typedef GLFWapplicationwillfinishlaunchingfun (*glfwSetApplicationWillFinishLaunching_func)(GLFWapplicationwillfinishlaunchingfun);
glfwSetApplicationWillFinishLaunching_func glfwSetApplicationWillFinishLaunching_impl;
#define glfwSetApplicationWillFinishLaunching glfwSetApplicationWillFinishLaunching_impl
typedef void (*glfwGetCocoaKeyEquivalent_func)(int, int, void*, void*);
glfwGetCocoaKeyEquivalent_func glfwGetCocoaKeyEquivalent_impl;
#define glfwGetCocoaKeyEquivalent glfwGetCocoaKeyEquivalent_impl
@@ -1986,9 +2118,9 @@ typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*);
glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl;
#define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl
typedef int (*glfwGetXKBScancode_func)(const char*, int);
glfwGetXKBScancode_func glfwGetXKBScancode_impl;
#define glfwGetXKBScancode glfwGetXKBScancode_impl
typedef int (*glfwGetNativeKeyForName_func)(const char*, int);
glfwGetNativeKeyForName_func glfwGetNativeKeyForName_impl;
#define glfwGetNativeKeyForName glfwGetNativeKeyForName_impl
typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc);
glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl;

View File

@@ -7,6 +7,7 @@
#include "state.h"
#include "glfw_tests.h"
#include "fonts.h"
#include "monotonic.h"
#include <structmember.h>
#include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w, bool);
@@ -17,7 +18,7 @@ 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);
extern size_t cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz);
extern double cocoa_cursor_blink_interval(void);
extern monotonic_t cocoa_cursor_blink_interval(void);
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
@@ -83,7 +84,7 @@ log_event(const char *format, ...) {
{
va_list vl;
fprintf(stderr, "[%.4f] ", glfwGetTime());
fprintf(stderr, "[%.4f] ", monotonic_t_to_s_double(glfwGetTime()));
va_start(vl, format);
vfprintf(stderr, format, vl);
va_end(vl);
@@ -231,14 +232,14 @@ refresh_callback(GLFWwindow *w) {
static int mods_at_last_key_or_button_event = 0;
static void
key_callback(GLFWwindow *w, int key, int scancode, int action, int mods, const char* text, int state) {
key_callback(GLFWwindow *w, GLFWkeyevent *ev) {
if (!set_callback_window(w)) return;
mods_at_last_key_or_button_event = mods;
mods_at_last_key_or_button_event = ev->mods;
global_state.callback_os_window->cursor_blink_zero_time = monotonic();
if (key >= 0 && key <= GLFW_KEY_LAST) {
global_state.callback_os_window->is_key_pressed[key] = action == GLFW_RELEASE ? false : true;
if (ev->key >= 0 && ev->key <= GLFW_KEY_LAST) {
global_state.callback_os_window->is_key_pressed[ev->key] = ev->action == GLFW_RELEASE ? false : true;
}
if (is_window_ready_for_callbacks()) on_key_input(key, scancode, action, mods, text, state);
if (is_window_ready_for_callbacks()) on_key_input(ev);
global_state.callback_os_window = NULL;
request_tick_callback();
}
@@ -248,7 +249,7 @@ cursor_enter_callback(GLFWwindow *w, int entered) {
if (!set_callback_window(w)) return;
if (entered) {
show_mouse_cursor(w);
double now = monotonic();
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
if (is_window_ready_for_callbacks()) enter_event();
request_tick_callback();
@@ -261,7 +262,7 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
mods_at_last_key_or_button_event = mods;
double now = monotonic();
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) {
global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false;
@@ -275,7 +276,7 @@ static void
cursor_pos_callback(GLFWwindow *w, double x, double y) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
double now = monotonic();
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
global_state.callback_os_window->cursor_blink_zero_time = now;
global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio;
@@ -289,7 +290,7 @@ static void
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) {
if (!set_callback_window(w)) return;
show_mouse_cursor(w);
double now = monotonic();
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags);
request_tick_callback();
@@ -308,7 +309,7 @@ window_focus_callback(GLFWwindow *w, int focused) {
focus_in_event();
global_state.callback_os_window->last_focused_counter = ++focus_counter;
}
double now = monotonic();
monotonic_t now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
global_state.callback_os_window->cursor_blink_zero_time = now;
if (is_window_ready_for_callbacks()) {
@@ -450,7 +451,7 @@ toggle_maximized_for_os_window(OSWindow *w) {
#ifdef __APPLE__
static int
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED, unsigned long flags) {
filter_option(int key UNUSED, int mods, unsigned int native_key UNUSED, unsigned long flags) {
if ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) {
if (OPT(macos_option_as_alt) == 3) return 1;
if (cocoa_alt_option_key_pressed(flags)) return 1;
@@ -460,20 +461,19 @@ filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED, unsigned l
static GLFWwindow *application_quit_canary = NULL;
static int
static bool
on_application_reopen(int has_visible_windows) {
if (has_visible_windows) return true;
set_cocoa_pending_action(NEW_OS_WINDOW, NULL);
// Without unjam wait_for_events() blocks until the next event
return false;
}
static int
static bool
intercept_cocoa_fullscreen(GLFWwindow *w) {
if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return 0;
if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return false;
toggle_fullscreen_for_os_window(global_state.callback_os_window);
global_state.callback_os_window = NULL;
return 1;
return true;
}
#endif
@@ -517,6 +517,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
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
@@ -548,8 +549,13 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
if (!common_context) common_context = application_quit_canary;
#endif
GLFWwindow *temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
GLFWwindow *temp_window = NULL;
if (!global_state.is_wayland) {
// On Wayland windows dont get a content scale until they receive an enterEvent anyway
// which wont happen until the event loop ticks, so using a temp window is useless.
temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
}
float xscale, yscale;
double xdpi, ydpi;
get_window_content_scale(temp_window, &xscale, &yscale, &xdpi, &ydpi);
@@ -564,8 +570,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
// is no startup notification in Wayland anyway. It amazes me that anyone
// uses Wayland as anything other than a butt for jokes.
if (global_state.is_wayland) glfwWindowHint(GLFW_VISIBLE, true);
GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window);
glfwDestroyWindow(temp_window); temp_window = NULL;
GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, temp_window ? temp_window : common_context);
if (temp_window) { glfwDestroyWindow(temp_window); temp_window = NULL; }
if (glfw_window == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; }
glfwMakeContextCurrent(glfw_window);
if (is_first_window) {
@@ -590,9 +596,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
PyObject *ret = PyObject_CallFunction(load_programs, "O", is_semi_transparent ? Py_True : Py_False);
if (ret == NULL) return NULL;
Py_DECREF(ret);
#ifdef __APPLE__
cocoa_create_global_menu();
#endif
#define CC(dest, shape) {\
if (!dest##_cursor) { \
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
@@ -602,10 +605,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
#undef CC
if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window);
if (OPT(cursor_blink_interval) < 0) {
OPT(cursor_blink_interval) = 0.5;
OPT(cursor_blink_interval) = ms_to_monotonic_t(500ll);
#ifdef __APPLE__
double cbi = cocoa_cursor_blink_interval();
if (cbi >= 0) OPT(cursor_blink_interval) = cbi / 2000.0;
monotonic_t cbi = cocoa_cursor_blink_interval();
if (cbi >= 0) OPT(cursor_blink_interval) = cbi / 2;
#endif
}
is_first_window = false;
@@ -655,7 +658,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window), OPT(macos_window_resizable));
} else log_error("Failed to load glfwGetCocoaWindow");
#endif
double now = monotonic();
monotonic_t now = monotonic();
w->is_focused = true;
w->cursor_blink_zero_time = now;
w->last_mouse_activity_at = now;
@@ -791,7 +794,7 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
glfwDBusSetUserNotificationHandler(dbus_user_notification_activated);
}
#endif
PyObject *ans = glfwInit() ? Py_True: Py_False;
PyObject *ans = glfwInit(monotonic_start_time) ? Py_True: Py_False;
if (ans == Py_True) {
OSWindow w = {0};
set_os_window_dpi(&w);
@@ -815,8 +818,8 @@ get_physical_dpi(GLFWmonitor *m) {
if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; }
const GLFWvidmode *vm = glfwGetVideoMode(m);
if (vm == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get video mode for monitor"); return NULL; }
float dpix = vm->width / (width / 25.4);
float dpiy = vm->height / (height / 25.4);
float dpix = (float)(vm->width / (width / 25.4));
float dpiy = (float)(vm->height / (height / 25.4));
return Py_BuildValue("ff", dpix, dpiy);
}
@@ -829,9 +832,9 @@ glfw_get_physical_dpi(PYNOARG) {
static PyObject*
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
int key, scancode;
if (!PyArg_ParseTuple(args, "ii", &key, &scancode)) return NULL;
return Py_BuildValue("s", glfwGetKeyName(key, scancode));
int key, native_key;
if (!PyArg_ParseTuple(args, "ii", &key, &native_key)) return NULL;
return Py_BuildValue("s", glfwGetKeyName(key, native_key));
}
static PyObject*
@@ -854,9 +857,9 @@ get_clipboard_string(PYNOARG) {
void
ring_audio_bell(OSWindow *w UNUSED) {
static double last_bell_at = -1;
double now = monotonic();
if (now - last_bell_at <= 0.1) return;
static monotonic_t last_bell_at = -1;
monotonic_t now = monotonic();
if (now - last_bell_at <= ms_to_monotonic_t(100ll)) return;
last_bell_at = now;
#ifdef __APPLE__
if (w->handle) {
@@ -1139,12 +1142,12 @@ dbus_send_notification(PyObject *self UNUSED, PyObject *args) {
#endif
id_type
add_main_loop_timer(double interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback) {
add_main_loop_timer(monotonic_t interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback) {
return glfwAddTimer(interval, repeats, callback, callback_data, free_callback);
}
void
update_main_loop_timer(id_type timer_id, double interval, bool enabled) {
update_main_loop_timer(id_type timer_id, monotonic_t interval, bool enabled) {
glfwUpdateTimer(timer_id, interval, enabled);
}

View File

@@ -31,9 +31,9 @@ static void* empty_thread_main(void* data UNUSED)
return 0;
}
static void key_callback(GLFWwindow *w UNUSED, int key, int scancode UNUSED, int action, int mods UNUSED, const char* text UNUSED, int state UNUSED)
static void key_callback(GLFWwindow *w UNUSED, GLFWkeyevent *ev)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
if (ev->key == GLFW_KEY_ESCAPE && ev->action == GLFW_PRESS) {
glfwSetWindowShouldClose(w, true);
wakeup_main_loop();
}

View File

@@ -121,7 +121,7 @@ trim_predicate(Image *img) {
static int
oldest_last(const void* a, const void *b) {
double ans = ((Image*)(b))->atime - ((Image*)(a))->atime;
monotonic_t ans = ((Image*)(b))->atime - ((Image*)(a))->atime;
return ans < 0 ? -1 : (ans == 0 ? 0 : 1);
}

View File

@@ -6,6 +6,7 @@
#pragma once
#include "data-types.h"
#include "monotonic.h"
typedef struct {
unsigned char action, transmission_type, compressed, delete_action;
@@ -50,7 +51,7 @@ typedef struct {
ImageRef *refs;
size_t refcnt, refcap;
double atime;
monotonic_t atime;
size_t used_storage;
} Image;

View File

@@ -26,7 +26,7 @@ key_to_bytes(int glfw_key, bool smkx, bool extended, int mods, int action) {
#define SPECIAL_INDEX(key) ((key & 0x7f) | ( (mods & 0xF) << 7))
#define IS_ALT_MODS(mods) (mods == GLFW_MOD_ALT || mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))
typedef struct { int mods, scancode; } NativeKey;
typedef struct { int mods, native_key; } NativeKey;
static NativeKey *native_special_keys = NULL;
static size_t native_special_keys_capacity = 0, native_special_keys_count = 0;
@@ -39,7 +39,7 @@ set_special_key_combo(int glfw_key, int mods, bool is_native) {
if (native_special_keys == NULL) fatal("Out of memory");
}
native_special_keys[native_special_keys_count].mods = mods;
native_special_keys[native_special_keys_count++].scancode = glfw_key;
native_special_keys[native_special_keys_count++].native_key = glfw_key;
} else {
uint16_t key = key_map[glfw_key];
if (key != UINT8_MAX) {
@@ -68,6 +68,7 @@ is_modifier_key(int key) {
case GLFW_KEY_RIGHT_CONTROL:
case GLFW_KEY_LEFT_SUPER:
case GLFW_KEY_RIGHT_SUPER:
case GLFW_KEY_CAPS_LOCK:
return true;
default:
return false;
@@ -96,7 +97,7 @@ is_ascii_control_char(char c) {
}
static inline bool
check_if_special(int key, int mods, int scancode) {
check_if_special(int key, int mods, int native_key) {
uint16_t qkey = (0 <= key && key < (ssize_t)arraysz(key_map)) ? key_map[key] : UINT8_MAX;
bool special = false;
if (qkey != UINT8_MAX) {
@@ -104,7 +105,8 @@ check_if_special(int key, int mods, int scancode) {
special = needs_special_handling[qkey];
}
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
if (native_key == native_special_keys[i].native_key && mods == native_special_keys[i].mods)
special = true;
}
return special;
}
@@ -121,23 +123,26 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
#define debug(...) if (OPT(debug_keyboard)) printf(__VA_ARGS__);
void
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
on_key_input(GLFWkeyevent *ev) {
Window *w = active_window();
int action = ev->action, native_key = ev->native_key, key = ev->key, mods = ev->mods;
const char *text = ev->text ? ev->text : "";
debug("on_key_input: glfw key: %d native_code: 0x%x action: %s mods: 0x%x text: '%s' state: %d ",
key, scancode,
key, native_key,
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
mods, text, state);
mods, text, ev->ime_state);
if (!w) { debug("no active window, ignoring\n"); return; }
if (OPT(mouse_hide_wait) < 0 && !is_modifier_key(key)) hide_mouse(global_state.callback_os_window);
Screen *screen = w->render_data.screen;
switch(state) {
switch(ev->ime_state) {
case 1: // update pre-edit text
update_ime_position(global_state.callback_os_window, w, screen);
screen_draw_overlay_text(screen, text);
debug("updated pre-edit text: '%s'\n", text);
return;
case 2: // commit text
if (text && *text) {
if (*text) {
schedule_write_to_child(w->id, 1, text, strlen(text));
debug("committed pre-edit text: %s\n", text);
} else debug("committed pre-edit text: (null)\n");
@@ -158,13 +163,13 @@ on_key_input(int key, int scancode, int action, int mods, const char* text, int
if (
action != GLFW_RELEASE &&
key != GLFW_KEY_LEFT_SHIFT && key != GLFW_KEY_RIGHT_SHIFT && key != GLFW_KEY_LEFT_ALT && key != GLFW_KEY_RIGHT_ALT && key != GLFW_KEY_LEFT_CONTROL && key != GLFW_KEY_RIGHT_CONTROL
) call_boss(process_sequence, "iiii", key, scancode, action, mods);
) call_boss(process_sequence, "iiii", key, native_key, action, mods);
return;
}
bool has_text = text && !is_ascii_control_char(text[0]);
bool has_text = text[0] && !is_ascii_control_char(text[0]);
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
if (check_if_special(key, mods, scancode)) {
PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", key, scancode, action, mods);
if (check_if_special(key, mods, native_key)) {
PyObject *ret = PyObject_CallMethod(global_state.boss, "dispatch_special_key", "iiii", key, native_key, action, mods);
if (ret == NULL) { PyErr_Print(); }
else {
bool consumed = ret == Py_True;
@@ -225,9 +230,9 @@ PYWRAP1(key_for_native_key_name) {
int case_sensitive = 0;
PA("s|p", &name, &case_sensitive);
#ifndef __APPLE__
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
int scancode = glfwGetXKBScancode(name, case_sensitive);
if (scancode) return Py_BuildValue("i", scancode);
if (glfwGetNativeKeyForName) { // if this function is called before GLFW is initialized glfwGetNativeKeyForName will be NULL
int native_key = glfwGetNativeKeyForName(name, case_sensitive);
if (native_key) return Py_BuildValue("i", native_key);
}
#endif
Py_RETURN_NONE;

View File

@@ -264,7 +264,7 @@ def key_to_bytes(key, smkx, extended, mods, action):
return bytes(data)
def interpret_key_event(key, scancode, mods, window, action):
def interpret_key_event(key, native_key, mods, window, action):
screen = window.screen
if (
action == defines.GLFW_PRESS or
@@ -275,17 +275,17 @@ def interpret_key_event(key, scancode, mods, window, action):
return b''
def get_shortcut(keymap, mods, key, scancode):
def get_shortcut(keymap, mods, key, native_key):
mods &= 0b1111
ans = keymap.get((mods, False, key))
if ans is None:
ans = keymap.get((mods, True, scancode))
ans = keymap.get((mods, True, native_key))
return ans
def shortcut_matches(s, mods, key, scancode):
def shortcut_matches(s, mods, key, native_key):
mods &= 0b1111
q = scancode if s[1] else key
q = native_key if s[1] else key
return s[0] & 0b1111 == mods & 0b1111 and s[2] == q

View File

@@ -6,6 +6,7 @@
*/
#include "data-types.h"
#include "monotonic.h"
#define CMD_BUF_SZ 2048
@@ -34,13 +35,13 @@ add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) {
}
static inline bool
read_response(int fd, double timeout, PyObject *ans) {
read_response(int fd, monotonic_t timeout, PyObject *ans) {
static char buf[CMD_BUF_SZ];
size_t pos = 0;
enum ReadState {START, STARTING_ESC, P, AT, K, I, T, T2, Y, HYPHEN, C, M, BODY, TRAILING_ESC};
enum ReadState state = START;
char ch;
double end_time = monotonic() + timeout;
monotonic_t end_time = monotonic() + timeout;
while(monotonic() <= end_time) {
ssize_t len = read(fd, &ch, 1);
if (len == 0) continue;
@@ -94,7 +95,7 @@ read_command_response(PyObject *self UNUSED, PyObject *args) {
int fd;
PyObject *ans;
if (!PyArg_ParseTuple(args, "idO!", &fd, &timeout, &PyList_Type, &ans)) return NULL;
if (!read_response(fd, timeout, ans)) return NULL;
if (!read_response(fd, s_double_to_monotonic_t(timeout), ans)) return NULL;
Py_RETURN_NONE;
}

216
kitty/launch.py Normal file
View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
from kitty.cli import parse_args
from kitty.fast_data_types import set_clipboard_string
from kitty.utils import set_primary_selection
def options_spec():
if not hasattr(options_spec, 'ans'):
OPTIONS = '''
--window-title --title
The title to set for the new window. By default, title is controlled by the
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.
--type
type=choices
default=window
choices=window,tab,os-window,overlay,background,clipboard,primary
Where to launch the child process, in a new kitty window in the current tab,
a new tab, or a new OS window or an overlay over the current window.
Note that if the current window already has an overlay, then it will
open a new window. The value of none means the process will be
run in the background. The values clipboard and primary are meant
to work with :option:`launch --stdin-source` to copy data to the system
clipboard or primary selection.
--keep-focus
type=bool-set
Keep the focus on the currently active window instead of switching
to the newly opened window.
--cwd
The working directory for the newly launched child. Use the special value
:code:`current` to use the working directory of the currently active window.
--env
type=list
Environment variables to set in the child process. Can be specified multiple
times to set different environment variables.
Syntax: :italic:`name=value`.
--copy-colors
type=bool-set
Set the colors of the newly created window to be the same as the colors in the
currently active window.
--copy-cmdline
type=bool-set
Ignore any specified command line and instead use the command line from the
currently active window.
--copy-env
type=bool-set
Copy the environment variables from the currently active window into the
newly launched child process.
--location
type=choices
default=last
choices=first,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.
--allow-remote-control
type=bool-set
Programs running in this window can control kitty (if remote control is
enabled). Note that any program with the right level of permissions can still
write to the pipes of any other program on the same computer and therefore can
control kitty. It can, however, be useful to block programs running on other
computers (for example, over ssh) or as other users.
--stdin-source
type=choices
default=none
choices=none,@selection,@screen,@screen_scrollback,@alternate,@alternate_scrollback
Pass the screen contents as :code:`STDIN` to the child process. @selection is
the currently selected text. @screen is the contents of the currently active
window. @screen_scrollback is the same as @screen, but includes the scrollback
buffer as well. @alternate is the secondary screen of the current active
window. For example if you run a full screen terminal application, the
secondary screen will be the screen you return to when quitting the
application.
--stdin-add-formatting
type=bool-set
When using :option:`launch --stdin-source` add formatting escape codes, without this
only plain text will be sent.
--stdin-add-line-wrap-markers
type=bool-set
When using :option:`launch --stdin-source` add a carriage return at every line wrap
location (where long lines are wrapped at screen edges). This is useful if you
want to pipe to program that wants to duplicate the screen layout of the
screen.
'''
options_spec.ans = OPTIONS
return options_spec.ans
def parse_launch_args(args=None):
args = list(args or ())
try:
opts, args = parse_args(args=args, ospec=options_spec)
except SystemExit as e:
raise ValueError from e
return opts, args
def get_env(opts, active_child):
env = {}
if opts.copy_env and active_child:
env.update(active_child.foreground_environ)
for x in opts.env:
parts = x.split('=', 1)
if len(parts) == 2:
env[parts[0]] = parts[1]
return env
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')
if opts.tab_title:
tab.set_title(opts.tab_title)
elif opts.type == 'os-window':
oswid = boss.add_os_window()
tm = boss.os_window_map[oswid]
tab = tm.new_tab(empty_tab=True)
if opts.tab_title:
tab.set_title(opts.tab_title)
else:
tab = target_tab or boss.active_tab
return tab
def launch(boss, opts, args, target_tab=None):
active = boss.active_window_for_cwd
active_child = getattr(active, 'child', None)
env = get_env(opts, active_child)
kw = {
'allow_remote_control': opts.allow_remote_control
}
if opts.cwd:
if opts.cwd == 'current':
if active_child:
kw['cwd_from'] = active_child.pid_for_cwd
else:
kw['cwd'] = opts.cwd
if opts.location != 'last':
kw['location'] = opts.location
if opts.window_title:
kw['override_title'] = opts.window_title
if opts.copy_colors and active:
kw['copy_colors_from'] = active
cmd = args or None
if opts.copy_cmdline and active_child:
cmd = active_child.foreground_cmdline
if cmd:
final_cmd = []
for x in cmd:
if x == '@selection' and active and not opts.copy_cmdline:
s = boss.data_for_at(active, x)
if s:
x = s
final_cmd.append(x)
kw['cmd'] = final_cmd
if opts.type == 'overlay' and active and not active.overlay_window_id:
kw['overlay_for'] = active.id
if opts.stdin_source and opts.stdin_source != 'none':
q = opts.stdin_source
if opts.stdin_add_line_wrap_markers:
q += '_wrap'
penv, stdin = boss.process_stdin_source(window=active, stdin=q)
if stdin:
kw['stdin'] = stdin
if penv:
env.update(penv)
if opts.type == 'background':
boss.run_background_process(kw['cmd'], cwd=kw.get('cwd'), cwd_from=kw.get('cwd_from'), env=env or None, stdin=kw.get('stdin'))
elif opts.type in ('clipboard', 'primary'):
if 'stdin' in kw:
func = set_clipboard_string if opts.type == 'clipboard' else set_primary_selection
func(kw['stdin'])
else:
tab = tab_for_window(boss, opts, target_tab)
new_window = tab.new_window(env=env or None, **kw)
if opts.keep_focus and active:
boss.set_active_window(active, switch_os_window_if_needed=True)
return new_window

View File

@@ -279,8 +279,8 @@ 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:
active_window_idx = current_active_window_idx + 1
if location == 'neighbor' 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 == 'first':
active_window_idx = 0
if active_window_idx is not None:

View File

@@ -214,7 +214,9 @@ def read_shell_environment(opts=None):
with os.fdopen(master, 'rb') as stdout, os.fdopen(slave, 'wb'):
raw = b''
from subprocess import TimeoutExpired
while True:
from time import monotonic
start_time = monotonic()
while monotonic() - start_time < 1.5:
try:
ret = p.wait(0.01)
except TimeoutExpired:
@@ -223,7 +225,10 @@ def read_shell_environment(opts=None):
raw += stdout.read()
if ret is not None:
break
if p.returncode == 0:
if p.returncode is None:
log_error('Timed out waiting for shell to quit while reading shell environment')
p.kill()
elif p.returncode == 0:
while True:
try:
x = stdout.read()
@@ -244,24 +249,32 @@ def read_shell_environment(opts=None):
return read_shell_environment.ans
def get_editor_from_env(shell_env):
for var in ('VISUAL', 'EDITOR'):
editor = shell_env.get(var)
if editor:
if 'PATH' in shell_env:
import shlex
editor_cmd = shlex.split(editor)
if not os.path.isabs(editor_cmd[0]):
editor_cmd[0] = shutil.which(editor_cmd[0], path=shell_env['PATH'])
if editor_cmd[0]:
editor = ' '.join(map(shlex.quote, editor_cmd))
else:
editor = None
if editor:
return editor
def setup_environment(opts, args):
extra_env = opts.env.copy()
if opts.editor == '.':
if 'EDITOR' not in os.environ:
editor = get_editor_from_env(os.environ)
if not editor:
shell_env = read_shell_environment(opts)
if 'EDITOR' in shell_env:
editor = shell_env['EDITOR']
if 'PATH' in shell_env:
import shlex
editor_cmd = shlex.split(editor)
if not os.path.isabs(editor_cmd[0]):
editor_cmd[0] = shutil.which(editor_cmd[0], path=shell_env['PATH'])
if editor_cmd[0]:
editor = ' '.join(map(shlex.quote, editor_cmd))
else:
editor = None
if editor:
os.environ['EDITOR'] = editor
editor = get_editor_from_env(shell_env)
if editor:
os.environ['EDITOR'] = editor
else:
os.environ['EDITOR'] = opts.editor
if args.listen_on:

89
kitty/monotonic.h Normal file
View File

@@ -0,0 +1,89 @@
/*
* monotonic.h
* Copyright (C) 2019 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include <stdint.h>
#include <time.h>
#define MONOTONIC_T_MAX INT64_MAX
#define MONOTONIC_T_MIN INT64_MIN
typedef int64_t monotonic_t;
static inline monotonic_t calc_nano_time(struct timespec time) {
int64_t result = (monotonic_t)time.tv_sec;
result *= 1000LL;
result *= 1000LL;
result *= 1000LL;
result += (monotonic_t)time.tv_nsec;
return result;
}
static inline struct timespec calc_time(monotonic_t nsec) {
struct timespec result;
result.tv_sec = nsec / (1000LL * 1000LL * 1000LL);
result.tv_nsec = nsec % (1000LL * 1000LL * 1000LL);
return result;
}
static inline monotonic_t s_double_to_monotonic_t(double time) {
time *= 1000.0;
time *= 1000.0;
time *= 1000.0;
return (monotonic_t)time;
}
static inline monotonic_t ms_double_to_monotonic_t(double time) {
time *= 1000.0;
time *= 1000.0;
return (monotonic_t)time;
}
static inline monotonic_t s_to_monotonic_t(monotonic_t time) {
return time * 1000ll * 1000ll * 1000ll;
}
static inline monotonic_t ms_to_monotonic_t(monotonic_t time) {
return time * 1000ll * 1000ll;
}
static inline int monotonic_t_to_ms(monotonic_t time) {
return (int)(time / 1000ll / 1000ll);
}
static inline double monotonic_t_to_s_double(monotonic_t time) {
return (double)time / 1000.0 / 1000.0 / 1000.0;
}
#ifdef MONOTONIC_START_MODULE
monotonic_t monotonic_start_time = 0;
#else
extern monotonic_t monotonic_start_time;
#endif
static inline monotonic_t monotonic_(void) {
struct timespec ts = {0};
#ifdef CLOCK_HIGHRES
clock_gettime(CLOCK_HIGHRES, &ts);
#elif CLOCK_MONOTONIC_RAW
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
#else
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
return calc_nano_time(ts);
}
static inline monotonic_t monotonic(void) {
return monotonic_() - monotonic_start_time;
}
static inline void init_monotonic(void) {
monotonic_start_time = monotonic_();
}

View File

@@ -13,6 +13,7 @@
#include <math.h>
#include "glfw-wrapper.h"
#include "control-codes.h"
#include "monotonic.h"
static MouseShape mouse_cursor_shape = BEAM;
typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
@@ -292,8 +293,8 @@ HANDLER(handle_move_event) {
bool handle_in_kitty = !in_tracking_mode || has_terminal_select_modifiers;
if (handle_in_kitty) {
if (screen->selection.in_progress && button == GLFW_MOUSE_BUTTON_LEFT) {
double now = monotonic();
if ((now - w->last_drag_scroll_at) >= 0.02 || mouse_cell_changed) {
monotonic_t now = monotonic();
if ((now - w->last_drag_scroll_at) >= ms_to_monotonic_t(20ll) || mouse_cell_changed) {
update_drag(false, w, false, 0);
w->last_drag_scroll_at = now;
}
@@ -338,7 +339,7 @@ distance(double x1, double y1, double x2, double y2) {
HANDLER(add_click) {
ClickQueue *q = &w->click_queue;
if (q->length == CLICK_QUEUE_SZ) { memmove(q->clicks, q->clicks + 1, sizeof(Click) * (CLICK_QUEUE_SZ - 1)); q->length--; }
double now = monotonic();
monotonic_t now = monotonic();
#define N(n) (q->clicks[q->length - n])
N(0).at = now; N(0).button = button; N(0).modifiers = modifiers; N(0).x = w->mouse_pos.x; N(0).y = w->mouse_pos.y;
q->length++;
@@ -608,16 +609,16 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
if (is_high_resolution) {
yoffset *= OPT(touch_scroll_multiplier);
if (yoffset * global_state.callback_os_window->pending_scroll_pixels < 0) {
global_state.callback_os_window->pending_scroll_pixels = 0; // change of direction
if (yoffset * screen->pending_scroll_pixels < 0) {
screen->pending_scroll_pixels = 0; // change of direction
}
double pixels = global_state.callback_os_window->pending_scroll_pixels + yoffset;
double pixels = screen->pending_scroll_pixels + yoffset;
if (fabs(pixels) < global_state.callback_os_window->fonts_data->cell_height) {
global_state.callback_os_window->pending_scroll_pixels = pixels;
screen->pending_scroll_pixels = pixels;
return;
}
s = (int)round(pixels) / (int)global_state.callback_os_window->fonts_data->cell_height;
global_state.callback_os_window->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height;
screen->pending_scroll_pixels = pixels - s * (int) global_state.callback_os_window->fonts_data->cell_height;
} else {
if (screen->linebuf == screen->main_linebuf || !screen->modes.mouse_tracking_mode) {
// Only use wheel_scroll_multiplier if we are scrolling kitty scrollback or in mouse
@@ -632,7 +633,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
// apparently on cocoa some mice generate really small yoffset values
// when scrolling slowly https://github.com/kovidgoyal/kitty/issues/1238
if (s == 0 && yoffset != 0) s = yoffset > 0 ? 1 : -1;
global_state.callback_os_window->pending_scroll_pixels = 0;
screen->pending_scroll_pixels = 0;
}
if (s == 0) return;
bool upwards = s > 0;

View File

@@ -9,6 +9,7 @@
#include "screen.h"
#include "graphics.h"
#include "charsets.h"
#include "monotonic.h"
#include <time.h>
extern PyTypeObject Screen_Type;
@@ -1196,7 +1197,7 @@ end:
}
static inline void
do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, double now, PyObject *dump_callback DUMP_UNUSED) {
do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, monotonic_t now, PyObject *dump_callback DUMP_UNUSED) {
enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING};
enum STATE state = START;
size_t read_buf_pos = 0;
@@ -1272,7 +1273,7 @@ FNAME(parse_bytes)(PyObject UNUSED *self, PyObject *args) {
void
FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, double now) {
FNAME(parse_worker)(Screen *screen, PyObject *dump_callback, monotonic_t now) {
#ifdef DUMP_COMMANDS
if (screen->read_buf_sz) {
Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, screen->read_buf_sz)); PyErr_Clear();

View File

@@ -1538,7 +1538,7 @@ screen_update_cell_data(Screen *self, void *address, FONTS_DATA_HANDLE fonts_dat
static inline bool
is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) {
return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false;
return start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y);
}
static inline void
@@ -1730,7 +1730,7 @@ static PyObject*
set_pending_timeout(Screen *self, PyObject *val) {
if (!PyFloat_Check(val)) { PyErr_SetString(PyExc_TypeError, "timeout must be a float"); return NULL; }
PyObject *ans = PyFloat_FromDouble(self->pending_mode.wait_time);
self->pending_mode.wait_time = PyFloat_AS_DOUBLE(val);
self->pending_mode.wait_time = s_double_to_monotonic_t(PyFloat_AS_DOUBLE(val));
return ans;
}
@@ -2267,6 +2267,13 @@ wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
}
static PyObject*
screen_is_emoji_presentation_base(PyObject UNUSED *self, PyObject *code_) {
unsigned long code = PyLong_AsUnsignedLong(code_);
if (is_emoji_presentation_base(code)) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
#define MND(name, args) {#name, (PyCFunction)name, args, #name},
#define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O)
@@ -2380,6 +2387,7 @@ PyTypeObject Screen_Type = {
static PyMethodDef module_methods[] = {
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""},
{"is_emoji_presentation_base", (PyCFunction)screen_is_emoji_presentation_base, METH_O, ""},
{"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""},
{NULL} /* Sentinel */
};

View File

@@ -7,6 +7,7 @@
#pragma once
#include "graphics.h"
#include "monotonic.h"
typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType;
@@ -66,6 +67,7 @@ typedef struct {
unsigned int columns, lines, margin_top, margin_bottom, charset, scrolled_by, last_selection_scrolled_by;
unsigned int last_rendered_cursor_x, last_rendered_cursor_y;
double pending_scroll_pixels;
CellPixelSize cell_size;
OverlayLine overlay_line;
id_type window_id;
@@ -86,13 +88,13 @@ typedef struct {
bool *tabstops, *main_tabstops, *alt_tabstops;
ScreenModes modes;
ColorProfile *color_profile;
double start_visual_bell_at;
monotonic_t start_visual_bell_at;
uint32_t parser_buf[PARSER_BUF_SZ];
unsigned int parser_state, parser_text_start, parser_buf_pos;
bool parser_has_pending_text;
uint8_t read_buf[READ_BUF_SZ], *write_buf;
double new_input_at;
monotonic_t new_input_at;
size_t read_buf_sz, write_buf_sz, write_buf_used;
pthread_mutex_t read_buf_lock, write_buf_lock;
@@ -101,7 +103,7 @@ typedef struct {
struct {
size_t capacity, used, stop_buf_pos;
uint8_t *buf;
double activated_at, wait_time;
monotonic_t activated_at, wait_time;
int state;
uint8_t stop_buf[32];
} pending_mode;
@@ -110,8 +112,8 @@ typedef struct {
} Screen;
void parse_worker(Screen *screen, PyObject *dump_callback, double now);
void parse_worker_dump(Screen *screen, PyObject *dump_callback, double now);
void parse_worker(Screen *screen, PyObject *dump_callback, monotonic_t now);
void parse_worker_dump(Screen *screen, PyObject *dump_callback, monotonic_t now);
void screen_align(Screen*);
void screen_restore_cursor(Screen *);
void screen_save_cursor(Screen *);

View File

@@ -502,7 +502,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index);
bind_vertex_array(vao_idx);
float current_inactive_text_alpha = (!can_be_focused || screen->cursor_render_info.is_focused) && is_active_window ? 1.0 : OPT(inactive_text_alpha);
float current_inactive_text_alpha = (!can_be_focused || screen->cursor_render_info.is_focused) && is_active_window ? 1.0f : (float)OPT(inactive_text_alpha);
set_cell_uniforms(current_inactive_text_alpha);
GLfloat w = (GLfloat)screen->columns * dx, h = (GLfloat)screen->lines * dy;
// The scissor limits below are calculated to ensure that they do not

View File

@@ -99,6 +99,19 @@ add_tab(id_type os_window_id) {
return 0;
}
static inline void
create_gpu_resources_for_window(Window *w) {
w->render_data.vao_idx = create_cell_vao();
w->render_data.gvao_idx = create_graphics_vao();
}
static inline void
release_gpu_resources_for_window(Window *w) {
remove_vao(w->render_data.vao_idx); remove_vao(w->render_data.gvao_idx);
w->render_data.vao_idx = 0; w->render_data.gvao_idx = 0;
}
static inline id_type
add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
WITH_TAB(os_window_id, tab_id);
@@ -108,8 +121,7 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
tab->windows[tab->num_windows].id = ++global_state.window_id_counter;
tab->windows[tab->num_windows].visible = true;
tab->windows[tab->num_windows].title = title;
tab->windows[tab->num_windows].render_data.vao_idx = create_cell_vao();
tab->windows[tab->num_windows].render_data.gvao_idx = create_graphics_vao();
create_gpu_resources_for_window(&tab->windows[tab->num_windows]);
Py_INCREF(tab->windows[tab->num_windows].title);
return tab->windows[tab->num_windows++].id;
END_WITH_TAB;
@@ -133,7 +145,7 @@ update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyO
static inline void
destroy_window(Window *w) {
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
remove_vao(w->render_data.vao_idx); remove_vao(w->render_data.gvao_idx);
release_gpu_resources_for_window(w);
}
static inline void
@@ -149,6 +161,70 @@ remove_window(id_type os_window_id, id_type tab_id, id_type id) {
END_WITH_TAB;
}
typedef struct {
unsigned int num_windows, capacity;
Window *windows;
} DetachedWindows;
static DetachedWindows detached_windows = {0};
static void
add_detached_window(Window *w) {
ensure_space_for(&detached_windows, windows, Window, detached_windows.num_windows + 1, capacity, 8, true);
memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window));
}
static inline void
detach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) {
if (tab->windows[i].id == id) {
make_os_window_context_current(osw);
release_gpu_resources_for_window(&tab->windows[i]);
add_detached_window(tab->windows + i);
zero_at_i(tab->windows, i);
remove_i_from_array(tab->windows, i, tab->num_windows);
break;
}
}
END_WITH_TAB;
}
static inline void
resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
if (screen) {
screen->cell_size.width = os_window->fonts_data->cell_width;
screen->cell_size.height = os_window->fonts_data->cell_height;
screen_dirty_sprite_positions(screen);
if (has_graphics) screen_rescale_images(screen);
}
}
static inline void
attach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < detached_windows.num_windows; i++) {
if (detached_windows.windows[i].id == id) {
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
Window *w = tab->windows + tab->num_windows++;
memcpy(w, detached_windows.windows + i, sizeof(Window));
zero_at_i(detached_windows.windows, i);
remove_i_from_array(detached_windows.windows, i, detached_windows.num_windows);
make_os_window_context_current(osw);
create_gpu_resources_for_window(w);
if (
w->render_data.screen->cell_size.width != osw->fonts_data->cell_width ||
w->render_data.screen->cell_size.height != osw->fonts_data->cell_height
) resize_screen(osw, w->render_data.screen, true);
else screen_dirty_sprite_positions(w->render_data.screen);
break;
}
}
END_WITH_TAB;
}
static inline void
destroy_tab(Tab *tab) {
for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id);
@@ -300,9 +376,14 @@ color_as_int(PyObject *color) {
#undef I
}
static inline double
repaint_delay(PyObject *val) {
return (double)(PyLong_AsUnsignedLong(val)) / 1000.0;
static inline monotonic_t
parse_s_double_to_monotonic_t(PyObject *val) {
return s_double_to_monotonic_t(PyFloat_AsDouble(val));
}
static inline monotonic_t
parse_ms_long_to_monotonic_t(PyObject *val) {
return ms_to_monotonic_t(PyLong_AsUnsignedLong(val));
}
static int kitty_mod = 0;
@@ -360,6 +441,11 @@ set_special_keys(PyObject *dict) {
}}
}
static inline float
PyFloat_AsFloat(PyObject *o) {
return (float)PyFloat_AsDouble(o);
}
PYWRAP0(next_window_id) {
return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1);
}
@@ -390,28 +476,28 @@ PYWRAP1(set_options) {
#define S(name, convert) SS(name, OPT(name), convert)
SS(kitty_mod, kitty_mod, PyLong_AsLong);
S(hide_window_decorations, PyObject_IsTrue);
S(visual_bell_duration, PyFloat_AsDouble);
S(visual_bell_duration, parse_s_double_to_monotonic_t);
S(enable_audio_bell, PyObject_IsTrue);
S(focus_follows_mouse, PyObject_IsTrue);
S(cursor_blink_interval, PyFloat_AsDouble);
S(cursor_stop_blinking_after, PyFloat_AsDouble);
S(background_opacity, PyFloat_AsDouble);
S(dim_opacity, PyFloat_AsDouble);
S(cursor_blink_interval, parse_s_double_to_monotonic_t);
S(cursor_stop_blinking_after, parse_s_double_to_monotonic_t);
S(background_opacity, PyFloat_AsFloat);
S(dim_opacity, PyFloat_AsFloat);
S(dynamic_background_opacity, PyObject_IsTrue);
S(inactive_text_alpha, PyFloat_AsDouble);
S(window_padding_width, PyFloat_AsDouble);
S(inactive_text_alpha, PyFloat_AsFloat);
S(window_padding_width, PyFloat_AsFloat);
S(scrollback_pager_history_size, PyLong_AsUnsignedLong);
S(cursor_shape, PyLong_AsLong);
S(url_style, PyLong_AsUnsignedLong);
S(tab_bar_edge, PyLong_AsLong);
S(mouse_hide_wait, PyFloat_AsDouble);
S(mouse_hide_wait, parse_s_double_to_monotonic_t);
S(wheel_scroll_multiplier, PyFloat_AsDouble);
S(touch_scroll_multiplier, PyFloat_AsDouble);
S(open_url_modifiers, convert_mods);
S(rectangle_select_modifiers, convert_mods);
S(terminal_select_modifiers, convert_mods);
S(click_interval, PyFloat_AsDouble);
S(resize_debounce_time, PyFloat_AsDouble);
S(click_interval, parse_s_double_to_monotonic_t);
S(resize_debounce_time, parse_s_double_to_monotonic_t);
S(url_color, color_as_int);
S(background, color_as_int);
S(foreground, color_as_int);
@@ -420,8 +506,8 @@ PYWRAP1(set_options) {
default_color = 0;
S(inactive_border_color, color_as_int);
S(bell_border_color, color_as_int);
S(repaint_delay, repaint_delay);
S(input_delay, repaint_delay);
S(repaint_delay, parse_ms_long_to_monotonic_t);
S(input_delay, parse_ms_long_to_monotonic_t);
S(sync_to_monitor, PyObject_IsTrue);
S(close_on_child_death, PyObject_IsTrue);
S(window_alert_on_bell, PyObject_IsTrue);
@@ -431,7 +517,7 @@ PYWRAP1(set_options) {
S(macos_show_window_title_in, window_title_in);
S(macos_window_resizable, PyObject_IsTrue);
S(macos_hide_from_tasks, PyObject_IsTrue);
S(macos_thicken_font, PyFloat_AsDouble);
S(macos_thicken_font, PyFloat_AsFloat);
S(tab_bar_min_tabs, PyLong_AsUnsignedLong);
S(disable_ligatures, PyLong_AsLong);
S(resize_draw_strategy, PyLong_AsLong);
@@ -675,16 +761,6 @@ PYWRAP1(global_font_size) {
return Py_BuildValue("d", global_state.font_sz_in_pts);
}
static inline void
resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
if (screen) {
screen->cell_size.width = os_window->fonts_data->cell_width;
screen->cell_size.height = os_window->fonts_data->cell_height;
screen_dirty_sprite_positions(screen);
if (has_graphics) screen_rescale_images(screen);
}
}
PYWRAP1(os_window_font_size) {
id_type os_window_id;
int force = 0;
@@ -743,6 +819,8 @@ PYWRAP0(destroy_global_data) {
THREE_ID_OBJ(update_window_title)
THREE_ID(remove_window)
THREE_ID(detach_window)
THREE_ID(attach_window)
PYWRAP1(resolve_key_mods) { int mods; PA("ii", &kitty_mod, &mods); return PyLong_FromLong(resolve_mods(mods)); }
PYWRAP1(add_tab) { return PyLong_FromUnsignedLongLong(add_tab(PyLong_AsUnsignedLongLong(args))); }
PYWRAP1(add_window) { PyObject *title; id_type a, b; PA("KKO", &a, &b, &title); return PyLong_FromUnsignedLongLong(add_window(a, b, title)); }
@@ -770,6 +848,8 @@ static PyMethodDef module_methods[] = {
MW(update_window_title, METH_VARARGS),
MW(remove_tab, METH_VARARGS),
MW(remove_window, METH_VARARGS),
MW(detach_window, METH_VARARGS),
MW(attach_window, METH_VARARGS),
MW(set_active_tab, METH_VARARGS),
MW(set_active_window, METH_VARARGS),
MW(swap_tabs, METH_VARARGS),
@@ -795,6 +875,15 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */
};
static void
finalize(void) {
while(detached_windows.num_windows--) {
destroy_window(&detached_windows.windows[detached_windows.num_windows]);
}
if (detached_windows.windows) free(detached_windows.windows);
detached_windows.capacity = 0;
}
bool
init_state(PyObject *module) {
global_state.font_sz_in_pts = 11.0;
@@ -808,6 +897,10 @@ init_state(PyObject *module) {
if (PyStructSequence_InitType2(&RegionType, &region_desc) != 0) return false;
Py_INCREF((PyObject *) &RegionType);
PyModule_AddObject(module, "Region", (PyObject *) &RegionType);
if (Py_AtExit(finalize) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the state at exit handler");
return false;
}
return true;
}
// }}}

View File

@@ -7,6 +7,7 @@
#pragma once
#include "data-types.h"
#include "screen.h"
#include "monotonic.h"
#define OPT(name) global_state.opts.name
@@ -14,7 +15,8 @@ typedef enum { LEFT_EDGE, TOP_EDGE, RIGHT_EDGE, BOTTOM_EDGE } Edge;
typedef enum { RESIZE_DRAW_STATIC, RESIZE_DRAW_SCALED, RESIZE_DRAW_BLANK, RESIZE_DRAW_SIZE } ResizeDrawStrategy;
typedef struct {
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier, touch_scroll_multiplier;
monotonic_t visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval;
double wheel_scroll_multiplier, touch_scroll_multiplier;
bool enable_audio_bell;
CursorShape cursor_shape;
unsigned int open_url_modifiers;
@@ -24,7 +26,7 @@ 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;
double repaint_delay, input_delay;
monotonic_t repaint_delay, input_delay;
bool focus_follows_mouse, 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;
@@ -44,7 +46,7 @@ typedef struct {
bool close_on_child_death;
bool window_alert_on_bell;
bool debug_keyboard;
double resize_debounce_time;
monotonic_t resize_debounce_time;
MouseShape pointer_shape_when_grabbed;
} Options;
@@ -59,7 +61,7 @@ typedef struct {
} WindowGeometry;
typedef struct {
double at;
monotonic_t at;
int button, modifiers;
double x, y;
} Click;
@@ -83,7 +85,7 @@ typedef struct {
} mouse_pos;
WindowGeometry geometry;
ClickQueue click_queue;
double last_drag_scroll_at;
monotonic_t last_drag_scroll_at;
} Window;
typedef struct {
@@ -114,7 +116,7 @@ typedef struct {
enum RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY };
typedef struct {
double last_resize_event_at;
monotonic_t last_resize_event_at;
bool in_progress;
bool from_os_notification;
bool os_says_resize_complete;
@@ -134,7 +136,7 @@ typedef struct {
ScreenRenderData tab_bar_render_data;
bool tab_bar_data_updated;
bool is_focused;
double cursor_blink_zero_time, last_mouse_activity_at;
monotonic_t cursor_blink_zero_time, last_mouse_activity_at;
double mouse_x, mouse_y;
double logical_dpi_x, logical_dpi_y, font_sz_in_pts;
bool mouse_button_pressed[20];
@@ -149,9 +151,8 @@ typedef struct {
float background_opacity;
FONTS_DATA_HANDLE fonts_data;
id_type temp_font_group_id;
double pending_scroll_pixels;
enum RENDER_STATE render_state;
double last_render_frame_received_at;
monotonic_t last_render_frame_received_at;
id_type last_focused_counter;
ssize_t gvao_idx;
} OSWindow;
@@ -239,8 +240,8 @@ void request_frame_render(OSWindow *w);
void request_tick_callback(void);
typedef void (* timer_callback_fun)(id_type, void*);
typedef void (* tick_callback_fun)(void*);
id_type add_main_loop_timer(double interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback);
id_type add_main_loop_timer(monotonic_t interval, bool repeats, timer_callback_fun callback, void *callback_data, timer_callback_fun free_callback);
void remove_main_loop_timer(id_type timer_id);
void update_main_loop_timer(id_type timer_id, double interval, bool enabled);
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);

View File

@@ -18,7 +18,7 @@ from .rgb import alpha_blend, color_from_int
TabBarData = namedtuple('TabBarData', 'title is_active needs_attention')
DrawData = namedtuple(
'DrawData', 'leading_spaces sep trailing_spaces bell_on_tab'
' bell_fg alpha active_bg inactive_bg default_bg title_template')
' bell_fg alpha active_fg active_bg inactive_fg inactive_bg default_bg title_template')
def as_rgb(x):
@@ -41,7 +41,7 @@ def draw_title(draw_data, screen, tab, index):
screen.draw(title)
def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, index):
def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, index, is_last):
if draw_data.leading_spaces:
screen.draw(' ' * draw_data.leading_spaces)
draw_title(draw_data, screen, tab, index)
@@ -60,7 +60,7 @@ def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length, in
return end
def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index):
def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index, is_last):
tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg
fade_colors = [as_rgb(color_as_int(alpha_blend(tab_bg, draw_data.default_bg, alpha))) for alpha in draw_data.alpha]
for bg in fade_colors:
@@ -87,6 +87,58 @@ def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index):
return end
def draw_tab_with_powerline(draw_data, screen, tab, before, max_title_length, index, is_last):
tab_bg = as_rgb(color_as_int(draw_data.active_bg if tab.is_active else draw_data.inactive_bg))
tab_fg = as_rgb(color_as_int(draw_data.active_fg if tab.is_active else draw_data.inactive_fg))
inactive_bg = as_rgb(color_as_int(draw_data.inactive_bg))
default_bg = as_rgb(color_as_int(draw_data.default_bg))
min_title_length = 1 + 2
if screen.cursor.x + min_title_length >= screen.columns:
screen.cursor.x -= 2
screen.cursor.bg = default_bg
screen.cursor.fg = inactive_bg
screen.draw('')
return screen.cursor.x
start_draw = 2
if tab.is_active and screen.cursor.x >= 2:
screen.cursor.x -= 2
screen.cursor.fg = inactive_bg
screen.cursor.bg = tab_bg
screen.draw('')
screen.cursor.fg = tab_fg
elif screen.cursor.x == 0:
screen.draw(' ')
start_draw = 1
if min_title_length >= max_title_length:
screen.draw('')
else:
draw_title(draw_data, screen, tab, index)
extra = screen.cursor.x + start_draw - before - max_title_length
if extra > 0 and extra + 1 < screen.cursor.x:
screen.cursor.x -= extra + 1
screen.draw('')
if tab.is_active or is_last:
screen.draw(' ')
screen.cursor.fg = tab_bg
if is_last:
screen.cursor.bg = default_bg
else:
screen.cursor.bg = inactive_bg
screen.draw('')
else:
screen.draw('')
end = screen.cursor.x
if end < screen.columns:
screen.draw(' ')
return end
class TabBar:
def __init__(self, os_window_id, opts):
@@ -123,10 +175,16 @@ class TabBar:
self.bell_fg = as_rgb(0xff0000)
self.draw_data = DrawData(
self.leading_spaces, self.sep, self.trailing_spaces, self.opts.bell_on_tab, self.bell_fg,
self.opts.tab_fade, self.opts.active_tab_background, self.opts.inactive_tab_background,
self.opts.tab_fade, self.opts.active_tab_foreground, self.opts.active_tab_background,
self.opts.inactive_tab_foreground, self.opts.inactive_tab_background,
self.opts.background, self.opts.tab_title_template
)
self.draw_func = draw_tab_with_separator if self.opts.tab_bar_style == 'separator' else draw_tab_with_fade
if self.opts.tab_bar_style == 'separator':
self.draw_func = draw_tab_with_separator
elif self.opts.tab_bar_style == 'powerline':
self.draw_func = draw_tab_with_powerline
else:
self.draw_func = draw_tab_with_fade
def patch_colors(self, spec):
if 'active_tab_foreground' in spec:
@@ -179,10 +237,13 @@ class TabBar:
s.cursor.fg = self.active_fg if t.is_active else 0
s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style
before = s.cursor.x
end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1)
end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1, t is last_tab)
cr.append((before, end))
if s.cursor.x > s.columns - max_title_length and t is not last_tab:
s.draw('')
s.cursor.x = s.columns - 2
s.cursor.bg = as_rgb(color_as_int(self.draw_data.default_bg))
s.cursor.fg = self.bell_fg
s.draw('')
break
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
self.cell_ranges = cr

View File

@@ -4,14 +4,14 @@
import weakref
from collections import deque, namedtuple
from functools import partial
from contextlib import suppress
from functools import partial
from .borders import Borders
from .child import Child
from .constants import appname, get_boss, is_macos, is_wayland
from .fast_data_types import (
add_tab, mark_tab_bar_dirty, next_window_id,
add_tab, attach_window, detach_window, mark_tab_bar_dirty, next_window_id,
pt_to_px, remove_tab, remove_window, ring_bell, set_active_tab, swap_tabs,
x11_window_id
)
@@ -38,7 +38,7 @@ def add_active_id_to_history(items, item_id, maxlen=64):
class Tab: # {{{
def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None):
def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None, no_initial_window=False):
self._active_window_idx = 0
self.tab_manager_ref = weakref.ref(tab_manager)
self.os_window_id = tab_manager.os_window_id
@@ -57,8 +57,10 @@ class Tab: # {{{
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
setattr(self, which + '_window', partial(self.nth_window, num=i))
self._last_used_layout = self._current_layout_name = None
if session_tab is None:
self.cwd = self.args.directory
self.cwd = self.args.directory
if no_initial_window:
self._set_current_layout(self.enabled_layouts[0])
elif session_tab is None:
sl = self.enabled_layouts[0]
self._set_current_layout(sl)
if special_window is None:
@@ -66,11 +68,33 @@ class Tab: # {{{
else:
self.new_special_window(special_window)
else:
self.cwd = session_tab.cwd or self.args.directory
if session_tab.cwd:
self.cwd = session_tab.cwd
l0 = session_tab.layout
self._set_current_layout(l0)
self.startup(session_tab)
def take_over_from(self, other_tab):
self.name, self.cwd = other_tab.name, other_tab.cwd
self.enabled_layouts = list(other_tab.enabled_layouts)
self._set_current_layout(other_tab._current_layout_name)
self._last_used_layout = other_tab._last_used_layout
orig_windows = deque(other_tab.windows)
orig_history = deque(other_tab.active_window_history)
orig_active = other_tab._active_window_idx
for window in other_tab.windows:
detach_window(other_tab.os_window_id, other_tab.id, window.id)
other_tab.windows = deque()
other_tab._active_window_idx = 0
self.active_window_history = orig_history
self.windows = orig_windows
self._active_window_idx = orig_active
for window in self.windows:
window.change_tab(self)
attach_window(self.os_window_id, self.id, window.id)
self.relayout()
def _set_current_layout(self, layout_name):
self._last_used_layout = self._current_layout_name
self.current_layout = self.create_layout_object(layout_name)
@@ -212,7 +236,7 @@ class Tab: # {{{
if self.current_layout.remove_all_biases():
self.relayout()
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None):
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None, env=None, allow_remote_control=False):
if cmd is None:
if use_shell:
cmd = resolved_shell(self.opts)
@@ -228,16 +252,21 @@ class Tab: # {{{
except Exception:
import traceback
traceback.print_exc()
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from)
ans = Child(cmd, cwd or self.cwd, self.opts, stdin, fenv, cwd_from, allow_remote_control=allow_remote_control)
ans.fork()
return ans
def _add_window(self, window, location=None):
self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location)
self.relayout_borders()
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
copy_colors_from=None, allow_remote_control=False
):
child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd, env=env)
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)
window = Window(self, child, self.opts, self.args, override_title=override_title, copy_colors_from=copy_colors_from)
if overlay_for is not None:
overlaid = next(w for w in self.windows if w.id == overlay_for)
@@ -245,12 +274,11 @@ class Tab: # {{{
overlaid.overlay_window_id = window.id
# Must add child before laying out so that resize_pty succeeds
get_boss().add_child(window)
self.active_window_idx = self.current_layout.add_window(self.windows, window, self.active_window_idx, location)
self.relayout_borders()
self._add_window(window, location=location)
return window
def new_special_window(self, special_window, location=None, copy_colors_from=None):
return self.new_window(False, *special_window, location=location, copy_colors_from=copy_colors_from)
def new_special_window(self, special_window, location=None, copy_colors_from=None, allow_remote_control=False):
return self.new_window(False, *special_window, location=location, copy_colors_from=copy_colors_from, allow_remote_control=allow_remote_control)
def close_window(self):
if self.windows:
@@ -265,13 +293,16 @@ class Tab: # {{{
if w.id == old_window_id:
return idx
def remove_window(self, window):
def remove_window(self, window, destroy=True):
idx = self.previous_active_window_idx(1)
next_window_id = None
if idx is not None:
next_window_id = self.windows[idx].id
active_window_idx = self.current_layout.remove_window(self.windows, window, self.active_window_idx)
remove_window(self.os_window_id, self.id, window.id)
if destroy:
remove_window(self.os_window_id, self.id, window.id)
else:
detach_window(self.os_window_id, self.id, window.id)
if window.overlay_for is not None:
for idx, q in enumerate(self.windows):
if q.id == window.overlay_for:
@@ -294,6 +325,32 @@ class Tab: # {{{
if active_window:
self.title_changed(active_window)
def detach_window(self, window):
underlaid_window = None
overlaid_window = window
if window.overlay_for:
for x in self.windows:
if x.id == window.overlay_for:
underlaid_window = x
break
elif window.overlay_window_id:
underlaid_window = window
overlaid_window = None
for x in self.windows:
if x.id == window.overlay_window_id:
overlaid_window = x
break
if overlaid_window is not None:
self.remove_window(overlaid_window, destroy=False)
if underlaid_window is not None:
self.remove_window(underlaid_window, destroy=False)
return underlaid_window, overlaid_window
def attach_window(self, window):
window.change_tab(self)
attach_window(self.os_window_id, self.id, window.id)
self._add_window(window)
def set_active_window_idx(self, idx):
if idx != self.active_window_idx:
self.active_window_idx = self.current_layout.set_active_window(self.windows, idx)
@@ -382,12 +439,17 @@ class Tab: # {{{
def __repr__(self):
return 'Tab(title={}, id={})'.format(self.name or self.title, hex(id(self)))
def make_active(self):
tm = self.tab_manager_ref()
if tm is not None:
tm.set_active_tab(self)
# }}}
class TabManager: # {{{
def __init__(self, os_window_id, opts, args, startup_session):
def __init__(self, os_window_id, opts, args, startup_session=None):
self.os_window_id = os_window_id
self.last_active_tab_id = None
self.opts, self.args = opts, args
@@ -397,9 +459,10 @@ class TabManager: # {{{
self.tab_bar = TabBar(self.os_window_id, opts)
self._active_tab_idx = 0
for t in startup_session.tabs:
self._add_tab(Tab(self, session_tab=t))
self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1)))
if startup_session is not None:
for t in startup_session.tabs:
self._add_tab(Tab(self, session_tab=t))
self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1)))
@property
def active_tab_idx(self):
@@ -545,10 +608,10 @@ 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):
def new_tab(self, special_window=None, cwd_from=None, as_neighbor=False, empty_tab=False):
nidx = self.active_tab_idx + 1
idx = len(self.tabs)
self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
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):

2
kitty/unicode-data.c generated
View File

@@ -1,4 +1,4 @@
// unicode data, built from the unicode standard on: 2019-08-02
// unicode data, built from the unicode standard on: 2019-10-01
// see gen-wcwidth.py
#include "data-types.h"

View File

@@ -164,7 +164,10 @@ def open_cmd(cmd, arg=None, cwd=None):
import subprocess
if arg is not None:
cmd = list(cmd)
cmd.append(arg)
if isinstance(arg, (list, tuple)):
cmd.extend(arg)
else:
cmd.append(arg)
return subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=cwd or None)
@@ -415,15 +418,12 @@ def get_editor():
ans = getattr(get_editor, 'ans', False)
if ans is False:
import shlex
ans = os.environ.get('EDITOR')
if not ans or not exe_exists(shlex.split(ans)[0]):
for q in ('vim', 'nvim', 'vi', 'emacs', 'kak', 'micro', 'nano', 'vis'):
r = exe_exists(q)
if r:
ans = r
break
else:
ans = 'vim'
for ans in (os.environ.get('VISUAL'), os.environ.get('EDITOR'), 'vim',
'nvim', 'vi', 'emacs', 'kak', 'micro', 'nano', 'vis'):
if ans and exe_exists(shlex.split(ans)[0]):
break
else:
ans = 'vim'
ans = shlex.split(ans)
get_editor.ans = ans
return ans

2
kitty/wcwidth-std.h generated
View File

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

View File

@@ -132,7 +132,7 @@ def text_sanitizer(as_ansi, add_wrap_markers):
class Window:
def __init__(self, tab, child, opts, args, override_title=None, copy_colors_from=None):
self.action_on_close = None
self.action_on_close = self.action_on_removal = None
self.layout_data = None
self.pty_resized_once = False
self.needs_attention = False
@@ -163,6 +163,11 @@ class Window:
else:
setup_colors(self.screen, opts)
def change_tab(self, tab):
self.tab_id = tab.id
self.os_window_id = tab.os_window_id
self.tabref = weakref.ref(tab)
@property
def title(self):
return self.override_title or self.child_title

View File

@@ -11,7 +11,9 @@ from kitty.fast_data_types import (
test_render_line, test_sprite_position_for, wcwidth
)
from kitty.fonts.box_drawing import box_chars
from kitty.fonts.render import render_string, setup_for_testing, shape_string
from kitty.fonts.render import (
coalesce_symbol_maps, render_string, setup_for_testing, shape_string
)
from . import BaseTest
@@ -122,3 +124,7 @@ class Rendering(BaseTest):
finally:
sys.stderr = orig
self.assertIn('LastResort', buf.getvalue())
def test_coalesce_symbol_maps(self):
q = {(2, 3): 'a', (4, 6): 'b', (5, 5): 'b', (7, 7): 'b', (9, 9): 'b', (1, 1): 'a'}
self.ae(coalesce_symbol_maps(q), {(1, 3): 'a', (4, 7): 'b', (9, 9): 'b'})

View File

@@ -200,8 +200,10 @@ def init_env(
if ccver < (5, 2) and cc == 'gcc':
missing_braces = '-Wno-missing-braces'
df = '-g3'
float_conversion = ''
if ccver >= (5, 0):
df += ' -Og'
float_conversion = '-Wfloat-conversion'
optimize = df if debug or sanitize else '-O3'
sanitize_args = get_sanitize_args(cc, ccver) if sanitize else set()
cppflags = os.environ.get(
@@ -210,11 +212,14 @@ def init_env(
cppflags = shlex.split(cppflags)
for el in extra_logging:
cppflags.append('-DDEBUG_{}'.format(el.upper().replace('-', '_')))
# gnu11 is needed to get monotonic.h to build on older Linux distros
std = 'c' if is_macos or ccver[0] >= 5 else 'gnu'
cflags = os.environ.get(
'OVERRIDE_CFLAGS', (
'-Wextra -Wno-missing-field-initializers -Wall -Wstrict-prototypes -std=c11'
'-Wextra {} -Wno-missing-field-initializers -Wall -Wstrict-prototypes -std={}11'
' -pedantic-errors -Werror {} {} -fwrapv {} {} -pipe {} -fvisibility=hidden'
).format(
float_conversion, std,
optimize,
' '.join(sanitize_args),
stack_protector,
@@ -270,9 +275,6 @@ def kitty_env():
gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs')
libpng = pkg_config('libpng', '--libs')
ans.ldpaths += pylib + font_libs + gl_libs + libpng
if not is_macos:
cflags.extend(pkg_config('libcanberra', '--cflags-only-I'))
ans.ldpaths += pkg_config('libcanberra', '--libs')
if is_macos:
ans.ldpaths.extend('-framework Cocoa'.split())
else:
@@ -382,8 +384,9 @@ def parallel_run(items):
return
pid, s = os.wait()
compile_cmd, w = workers.pop(pid, (None, None))
if compile_cmd is not None and ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0) and failed is None:
failed = compile_cmd
if compile_cmd is not None and ((s & 0xff) != 0 or ((s >> 8) & 0xff) != 0):
if failed is None:
failed = compile_cmd
elif compile_cmd.on_success is not None:
compile_cmd.on_success()
@@ -759,7 +762,7 @@ def macos_info_plist():
NSRequiresAquaSystemAppearance='NO',
NSHumanReadableCopyright=time.strftime(
'Copyright %Y, Kovid Goyal'),
CFBundleGetInfoString='kitty, an OpenGL based terminal emulator https://sw.kovidgoyal.net/kitty',
CFBundleGetInfoString='kitty, an OpenGL based terminal emulator https://sw.kovidgoyal.net/kitty/',
CFBundleIconFile=appname + '.icns',
NSHighResolutionCapable=True,
NSSupportsAutomaticGraphicsSwitching=True,