Compare commits

..

216 Commits

Author SHA1 Message Date
Kovid Goyal
1ba7b5f5a3 version 0.13.1 2018-12-06 13:23:33 +05:30
Kovid Goyal
f895b7f8cb ... 2018-12-06 13:19:58 +05:30
Kovid Goyal
16542e96bf Fix resizing window smaller and then restoring causing some wrapped lines to not be properly unwrapped
Fix #1206
2018-12-06 12:56:24 +05:30
Kovid Goyal
3005b8b9d6 ... 2018-12-06 11:05:11 +05:30
Kovid Goyal
35653ef4b4 Linux: Fix a regression in the previous release that caused automatic selection of fonts when using aliases such as "monospace" to not work
Fix #1209
2018-12-06 08:42:13 +05:30
Kovid Goyal
36b3582825 Fix passing input via the pipe action to a program without a window not working. 2018-12-06 08:20:25 +05:30
Kovid Goyal
c17c801a31 Detect URL when clicking to activate URL
Makes the case where the screen contents have changed
between the last mouse move and the click work. See #1203
2018-12-05 14:43:01 +05:30
Kovid Goyal
460c353898 version 0.13.0 2018-12-05 14:21:26 +05:30
Kovid Goyal
3fa7007e91 Use the Wu line anti-alias algorithm for drawing undercurls
Fixes #853
2018-12-05 14:02:21 +05:30
Kovid Goyal
4cf6373203 Generate OpenGL bindings with updated glad
See #1179
2018-12-04 16:31:26 +05:30
Kovid Goyal
e0b3bb1830 Move URL parser enum into parser function 2018-12-04 06:59:03 +05:30
Kovid Goyal
47ec372c30 Fix hover detection of URLs not working when hovering over the first colon and slash characters in short URLs
Fixes #1201
2018-12-04 06:52:26 +05:30
Kovid Goyal
d5366a421f ... 2018-12-03 21:35:03 +05:30
Kovid Goyal
3d5977669e Micro optimization 2018-12-03 20:04:58 +05:30
Kovid Goyal
36da668e6f Forgot to fill bitmap offset data when rendering colored glyphs 2018-12-03 20:00:24 +05:30
Kovid Goyal
052913c656 Merge branch 'typo' of https://github.com/Luflosi/kitty 2018-12-03 15:38:18 +05:30
Luflosi
0ef055a2b2 Fix typo in docs 2018-12-03 10:58:23 +01:00
Kovid Goyal
a7bd6922e5 Follow xterm's behavior for the menu key
Fixes #597
Also generate the modified variants of the function keys.
2018-12-03 14:04:26 +05:30
Kovid Goyal
8d9ba4a772 Handle ctrl-c while paging help gracefully 2018-12-03 13:53:13 +05:30
Kovid Goyal
222beedc2a Fix freetype based rendering of non-hinted fonts at small sizes
Now use the post hinting bitmap bearing values instead of the hinting
independent glyph metrics bearing values. Fixes #1173
2018-12-02 14:12:46 +05:30
Kovid Goyal
8ba8320189 Disable brew based build since harfbuzz is currently broken in brew 2018-12-02 14:08:35 +05:30
Kovid Goyal
3501a2a1da ... 2018-12-02 13:52:06 +05:30
Kovid Goyal
819ee94a70 Merge branch 'reset_osc' of https://github.com/martinetd/kitty 2018-11-30 13:52:42 +05:30
Kovid Goyal
7e7dbc5ecf Merge branch 'history_crash' of https://github.com/martinetd/kitty 2018-11-30 13:45:27 +05:30
Dominique Martinet
3a6bc94d25 pager history: fix bufsize calculation
bufsize was bigger than the actual allocated memory

Fixes: b9e6557f72 ("Allow arbitrary sized lines in the secondary scrollback buffer")
2018-11-30 16:06:05 +09:00
Dominique Martinet
07a4cec616 reset terminfo: add an empty OSC sequence to rs1 string
In OSC/OTH mode, the parser accumulate bytes until it fills up or
encounters some end-of-osc sequence.
After entering in either of these mode by mistake, for example by
displaying random bytes (e.g. cat a binary) nothing would print anymore
but input would still work.

In that situation, being able to hit `reset` and recover a usable
terminal helps.

You can try with `echo -e '\xc2\x9e'` for PM
2018-11-30 15:51:38 +09:00
Kovid Goyal
60de840d69 Merge branch 'selection_wide_hilight' of https://github.com/martinetd/kitty 2018-11-30 09:27:05 +05:30
Kovid Goyal
928167c0b2 Merge branch 'x11_selection_timeout' of https://github.com/martinetd/kitty
Fixes #1042
2018-11-30 09:20:44 +05:30
Dominique Martinet
2aac220cdb selection: hilight 2nd half of wide characters last in line 2018-11-30 11:50:26 +09:00
Dominique Martinet
d8a74e8a3c glfw/x11_window/selection: timeout if no answer for two seconds
This prevents kitty to hang forever if a bad client does not give selection
2018-11-30 11:42:45 +09:00
Kovid Goyal
019e6efeda Reduce overlap with text when rendering wavy underline at small font sizes 2018-11-27 07:19:30 +05:30
Kovid Goyal
a7a7216b30 Allow adding line wrap markers when piping screen contents
Also make some data such as scroll position, cursor position and screen
geometry available via an env var when piping. See #719
2018-11-26 13:30:03 +05:30
Kovid Goyal
ae8ca5272b ... 2018-11-25 11:51:40 +05:30
Kovid Goyal
06c1236cc6 Merge branch 'master' of https://github.com/bodqhrohro/kitty 2018-11-23 23:01:42 +05:30
bodqhrohro
4d3982c5cf Fix memory leak on compose table creation 2018-11-23 19:04:09 +02:00
Kovid Goyal
a0ff625f61 When double or triple clicking ignore clicks if they are "far" from each other
Fixes #1093
2018-11-23 11:46:02 +05:30
Kovid Goyal
b44345d78e Ignore failures to update the NSGL layer
Fixes #1168
2018-11-22 18:25:22 +05:30
Luflosi
96229becd1 Make final_color computation consistent across cases 2018-11-22 11:36:29 +01:00
Kovid Goyal
b269cab0b8 Fix unused function on macOS 2018-11-22 15:49:01 +05:30
Kovid Goyal
0b3c8210f3 Various Wayland DPI scaling fixes
Use the glfw window content scale api to get the DPI for windows.
Likely, more accurate than our logic to find the overlapping monitor
to get the DPI.

Also, after a DPI change event send a fake resize event to workaround
render surface not being properly updated.

Fixes #1143
2018-11-22 15:37:33 +05:30
Kovid Goyal
074614bc8e Fix incorrect text-antialiasing when using very low background opacity
Fix #1005
2018-11-22 10:26:40 +05:30
Kovid Goyal
4ceb4cfc22 Fix a crash if the client program tries to restore more modes than it saves
Fixes #1166
2018-11-22 08:57:43 +05:30
Kovid Goyal
0bb08d8bdd When parsing OSC 52 clipboard control codes, handle an empty control parameter
This is apparently used by tmux. See #782
2018-11-21 19:37:39 +05:30
Kovid Goyal
b9e6557f72 Allow arbitrary sized lines in the secondary scrollback buffer 2018-11-21 17:55:43 +05:30
Kovid Goyal
7ed268ea2f Refactor some code into a style I like 2018-11-21 10:54:25 +05:30
Kovid Goyal
a91b3b9487 Revert addition of RGB to terminfo
See discussion 18fe2e8dfa
2018-11-21 10:33:55 +05:30
Kovid Goyal
cdf236d01b Fix #1161 2018-11-21 10:12:05 +05:30
Kovid Goyal
f54efb7886 Merge branch 'asan-launcher.dSYM' of https://github.com/Luflosi/kitty 2018-11-20 22:37:56 +05:30
Luflosi
0ffee56170 Ignore asan-launcher.dSYM directory 2018-11-20 16:03:34 +01:00
Kovid Goyal
ab97646576 Have line_as_ansi indicate when the output is truncated 2018-11-20 11:13:49 +05:30
Kovid Goyal
33b62dcabc Turn on the scrollback pager for all tests 2018-11-20 10:42:17 +05:30
Kovid Goyal
39e6e91d69 Port new coca drag'n drop code from upstream 2018-11-20 10:24:55 +05:30
Kovid Goyal
dedd80c6b5 Fix #1157 2018-11-20 08:51:45 +05:30
Kovid Goyal
df11da74e9 Fix #1156 2018-11-20 08:49:07 +05:30
Kovid Goyal
9991dd6ce4 Generate hash based .pyc files when building
Fixes #1155
2018-11-20 08:42:08 +05:30
Kovid Goyal
e777364f91 Add secondary pager history when requesting history for pipe or @ commands 2018-11-19 19:57:15 +05:30
Kovid Goyal
a32ed5ae5f Avoid creating lots of newline string objects in pagerhist 2018-11-19 12:11:04 +05:30
Kovid Goyal
db1e48848d More robust no modifier check for CSI parsing 2018-11-19 09:53:01 +05:30
Kovid Goyal
c02fa79591 Fix #1153 2018-11-19 09:35:46 +05:30
Kovid Goyal
6f0a59c696 If a permission error occurs creating the kitty config directory use a temp dir as the config directory. Fixes #1152 2018-11-18 20:02:55 +05:30
Kovid Goyal
bc715d4348 Merge branch 'fix-set-background-opacity' of https://github.com/kdarkhan/kitty 2018-11-17 16:01:33 +05:30
Darkhan Kubigenov
55e4761302 Fix set_background_opacity with increment/decrement 2018-11-17 15:21:42 +06:00
Kovid Goyal
18fe2e8dfa Add the new RGB boolean capability to terminfo to indicate we support truecolor
See https://gist.github.com/XVilka/8346728#detection
2018-11-15 13:59:15 +05:30
Kovid Goyal
18a07a78b5 Move the os window clearing into render_os_window
Makes more sense to clear the buffer immediately before rendering rather
than immediately after swapping.
2018-11-14 12:11:26 +05:30
Kovid Goyal
aa7024b264 Bump required version of wayland protocols 2018-11-14 09:16:14 +05:30
Kovid Goyal
c67ab2a6ff ... 2018-11-14 09:01:51 +05:30
Kovid Goyal
d15eab31da Merge branch 'master' of https://github.com/tarmack/kitty 2018-11-14 09:01:22 +05:30
Tarmack
a6900443d5 Wayland primary selection migrated to unstable protocol
This change is actually backwards compatible with the GTK implementation before.
2018-11-13 20:58:02 +01:00
Kovid Goyal
235bcacc7b Make the buffer clearing more robust
Now happens immediately after the buffer is swapped. Fixes #1104
2018-11-13 20:59:39 +05:30
Kovid Goyal
3e26f96744 Add some common synonyms for unicode word searching
Fixes #1133
2018-11-13 15:46:33 +05:30
Kovid Goyal
718111582a Fix #1136 2018-11-12 09:15:41 +05:30
Kovid Goyal
f5cdbe53ea Merge branch 'patch-3' of https://github.com/maximbaz/kitty 2018-11-11 21:38:39 +05:30
Maxim Baz
bf761a7189 Fix typo in changelog 2018-11-11 13:04:53 +01:00
Kovid Goyal
05ee01b4dd Merge branch 'scroll-wayland' of https://github.com/maximbaz/kitty 2018-11-11 12:03:39 +05:30
Kovid Goyal
53166d659e Consolidate options for hiding window decorations
Merged into a single option, hide_window_decorations. Old
aliases still work if set to yes.

Fixes #1127
2018-11-11 11:56:44 +05:30
Kovid Goyal
34aea3bca1 Merge branch 'hints-add-trailing-space' of https://github.com/maximbaz/kitty 2018-11-11 11:03:11 +05:30
Maxim Baz
f26519863f Don't include --progam in the condition 2018-11-10 13:24:39 +01:00
Maxim Baz
a57e96500a hints: optionally add trailing space 2018-11-10 13:19:40 +01:00
Maxim Baz
5e27c21856 Implement touch_scroll_multiplier 2018-11-10 12:33:47 +01:00
Kovid Goyal
4601cd13dc icat kitten: Implement reading image data from STDIN, if STDIN is not connected to a terminal
Fixes #1130
2018-11-10 12:31:09 +05:30
Kovid Goyal
ad92541ff3 Merge branch 'microoptimization' of https://github.com/Luflosi/kitty 2018-11-10 10:50:03 +05:30
Maxim Baz
3f77a2044e Make high resolution scrolling more responsive and configurable 2018-11-09 13:22:03 +01:00
Luflosi
75e57a48e0 Microoptimisation 2018-11-09 10:19:42 +01:00
Kovid Goyal
160fbd2071 Add a terminfo entry for full keyboard mode
Fixes #1122
2018-11-05 08:11:35 +05:30
Kovid Goyal
f5743f4610 Forgot to multiply xscale 2018-11-05 07:56:33 +05:30
Kovid Goyal
82f9aecacb macOS: Fix touch scrolling sensitivity low on retina screens
Fixes #1112
2018-11-04 12:07:55 +05:30
Kovid Goyal
be6556c884 X11: Fix missing check for NET_WM_STATE 2018-11-04 10:20:38 +05:30
Kovid Goyal
f941ffb1fe Merge branch 'document-ctrl-l' of https://github.com/maximbaz/kitty 2018-11-04 10:04:39 +05:30
Maxim Baz
cbdba0b6aa Document how to achieve alternative behavior for Ctrl+L, ref #1113 2018-11-03 20:29:40 +01:00
Kovid Goyal
ce04f102a7 Technically, scroll_until_empty is scroll_until_cursor 2018-11-03 14:23:50 +05:30
Kovid Goyal
fc3bd2d15f Add an action to the `clear_terminal` function to scroll the screen contents into the scrollback buffer
Now if you want to map a key combination that will both scroll the
current screen contents into the scrollback buffer and clear the screen,
you can do something like:

map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c

Fixes #1113
2018-11-03 14:18:09 +05:30
Kovid Goyal
a958cabf01 Fix :kbd:ctrl+shift+special key not working in normal and application keyboard modes
Fixes #1114
2018-11-03 09:03:50 +05:30
Kovid Goyal
d0a2eab79e ... 2018-11-02 12:47:13 +05:30
Kovid Goyal
b835afefee Merge branch 'fix-grid-neighbors' of https://github.com/strix/kitty 2018-11-02 10:32:27 +05:30
Brance Boren
d0a7be90f9 Fixed call to Tall.neighbors_for_window 2018-11-01 14:55:13 -06:00
Kovid Goyal
1f3550b84c ... 2018-11-01 07:55:29 +05:30
Kovid Goyal
939212e1d4 Update changelog 2018-10-31 16:11:45 +05:30
Kovid Goyal
7db6318806 Only warn about failure to primary select once 2018-10-31 09:09:46 +05:30
Kovid Goyal
e5d9182606 Since we now have primary selections on wayland, remove it from the click_on_select option's description 2018-10-31 09:04:55 +05:30
Tarmack
d519553581 Implement primary selection for Wayland 2018-10-31 09:03:22 +05:30
Kovid Goyal
e99b1be023 macOS: Ensure stored mouse position is correct when switching to a kitty window
Fixes #1103
2018-10-31 08:38:50 +05:30
Kovid Goyal
e460866ebc Make the bounds check cleaner 2018-10-30 15:02:48 +05:30
Kovid Goyal
23aea68e32 Fix second cell of emoji created using variation selectors not having the same attributes as the first cell
Fix #1109
2018-10-30 15:00:47 +05:30
Kovid Goyal
65c2cbe93a Merge branch 'adjust_column_width_frac' of https://github.com/jjrams/kitty 2018-10-30 11:49:58 +05:30
Kovid Goyal
ee31ba0c76 Merge branch 'multi-pua' of https://github.com/blueyed/kitty 2018-10-30 11:49:05 +05:30
Kovid Goyal
3ed9d9a069 Merge branch 'system_double_click_interval' of https://github.com/Luflosi/kitty 2018-10-28 11:18:43 +05:30
jjrams
721e51c117 Fix typo when using adjust_column_width_frac 2018-10-27 12:24:37 -07:00
Luflosi
09daaf1ec3 Change click_interval default to -1.0 and add help text for negative click_interval values 2018-10-27 17:55:16 +02:00
Kovid Goyal
b2d62f10d2 Dont send frame requests is sync_to_monitor is disabled 2018-10-27 14:04:14 +05:30
Kovid Goyal
0d542b2f60 ... 2018-10-27 13:32:16 +05:30
Kovid Goyal
04fd058e0f Wayland: Improve FPS for rendering with vsync
Now a new frame event is requested after every render. This means
that effective FPS should be close to the refresh rate of the output
source.
2018-10-27 12:33:48 +05:30
Kovid Goyal
31a0a9427e ... 2018-10-27 11:35:25 +05:30
Kovid Goyal
04f10ba570 Fix leak of Wayland callback structure when doing a copy 2018-10-27 11:34:54 +05:30
Kovid Goyal
34b7b2e4e8 Move to using upstream glfw fix for mojave initial render issue 2018-10-26 22:29:05 +05:30
Luflosi
09183772d3 Respect the default system double-click interval 2018-10-26 11:57:33 +02:00
Kovid Goyal
3e4eca5816 Wayland: Fix cursor being reset continuously
Should only do that if the cursor is actually supposed to be animated.
2018-10-26 11:23:34 +05:30
Kovid Goyal
30c6c4697f Merge branch 'x11_enter' of https://github.com/martinetd/kitty 2018-10-26 10:26:10 +05:30
Kovid Goyal
b650275d45 Wayland: Fix copying from hidden kitty windows hanging
Fix #1051
2018-10-26 10:25:08 +05:30
Kovid Goyal
174bd5c57f Commit the wl_surface to ensure that we receive the frame event 2018-10-26 10:12:54 +05:30
Dominique Martinet
ec51a219a7 glfw x11: update cursor position on enter event
kitty would have an incorrect position when clicking after changing window
without moving (e.g. change workspace, create new kitty etc)

For new kitty window, initial position would be 0 and selection paste would
not work because of this
2018-10-26 13:19:12 +09:00
Kovid Goyal
cca2fb967c Avoid malloc for frame callbacks 2018-10-26 08:49:17 +05:30
Kovid Goyal
5b547d9b06 Initial attempt at supporting Wayland frame events
Does not actually work because the wayland frame callback is not called
at all, fo rsome reason I cannot determine.
2018-10-26 07:42:57 +05:30
Luflosi
facb2df3f6 Merge branch 'master' of github:kovidgoyal/kitty into system_double_click_interval 2018-10-25 13:18:43 +02:00
Kovid Goyal
8395076da5 Cocoa: Cleanup
From upstream
2018-10-25 10:32:17 +05:30
Kovid Goyal
e113e0cba7 Allow hiding the tab bar completely, by setting :opt:tab_bar_style to `hidden`.
Fixes #1014
2018-10-25 10:05:43 +05:30
Kovid Goyal
407d19c2dc Remove code to import/export from glfw
Since kitty now uses its own private fork, there is no point.
2018-10-24 14:18:01 +05:30
Luflosi
382367506a [WIP] Respect the default system click_interval 2018-10-24 10:44:24 +02:00
Kovid Goyal
bd5a85a4e7 Fix compilation on ancient linux distros 2018-10-24 08:40:25 +05:30
Kovid Goyal
631f97eb26 Linux: Fix a crash when using the GNU Unifont as a fallback font
Fixes #1087
2018-10-24 08:20:37 +05:30
Kovid Goyal
0c0ce4844c Dont use deprecated FT_Bitmap_New 2018-10-24 07:07:57 +05:30
Kovid Goyal
6bf290149e Allow specifying monospace when calling fc_match 2018-10-24 06:45:06 +05:30
Kovid Goyal
60fa812b60 ... 2018-10-24 06:36:30 +05:30
Kovid Goyal
61f3a39aa0 macOS: Fix incorrect text sizes calaculated when using an external display that is set to mirror the main display
Fix #1056
2018-10-24 06:28:58 +05:30
Kovid Goyal
a6949df727 Linux: Fix match rules used as aliases in Fontconfig configuration not being respected
Fixes #1085
2018-10-23 12:22:18 +05:30
Kovid Goyal
d839a31d7f oops forgot to convert poll timeout to milliseconds 2018-10-23 11:26:37 +05:30
Kovid Goyal
1de510f46f poll at zero timeout as well 2018-10-23 11:09:38 +05:30
Kovid Goyal
0359bbe4be ... 2018-10-23 11:08:13 +05:30
Kovid Goyal
40b355e593 macOS: Fix rendering frames-per-second very low when processing large amounts of input in small chunks
We do this by debouncing wakeup events sent to the main loop by the I/O
thread. Use in the input_delay time to debounce. Apparently processing
wakeup events is very expensive in Cocoa.

Fixes #1082
2018-10-23 10:27:56 +05:30
Kovid Goyal
adf9c4fc5f Remove unused var 2018-10-23 09:05:56 +05:30
Kovid Goyal
ae9d049011 Better fix for initial window render failure on mojave 2018-10-23 09:04:12 +05:30
Kovid Goyal
d24977d164 Disabe brew python upgrade since it is failing for some reason on Travis 2018-10-23 08:35:55 +05:30
Kovid Goyal
3035d64127 Forgot to apply sendEvent micro-optimization to a couple more places 2018-10-23 08:00:20 +05:30
Kovid Goyal
70c9c058e2 Micro-optimization of cocoa event polling 2018-10-21 20:56:27 +05:30
Kovid Goyal
1d9f73adec ... 2018-10-21 20:30:31 +05:30
Kovid Goyal
fca95af49b macOS: Fix IME input for east asian languages
Fix #910
2018-10-21 20:27:03 +05:30
Kovid Goyal
62fe392041 Handle overlay windows when recording window history 2018-10-21 11:56:23 +05:30
Kovid Goyal
097d11819b Add a new `nth_window action that can be used to go to the nth window and also previously active windows, using negative numbers. Similarly, goto_tab` now accepts negative numbers to go to previous windows
Fix #1040
2018-10-21 10:59:06 +05:30
Kovid Goyal
691286f376 Add a minimal example for a python script to display PNG images
See #1080
2018-10-21 07:48:31 +05:30
Kovid Goyal
c910f6c832 When using --debug-keyboard also output matched action for keypresses, if any. Fixes #1021 2018-10-20 14:08:33 +05:30
Kovid Goyal
65f9ac32ef Fix :opt:mouse_hide_wait only taking effect after an event such as cursor blink or key press
Fixes #1073
2018-10-20 14:01:55 +05:30
Kovid Goyal
27f2a2e3d5 Merge branch 'filter_tic_output' of https://github.com/Luflosi/kitty 2018-10-18 18:43:57 +05:30
Luflosi
88aab20376 Loop through lines of tic output to correctly handle multiple lines 2018-10-18 12:25:19 +02:00
Luflosi
a045ac3bd7 Decode tic stderr using utf-8 2018-10-18 12:15:37 +02:00
Kovid Goyal
722b605407 Merge branch 'fix_typo_in_scrollback_lines_docs' of https://github.com/Luflosi/kitty 2018-10-18 10:47:22 +05:30
Kovid Goyal
7f53745077 Fix #1062 2018-10-18 08:30:47 +05:30
Kovid Goyal
7f902c4c03 Fix expansion of env vars not working in the :opt:env directive
Fixes #1075
2018-10-18 08:12:10 +05:30
Luflosi
1205c2055f Fix typo in scrollback_lines docs 2018-10-16 14:41:11 +02:00
Luflosi
f08f0f9dbd Filter tic output to remove "older tic versions may treat the description field as an alias" 2018-10-15 17:17:12 +02:00
Kovid Goyal
093c05e16a When running kitty @ ls also output the current layout for each tab 2018-10-12 18:19:50 +05:30
Kovid Goyal
787100a4dc Modify the kittens sub-system to allow creating custom kittens without any user interface.
See #870
2018-10-12 18:09:37 +05:30
Kovid Goyal
c493583f25 macOS: Fix drag and drop of files not working on mojave
Fixes #1058
2018-10-11 18:36:24 +05:30
Kovid Goyal
b4afbe47b3 wayland: add support for XCURSOR_THEME and XCURSOR_SIZE
These can be set by the compositor or the user to configure the
xcursor theme and size.
2018-10-11 13:39:44 +05:30
Kovid Goyal
6c4aeeeadd ... 2018-10-10 09:39:53 +05:30
Kovid Goyal
5b01519367 Merge branch 'fix-wl-key-repeat' of https://github.com/tarmack/kitty 2018-10-09 18:22:45 +05:30
Tarmack
c494759606 Divide a second over the key repeat Hz value for the timer value 2018-10-09 14:30:36 +02:00
Kovid Goyal
2102c48b87 Mir: Remove this experimental backend
As of the release of Mir 1.0, libmirclient has been deprecated[1] and
its developers recommend clients using it to switch to Wayland.  This
patch removes support for libmirclient and instruct users to use the
experimental Wayland backend instead.

[1] https://discourse.ubuntu.com/t/mir-news-28th-september-2018/8184
2018-10-06 07:53:41 +05:30
Kovid Goyal
c498f4e8ab Linux: Include the null byte in joystick path
This potential bug was found thanks to gcc 8’s -Wstringop-truncation
warning.
2018-10-06 07:49:59 +05:30
Kovid Goyal
05a0166ebd Forgot some mojave substitutions 2018-10-03 22:03:49 +05:30
Kovid Goyal
0484365de6 More fixes for compiling on mojave 2018-10-03 21:50:39 +05:30
Kovid Goyal
cbab3b56de Fixes for compilation on mojave, needs testing 2018-10-03 20:51:25 +05:30
Kovid Goyal
715f7985c7 Fix the `*_with_cwd` actions using the cwd of the overlay window rather than the underlying window's cwd
Fix #1045
2018-10-03 19:26:01 +05:30
Kovid Goyal
2637018b53 Handle broken pipe errors during test compile 2018-10-03 12:46:08 +05:30
Kovid Goyal
1fa66ca45d Wayland: Rename handlers for more unicity 2018-10-03 12:30:48 +05:30
Kovid Goyal
13b10f691d Wayland: Add support for xdg-decoration
This allows compositors which prefer to draw the decorations around
clients to do so, rather than letting GLFW draw its own decorations.
The appearance is thus entirely subject to the compositor used, but
should generally be better than the current solid colour decorations we
have, which we continue to use when the compositor doesn’t support this
protocol or tells us to draw the decorations ourselves.
2018-10-03 12:27:51 +05:30
Kovid Goyal
3fe7d91713 Add some needed includes 2018-10-03 11:06:01 +05:30
Kovid Goyal
98864091ff Move memfd existence test to glfw.py 2018-10-03 11:02:51 +05:30
Kovid Goyal
7750a461aa Fix compilation on systems that dont have the memfd_create syscall 2018-10-03 10:56:47 +05:30
Kovid Goyal
ac98b85157 Wayland: Use memfd_create() and seals if available
This allows the compositor to avoid having to setup and teardown a
SIGBUS signal handler whenever it needs to read from this surface, as it
knows we won’t be able to shrink the file and so doesn’t have to protect
against that.

Only enabled on Linux, since only Linux has memfd_create()
2018-10-03 09:47:04 +05:30
Kovid Goyal
21f6c6a5a8 Wayland: Check for buffer creation failure
This prevents unusable decoration objects from being created, and
invalid cursors from being returned to the user.
2018-10-02 11:30:13 +05:30
Kovid Goyal
b8d1491b99 Merge branch 'pipe-scrollback-secondary-screen' of https://github.com/maximbaz/kitty 2018-10-01 06:47:34 +05:30
Maxim Baz
d9c168d824 Account for secondary screen not having history buffer 2018-09-30 16:45:24 +02:00
Maxim Baz
ee7f4ee1a4 Don't guard against alternate_screen in as_text
Complementary to 15f534a8f9
2018-09-30 14:13:17 +02:00
Kovid Goyal
ee3ce3c7d8 Move cell_as_sgr to line.c 2018-09-30 08:58:33 +05:30
Kovid Goyal
a57f38dbd5 Merge branch 'pagerhist' of https://github.com/martinetd/kitty 2018-09-30 08:41:52 +05:30
Daniel Hahler
3c4cb3c35b get_glyph_width for core text?! 2018-09-29 18:56:54 +02:00
Daniel Hahler
12cfe6d1d1 render_line: use cells based on bitmap width
[ci skip]
2018-09-29 18:38:53 +02:00
Daniel Hahler
c64e8fc434 MAX_NUM_EXTRA_GLYPHS_PUA=4 2018-09-29 16:49:40 +02:00
Kovid Goyal
15f534a8f9 Add access to secondary screen scrollback via the pipe function 2018-09-29 19:59:01 +05:30
Daniel Hahler
8e09e809aa fixup! render_glyphs_in_cells: skip centering glyph for PUA 2018-09-29 16:23:16 +02:00
Daniel Hahler
fec5a80b89 Init bm
Not sure why this became necessary though?!

Fixes:

    Linking kitty/fast_data_types ...
    kitty/freetype.c: In function ‘render_glyphs_in_cells’:
    kitty/freetype.c:514:82: warning: ‘bm.right_edge’ may be used uninitialized in this function [-Wmaybe-uninitialized]
             if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
                                                                                      ^
    kitty/freetype.c:490:21: note: ‘bm.right_edge’ was declared here
         ProcessedBitmap bm;
                         ^
2018-09-29 16:20:10 +02:00
Daniel Hahler
1d50a188c4 render_glyphs_in_cells: skip centering glyph for PUA 2018-09-29 16:19:53 +02:00
Kovid Goyal
b0d34b136f version 0.12.3 2018-09-29 09:15:40 +05:30
Kovid Goyal
7eb234c250 Move kitty.app build instructions from the FAQ to the build page 2018-09-29 08:47:30 +05:30
Daniel Hahler
d52434c0aa fixup! Handle multiple spaces after PUA glyphs
[ci skip]
2018-09-28 23:01:35 +02:00
Daniel Hahler
efc0b830cf Handle multiple spaces after PUA glyphs 2018-09-28 15:34:14 +02:00
Kovid Goyal
4a8c4c4601 Unicode input: Fix an error when searching for the string 'fir'
Fixes #1035
2018-09-28 19:01:20 +05:30
Kovid Goyal
e498cedf56 remove unused import 2018-09-27 08:52:40 +05:30
Kovid Goyal
6c8a52875e Fix #1029 2018-09-27 08:40:16 +05:30
Kovid Goyal
c1397fd366 Merge branch 'unicode_input-reverse' of https://github.com/blueyed/kitty 2018-09-27 08:35:25 +05:30
Daniel Hahler
3323ddcdef unicode_input: use reverse mode for menu selection 2018-09-26 22:50:22 +02:00
Kovid Goyal
1be394cda7 Update CHANGELOG 2018-09-26 19:54:42 +05:30
Kovid Goyal
f292092ffc ... 2018-09-26 19:47:53 +05:30
Kovid Goyal
b82e74f99a Fix for kitty window not being rendered until moved/resized on macOS Mojave
Fixes #887
2018-09-26 19:43:04 +05:30
Kovid Goyal
9b293ad66a Add some more explanation of why TERM is important 2018-09-24 09:28:21 +05:30
Dominique Martinet
618b486fd5 pager history buffer: remove dead code looking for next new line 2018-09-22 19:10:37 +09:00
Dominique Martinet
1986403e42 Add scrollback benchmark 2018-09-22 19:10:37 +09:00
Dominique Martinet
cf51f80b79 cell_as_sgr: avoid unnecessary formatting functions 2018-09-22 19:10:37 +09:00
Dominique Martinet
2fe17aa889 cursor_as_sgr now works on GPUCells becomes cell_as_sgr 2018-09-22 19:10:09 +09:00
Dominique Martinet
f76c8ad30b pager history buffer: alloc buffer progressively
Realloc buffer 1MB at a time until the configured size is reached
2018-09-22 19:10:09 +09:00
Dominique Martinet
c421d3bb59 line_as_ansi: don't reset SGR at start of line
less does not carry the mode over from the previous line anyway, let's
save a few bytes for every line
2018-09-22 19:10:09 +09:00
Dominique Martinet
3d862da2be pager history buffer: implement rewrap
rewarp the buffer the first time it is displayed after the window
changed
2018-09-22 19:10:09 +09:00
Dominique Martinet
10b15caad9 tests: add options param to create_screen()
Always apply defaults when creating a screen (the C-side global state
default will not have the intended values without this), and provide a
way to override default values with a new optional parameter
2018-09-22 18:05:13 +09:00
Dominique Martinet
e08238d5a0 pager history buffer: add config option, keep buffer on resize 2018-09-22 18:02:11 +09:00
Dominique Martinet
d0104660c8 Implement pager scrollback history buffer
For now, use big fixed-size buffer that is lost on resize
2018-09-22 18:02:11 +09:00
Dominique Martinet
a4c2a9ef0d line_as_ansi: only compute sgr if needed 2018-09-22 18:02:11 +09:00
Dominique Martinet
ee39d7ba14 line_as_ansi: remove dead undefs
CHECK_BOOL, CHECK_COLOR and WRITE_COLOR have been removed in
commit 27cd303a05 ("Centralize code to convert to SGR")
2018-09-22 18:02:11 +09:00
86 changed files with 20503 additions and 18922 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@ logo/*.iconset
compile_commands.json
glad/out
asan-launcher
asan-launcher.dSYM
kitty-profile
dev
__pycache__

View File

@@ -89,9 +89,10 @@ matrix:
language: generic
env: SWBASE=/Users/Shared/buildbot/sw SW=$SWBASE/sw PATH=$SW/bin:$PATH
- os: osx
language: generic
env: USE_BREW=1 BUILD_PKG=osx-bundle
# Disabled because harfbuzz is currently broken in brew
# - os: osx
# language: generic
# env: USE_BREW=1 BUILD_PKG=osx-bundle
env:
global:
@@ -106,7 +107,7 @@ install: |
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
if [[ "$USE_BREW" == "1" ]]; then
brew update;
brew upgrade python;
# brew upgrade python;
brew install harfbuzz --without-graphite2 --without-icu4c --without-freetype;
brew install imagemagick
brew install optipng

View File

@@ -28,7 +28,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`` and ``libxkbcommon-x11-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
@@ -57,6 +57,25 @@ If that works, you can create a script to launch |kitty|:
And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using
just ``kitty``.
Building kitty.app on macOS from source
-------------------------------------------
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
for the logo generation step). And run::
make app
This :file:`kitty.app` unlike the released one does not include its own copy of
python and the other dependencies. So if you ever un-install/upgrade those dependencies
you might have to rebuild the app.
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
is designed to run on Linux and is not for the faint of heart.
Note for Linux/macOS packagers
----------------------------------

View File

@@ -3,6 +3,125 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
0.13.1 [2018-12-06]
------------------------------
- Fix passing input via the pipe action to a program without a window not
working.
- Linux: Fix a regression in the previous release that caused automatic
selection of bold/italic fonts when using aliases such as "monospace" to not
work (:iss:`1209`)
- Fix resizing window smaller and then restoring causing some wrapped lines to not
be properly unwrapped (:iss:`1206`)
0.13.0 [2018-12-05]
------------------------------
- Add an option :opt:`scrollback_pager_history_size` to tell kitty to store
extended scrollback to use when viewing the scrollback buffer in a pager
(:iss:`970`)
- Modify the kittens sub-system to allow creating custom kittens without any
user interface. This is useful for creating more complex actions that can
be bound to key presses in :file:`kitty.conf`. See
doc:`kittens/custom`. (:iss:`870`)
- Add a new ``nth_window`` action that can be used to go to the nth window and
also previously active windows, using negative numbers. Similarly,
``goto_tab`` now accepts negative numbers to go to previously active tabs
(:iss:`1040`)
- Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to
``hidden``. (:iss:`1014`)
- Allow private use unicode characters to stretch over more than a single
neighboring space (:pull:`1036`)
- Add a new :opt:`touch_scroll_multiplier` option to modify the amount
scrolled by high precision scrolling devices such as touchpads (:pull:`1129`)
- icat kitten: Implement reading image data from STDIN, if STDIN is not
connected to a terminal (:iss:`1130`)
- hints kitten: Insert trailing spaces after matches when using the
``--multiple`` option. Also add a separate ``--add-trailing-space``
option to control this behavior (:pull:`1132`)
- Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather
than the underlying window's cwd (:iss:`1045`)
- Fix incorrect key repeat rate on wayland (:pull:`1055`)
- macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`)
- macOS: Fix IME input for East Asian languages (:iss:`910`)
- macOS: Fix rendering frames-per-second very low when processing
large amounts of input in small chunks (:pull:`1082`)
- macOS: Fix incorrect text sizes calculated when using an external display
that is set to mirror the main display (:iss:`1056`)
- macOS: Use the system default double click interval (:pull:`1090`)
- macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`)
- Linux: Fix incorrect rendering of some fonts when hinting is disabled at
small sizes (:iss:`1173`)
- Linux: Fix match rules used as aliases in Fontconfig configuration not being
respected (:iss:`1085`)
- Linux: Fix a crash when using the GNU Unifont as a fallback font
(:iss:`1087`)
- Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`)
- Wayland: Add support for the primary selection protocol
implemented by some compositors (:pull:`1095`)
- Fix expansion of env vars not working in the :opt:`env` directive
(:iss:`1075`)
- Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor
blink or key press (:iss:`1073`)
- Fix the ``set_background_opacity`` action not working correctly
(:pull:`1147`)
- Fix second cell of emoji created using variation selectors not having
the same attributes as the first cell (:iss:`1109`)
- Fix focusing neighboring windows in the grid layout with less than 4 windows
not working (:iss:`1115`)
- Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard
modes (:iss:`1114`)
- Add a terminfo entry for full keyboard mode.
- Fix incorrect text-antialiasing when using very low background opacity
(:iss:`1005`)
- When double or triple clicking ignore clicks if they are "far" from each
other (:iss:`1093`)
- Follow xterm's behavior for the menu key (:iss:`597`)
- Fix hover detection of URLs not working when hovering over the first colon
and slash characters in short URLs (:iss:`1201`)
0.12.3 [2018-09-29]
------------------------------
- macOS: Fix kitty window not being rendered on macOS Mojave until the window is
moved or resized at least once (:iss:`887`)
- Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`)
0.12.2 [2018-09-24]
------------------------------
@@ -133,6 +252,9 @@ Changelog
- Add an option :opt:`env` to set environment variables in child processes
from kitty.conf
- Add an action to the ``clear_terminal`` function to scroll the screen
contents into the scrollback buffer (:iss:`1113`)
- Implement high precision scrolling with the trackpad on platforms such as
macOS and Wayland that implement it. (:pull:`819`)
@@ -342,7 +464,7 @@ Changelog
- Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`)
- Add a config option (:opt:`x11_hide_window_decorations`) to hide window
- Add a config option (``x11_hide_window_decorations``) to hide window
decorations under X11/Wayland (:iss:`607`)
- Add an option to @set-window-title to make the title change non-permanent

View File

@@ -25,24 +25,6 @@ these characters are followed by a space or empty cell in which case kitty
makes use of the extra cell to render them in two cells.
How do I build kitty.app on macOS?
----------------------------------------
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
for the logo generation step). And run::
make app
This :file:`kitty.app` unlike the released one does not include its own copy of
python and the other dependencies. So if you ever un-install/upgrade those dependencies
you might have to rebuild the app.
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
is designed to run on Linux and is not for the faint of heart.
Using a color theme with a background color does not work well in vim?
-----------------------------------------------------------------------

View File

@@ -75,6 +75,47 @@ You can also use the *CSI t* escape code to get the screen size. Send
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
pixels. This escape code is supported in many terminals, not just kitty.
A minimal example
------------------
Some minimal python code to display PNG images in kitty, using the most basic
features of the graphics protocol:
.. code-block:: python
import sys
from base64 import standard_b64encode
def serialize_gr_command(cmd, payload=None):
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
ans = []
w = ans.append
w(b'\033_G'), w(cmd.encode('ascii'))
if payload:
w(b';')
w(payload)
w(b'\033\\')
return b''.join(ans)
def write_chunked(cmd, data):
data = standard_b64encode(data)
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
cmd['m'] = m
sys.stdout.buffer.write(serialize_gr_command(cmd, chunk))
sys.stdout.flush()
cmd.clear()
write_chunked({'a': 'T', 'f': 100}, open(sys.argv[-1], 'rb').read())
Save this script as :file:`png.py`, then you can use it to display any PNG
file in kitty as::
python png.py file.png
The graphics escape code
---------------------------

View File

@@ -160,6 +160,13 @@ windows and move windows around (similar to window movement in vim)::
map shift+down move_window up
...
You can also define a shortcut to switch to the previously active window::
map ctrl+p nth_window -1
``nth_window`` will focus the nth window for positive numbers and the
previously active windows for negative numbers.
Other keyboard shortcuts
----------------------------------
@@ -370,6 +377,9 @@ arbitrary, command running in a new window, tab or overlay, for example::
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
key. See :sc:`show_scrollback` for details.
If you wish to store very large amounts of scrollback to view using the piping or
:sc:`show_scrollback` features, you can use the :opt:`scrollback_pager_history_size`
option.
Frequently Asked Questions
---------------------------------

View File

@@ -78,3 +78,45 @@ This will send the plain text of the active window to the kitten's
instead. If you want line wrap markers as well, use ``screen-ansi``
or just ``screen``. For the scrollback buffer as well, use
``history``, ``ansi-history`` or ``screen-history``.
Using kittens to script kitty, without any terminal UI
-----------------------------------------------------------
If you would like your kitten to script kitty, without bothering to write a
terminal program, you can tell the kittens system to run the
``handle_result()`` function without first running the ``main()`` function.
For example, here is a kitten that "zooms/unzooms" the current terminal window
by switching to the stack layout or back to the previous layout.
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
.. code-block:: py
def main(args):
pass
def handle_result(args, answer, target_window_id, boss):
tab = boss.active_tab
if tab is not None:
if tab.current_layout.name == 'stack':
tab.last_used_layout()
else:
tab.goto_layout('stack')
handle_result.no_ui = True
Now in kitty.conf add::
map f11 kitten zoom_toggle.py
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
more fancy, switching the kitty OS window to fullscreen as well as changing the
layout, by simply adding the line::
boss.toggle_fullscreen()
to the ``handle_result()`` function, above.

88
docs/pipe.rst Normal file
View File

@@ -0,0 +1,88 @@
Working with the screen and history buffer contents
======================================================
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
can be displayed in a kitty window or overlay.
For example, the following in :file:`kitty.conf` will open the scrollback
buffer in less in an overlay window, when you press :kbd:`F1`::
map f1 pipe @ansi overlay less +G -R
The syntax of the ``pipe`` function is::
pipe <input placeholder> <destination window type> <command line to run>
The piping environment
--------------------------
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.
You can choose where to run the pipe program:
``overlay``
An overlay window over the current kitty window
``window``
A new kitty window
``os_window``
A new top-level window
``tab``
A new window in a new tab
``none``
Run it in the background
Input placeholders
--------------------
There are various different kinds of placeholders
``@text``
Plain text, current screen + scrollback buffer
``@ansi``
Text with formatting, current screen + scrollback buffer
``@screen``
Plain text, only current screen
``@ansi_screen``
Text with formatting, only current screen
``@alternate``
Plain text, secondary screen. The secondary screen is the screen not currently displayed. For
example if you run a fullscreen terminal application, the secondary screen will
be the screen you return to when quitting the application.
``@ansi_alternate``
Text with formatting, secondary screen.
``@alternate_scrollback``
Plain text, secondary screen + scrollback, if any.
``@ansi_alternate_scrollback``
Text with formatting, secondary screen + scrollback, if any.
``none``
No input
You can also add the suffix ``_wrap`` to the placeholder, in which case kitty
will insert the 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.

View File

@@ -6,7 +6,7 @@ These are typically in the form of new or re-purposed escape codes. While these
extensions are currently |kitty| specific, it would be nice to get some of them
adopted more broadly, to push the state of terminal emulators forward.
The goal of these extensions is to be as small an unobtrusive as possible,
The goal of these extensions is to be as small and unobtrusive as possible,
while filling in some gaps in the existing xterm protocol. In particular, one
of the goals of this specification is explicitly not to "re-imagine" the tty.
The tty should remain what it is -- a device for efficiently processing text
@@ -141,6 +141,8 @@ For example::
This encoding means each key event is represented by 8 or 9 printable ascii
only bytes, for maximum robustness.
Support for this mode is indicated by the ``fullkbd`` boolean capability
in the terminfo database, in case querying for it via DECQRM is inconvenient.
.. _ext_styles:

View File

@@ -87,6 +87,11 @@ def parse_ucd():
if category.startswith('M'):
marks.add(codepoint)
# Some common synonyms
word_search_map['bee'] |= word_search_map['honeybee']
word_search_map['lambda'] |= word_search_map['lamda']
word_search_map['lamda'] |= word_search_map['lambda']
def split_two(line):
spec, rest = line.split(';', 1)
@@ -360,16 +365,15 @@ def gen_names():
root = TrieNode()
all_trie_nodes.append(root)
def add_word(word_idx):
word = word_map[word_idx]
def add_word(word_idx, word):
parent = root
for letter in map(ord, word):
idx = parent.add_letter(letter)
parent = all_trie_nodes[idx]
parent.match_offset = offsets_array[word_idx]
for i in range(len(word_map)):
add_word(i)
for i, word in enumerate(word_map):
add_word(i, word)
children_array = [0]
for node in all_trie_nodes:
if node.children:

View File

@@ -27,12 +27,10 @@
#include <stdint.h>
#include <dlfcn.h>
#include <Carbon/Carbon.h>
#if defined(__OBJC__)
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#else
#include <Carbon/Carbon.h>
#include <ApplicationServices/ApplicationServices.h>
typedef void* id;
#endif

View File

@@ -50,6 +50,11 @@
#define NSEventTypeKeyUp NSKeyUp
#endif
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
#define NSPasteboardTypeFileURL NSFilenamesPboardType
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
#define NSPasteboardTypeString NSStringPboardType
#endif
// Returns the style mask corresponding to the window settings
//
@@ -450,6 +455,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
_glfwInputWindowFocus(window, GLFW_TRUE);
updateCursorMode(window);
if (_glfw.ns.disabledCursorWindow != window && cursorInClientArea(window))
{
double x = 0, y = 0;
_glfwPlatformGetCursorPos(window, &x, &y);
_glfwInputCursorPos(window, x, y);
}
}
- (void)windowDidResignKey:(NSNotification *)notification
@@ -549,8 +560,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
markedText = [[NSMutableAttributedString alloc] init];
[self updateTrackingAreas];
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSFilenamesPboardType, nil]];
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL]];
}
return self;
@@ -587,6 +597,21 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
return YES;
}
- (void)updateLayer
{
if (window->context.client != GLFW_NO_API) {
@try {
[window->context.nsgl.object update];
} @catch (NSException *e) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Failed to update NSGL Context object with error: %s (%s)",
[[e name] UTF8String], [[e reason] UTF8String]);
}
}
_glfwInputWindowDamage(window);
}
- (id)makeBackingLayer
{
if (window->ns.layer)
@@ -828,28 +853,22 @@ is_ascii_control_char(char x) {
window->ns.deadKeyState = 0;
return;
}
debug_key(@"scancode: 0x%x (%s) %schar_count: %lu deadKeyState: %u ", scancode, safe_name_for_scancode(scancode), format_mods(mods), char_count, window->ns.deadKeyState);
if (process_text) {
// We check if cocoa wants to insert text, as UCKeyTranslate
// inserts text even when the cmd key is pressed. For instance,
// cmd+a will result in the text a.
// this will call insertText which will fill up _glfw.ns.text
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
_glfw.ns.text[0] = 0;
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
} else {
window->ns.deadKeyState = 0;
}
if (window->ns.deadKeyState && char_count == 0) {
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) {
// 0x75 is the delete key which needs to be ignored during a compose sequence
debug_key(@"Ignoring dead key.\n");
return;
}
}
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
scancode, safe_name_for_scancode(scancode), format_mods(mods),
format_text(_glfw.ns.text), _glfwGetKeyName(key));
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
debug_key(@"text: %s glfw_key: %s\n",
format_text(_glfw.ns.text), _glfwGetKeyName(key));
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
}
@@ -889,6 +908,12 @@ is_ascii_control_char(char x) {
deltaX = [event scrollingDeltaX];
deltaY = [event scrollingDeltaY];
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
if (flags) {
float xscale = 1, yscale = 1;
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
if (xscale > 0) deltaX *= xscale;
if (yscale > 0) deltaY *= yscale;
}
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
_glfwInputScroll(window, deltaX, deltaY, flags);
@@ -896,45 +921,34 @@ is_ascii_control_char(char x) {
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
== NSDragOperationGeneric)
{
[self setNeedsDisplay:YES];
return NSDragOperationGeneric;
}
return NSDragOperationNone;
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
[self setNeedsDisplay:YES];
return YES;
// HACK: We don't know what to say here because we don't know what the
// application wants to do with the paths
return NSDragOperationGeneric;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard* pasteboard = [sender draggingPasteboard];
NSArray* files = [pasteboard propertyListForType:NSFilenamesPboardType];
const NSRect contentRect = [window->ns.view frame];
_glfwInputCursorPos(window,
[sender draggingLocation].x,
contentRect.size.height - [sender draggingLocation].y);
NSPasteboard* pasteboard = [sender draggingPasteboard];
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
options:options];
if (!urls) return NO;
const NSUInteger count = [urls count];
const NSUInteger count = [files count];
if (count)
{
NSEnumerator* e = [files objectEnumerator];
char** paths = calloc(count, sizeof(char*));
NSUInteger i;
for (i = 0; i < count; i++)
paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
for (NSUInteger i = 0; i < count; i++)
paths[i] = _glfw_strdup([[urls objectAtIndex:i] fileSystemRepresentation]);
_glfwInputDrop(window, (int) count, (const char**) paths);
for (i = 0; i < count; i++)
for (NSUInteger i = 0; i < count; i++)
free(paths[i]);
free(paths);
}
@@ -942,11 +956,6 @@ is_ascii_control_char(char x) {
return YES;
}
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
[self setNeedsDisplay:YES];
}
- (BOOL)hasMarkedText
{
return [markedText length] > 0;
@@ -1569,6 +1578,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = (float) (pixels.size.height / points.size.height);
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return [NSEvent doubleClickInterval];
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
[window->ns.object miniaturize:nil];
@@ -1791,7 +1805,7 @@ void _glfwPlatformPollEvents(void)
if (event == nil)
break;
[NSApp sendEvent:event];
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
}
[_glfw.ns.autoreleasePool drain];
@@ -1807,7 +1821,7 @@ void _glfwPlatformWaitEvents(void)
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
_glfwPlatformPollEvents();
}
@@ -1819,8 +1833,7 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
untilDate:date
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event)
[NSApp sendEvent:event];
if (event && [event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
_glfwPlatformPollEvents();
}
@@ -1940,7 +1953,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:src->width * 4
bitsPerPixel:32];
if (rep == nil)
@@ -2002,26 +2015,26 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
void _glfwPlatformSetClipboardString(const char* string)
{
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
NSArray* types = [NSArray arrayWithObjects:NSPasteboardTypeString, nil];
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:types owner:nil];
[pasteboard setString:[NSString stringWithUTF8String:string]
forType:NSStringPboardType];
forType:NSPasteboardTypeString];
}
const char* _glfwPlatformGetClipboardString(void)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (![[pasteboard types] containsObject:NSStringPboardType])
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"Cocoa: Failed to retrieve string from pasteboard");
return NULL;
}
NSString* object = [pasteboard stringForType:NSStringPboardType];
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
if (!object)
{
_glfwInputError(GLFW_PLATFORM_ERROR,

5
glfw/egl_context.h vendored
View File

@@ -43,10 +43,6 @@ typedef Window EGLNativeWindowType;
#define EGLAPIENTRY
typedef struct wl_display* EGLNativeDisplayType;
typedef struct wl_egl_window* EGLNativeWindowType;
#elif defined(_GLFW_MIR)
#define EGLAPIENTRY
typedef MirEGLNativeDisplayType EGLNativeDisplayType;
typedef MirEGLNativeWindowType EGLNativeWindowType;
#else
#error "No supported EGL platform selected"
#endif
@@ -216,4 +212,3 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
const _GLFWfbconfig* fbconfig,
Visual** visual, int* depth);
#endif /*_GLFW_X11*/

View File

@@ -5,7 +5,6 @@
import json
import os
import re
import shutil
import sys
_plat = sys.platform.lower()
@@ -22,7 +21,7 @@ def wayland_protocol_file_name(base, ext='c'):
return 'wayland-{}-client-protocol.{}'.format(base, ext)
def init_env(env, pkg_config, at_least_version, module='x11'):
def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
ans = env.copy()
ans.cflags = [
x for x in ans.cflags
@@ -32,10 +31,11 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
ans.cflags.append('-pthread')
ans.ldpaths.append('-pthread')
ans.cflags.append('-fpic')
ans.cflags.append('-D_GLFW_' + module.upper())
ans.cflags.append('-D_GLFW_BUILD_DLL')
ans.cppflags.append('-D_GLFW_' + module.upper())
ans.cppflags.append('-D_GLFW_BUILD_DLL')
if is_macos:
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
ans.ldpaths.extend(
"-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo".
split()
@@ -76,6 +76,14 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
ans.ldpaths.extend(pkg_config(dep, '--libs'))
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
#include <unistd.h>
#include <sys/syscall.h>
int main(void) {
return syscall(__NR_memfd_create, "test", 0);
}''')
if has_memfd_create:
ans.cppflags.append('-DHAS_MEMFD_CREATE')
return ans
@@ -179,7 +187,7 @@ class Function:
return ans
def generate_wrappers(glfw_header, glfw_native_header):
def generate_wrappers(glfw_header):
src = open(glfw_header).read()
functions = []
first = None
@@ -192,6 +200,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
functions.append(Function(decl))
for line in '''\
void* glfwGetCocoaWindow(GLFWwindow* window)
void* glfwGetNSGLContext(GLFWwindow *window)
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
@@ -199,9 +208,10 @@ def generate_wrappers(glfw_header, glfw_native_header):
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
void* glfwGetX11Display(void)
int32_t glfwGetX11Window(GLFWwindow* window)
void glfwSetX11SelectionString(const char* string)
const char* glfwGetX11SelectionString(void)
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
int glfwGetXKBScancode(const char* key_name, int case_sensitive)
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
'''.splitlines():
if line:
functions.append(Function(line.strip(), check_fail=False))
@@ -220,7 +230,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
{}
const char* load_glfw(const char* path);
@@ -258,53 +268,9 @@ unload_glfw() {
f.write(code)
def from_glfw(glfw_dir):
os.chdir(glfw_dir)
sinfo = collect_source_information()
files_to_copy = set()
for x in sinfo.values():
if isinstance(x, dict):
headers, sources = x['headers'], x['sources']
for name in headers + sources:
files_to_copy.add(os.path.abspath(os.path.join('src', name)))
glfw_header = os.path.abspath('include/GLFW/glfw3.h')
glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h')
os.chdir(base)
for x in os.listdir('.'):
if x.rpartition('.') in ('c', 'h'):
os.unlink(x)
for src in files_to_copy:
shutil.copy2(src, '.')
shutil.copy2(glfw_header, '.')
json.dump(
sinfo,
open('source-info.json', 'w'),
indent=2,
ensure_ascii=False,
sort_keys=True
)
generate_wrappers(glfw_header, glfw_native_header)
def to_glfw(glfw_dir):
src = base
for x in os.listdir(src):
if x in ('glfw.py', 'glfw3.h', '__pycache__', 'source-info.json') or x.startswith('wayland-'):
continue
xp = os.path.join(src, x)
shutil.copyfile(xp, os.path.join(glfw_dir, 'src', x))
shutil.copyfile(os.path.join(src, 'glfw3.h'), os.path.join(glfw_dir, 'include/GLFW/glfw3.h'))
def main():
glfw_dir = os.path.abspath(os.path.join(base, '../../glfw'))
q = sys.argv[1].lower().replace('_', '-')
if q == 'from-glfw':
from_glfw(glfw_dir)
elif q == 'to-glfw':
to_glfw(glfw_dir)
else:
raise SystemExit('First argument must be one of to-glfw or from-glfw')
os.chdir(os.path.dirname(os.path.abspath(__file__)))
generate_wrappers('glfw3.h')
if __name__ == '__main__':

26
glfw/glfw3.h vendored
View File

@@ -2930,6 +2930,32 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
*/
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);
/*! @brief Returns the double click time interval.
*
* This function returns the maximum time between clicks to count as a
* double click.
*
* The double click interval is a positive finite number greater than zero,
* where zero means that no click is ever recognized as a double click. If the
* system does not support a double click interval, this function always returns one half.
*
* @return The double click interval.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref double_click
* @sa @ref click_interval
* @sa @ref double_click_interval
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* window);
/*! @brief Returns the opacity of the whole window.
*
* This function returns the opacity of the window, including any decorations.

16
glfw/input.c vendored
View File

@@ -1375,6 +1375,22 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle)
return _glfwPlatformGetClipboardString();
}
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle, const char* string)
{
assert(string != NULL);
_GLFW_REQUIRE_INIT();
_glfwPlatformSetPrimarySelectionString(string);
}
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetPrimarySelectionString();
}
#endif
GLFWAPI double glfwGetTime(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);

10
glfw/internal.h vendored
View File

@@ -129,7 +129,6 @@ typedef enum VkStructureType
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
@@ -191,8 +190,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
#include "x11_platform.h"
#elif defined(_GLFW_WAYLAND)
#include "wl_platform.h"
#elif defined(_GLFW_MIR)
#include "mir_platform.h"
#elif defined(_GLFW_OSMESA)
#include "null_platform.h"
#else
@@ -571,8 +568,6 @@ struct _GLFWlibrary
GLFWbool KHR_xcb_surface;
#elif defined(_GLFW_WAYLAND)
GLFWbool KHR_wayland_surface;
#elif defined(_GLFW_MIR)
GLFWbool KHR_mir_surface;
#endif
} vk;
@@ -629,6 +624,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
void _glfwPlatformSetClipboardString(const char* string);
const char* _glfwPlatformGetClipboardString(void);
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
void _glfwPlatformSetPrimarySelectionString(const char* string);
const char* _glfwPlatformGetPrimarySelectionString(void);
#endif
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
void _glfwPlatformUpdateGamepadGUID(char* guid);
@@ -658,6 +657,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* right, int* bottom);
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale);
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);

View File

@@ -228,7 +228,7 @@ static GLFWbool openJoystickDevice(const char* path)
return GLFW_FALSE;
}
strncpy(linjs.path, path, sizeof(linjs.path));
strncpy(linjs.path, path, sizeof(linjs.path) - 1);
memcpy(&js->linjs, &linjs, sizeof(linjs));
pollAbsState(js);
@@ -431,4 +431,3 @@ int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
void _glfwPlatformUpdateGamepadGUID(char* guid)
{
}

54
glfw/memfd.h vendored Normal file
View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#ifdef HAS_MEMFD_CREATE
#include <unistd.h>
#include <sys/syscall.h>
static inline int memfd_create(const char *name, unsigned int flags) {
return syscall(__NR_memfd_create, name, flags);
}
#ifndef F_LINUX_SPECIFIC_BASE
#define F_LINUX_SPECIFIC_BASE 1024
#endif
#ifndef F_ADD_SEALS
#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */
#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */
#define F_SEAL_GROW 0x0004 /* prevent file from growing */
#define F_SEAL_WRITE 0x0008 /* prevent writes */
#endif
#ifndef MFD_CLOEXEC
#define MFD_CLOEXEC 0x0001U
#define MFD_ALLOW_SEALING 0x0002U
#endif
#else
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
static inline int
createTmpfileCloexec(char* tmpname)
{
int fd;
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
return fd;
}
#endif

View File

@@ -27,6 +27,11 @@
#include "internal.h"
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
#define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
#endif
static void makeContextCurrentNSGL(_GLFWwindow* window)
{
if (window)
@@ -49,7 +54,7 @@ static void swapIntervalNSGL(int interval)
GLint sync = interval;
[window->context.nsgl.object setValues:&sync
forParameter:NSOpenGLCPSwapInterval];
forParameter:NSOpenGLContextParameterSwapInterval];
}
static int extensionSupportedNSGL(const char* extension)
@@ -299,7 +304,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
if (fbconfig->transparent)
{
GLint opaque = 0;
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLContextParameterSurfaceOpacity];
}
[window->context.nsgl.object setView:window->ns.view];
@@ -332,4 +337,3 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
return window->context.nsgl.object;
}

5
glfw/null_window.c vendored
View File

@@ -148,6 +148,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = 1.f;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
}

View File

@@ -71,7 +71,9 @@
"stable/viewporter/viewporter.xml",
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
"unstable/primary-selection/primary-selection-unstable-v1.xml"
],
"sources": [
"wl_init.c",
@@ -91,7 +93,7 @@
},
"wayland_protocols": [
1,
12
17
],
"win32": {
"headers": [
@@ -145,4 +147,4 @@
"null_joystick.c"
]
}
}
}

4
glfw/vulkan.c vendored
View File

@@ -136,9 +136,6 @@ GLFWbool _glfwInitVulkan(int mode)
#elif defined(_GLFW_WAYLAND)
else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0)
_glfw.vk.KHR_wayland_surface = GLFW_TRUE;
#elif defined(_GLFW_MIR)
else if (strcmp(ep[i].extensionName, "VK_KHR_mir_surface") == 0)
_glfw.vk.KHR_mir_surface = GLFW_TRUE;
#endif
}
@@ -326,4 +323,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
}

5
glfw/win32_window.c vendored
View File

@@ -1602,6 +1602,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
_glfwGetMonitorContentScaleWin32(handle, xscale, yscale);
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
ShowWindow(window->win32.handle, SW_MINIMIZE);

9
glfw/window.c vendored
View File

@@ -721,6 +721,15 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle,
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
}
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(0.5f);
return _glfwPlatformGetDoubleClickInterval(window);
}
GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;

49
glfw/wl_init.c vendored
View File

@@ -29,6 +29,8 @@
#include "backend_utils.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
@@ -407,7 +409,7 @@ static void
dispatchPendingKeyRepeats(id_type timer_id, void *data) {
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, ((double)_glfw.wl.keyboardRepeatRate) / 1000.0);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
}
@@ -561,8 +563,11 @@ static void registryHandleGlobal(void* data,
_glfw.wl.seatVersion);
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
}
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
_glfwSetupWaylandDataDevice();
if (_glfw.wl.seat) {
if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice();
if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
_glfwSetupWaylandPrimarySelectionDevice();
}
}
}
else if (strcmp(interface, "xdg_wm_base") == 0)
@@ -571,6 +576,12 @@ static void registryHandleGlobal(void* data,
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
}
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
{
_glfw.wl.decorationManager =
wl_registry_bind(registry, name,
&zxdg_decoration_manager_v1_interface, 1);
}
else if (strcmp(interface, "wp_viewporter") == 0)
{
_glfw.wl.viewporter =
@@ -607,6 +618,17 @@ static void registryHandleGlobal(void* data,
_glfwSetupWaylandDataDevice();
}
}
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 ||
strcmp(interface, "gtk_primary_selection_device_manager") == 0)
{
_glfw.wl.primarySelectionDeviceManager =
wl_registry_bind(registry, name,
&zwp_primary_selection_device_manager_v1_interface,
1);
if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
_glfwSetupWaylandPrimarySelectionDevice();
}
}
}
@@ -714,7 +736,17 @@ int _glfwPlatformInit(void)
if (_glfw.wl.pointer && _glfw.wl.shm)
{
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
char *cursorSizeEnd;
int cursorSize = 32;
if (cursorSizeStr)
{
errno = 0;
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
cursorSize = (int)cursorSizeLong;
}
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
if (!_glfw.wl.cursorTheme)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -763,6 +795,8 @@ void _glfwPlatformTerminate(void)
wl_shell_destroy(_glfw.wl.shell);
if (_glfw.wl.viewporter)
wp_viewporter_destroy(_glfw.wl.viewporter);
if (_glfw.wl.decorationManager)
zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager);
if (_glfw.wl.wmBase)
xdg_wm_base_destroy(_glfw.wl.wmBase);
if (_glfw.wl.pointer)
@@ -788,6 +822,10 @@ void _glfwPlatformTerminate(void)
wl_data_device_destroy(_glfw.wl.dataDevice);
if (_glfw.wl.dataDeviceManager)
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
if (_glfw.wl.primarySelectionDevice)
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
if (_glfw.wl.primarySelectionDeviceManager)
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
if (_glfw.wl.registry)
wl_registry_destroy(_glfw.wl.registry);
if (_glfw.wl.display)
@@ -797,7 +835,8 @@ void _glfwPlatformTerminate(void)
}
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
free(_glfw.wl.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL;
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
}

49
glfw/wl_monitor.c vendored
View File

@@ -32,16 +32,16 @@
#include <errno.h>
static void geometry(void* data,
struct wl_output* output,
int32_t x,
int32_t y,
int32_t physicalWidth,
int32_t physicalHeight,
int32_t subpixel,
const char* make,
const char* model,
int32_t transform)
static void outputHandleGeometry(void* data,
struct wl_output* output,
int32_t x,
int32_t y,
int32_t physicalWidth,
int32_t physicalHeight,
int32_t subpixel,
const char* make,
const char* model,
int32_t transform)
{
struct _GLFWmonitor *monitor = data;
char name[1024];
@@ -55,12 +55,12 @@ static void geometry(void* data,
monitor->name = _glfw_strdup(name);
}
static void mode(void* data,
struct wl_output* output,
uint32_t flags,
int32_t width,
int32_t height,
int32_t refresh)
static void outputHandleMode(void* data,
struct wl_output* output,
uint32_t flags,
int32_t width,
int32_t height,
int32_t refresh)
{
struct _GLFWmonitor *monitor = data;
GLFWvidmode mode;
@@ -81,16 +81,16 @@ static void mode(void* data,
monitor->wl.currentMode = monitor->modeCount - 1;
}
static void done(void* data, struct wl_output* output)
static void outputHandleDone(void* data, struct wl_output* output)
{
struct _GLFWmonitor *monitor = data;
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
}
static void scale(void* data,
struct wl_output* output,
int32_t factor)
static void outputHandleScale(void* data,
struct wl_output* output,
int32_t factor)
{
struct _GLFWmonitor *monitor = data;
@@ -98,10 +98,10 @@ static void scale(void* data,
}
static const struct wl_output_listener outputListener = {
geometry,
mode,
done,
scale,
outputHandleGeometry,
outputHandleMode,
outputHandleDone,
outputHandleScale,
};
@@ -205,4 +205,3 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->wl.output;
}

43
glfw/wl_platform.h vendored
View File

@@ -56,9 +56,11 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@@ -143,6 +145,7 @@ typedef struct _GLFWwindowWayland
struct {
struct xdg_surface* surface;
struct xdg_toplevel* toplevel;
struct zxdg_toplevel_decoration_v1* decoration;
} xdg;
_GLFWcursor* currentCursor;
@@ -169,18 +172,34 @@ typedef struct _GLFWwindowWayland
GLFWbool justCreated;
struct {
GLFWbool serverSide;
struct wl_buffer* buffer;
_GLFWdecorationWayland top, left, right, bottom;
int focus;
} decorations;
struct {
unsigned long long id;
void(*callback)(unsigned long long id);
struct wl_callback *current_wl_callback;
} frameCallbackData;
} _GLFWwindowWayland;
typedef enum _GLFWWaylandOfferType
{
EXPIRED,
CLIPBOARD,
DRAG_AND_DROP,
PRIMARY_SELECTION
}_GLFWWaylandOfferType ;
typedef struct _GLFWWaylandDataOffer
{
struct wl_data_offer *id;
const char *mime;
int offer_type;
_GLFWWaylandOfferType offer_type;
size_t idx;
int is_self_offer;
int has_uri_list;
@@ -189,6 +208,17 @@ typedef struct _GLFWWaylandDataOffer
struct wl_surface *surface;
} _GLFWWaylandDataOffer;
typedef struct _GLFWWaylandPrimaryOffer
{
struct zwp_primary_selection_offer_v1 *id;
const char *mime;
_GLFWWaylandOfferType offer_type;
size_t idx;
int is_self_offer;
int has_uri_list;
struct wl_surface *surface;
} _GLFWWaylandPrimaryOffer;
// Wayland-specific global data
//
typedef struct _GLFWlibraryWayland
@@ -203,6 +233,7 @@ typedef struct _GLFWlibraryWayland
struct wl_pointer* pointer;
struct wl_keyboard* keyboard;
struct xdg_wm_base* wmBase;
struct zxdg_decoration_manager_v1* decorationManager;
struct wp_viewporter* viewporter;
struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints;
@@ -210,6 +241,9 @@ typedef struct _GLFWlibraryWayland
struct wl_data_device_manager* dataDeviceManager;
struct wl_data_device* dataDevice;
struct wl_data_source* dataSourceForClipboard;
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
struct zwp_primary_selection_device_v1* primarySelectionDevice;
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
int compositorVersion;
int seatVersion;
@@ -250,11 +284,13 @@ typedef struct _GLFWlibraryWayland
} egl;
EventLoopData eventLoopData;
char* pasteString;
char* clipboardString;
char* clipboardSourceString;
struct wl_data_offer* clipboardSourceOffer;
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
size_t primarySelectionOffersCounter;
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@@ -285,4 +321,5 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwSetupWaylandDataDevice();
void _glfwSetupWaylandPrimarySelectionDevice();
void animateCursorImage(id_type timer_id, void *data);

361
glfw/wl_window.c vendored
View File

@@ -28,6 +28,7 @@
#include "internal.h"
#include "backend_utils.h"
#include "memfd.h"
#include <stdio.h>
#include <stdlib.h>
@@ -115,18 +116,6 @@ static const struct wl_shell_surface_listener shellSurfaceListener = {
handlePopupDone
};
static int
createTmpfileCloexec(char* tmpname)
{
int fd;
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
return fd;
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
@@ -150,11 +139,21 @@ createTmpfileCloexec(char* tmpname)
static int
createAnonymousFile(off_t size)
{
int ret;
#ifdef HAS_MEMFD_CREATE
int fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd < 0) return -1;
// We can add this seal before calling posix_fallocate(), as the file
// is currently zero-sized anyway.
//
// There is also no need to check for the return value, we couldnt do
// anything with it anyway.
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
#else
static const char template[] = "/glfw-shared-XXXXXX";
const char* path;
char* name;
int fd;
int ret;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
@@ -173,6 +172,7 @@ createAnonymousFile(off_t size)
if (fd < 0)
return -1;
#endif
ret = posix_fallocate(fd, 0, size);
if (ret != 0)
{
@@ -198,7 +198,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %m",
length);
return GLFW_FALSE;
return NULL;
}
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
@@ -207,7 +207,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: mmap failed: %m");
close(fd);
return GLFW_FALSE;
return NULL;
}
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
@@ -272,11 +272,13 @@ static void createDecorations(_GLFWwindow* window)
const GLFWimage image = { 1, 1, data };
GLFWbool opaque = (data[3] == 255);
if (!_glfw.wl.viewporter)
if (!_glfw.wl.viewporter || !window->decorated || window->wl.decorations.serverSide)
return;
if (!window->wl.decorations.buffer)
window->wl.decorations.buffer = createShmBuffer(&image);
if (!window->wl.decorations.buffer)
return;
createDecoration(&window->wl.decorations.top, window->wl.surface,
window->wl.decorations.buffer, opaque,
@@ -317,6 +319,20 @@ static void destroyDecorations(_GLFWwindow* window)
destroyDecoration(&window->wl.decorations.bottom);
}
static void xdgDecorationHandleConfigure(void* data,
struct zxdg_toplevel_decoration_v1* decoration,
uint32_t mode)
{
_GLFWwindow* window = data;
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
if (!window->wl.decorations.serverSide)
createDecorations(window);
}
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
xdgDecorationHandleConfigure,
};
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
{
@@ -489,9 +505,6 @@ static GLFWbool createSurface(_GLFWwindow* window,
if (!window->wl.transparent)
setOpaqueRegion(window);
if (window->decorated && !window->monitor)
createDecorations(window);
return GLFW_TRUE;
}
@@ -512,7 +525,8 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refres
monitor->wl.output);
}
setIdleInhibitor(window, GLFW_TRUE);
destroyDecorations(window);
if (!window->wl.decorations.serverSide)
destroyDecorations(window);
}
static GLFWbool createShellSurface(_GLFWwindow* window)
@@ -548,11 +562,13 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
{
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
setIdleInhibitor(window, GLFW_FALSE);
createDecorations(window);
}
else
{
wl_shell_surface_set_toplevel(window->wl.shellSurface);
setIdleInhibitor(window, GLFW_FALSE);
createDecorations(window);
}
wl_surface_commit(window->wl.surface);
@@ -641,6 +657,27 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
xdgSurfaceHandleConfigure
};
static void setXdgDecorations(_GLFWwindow* window)
{
if (_glfw.wl.decorationManager)
{
window->wl.xdg.decoration =
zxdg_decoration_manager_v1_get_toplevel_decoration(
_glfw.wl.decorationManager, window->wl.xdg.toplevel);
zxdg_toplevel_decoration_v1_add_listener(window->wl.xdg.decoration,
&xdgDecorationListener,
window);
zxdg_toplevel_decoration_v1_set_mode(
window->wl.xdg.decoration,
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
else
{
window->wl.decorations.serverSide = GLFW_FALSE;
createDecorations(window);
}
}
static GLFWbool createXdgSurface(_GLFWwindow* window)
{
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
@@ -688,10 +725,12 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
{
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
setIdleInhibitor(window, GLFW_FALSE);
setXdgDecorations(window);
}
else
{
setIdleInhibitor(window, GLFW_FALSE);
setXdgDecorations(window);
}
if (strlen(window->wl.appId))
xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId);
@@ -752,6 +791,7 @@ incrementCursorImage(_GLFWwindow* window)
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
setCursorImage(&cursor->wl);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
return;
}
}
@@ -912,6 +952,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
window->context.destroy(window);
destroyDecorations(window);
if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
if (window->wl.decorations.buffer)
wl_buffer_destroy(window->wl.decorations.buffer);
@@ -932,6 +974,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
free(window->wl.title);
free(window->wl.monitors);
if (window->wl.frameCallbackData.current_wl_callback)
wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
}
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
@@ -1025,7 +1069,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* left, int* top,
int* right, int* bottom)
{
if (window->decorated && !window->monitor)
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
{
if (top)
*top = _GLFW_DECORATION_TOP;
@@ -1047,6 +1091,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = (float) window->wl.scale;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
if (_glfw.wl.wmBase)
@@ -1167,7 +1216,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
else if (window->wl.shellSurface)
wl_shell_surface_set_toplevel(window->wl.shellSurface);
setIdleInhibitor(window, GLFW_FALSE);
if (window->decorated)
if (!_glfw.wl.decorationManager)
createDecorations(window);
}
_glfwInputWindowMonitor(window, monitor);
@@ -1304,6 +1353,8 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
int xhot, int yhot, int count)
{
cursor->wl.buffer = createShmBuffer(image);
if (!cursor->wl.buffer)
return GLFW_FALSE;
cursor->wl.width = image->width;
cursor->wl.height = image->height;
cursor->wl.xhot = xhot;
@@ -1491,13 +1542,13 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
}
}
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd)
static void send_text(char *text, int fd)
{
if (_glfw.wl.clipboardString) {
size_t len = strlen(_glfw.wl.clipboardString), pos = 0;
if (text) {
size_t len = strlen(text), pos = 0;
double start = glfwGetTime();
while (pos < len && glfwGetTime() - start < 2.0) {
ssize_t ret = write(fd, _glfw.wl.clipboardString + pos, len - pos);
ssize_t ret = write(fd, text + pos, len - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -1513,22 +1564,27 @@ static void _glfwSendClipboardText(void *data, struct wl_data_source *data_sourc
close(fd);
}
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(pipefd[1]);
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd) {
send_text(_glfw.wl.clipboardString, fd);
}
static void _glfwSendPrimarySelectionText(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source,
const char *mime_type, int fd) {
send_text(_glfw.wl.primarySelectionString, fd);
}
static char* read_offer_string(int data_pipe) {
wl_display_flush(_glfw.wl.display);
size_t sz = 0, capacity = 0;
char *buf = NULL;
struct pollfd fds;
fds.fd = pipefd[0];
fds.fd = data_pipe;
fds.events = POLLIN;
double start = glfwGetTime();
#define bail(...) { \
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
free(buf); buf = NULL; \
close(pipefd[0]); \
close(data_pipe); \
return NULL; \
}
@@ -1548,12 +1604,12 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime)
bail("Wayland: Failed to allocate memory to read clipboard data");
}
}
ret = read(pipefd[0], buf + sz, capacity - sz - 1);
ret = read(data_pipe, buf + sz, capacity - sz - 1);
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
}
if (ret == 0) { close(pipefd[0]); buf[sz] = 0; return buf; }
if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; }
sz += ret;
start = glfwGetTime();
}
@@ -1562,13 +1618,20 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime)
}
static const char* _glfwReceiveClipboardText(struct wl_data_offer *data_offer, const char *mime)
{
if (_glfw.wl.clipboardSourceOffer == data_offer && _glfw.wl.clipboardSourceString)
return _glfw.wl.clipboardSourceString;
free(_glfw.wl.clipboardSourceString);
_glfw.wl.clipboardSourceString = read_data_offer(data_offer, mime);
return _glfw.wl.clipboardSourceString;
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
close(pipefd[1]);
return read_offer_string(pipefd[0]);
}
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(pipefd[1]);
return read_offer_string(pipefd[0]);
}
static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) {
@@ -1577,6 +1640,12 @@ static void data_source_canceled(void *data, struct wl_data_source *wl_data_sour
wl_data_source_destroy(wl_data_source);
}
static void primary_selection_source_canceled(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source) {
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
_glfw.wl.dataSourceForPrimarySelection = NULL;
zwp_primary_selection_source_v1_destroy(primary_selection_source);
}
static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) {
}
@@ -1586,6 +1655,11 @@ const static struct wl_data_source_listener data_source_listener = {
.target = data_source_target,
};
const static struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
.send = _glfwSendPrimarySelectionText,
.cancelled = primary_selection_source_canceled,
};
static void prune_unclaimed_data_offers() {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
@@ -1595,29 +1669,63 @@ static void prune_unclaimed_data_offers() {
}
}
static void prune_unclaimed_primary_selection_offers() {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[i].id);
memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
}
}
}
static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer)
{
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == data_offer) {
_glfw.wl.dataOffers[i].offer_type = 1;
} else if (_glfw.wl.dataOffers[i].offer_type == 1) {
_glfw.wl.dataOffers[i].offer_type = 0; // previous selection offer
_glfw.wl.dataOffers[i].offer_type = CLIPBOARD;
} else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer
}
}
prune_unclaimed_data_offers();
}
static void mark_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1* primary_selection_device,
struct zwp_primary_selection_offer_v1 *primary_selection_offer) {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) {
_glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION;
} else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
_glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer
}
}
prune_unclaimed_primary_selection_offers();
}
static void set_offer_mimetype(struct _GLFWWaylandDataOffer* offer, const char* mime) {
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
offer->mime = "text/plain;charset=utf-8";
else if (!offer->mime && strcmp(mime, "text/plain") == 0)
offer->mime = "text/plain";
else if (strcmp(mime, clipboard_mime()) == 0)
offer->is_self_offer = 1;
else if (strcmp(mime, URI_LIST_MIME) == 0)
offer->has_uri_list = 1;
}
static void handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) {
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
_glfw.wl.dataOffers[i].mime = "text/plain;charset=utf-8";
else if (!_glfw.wl.dataOffers[i].mime && strcmp(mime, "text/plain") == 0)
_glfw.wl.dataOffers[i].mime = "text/plain";
else if (strcmp(mime, clipboard_mime()) == 0)
_glfw.wl.dataOffers[i].is_self_offer = 1;
else if (strcmp(mime, URI_LIST_MIME) == 0)
_glfw.wl.dataOffers[i].has_uri_list = 1;
set_offer_mimetype(&_glfw.wl.dataOffers[i], mime);
break;
}
}
}
static void handle_primary_selection_offer_mimetype(void *data, struct zwp_primary_selection_offer_v1* id, const char *mime) {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id == id) {
set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime);
break;
}
}
@@ -1648,6 +1756,10 @@ static const struct wl_data_offer_listener data_offer_listener = {
.action = data_offer_action,
};
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
.offer = handle_primary_selection_offer_mimetype,
};
static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
size_t smallest_idx = SIZE_MAX, pos = 0;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
@@ -1669,15 +1781,36 @@ end:
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
}
static void handle_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
size_t smallest_idx = SIZE_MAX, pos = 0;
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].idx && _glfw.wl.primarySelectionOffers[i].idx < smallest_idx) {
smallest_idx = _glfw.wl.primarySelectionOffers[i].idx;
pos = i;
}
if (_glfw.wl.primarySelectionOffers[i].id == NULL) {
_glfw.wl.primarySelectionOffers[i].id = id;
_glfw.wl.primarySelectionOffers[i].idx = ++_glfw.wl.primarySelectionOffersCounter;
goto end;
}
}
if (_glfw.wl.primarySelectionOffers[pos].id) zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[pos].id);
memset(_glfw.wl.primarySelectionOffers + pos, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
_glfw.wl.primarySelectionOffers[pos].id = id;
_glfw.wl.primarySelectionOffers[pos].idx = ++_glfw.wl.primarySelectionOffersCounter;
end:
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL);
}
static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) {
_glfw.wl.dataOffers[i].offer_type = 2;
_glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP;
_glfw.wl.dataOffers[i].surface = surface;
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
wl_data_offer_accept(id, serial, mime);
} else if (_glfw.wl.dataOffers[i].offer_type == 2) {
_glfw.wl.dataOffers[i].offer_type = 0; // previous drag offer
} else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer
}
}
prune_unclaimed_data_offers();
@@ -1685,18 +1818,16 @@ static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32
static void drag_leave(void *data, struct wl_data_device *wl_data_device) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == 2) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
}
}
}
static void drop(void *data, struct wl_data_device *wl_data_device) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == 2) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
if (uri_list) {
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
@@ -1738,24 +1869,38 @@ const static struct wl_data_device_listener data_device_listener = {
.leave = drag_leave,
};
const static struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
.data_offer = handle_primary_selection_offer,
.selection = mark_primary_selection_offer,
};
static void
copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (!_glfw.wl.dataDevice) return;
if (data == (void*)_glfw.wl.dataSourceForClipboard) {
clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) {
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
}
wl_callback_destroy(callback);
}
const static struct wl_callback_listener copy_callback_listener = {
.done = copy_callback_done
};
static void
primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) {
zwp_primary_selection_device_v1_set_selection(_glfw.wl.primarySelectionDevice, data, serial);
}
wl_callback_destroy(callback);
}
void _glfwSetupWaylandDataDevice() {
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
}
void _glfwSetupWaylandPrimarySelectionDevice() {
_glfw.wl.primarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device(_glfw.wl.primarySelectionDeviceManager, _glfw.wl.seat);
if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL);
}
static inline GLFWbool _glfwEnsureDataDevice() {
if (!_glfw.wl.dataDeviceManager)
{
@@ -1804,15 +1949,71 @@ void _glfwPlatformSetClipboardString(const char* string)
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
wl_callback_add_listener(callback, &copy_callback_listener, _glfw.wl.dataSourceForClipboard);
const static struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
}
const char* _glfwPlatformGetClipboardString(void)
{
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == 1) {
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
free(_glfw.wl.pasteString);
_glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
return _glfw.wl.pasteString;
}
}
return NULL;
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
if (!_glfw.wl.primarySelectionDevice) {
static GLFWbool warned_about_primary_selection_device = GLFW_FALSE;
if (!warned_about_primary_selection_device) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy no primary selection device available");
warned_about_primary_selection_device = GLFW_TRUE;
}
return;
}
if (_glfw.wl.primarySelectionString == string) return;
free(_glfw.wl.primarySelectionString);
_glfw.wl.primarySelectionString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForPrimarySelection)
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
if (!_glfw.wl.dataSourceForPrimarySelection)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create primary selection source");
return;
}
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
const static struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
if (_glfw.wl.primarySelectionOffers[i].is_self_offer) {
return _glfw.wl.primarySelectionString;
}
free(_glfw.wl.pasteString);
_glfw.wl.pasteString = read_primary_selection_offer(_glfw.wl.primarySelectionOffers[i].id, _glfw.wl.primarySelectionOffers[i].mime);
return _glfw.wl.pasteString;
}
}
return NULL;
@@ -1886,6 +2087,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
}
static void
frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time) {
_GLFWwindow* window = (_GLFWwindow*) data;
if (callback == window->wl.frameCallbackData.current_wl_callback) {
window->wl.frameCallbackData.callback(window->wl.frameCallbackData.id);
window->wl.frameCallbackData.current_wl_callback = NULL;
}
wl_callback_destroy(callback);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////
@@ -1907,3 +2117,16 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}
GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, void(*callback)(unsigned long long id)) {
_GLFWwindow* window = (_GLFWwindow*) handle;
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
window->wl.frameCallbackData.id = id;
window->wl.frameCallbackData.callback = callback;
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
if (window->wl.frameCallbackData.current_wl_callback) {
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
wl_surface_commit(window->wl.surface);
}
}

78
glfw/x11_window.c vendored
View File

@@ -873,6 +873,7 @@ static const char* getSelectionString(Atom selection)
Atom actualType;
int actualFormat;
unsigned long itemCount, bytesAfter;
double start = glfwGetTime();
XEvent notification, dummy;
XConvertSelection(_glfw.x11.display,
@@ -887,7 +888,10 @@ static const char* getSelectionString(Atom selection)
SelectionNotify,
&notification))
{
waitForX11Event(-1);
double time = glfwGetTime();
if (time - start > 2)
return "";
waitForX11Event(2.0 - (time - start));
}
if (notification.xselection.property == None)
@@ -918,12 +922,16 @@ static const char* getSelectionString(Atom selection)
for (;;)
{
start = glfwGetTime();
while (!XCheckIfEvent(_glfw.x11.display,
&dummy,
isSelPropNewValueNotify,
(XPointer) &notification))
{
waitForX11Event(-1);
double time = glfwGetTime();
if (time - start > 2)
return "";
waitForX11Event(2.0 - (time - start));
}
XFree(data);
@@ -1291,12 +1299,20 @@ static void processEvent(XEvent *event)
case EnterNotify:
{
// XEnterWindowEvent is XCrossingEvent
const int x = event->xcrossing.x;
const int y = event->xcrossing.y;
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
// ignore the defined cursor for hidden cursor mode
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
updateCursorImage(window);
_glfwInputCursorEnter(window, GLFW_TRUE);
_glfwInputCursorPos(window, x, y);
window->x11.lastCursorPosX = x;
window->x11.lastCursorPosY = y;
return;
}
@@ -2100,6 +2116,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = _glfw.x11.contentScaleY;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
if (window->x11.overrideRedirect)
@@ -2278,6 +2299,10 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
Atom* states;
unsigned long i;
GLFWbool maximized = GLFW_FALSE;
if (!_glfw.x11.NET_WM_STATE ||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT ||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
return maximized;
const unsigned long count =
_glfwGetWindowPropertyX11(window->x11.handle,
_glfw.x11.NET_WM_STATE,
@@ -2667,6 +2692,29 @@ const char* _glfwPlatformGetClipboardString(void)
return getSelectionString(_glfw.x11.CLIPBOARD);
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.PRIMARY,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of primary selection");
}
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
return getSelectionString(_glfw.x11.PRIMARY);
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface)
@@ -2838,32 +2886,6 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
return window->x11.handle;
}
GLFWAPI void glfwSetX11SelectionString(const char* string)
{
_GLFW_REQUIRE_INIT();
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.PRIMARY,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of primary selection");
}
}
GLFWAPI const char* glfwGetX11SelectionString(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return getSelectionString(_glfw.x11.PRIMARY);
}
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}

1
glfw/xkb_glfw.c vendored
View File

@@ -255,6 +255,7 @@ load_compose_tables(_GLFWXKBData *xkb) {
if (!xkb->states.composeState) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
}
xkb_compose_table_unref(compose_table);
}
GLFWbool

View File

@@ -84,9 +84,17 @@ class Hints(Handler):
self.args = args
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
self.multiple = args.multiple
self.match_suffix = self.get_match_suffix(args)
self.chosen = []
self.reset()
def get_match_suffix(self, args):
if args.add_trailing_space == 'always':
return ' '
if args.add_trailing_space == 'never':
return ''
return ' ' if args.multiple else ''
def reset(self):
self.current_input = ''
self.current_text = None
@@ -112,7 +120,7 @@ class Hints(Handler):
if encode_hint(idx).startswith(self.current_input)
]
if len(matches) == 1:
self.chosen.append(matches[0].text)
self.chosen.append(matches[0].text + self.match_suffix)
if self.multiple:
self.ignore_mark_indices.add(matches[0].index)
self.reset()
@@ -130,7 +138,7 @@ class Hints(Handler):
elif key_event is enter_key and self.current_input:
try:
idx = decode_hint(self.current_input)
self.chosen.append(self.index_map[idx].text)
self.chosen.append(self.index_map[idx].text + self.match_suffix)
self.ignore_mark_indices.add(idx)
except Exception:
self.current_input = ''
@@ -363,6 +371,13 @@ The minimum number of characters to consider a match.
type=bool-set
Select multiple matches and perform the action on all of them together at the end.
In this mode, press :kbd:`Esc` to finish selecting.
--add-trailing-space
default=auto
choices=auto,always,never
Add trailing space after matched text. Defaults to auto, which adds the space
when used together with --multiple.
'''.format(','.join(sorted(URL_PREFIXES))).format
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
usage = ''

View File

@@ -174,7 +174,7 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
write_chunked(cmd, data)
def process(path, args):
def process(path, args, is_tempfile):
m = identify(path)
ss = screen_size()
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
@@ -183,7 +183,7 @@ def process(path, args):
needs_scaling = needs_scaling or args.scale_up
if m.fmt == 'png' and not needs_scaling:
outfile = path
transmit_mode = 'f'
transmit_mode = 't' if is_tempfile else 'f'
fmt = 100
width, height = m.width, m.height
else:
@@ -249,7 +249,8 @@ def parse_place(raw):
help_text = (
'A cat like utility to display images in the terminal.'
' You can specify multiple image files and/or directories.'
' Directories are scanned recursively for image files.'
' Directories are scanned recursively for image files. If STDIN'
' is not a terminal, image data will be read from it as well.'
)
usage = 'image-file ...'
@@ -267,6 +268,13 @@ def main(args=sys.argv):
if not sys.stdout.isatty():
sys.stdout = open(os.ctermid(), 'w')
stdin_data = None
if not sys.stdin.isatty():
stdin_data = sys.stdin.buffer.read()
items.insert(0, stdin_data)
sys.stdin.close()
sys.stdin = open(os.ctermid(), 'r')
screen_size = screen_size_function()
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
if screen_size().width == 0:
@@ -298,16 +306,22 @@ def main(args=sys.argv):
if not items:
raise SystemExit('You must specify at least one file to cat')
if args.place:
if len(items) > 1 or os.path.isdir(items[0]):
if len(items) > 1 or (isinstance(items[0], str) and os.path.isdir(items[0])):
raise SystemExit('The --place option can only be used with a single image')
sys.stdout.buffer.write(b'\0337') # save cursor
for item in items:
is_tempfile = False
try:
if isinstance(item, bytes):
tf = NamedTemporaryFile(prefix='stdin-image-data-', delete=False)
tf.write(item), tf.close()
item = tf.name
is_tempfile = True
if os.path.isdir(item):
for x in scan(item):
process(item, args)
else:
process(item, args)
process(item, args, is_tempfile)
except NoImageMagick as e:
raise SystemExit(str(e))
except ConvertFailed as e:

View File

@@ -46,6 +46,7 @@ def create_kitten_handler(kitten, orig_args):
m = import_kitten_main_module(config_dir, kitten)
ans = partial(m['end'], [kitten] + orig_args)
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
ans.no_ui = getattr(m['end'], 'no_ui', False)
return ans

View File

@@ -21,7 +21,7 @@ from ..tui.line_edit import LineEdit
from ..tui.handler import Handler
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, color_code, colored, cursor, faint, set_line_wrapping,
clear_screen, colored, cursor, faint, set_line_wrapping,
set_window_title, sgr, styled
)
@@ -159,30 +159,32 @@ class Table:
self.last_cols, self.last_rows = cols, rows
self.layout_dirty = False
def safe_chr(codepoint):
return chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
if self.mode is NAME:
def as_parts(i, codepoint):
return encode_hint(i).ljust(idx_size), chr(codepoint), name(codepoint)
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), name(codepoint)
def cell(i, idx, c, desc):
is_current = i == self.current_idx
if is_current:
yield sgr(color_code('gray', base=40))
yield colored(idx, 'green') + ' '
yield colored(c, 'black' if is_current else 'gray', True) + ' '
text = colored(idx, 'green') + ' ' + sgr('49') + c + ' '
w = wcswidth(c)
if w < 2:
yield ' ' * (2 - w)
text += ' ' * (2 - w)
if len(desc) > space_for_desc:
desc = desc[:space_for_desc - 1] + ''
yield faint(desc)
text += desc[:space_for_desc - 1] + ''
else:
text += desc
extra = space_for_desc - len(desc)
if extra > 0:
yield ' ' * extra
if is_current:
yield sgr('49')
text += ' ' * extra
yield styled(text, reverse=True if is_current else None)
else:
def as_parts(i, codepoint):
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), ''
def cell(i, idx, c, desc):
yield colored(idx, 'green') + ' '

File diff suppressed because one or more lines are too long

View File

@@ -48,21 +48,29 @@ def listen_on(spec):
return s.fileno()
def data_for_at(w, arg):
def data_for_at(w, arg, add_wrap_markers=False):
def as_text(**kw):
kw['add_wrap_markers'] = add_wrap_markers
return w.as_text(**kw)
if arg == '@selection':
return w.text_for_selection()
if arg == '@ansi':
return w.as_text(as_ansi=True, add_history=True)
return as_text(as_ansi=True, add_history=True)
if arg == '@text':
return w.as_text(add_history=True)
return as_text(add_history=True)
if arg == '@screen':
return w.as_text()
return as_text()
if arg == '@ansi_screen':
return w.as_text(as_ansi=True)
return as_text(as_ansi=True)
if arg == '@alternate':
return w.as_text(alternate_screen=True)
return as_text(alternate_screen=True)
if arg == '@alternate_scrollback':
return as_text(alternate_screen=True, add_history=True)
if arg == '@ansi_alternate':
return w.as_text(as_ansi=True, alternate_screen=True)
return as_text(as_ansi=True, alternate_screen=True)
if arg == '@ansi_alternate_scrollback':
return as_text(as_ansi=True, alternate_screen=True, add_history=True)
class DumpCommands: # {{{
@@ -236,8 +244,15 @@ class Boss:
def new_os_window(self, *args):
self._new_os_window(args)
def new_os_window_with_cwd(self, *args):
@property
def active_window_for_cwd(self):
w = self.active_window
if w is not None and w.overlay_for is not None and w.overlay_for in self.window_id_map:
w = self.window_id_map[w.overlay_for]
return w
def new_os_window_with_cwd(self, *args):
w = self.active_window_for_cwd
cwd_from = w.child.pid if w is not None else None
self._new_os_window(args, cwd_from)
@@ -362,6 +377,9 @@ class Boss:
reset = action == 'reset'
how = 3 if action == 'scrollback' else 2
for w in windows:
if action == 'scroll':
w.screen.scroll_until_cursor()
continue
w.screen.cursor.x = w.screen.cursor.y = 0
if reset:
w.screen.reset()
@@ -445,10 +463,10 @@ class Boss:
_('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity'))
os_window_id = window.os_window_id
if opacity[0] in '+-':
opacity = background_opacity_of(os_window_id)
if opacity is None:
old_opacity = background_opacity_of(os_window_id)
if old_opacity is None:
return
opacity += float(opacity)
opacity = old_opacity + float(opacity)
elif opacity == 'default':
opacity = self.opts.background_opacity
else:
@@ -541,6 +559,8 @@ class Boss:
if key_action is not None:
f = getattr(self, key_action.func, None)
if f is not None:
if self.args.debug_keyboard:
print('Keypress matched action:', f.__name__)
passthrough = f(*key_action.args)
if passthrough is not True:
return True
@@ -554,6 +574,8 @@ class Boss:
f = getattr(tab, key_action.func, getattr(window, key_action.func, None))
if f is not None:
passthrough = f(*key_action.args)
if self.args.debug_keyboard:
print('Keypress matched action:', f.__name__)
if passthrough is not True:
return True
return False
@@ -631,17 +653,20 @@ class Boss:
return output
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
orig_args, args = list(args), list(args)
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
if window is None:
w = self.active_window
tab = self.active_tab
else:
w = window
tab = w.tabref()
if end_kitten.no_ui:
end_kitten(None, getattr(w, 'id', None), self)
return
if w is not None and tab is not None and w.overlay_for is None:
orig_args, args = list(args), list(args)
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
args[0:0] = [config_dir, kitten]
if input_data is None:
type_of_input = end_kitten.type_of_input
@@ -793,12 +818,27 @@ class Boss:
prev_tab = previous_tab
def process_stdin_source(self, window=None, stdin=None):
w = window or self.active_window
env = None
if stdin:
add_wrap_markers = stdin.endswith('_wrap')
if add_wrap_markers:
stdin = stdin[:-len('_wrap')]
stdin = data_for_at(w, stdin, add_wrap_markers=add_wrap_markers)
if stdin is not None:
pipe_data = w.pipe_data(stdin, has_wrap_markers=add_wrap_markers) if w else {}
if pipe_data:
env = {
'KITTY_PIPE_DATA':
'{scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}'.format(**pipe_data)
}
stdin = stdin.encode('utf-8')
return env, stdin
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
w = window or self.active_window
if stdin:
stdin = data_for_at(w, stdin)
if stdin is not None:
stdin = stdin.encode('utf-8')
env, stdin = self.process_stdin_source(w, stdin)
cmdline = []
for arg in cmd:
if arg == '@selection':
@@ -807,7 +847,7 @@ class Boss:
continue
cmdline.append(arg)
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)
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
def pipe(self, source, dest, exe, *args):
cmd = [exe] + list(args)
@@ -830,7 +870,12 @@ class Boss:
self._new_os_window(create_window(), cwd_from=cwd_from)
else:
import subprocess
subprocess.Popen(cmd)
env, stdin = self.process_stdin_source(stdin=source, window=window)
if stdin:
p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE)
p.communicate(stdin)
else:
subprocess.Popen(cmd)
def args_to_special_window(self, args, cwd_from=None):
args = list(args)
@@ -874,7 +919,7 @@ class Boss:
self._create_tab(args)
def new_tab_with_cwd(self, *args):
w = self.active_window
w = self.active_window_for_cwd
cwd_from = w.child.pid if w is not None else None
self._create_tab(args, cwd_from=cwd_from)
@@ -890,7 +935,7 @@ class Boss:
self._new_window(args)
def new_window_with_cwd(self, *args):
w = self.active_window
w = self.active_window_for_cwd
if w is None:
return self.new_window(*args)
cwd_from = w.child.pid if w is not None else None

View File

@@ -102,11 +102,11 @@ void main() {
// SIMPLE
#ifdef TRANSPARENT
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
final_color = vec4(final_color.rgb, final_color.a);
#else
// since background alpha is 1.0, it is effectively pre-multiplied
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
final_color = vec4(final_color.rgb, final_color.a);
#endif
#endif

View File

@@ -565,7 +565,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
}
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
}
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) {
if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window);
else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at);
}
Tab *tab = os_window->tabs + os_window->active_tab;
*active_window_bg = OPT(background);
for (unsigned int i = 0; i < tab->num_windows; i++) {
@@ -599,6 +602,8 @@ 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) {
// 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;
BorderRects *br = &tab->border_rects;
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, os_window);
@@ -620,6 +625,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
os_window->focused_at_last_render = os_window->is_focused;
os_window->is_damaged = false;
if (global_state.is_wayland && OPT(sync_to_monitor)) wayland_request_frame_render(os_window);
#undef WD
#undef TD
}
@@ -635,6 +641,10 @@ render(double now) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
if (global_state.is_wayland && w->wayland_render_state != RENDER_FRAME_READY && OPT(sync_to_monitor)) {
if (w->wayland_render_state == RENDER_FRAME_NOT_REQUESTED) wayland_request_frame_render(w);
continue;
}
bool needs_render = w->is_damaged;
make_os_window_context_current(w);
if (w->viewport_size_dirty) {
@@ -864,7 +874,7 @@ read_bytes(int fd, Screen *screen) {
while(true) {
len = read(fd, screen->read_buf + orig_sz, available_buffer_space);
if (len < 0) {
if (errno == EINTR) continue;
if (errno == EINTR || errno == EAGAIN) continue;
if (errno != EIO) perror("Call to read() from child fd failed");
return false;
}
@@ -981,7 +991,8 @@ io_loop(void *data) {
// The I/O thread loop
size_t i;
int ret;
bool has_more, data_received;
bool has_more, data_received, has_pending_wakeups = false;
double last_main_loop_wakeup_at = -1, now = -1;
Screen *screen;
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyChildMon");
@@ -1000,7 +1011,14 @@ io_loop(void *data) {
fds[EXTRA_FDS + i].events = (screen->read_buf_sz < READ_BUF_SZ ? POLLIN : 0) | (screen->write_buf_used ? POLLOUT : 0);
screen_mutex(unlock, read); screen_mutex(unlock, write);
}
ret = poll(fds, self->count + EXTRA_FDS, -1);
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));
else ret = 0;
} else {
ret = poll(fds, self->count + EXTRA_FDS, -1);
}
if (ret > 0) {
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
if (fds[1].revents && POLLIN) {
@@ -1044,8 +1062,17 @@ io_loop(void *data) {
perror("Call to poll() failed");
}
}
if (data_received) wakeup_main_loop();
#define WAKEUP { wakeup_main_loop(); last_main_loop_wakeup_at = now; has_pending_wakeups = false; }
// we only wakeup the main loop after input_delay as wakeup is an expensive operation
// on some platforms, such as cocoa
if (data_received) {
if ((now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
else has_pending_wakeups = true;
} else {
if (has_pending_wakeups && (now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
}
}
#undef WAKEUP
children_mutex(lock);
for (i = 0; i < self->count; i++) children[i].needs_removal = true;
remove_children(self);

View File

@@ -275,7 +275,10 @@ def print_help_for_seq(seq, usage, message, appname):
if print_help_for_seq.allow_pager and sys.stdout.isatty():
import subprocess
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
p.communicate(text.encode('utf-8'))
try:
p.communicate(text.encode('utf-8'))
except KeyboardInterrupt:
raise SystemExit(1)
raise SystemExit(p.wait())
else:
print(text)

View File

@@ -16,7 +16,7 @@ from .conf.utils import (
parse_config_base, python_string, to_bool, to_cmdline
)
from .config_data import all_options, parse_mods, type_map
from .constants import cache_dir, defconf
from .constants import cache_dir, defconf, is_macos
from .utils import log_error
named_keys = {
@@ -173,6 +173,16 @@ def pipe(func, rest):
return func, rest
@func_with_args('nth_window')
def nth_window(func, rest):
try:
num = int(rest)
except Exception:
log_error('Invalid nth_window number: {}'.format(rest))
num = 1
return func, [num]
def parse_key_action(action):
parts = action.split(' ', 1)
func = parts[0]
@@ -302,6 +312,15 @@ special_handlers = {}
def special_handler(func):
special_handlers[func.__name__.partition('_')[2]] = func
return func
def deprecated_handler(*names):
def special_handler(func):
for name in names:
special_handlers[name] = func
return func
return special_handler
@special_handler
@@ -326,6 +345,15 @@ def handle_clear_all_shortcuts(key, val, ans):
ans['key_definitions'] = [None]
@deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar')
def handle_deprecated_hide_window_decorations_aliases(key, val, ans):
if not hasattr(handle_deprecated_hide_window_decorations_aliases, key):
handle_deprecated_hide_window_decorations_aliases.key = True
log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key))
if to_bool(val):
ans['hide_window_decorations'] = True
def expandvars(val, env):
def sub(m):
@@ -337,7 +365,7 @@ def expandvars(val, env):
result = m.group()
return result
return re.sub(r'$\{(\S+)\}', sub, val)
return re.sub(r'\$\{(\S+?)\}', sub, val)
@special_handler
@@ -542,4 +570,6 @@ def load_config(*paths, overrides=None):
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
log_error('Cannot use both macos_titlebar_color and background_opacity')
opts.macos_titlebar_color = 0
if (is_macos and getattr(opts, 'macos_hide_titlebar', False)) or (not is_macos and getattr(opts, 'x11_hide_window_decorations', False)):
opts.hide_window_decorations = True
return opts

View File

@@ -140,7 +140,8 @@ For example::
'shortcuts.tab': [
_('Tab management'), '',
_('''\
You can also create shortcuts to go to specific tabs, with 1 being the first tab::
You can also create shortcuts to go to specific tabs, with 1 being the first
tab, 2 the second tab and -1 being the previously active tab::
map ctrl+alt+1 goto_tab 1
map ctrl+alt+2 goto_tab 2
@@ -323,10 +324,15 @@ def scrollback_lines(x):
return x
def scrollback_pager_history_size(x):
ans = int(max(0, float(x)) * 1024 * 1024)
return min(ans, 4096 * 1024 * 1024 - 1)
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
Number of lines of history to keep in memory for scrolling back. Memory is allocated
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
very large scrollback is not recommended a it can slow down resizing of the terminal
very large scrollback is not recommended as it can slow down resizing of the terminal
and also use large amounts of RAM.'''))
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
@@ -336,10 +342,26 @@ use can handle ANSI escape sequences for colors and text formatting.
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
representing which line should be at the top of the screen.'''))
o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size, long_text=_('''
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
This separate buffer is not available for interactive scrolling but will be
piped to the pager program when viewing scrollback buffer in a separate window.
The current implementation stores one character in 4 bytes, so approximatively
2500 lines per megabyte at 100 chars per line. A value of zero or less disables
this feature. The maximum allowed size is 4GB.'''))
o('wheel_scroll_multiplier', 5.0, long_text=_('''
Modify the amount scrolled by the mouse wheel. Note this is only used for low
precision scrolling devices, not for high precision scrolling on platforms such
as macOS and Wayland. Use negative numbers to change scroll direction.'''))
o('touch_scroll_multiplier', 1.0, long_text=_('''
Modify the amount scrolled by a touchpad. Note this is only used for high
precision scrolling devices on platforms such as macOS and Wayland.
Use negative numbers to change scroll direction.'''))
# }}}
# }}}
g('mouse') # {{{
@@ -372,7 +394,7 @@ operating system's default URL handler.'''))
o('copy_on_select', False, long_text=_('''
Copy to clipboard on select. With this enabled, simply selecting text with
the mouse will cause the text to be copied to clipboard. Useful on platforms
such as macOS/Wayland that do not have the concept of primary selections. Note
such as macOS that do not have the concept of primary selections. Note
that this is a security risk, as all programs, including websites open in your
browser can read the contents of the clipboard.'''))
@@ -385,9 +407,9 @@ Characters considered part of a word when double clicking. In addition to these
any character that is marked as an alpha-numeric character in the unicode
database will be matched.'''))
o('click_interval', 0.5, option_type=positive_float, long_text=_('''
The interval between successive clicks to detect
double/triple clicks (in seconds)'''))
o('click_interval', -1.0, option_type=float, long_text=_('''
The interval between successive clicks to detect double/triple clicks (in seconds).
Negative numbers will use the system default instead, if available, or fallback to 0.5.'''))
o('mouse_hide_wait', 3.0, option_type=positive_float, long_text=_('''
Hide mouse cursor after the specified number of seconds
@@ -524,6 +546,12 @@ o('inactive_text_alpha', 1.0, option_type=unit_float, long_text=_('''
Fade the text in inactive windows by the specified amount (a number between
zero and one, with zero being fully faded).
'''))
o('hide_window_decorations', False, long_text=_('''
Hide the window decorations (title-bar and window borders).
Whether this works and exactly what effect it has depends on the
window manager/operating system.
'''))
# }}}
g('tabbar') # {{{
@@ -558,8 +586,8 @@ 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'), long_text=_('''
The tab bar style, can be one of: :code:`fade` or :code:`separator`. In the fade style,
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.
'''))
@@ -690,7 +718,7 @@ Specify environment variables to set in all child processes. Note that
environment variables are expanded recursively, so if you use::
env MYVAR1=a
env MYVAR2=${MYVAR}/${HOME}/b
env MYVAR2=${MYVAR1}/${HOME}/b
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
'''))
@@ -724,9 +752,13 @@ program, even one running on a remote server via SSH can read your clipboard.
'''))
o('term', 'xterm-kitty', long_text=_('''
The value of the TERM environment variable to set. Changing this can break
many terminal programs, only change it if you know what you are doing, not
because you read some advice on Stack Overflow to change it.
The value of the TERM environment variable to set. Changing this can break many
terminal programs, only change it if you know what you are doing, not because
you read some advice on Stack Overflow to change it. The TERM variable if used
by various programs to get information about the capabilities and behavior of
the terminal. If you change it, depending on what programs you run, and how
different the terminal you are changing it to is, various things from
key-presses, to colors, to various advanced features may not work.
'''))
# }}}
@@ -751,18 +783,9 @@ an arbitrary color, such as :code:`#12af59` or :code:`red`. WARNING: This option
using a hack, as there is no proper Cocoa API for it. It sets the background
color of the entire window and makes the titlebar transparent. As such it is
incompatible with :opt:`background_opacity`. If you want to use both, you are
probably better off just hiding the titlebar with :opt:`macos_hide_titlebar`.
probably better off just hiding the titlebar with :opt:`hide_window_decorations`.
'''))
o('macos_hide_titlebar', False, long_text=_('''
Hide the kitty window's title bar on macOS.'''))
o('x11_hide_window_decorations', False, long_text=_('''
Hide the window decorations (title bar and window borders) on X11 and Wayland.
Whether this works and exactly what effect it has depends on the window
manager, as it is the job of the window manager/compositor to draw window
decorations.'''))
o('macos_option_as_alt', True, long_text=_('''
Use the option key as an alt key. With this set to no, kitty will use
the macOS native :kbd:`Option+Key` = unicode character behavior. This will
@@ -854,18 +877,9 @@ the following opens the scrollback buffer in less in an overlay window::
map f1 pipe @ansi overlay less +G -R
Placeholders available are: @text (which is plain text) and @ansi (which
includes text styling escape codes). For only the current screen, use @screen
or @ansi_screen. For the secondary screen, use @alternate and @ansi_alternate.
The secondary screen is the screen not currently displayed. For
example if you run a fullscreen terminal application, the secondary screen will
be the screen you return to when quitting the application. You can also use
``none`` for no :file:`STDIN` input.
To open in a new window, tab or new OS window, use ``window``, ``tab``, or
``os_window`` respectively. You can also use ``none`` in which case the data
will be piped into the program without creating any windows, useful if the
program is a GUI program that creates its own windows. '''))
For more details on piping screen and buffer contents to external programs,
see :doc:`pipe`.
'''))
# }}}
@@ -973,12 +987,21 @@ k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset
long_text=_('''
You can create shortcuts to clear/reset the terminal. For example::
# Reset the terminal
map kitty_mod+f9 clear_terminal reset active
# Clear the terminal screen by erasing all contents
map kitty_mod+f10 clear_terminal clear active
# Clear the terminal scrollback by erasing it
map kitty_mod+f11 clear_terminal scrollback active
# Scroll the contents of the screen into the scrollback
map kitty_mod+f12 clear_terminal scroll active
These will reset screen/clear screen/clear screen+scrollback respectively. If you want to
operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
If you want to operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
It is also possible to remap Ctrl+L to both scroll the current screen contents into the scrollback buffer
and clear the screen, instead of just clearing the screen::
map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c
'''))
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
add_to_default=False, long_text=_('''

View File

@@ -7,9 +7,8 @@ import pwd
import sys
from collections import namedtuple
appname = 'kitty'
version = (0, 12, 2)
version = (0, 13, 1)
str_version = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos = 'darwin' in _plat
@@ -61,6 +60,18 @@ def _get_config_dir():
os.makedirs(ans, exist_ok=True)
except FileExistsError:
raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans))
except PermissionError:
import tempfile
import atexit
ans = tempfile.mkdtemp(prefix='kitty-conf-')
def cleanup():
import shutil
try:
shutil.rmtree(ans)
except Exception:
pass
atexit.register(cleanup)
return ans
@@ -125,4 +136,4 @@ if os.environ.get('WAYLAND_DISPLAY') and 'KITTY_ENABLE_WAYLAND' in os.environ an
is_wayland = True
supports_primary_selection = not is_macos and not is_wayland
supports_primary_selection = not is_macos

View File

@@ -221,6 +221,15 @@ is_glyph_empty(PyObject *s, glyph_index g) {
return bounds.size.width <= 0;
}
int
get_glyph_width(PyObject *s, glyph_index g) {
CTFace *self = (CTFace*)s;
CGGlyph gg = g;
CGRect bounds;
CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1);
return bounds.size.width;
}
static inline float
scaled_point_sz(FONTS_DATA_HANDLE fg) {
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
@@ -383,7 +392,7 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
}
static inline bool
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg) {
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
unsigned int canvas_width = cell_width * num_cells;
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs);
if (allow_resize) {
@@ -394,7 +403,7 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
CGFloat sz = CTFontGetSize(ct_font);
sz *= canvas_width / right;
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg);
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
CFRelease(new_font);
return ret;
}
@@ -420,10 +429,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
}
bool
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
CTFace *self = (CTFace*)s;
for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint;
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg);
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
}

View File

@@ -211,55 +211,15 @@ END_ALLOW_CASE_RANGE
#undef RANGE
}
static inline int
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
switch(val & 0xff) {
case 1:
val >>= 8;
if (val < 16 && simple_code) {
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
}
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
case 2:
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
default:
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
}
}
static inline const char*
decoration_as_sgr(uint8_t decoration) {
switch(decoration) {
case 1: return "4";
case 2: return "4:2";
case 3: return "4:3";
default: return "24";
}
}
const char*
cursor_as_sgr(Cursor *self, Cursor *prev) {
static char buf[128];
#define SZ sizeof(buf) - (p - buf) - 2
#define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); }
char *p = buf;
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
if (intensity_differs) {
if (!self->bold && !self->dim) { P("%d", 22); }
else { if (self->bold) P("%d", 1); if (self->dim) P("%d", 2); }
}
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
if (self->strikethrough != prev->strikethrough) P("%d", self->strikethrough ? 9 : 29);
if (self->decoration != prev->decoration) P("%s", decoration_as_sgr(self->decoration));
if (self->fg != prev->fg) p += color_as_sgr(p, SZ, self->fg, 30, 90, 38);
if (self->bg != prev->bg) p += color_as_sgr(p, SZ, self->bg, 40, 100, 48);
if (self->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, self->decoration_fg, 0, 0, DECORATION_FG_CODE);
#undef P
#undef SZ
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
*p = 0; // ensure string is null-terminated
return buf;
cursor_as_sgr(const Cursor *self) {
GPUCell blank_cell = { 0 }, cursor_cell = {
.attrs = CURSOR_TO_ATTRS(self, 1),
.fg = self->fg & COL_MASK,
.bg = self->bg & COL_MASK,
.decoration_fg = self->decoration_fg & COL_MASK,
};
return cell_as_sgr(&cursor_cell, &blank_cell);
}
static PyObject *

View File

@@ -173,11 +173,20 @@ typedef struct {
line_attrs_type *line_attrs;
} HistoryBufSegment;
typedef struct {
index_type bufsize, maxsz;
Py_UCS4 *buffer;
index_type start, end;
index_type bufend;
bool rewrap_needed;
} PagerHistoryBuf;
typedef struct {
PyObject_HEAD
index_type xnum, ynum, num_segments;
HistoryBufSegment* segments;
HistoryBufSegment *segments;
PagerHistoryBuf *pagerhist;
Line *line;
index_type start_of_data, count;
} HistoryBuf;
@@ -254,7 +263,7 @@ const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, siz
Line* alloc_line();
Cursor* alloc_cursor();
LineBuf* alloc_linebuf(unsigned int, unsigned int);
HistoryBuf* alloc_historybuf(unsigned int, unsigned int);
HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int);
ColorProfile* alloc_color_profile();
PyObject* create_256_color_table();
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
@@ -267,7 +276,8 @@ void cursor_copy_to(Cursor *src, Cursor *dest);
void cursor_reset_display_attrs(Cursor*);
void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count);
void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
const char* cursor_as_sgr(Cursor*, Cursor*);
const char* cell_as_sgr(GPUCell *, GPUCell *);
const char* cursor_as_sgr(const Cursor *);
double monotonic();
PyObject* cm_thread_write(PyObject *self, PyObject *args);

View File

@@ -114,6 +114,7 @@ _fc_match(FcPattern *pat) {
FcResult result;
FcConfigSubstitute(NULL, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
/* printf("fc_match = %s\n", FcNameUnparse(pat)); */
match = FcFontMatch(NULL, pat, &result);
if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; }
ans = pattern_as_dict(match);
@@ -145,16 +146,22 @@ end:
static PyObject*
fc_match(PyObject UNUSED *self, PyObject *args) {
char *family = NULL;
int bold = 0, italic = 0, allow_bitmapped_fonts = 0;
int bold = 0, italic = 0, allow_bitmapped_fonts = 0, monospaced = 0;
double size_in_pts = 0, dpi = 0;
FcPattern *pat = NULL;
PyObject *ans = NULL;
if (!PyArg_ParseTuple(args, "|zpppdd", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
if (!PyArg_ParseTuple(args, "|zppppdd", &family, &bold, &italic, &monospaced, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();
if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family");
if (monospaced) {
// pass the family,monospace as the family parameter to fc-match,
// which will fallback to using monospace if the family does not match.
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family");
AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing");
}
if (!allow_bitmapped_fonts) {
AP(FcPatternAddBool, FC_OUTLINE, true, "outline");
AP(FcPatternAddBool, FC_SCALABLE, true, "scalable");

View File

@@ -14,6 +14,7 @@
#define MISSING_GLYPH 4
#define MAX_NUM_EXTRA_GLYPHS 8
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
#define MAX_NUM_EXTRA_GLYPHS_PUA 4
typedef void (*send_sprite_to_gpu_func)(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
@@ -395,7 +396,7 @@ calc_cell_metrics(FontGroup *fg) {
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px);
if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac);
if (OPT(adjust_column_width_frac) != 0.f) cell_width *= OPT(adjust_column_width_frac);
int line_height_adjustment = cell_height - before_cell_height;
if (cell_height < 4) fatal("line height too small after adjustment");
if (cell_height > 1000) fatal("line height too large after adjustment");
@@ -635,8 +636,13 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
return ans;
}
static inline bool
is_private_use(char_type ch) {
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
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) {
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);
@@ -651,7 +657,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
clear_canvas(fg);
bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
if (PyErr_Occurred()) PyErr_Print();
for (unsigned int i = 0; i < num_cells; i++) {
@@ -901,17 +907,17 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
static inline void
merge_groups_for_pua_space_ligature() {
if (G(group_idx) == 1) {
while (G(group_idx) > 0) {
Group *g = G(groups), *g1 = G(groups) + 1;
g->num_cells += g1->num_cells;
g->num_glyphs += g1->num_glyphs;
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
G(group_idx) = 0;
G(group_idx)--;
}
}
static inline void
render_groups(FontGroup *fg, Font *font) {
render_groups(FontGroup *fg, Font *font, bool center_glyph) {
unsigned idx = 0;
ExtraGlyphs ed;
while (idx <= G(group_idx)) {
@@ -924,7 +930,7 @@ render_groups(FontGroup *fg, Font *font) {
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);
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);
idx++;
}
}
@@ -970,12 +976,12 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
#undef G
static inline void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph) {
switch(font_idx) {
default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx]);
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
render_groups(fg, &fg->fonts[font_idx]);
render_groups(fg, &fg->fonts[font_idx], center_glyph);
break;
case BLANK_FONT:
while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; }
@@ -989,16 +995,12 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
}
}
static inline bool
is_private_use(char_type ch) {
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
void
render_line(FONTS_DATA_HANDLE fg_, Line *line) {
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph);
FontGroup *fg = (FontGroup*)fg_;
ssize_t run_font_idx = NO_FONT;
bool center_glyph = false;
index_type first_cell_in_run, i;
attrs_type prev_width = 0;
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
@@ -1006,21 +1008,48 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line) {
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);
if (is_private_use(cpu_cell->ch) && i + 1 < line->xnum && (line->cpu_cells[i+1].ch == ' ' || line->cpu_cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
// We have a private use char followed by a space char, render it as a two cell ligature.
GPUCell *space_cell = line->gpu_cells + i+1;
// Ensure the space cell uses the foreground colors from the PUA cell
// This is needed because there are stupid applications like
// powerline that use PUA+space with different foreground colors
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
space_cell->fg = gpu_cell->fg; space_cell->decoration_fg = gpu_cell->decoration_fg;
RENDER;
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, 2, cell_font_idx, true);
run_font_idx = NO_FONT;
first_cell_in_run = i + 2;
prev_width = line->gpu_cells[i+1].attrs & WIDTH_MASK;
i++;
continue;
if (is_private_use(cpu_cell->ch)
&& cell_font_idx != BOX_FONT
&& cell_font_idx != MISSING_FONT) {
int desired_cells;
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);
} else {
desired_cells = 1;
}
int num_spaces = 0;
while ((line->cpu_cells[i+num_spaces+1].ch == ' ' || line->cpu_cells[i+num_spaces+1].ch == 0)
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
&& num_spaces < desired_cells
&& i + num_spaces + 1 < line->xnum) {
num_spaces++;
// We have a private use char followed by space(s), render it as a multi-cell ligature.
GPUCell *space_cell = line->gpu_cells + i + num_spaces;
// Ensure the space cell uses the foreground/background colors from the PUA cell.
// This is needed because there are stupid applications like
// powerline that use PUA+space with different foreground colors
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
space_cell->fg = gpu_cell->fg;
space_cell->bg = gpu_cell->bg;
space_cell->decoration_fg = gpu_cell->decoration_fg;
}
if (num_spaces) {
center_glyph = true;
RENDER;
center_glyph = false;
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph);
run_font_idx = NO_FONT;
first_cell_in_run = i + num_spaces + 1;
prev_width = line->gpu_cells[i+num_spaces].attrs & WIDTH_MASK;
i += num_spaces;
continue;
}
}
prev_width = gpu_cell->attrs & WIDTH_MASK;
if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx;

View File

@@ -17,11 +17,12 @@
// API that font backends need to implement
typedef uint16_t glyph_index;
unsigned int glyph_id_for_codepoint(PyObject *, char_type);
int get_glyph_width(PyObject *, glyph_index);
bool is_glyph_empty(PyObject *, glyph_index);
hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph);
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);

View File

@@ -44,8 +44,12 @@ def list_fonts():
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
def family_name_to_key(family):
return re.sub(r'\s+', ' ', family.lower())
def find_best_match(family, bold=False, italic=False, monospaced=True):
q = re.sub(r'\s+', ' ', family.lower())
q = family_name_to_key(family)
font_map = all_fonts_map(monospaced)
def score(candidate):
@@ -61,6 +65,22 @@ def find_best_match(family, bold=False, italic=False, monospaced=True):
candidates.sort(key=score)
return candidates[0]
# Use fc-match to see if we can find a monospaced font that matches family
possibility = fc_match(family, False, False)
for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')):
val = possibility.get(key)
if val:
candidates = font_map[map_key].get(family_name_to_key(val))
if candidates:
if len(candidates) == 1:
# happens if the family name is an alias, so we search with
# the actual family name to see if we can find all the
# fonts in the family.
family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family']))
if family_name_candidates and len(family_name_candidates) > 1:
candidates = family_name_candidates
return sorted(candidates, key=score)[0]
# Use fc-match with a generic family
family = 'monospace' if monospaced else 'sans-serif'
return fc_match(family, bold, italic)

View File

@@ -5,7 +5,7 @@
import ctypes
import sys
from functools import partial
from math import ceil, pi, cos, sqrt
from math import ceil, pi, cos, floor
from kitty.config import defaults
from kitty.constants import is_macos
@@ -115,28 +115,30 @@ def add_dline(buf, cell_width, position, thickness, cell_height):
def add_curl(buf, cell_width, position, thickness, cell_height):
xfactor = 2.0 * pi / cell_width
yfactor = max(thickness, 2)
max_x, max_y = cell_width - 1, cell_height - 1
xfactor = 2.0 * pi / max_x
half_height = max(thickness // 2, 1)
def clamp_y(y):
return max(0, min(int(y), cell_height - 1))
def clamp_x(x):
return max(0, min(int(x), cell_width - 1))
def add_intensity(x, y, distance):
def add_intensity(x, y, val):
y += position
y = min(y, max_y)
idx = cell_width * y + x
buf[idx] = min(
255, buf[idx] + int(255 * (1 - distance))
)
buf[idx] = min(255, buf[idx] + val)
for x_exact in range(cell_width):
y_exact = yfactor * cos(x_exact * xfactor) + position
y = clamp_y(ceil(y_exact))
x_before, x_after = map(clamp_x, (x_exact - 1, x_exact + 1))
for x in {x_before, x_exact, x_after}:
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
add_intensity(x, y, dist)
# Ensure all space at bottom of cell is used
if position + half_height < max_y:
position += max_y - (position + half_height)
if position + half_height > max_y:
position -= position + half_height - max_y
# Use the Wu antialias algorithm to draw the curve
# cosine waves always have slope <= 1 so are never steep
for x in range(cell_width):
y = half_height * cos(x * xfactor)
y1, y2 = floor(y), ceil(y)
i1 = int(255 * abs(y - y1))
add_intensity(x, y1, 255 - i1)
add_intensity(x, y2, i1)
def render_special(

View File

@@ -16,6 +16,10 @@
#define HARFBUZZ_HAS_CHANGE_FONT
#endif
#if FREETYPE_MAJOR == 2 && FREETYPE_MINOR < 7
#define FT_Bitmap_Init FT_Bitmap_New
#endif
#include FT_FREETYPE_H
#include FT_BITMAP_H
typedef struct {
@@ -232,27 +236,6 @@ load_glyph(Face *self, int glyph_index, int load_type) {
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
int error = FT_Load_Glyph(self->face, glyph_index, flags);
if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; }
// Embedded bitmap glyph?
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && load_type != FT_LOAD_DEFAULT) {
FT_Bitmap bitmap;
FT_Bitmap_New(&bitmap);
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
error = FT_Bitmap_Convert(library, &self->face->glyph->bitmap, &bitmap, 1);
if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; }
// Normalize gray levels to the range [0..255]
bitmap.num_grays = 256;
unsigned int stride = bitmap.pitch < 0 ? -bitmap.pitch : bitmap.pitch;
for (unsigned int i = 0; i < bitmap.rows; ++i) {
// We only have 2 levels
for (unsigned int j = 0; j < bitmap.width; ++j) bitmap.buffer[i * stride + j] *= 255;
}
error = FT_Bitmap_Copy(library, &bitmap, &self->face->glyph->bitmap);
if (error) { set_freetype_error("Failed to copy bitmap, with error:", error); return false; }
FT_Bitmap_Done(library, &bitmap);
}
return true;
}
@@ -294,6 +277,20 @@ is_glyph_empty(PyObject *s, glyph_index g) {
#undef M
}
int
get_glyph_width(PyObject *s, glyph_index g) {
Face *self = (Face*)s;
if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; }
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
#define M self->face->glyph->metrics
#define B self->face->glyph->bitmap
/* printf("glyph: %u bitmap.width: %d bitmap.rows: %d horiAdvance: %ld horiBearingX: %ld horiBearingY: %ld vertBearingX: %ld vertBearingY: %ld vertAdvance: %ld width: %ld height: %ld\n", */
/* g, B.width, B.rows, M.horiAdvance, M.horiBearingX, M.horiBearingY, M.vertBearingX, M.vertBearingY, M.vertAdvance, M.width, M.height); */
return bitmap->width;
#undef M
#undef B
}
hb_font_t*
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }
@@ -305,8 +302,17 @@ typedef struct {
FT_Pixel_Mode pixel_mode;
bool needs_free;
unsigned int factor, right_edge;
int bitmap_left, bitmap_top;
} ProcessedBitmap;
static inline void
free_processed_bitmap(ProcessedBitmap *bm) {
if (bm->needs_free) {
bm->needs_free = false;
free(bm->buf); bm->buf = NULL;
}
}
static inline void
trim_borders(ProcessedBitmap *ans, size_t extra) {
bool column_has_text = false;
@@ -324,17 +330,48 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
ans->width -= extra;
}
static inline void
populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) {
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
ans->rows = bitmap->rows;
if (copy_buf) {
ans->buf = calloc(ans->rows, ans->stride);
if (!ans->buf) fatal("Out of memory");
ans->needs_free = true;
memcpy(ans->buf, bitmap->buffer, ans->rows * ans->stride);
} else ans->buf = bitmap->buffer;
ans->start_x = 0; ans->width = bitmap->width;
ans->pixel_mode = bitmap->pixel_mode;
ans->bitmap_top = slot->bitmap_top; ans->bitmap_left = slot->bitmap_left;
}
static inline bool
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) {
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false;
unsigned int max_width = cell_width * num_cells;
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
ans->buf = bitmap->buffer;
ans->start_x = 0; ans->width = bitmap->width;
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
ans->rows = bitmap->rows;
ans->pixel_mode = bitmap->pixel_mode;
// Embedded bitmap glyph?
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
FT_Bitmap bitmap;
FT_Bitmap_Init(&bitmap);
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
int error = FT_Bitmap_Convert(library, &self->face->glyph->bitmap, &bitmap, 1);
if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; }
// Normalize gray levels to the range [0..255]
bitmap.num_grays = 256;
unsigned int stride = bitmap.pitch < 0 ? -bitmap.pitch : bitmap.pitch;
for (unsigned int i = 0; i < bitmap.rows; ++i) {
// We only have 2 levels
for (unsigned int j = 0; j < bitmap.width; ++j) bitmap.buffer[i * stride + j] *= 255;
}
populate_processed_bitmap(self->face->glyph, &bitmap, ans, true);
FT_Bitmap_Done(library, &bitmap);
} else {
populate_processed_bitmap(self->face->glyph, &self->face->glyph->bitmap, ans, false);
}
if (ans->width > max_width) {
size_t extra = ans->width - max_width;
if (italic && extra < cell_width / 2) {
@@ -345,8 +382,9 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
// bad, we just crop the bitmap on the right. See https://github.com/kovidgoyal/kitty/issues/352
} else if (rescale && self->is_scalable && extra > 1) {
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
float ar = (float)max_width / (float)bitmap->width;
float ar = (float)max_width / (float)ans->width;
if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0, fg->cell_height)) {
free_processed_bitmap(ans);
if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false, fg)) return false;
if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0, fg->cell_height)) return false;
} else return false;
@@ -422,6 +460,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;
detect_right_edge(ans);
return true;
}
@@ -444,16 +484,14 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect
}
static inline void
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) {
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline) {
// We want the glyph to be positioned inside the cell based on the bearingX
// and bearingY values, making sure that it does not overflow the cell.
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width + bm->start_x }, dest = { .bottom = cell_height, .right = cell_width };
// Calculate column bounds
float bearing_x = (float)metrics->horiBearingX / 64.f;
bearing_x /= bm->factor;
int32_t xoff = (ssize_t)(x_offset + bearing_x);
int32_t xoff = (ssize_t)(x_offset + bm->bitmap_left);
uint32_t extra;
if (xoff < 0) src.left += -xoff;
else dest.left = xoff;
@@ -464,9 +502,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
}
// Calculate row bounds
float bearing_y = (float)metrics->horiBearingY / 64.f;
bearing_y /= bm->factor;
int32_t yoff = (ssize_t)(y_offset + bearing_y);
int32_t yoff = (ssize_t)(y_offset + bm->bitmap_top);
if ((yoff > 0 && (size_t)yoff > baseline)) {
dest.top = 0;
} else {
@@ -483,7 +519,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
bool
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
Face *self = (Face*)f;
bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color;
float x = 0.f, y = 0.f, x_offset = 0.f;
@@ -502,17 +538,20 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
}
x_offset = x + (float)positions[i].x_offset / 64.0f;
y = (float)positions[i].y_offset / 64.0f;
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, &self->face->glyph->metrics, baseline);
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline);
}
x += (float)positions[i].x_advance / 64.0f;
if (bm.needs_free) free(bm.buf);
free_processed_bitmap(&bm);
}
// center the glyphs in the canvas
unsigned int right_edge = (unsigned int)x, delta;
// x_advance is wrong for colored bitmaps that have been downsampled
if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width;
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
right_shift_canvas(canvas, canvas_width, cell_height, delta);
if (center_glyph && num_glyphs) {
unsigned int right_edge = (unsigned int)x, delta;
// x_advance is wrong for colored bitmaps that have been downsampled
if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width;
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
right_shift_canvas(canvas, canvas_width, cell_height, delta);
}
}
return true;
}

5606
kitty/gl-wrapper.c generated

File diff suppressed because it is too large Load Diff

87
kitty/gl-wrapper.h generated
View File

@@ -1,5 +1,5 @@
/*
OpenGL loader generated by glad 0.1.24a0 on Wed Jun 20 12:32:33 2018.
OpenGL loader generated by glad 0.1.28 on Tue Dec 4 16:30:01 2018.
Language/Generator: C/C++ Debug
Specification: gl
APIs: gl=3.3
@@ -13,10 +13,11 @@
Loader: True
Local files: False
Omit khrplatform: False
Reproducible: False
Commandline:
--profile="core" --api="gl=3.3" --generator="c-debug" --spec="gl" --extensions="GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_KHR_debug"
Online:
http://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
https://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
*/
#ifndef __glad_h_
#define __glad_h_
@@ -39,6 +40,9 @@
#ifndef APIENTRYP
#define APIENTRYP APIENTRY *
#endif
#ifndef GLAPIENTRY
#define GLAPIENTRY APIENTRY
#endif
#ifdef __cplusplus
extern "C" {
#endif
@@ -79,59 +83,21 @@ GLAPI int gladLoadGLLoader(GLADloadproc);
typedef void (* GLADcallback)(const char *name, void *funcptr, int len_args, ...);
GLAPI void glad_set_pre_callback(GLADcallback cb);
GLAPI void glad_set_post_callback(GLADcallback cb);
#include <stddef.h>
#include "khrplatform.h"
#ifndef GLEXT_64_TYPES_DEFINED
/* This code block is duplicated in glxext.h, so must be protected */
#define GLEXT_64_TYPES_DEFINED
/* Define int32_t, int64_t, and uint64_t types for UST/MSC */
/* (as used in the GL_EXT_timer_query extension). */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#include <inttypes.h>
#elif defined(__sun__) || defined(__digital__)
#include <inttypes.h>
#if defined(__STDC__)
#if defined(__arch64__) || defined(_LP64)
typedef long int int64_t;
typedef unsigned long int uint64_t;
#else
typedef long long int int64_t;
typedef unsigned long long int uint64_t;
#endif /* __arch64__ */
#endif /* __STDC__ */
#elif defined( __VMS ) || defined(__sgi)
#include <inttypes.h>
#elif defined(__SCO__) || defined(__USLC__)
#include <stdint.h>
#elif defined(__UNIXOS2__) || defined(__SOL64__)
typedef long int int32_t;
typedef long long int int64_t;
typedef unsigned long long int uint64_t;
#elif defined(_WIN32) && defined(__GNUC__)
#include <stdint.h>
#elif defined(_WIN32)
typedef __int32 int32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
/* Fallback if nothing above works */
#include <inttypes.h>
#endif
#endif
typedef unsigned int GLenum;
typedef unsigned char GLboolean;
typedef unsigned int GLbitfield;
typedef void GLvoid;
typedef signed char GLbyte;
typedef short GLshort;
typedef khronos_int8_t GLbyte;
typedef khronos_uint8_t GLubyte;
typedef khronos_int16_t GLshort;
typedef khronos_uint16_t GLushort;
typedef int GLint;
typedef int GLclampx;
typedef unsigned char GLubyte;
typedef unsigned short GLushort;
typedef unsigned int GLuint;
typedef khronos_int32_t GLclampx;
typedef int GLsizei;
typedef float GLfloat;
typedef float GLclampf;
typedef khronos_float_t GLfloat;
typedef khronos_float_t GLclampf;
typedef double GLdouble;
typedef double GLclampd;
typedef void *GLeglClientBufferEXT;
@@ -143,25 +109,17 @@ typedef void *GLhandleARB;
#else
typedef unsigned int GLhandleARB;
#endif
typedef unsigned short GLhalfARB;
typedef unsigned short GLhalf;
typedef GLint GLfixed;
typedef khronos_uint16_t GLhalf;
typedef khronos_uint16_t GLhalfARB;
typedef khronos_int32_t GLfixed;
typedef khronos_intptr_t GLintptr;
typedef khronos_intptr_t GLintptrARB;
typedef khronos_ssize_t GLsizeiptr;
typedef int64_t GLint64;
typedef uint64_t GLuint64;
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
typedef long GLintptrARB;
#else
typedef ptrdiff_t GLintptrARB;
#endif
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
typedef long GLsizeiptrARB;
#else
typedef ptrdiff_t GLsizeiptrARB;
#endif
typedef int64_t GLint64EXT;
typedef uint64_t GLuint64EXT;
typedef khronos_ssize_t GLsizeiptrARB;
typedef khronos_int64_t GLint64;
typedef khronos_int64_t GLint64EXT;
typedef khronos_uint64_t GLuint64;
typedef khronos_uint64_t GLuint64EXT;
typedef struct __GLsync *GLsync;
struct _cl_context;
struct _cl_event;
@@ -814,7 +772,6 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
#define GL_MAX_SAMPLES 0x8D57
#define GL_INDEX 0x8222
#define GL_FRAMEBUFFER_SRGB 0x8DB9
#define GL_HALF_FLOAT 0x140B
#define GL_MAP_READ_BIT 0x0001

11
kitty/glfw-wrapper.c generated
View File

@@ -131,6 +131,9 @@ load_glfw(const char* path) {
*(void **) (&glfwGetWindowContentScale_impl) = dlsym(handle, "glfwGetWindowContentScale");
if (glfwGetWindowContentScale_impl == NULL) fail("Failed to load glfw function glfwGetWindowContentScale with error: %s", dlerror());
*(void **) (&glfwGetDoubleClickInterval_impl) = dlsym(handle, "glfwGetDoubleClickInterval");
if (glfwGetDoubleClickInterval_impl == NULL) fail("Failed to load glfw function glfwGetDoubleClickInterval with error: %s", dlerror());
*(void **) (&glfwGetWindowOpacity_impl) = dlsym(handle, "glfwGetWindowOpacity");
if (glfwGetWindowOpacity_impl == NULL) fail("Failed to load glfw function glfwGetWindowOpacity with error: %s", dlerror());
@@ -358,6 +361,8 @@ load_glfw(const char* path) {
*(void **) (&glfwGetCocoaWindow_impl) = dlsym(handle, "glfwGetCocoaWindow");
*(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext");
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
@@ -372,12 +377,14 @@ load_glfw(const char* path) {
*(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window");
*(void **) (&glfwSetX11SelectionString_impl) = dlsym(handle, "glfwSetX11SelectionString");
*(void **) (&glfwSetPrimarySelectionString_impl) = dlsym(handle, "glfwSetPrimarySelectionString");
*(void **) (&glfwGetX11SelectionString_impl) = dlsym(handle, "glfwGetX11SelectionString");
*(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString");
*(void **) (&glfwGetXKBScancode_impl) = dlsym(handle, "glfwGetXKBScancode");
*(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent");
return NULL;
}

26
kitty/glfw-wrapper.h generated
View File

@@ -1387,7 +1387,7 @@ typedef struct GLFWgamepadstate
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
typedef int (*glfwInit_func)();
glfwInit_func glfwInit_impl;
#define glfwInit glfwInit_impl
@@ -1544,6 +1544,10 @@ typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*);
glfwGetWindowContentScale_func glfwGetWindowContentScale_impl;
#define glfwGetWindowContentScale glfwGetWindowContentScale_impl
typedef double (*glfwGetDoubleClickInterval_func)(GLFWwindow*);
glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl;
#define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl
typedef float (*glfwGetWindowOpacity_func)(GLFWwindow*);
glfwGetWindowOpacity_func glfwGetWindowOpacity_impl;
#define glfwGetWindowOpacity glfwGetWindowOpacity_impl
@@ -1848,6 +1852,10 @@ typedef void* (*glfwGetCocoaWindow_func)(GLFWwindow*);
glfwGetCocoaWindow_func glfwGetCocoaWindow_impl;
#define glfwGetCocoaWindow glfwGetCocoaWindow_impl
typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*);
glfwGetNSGLContext_func glfwGetNSGLContext_impl;
#define glfwGetNSGLContext glfwGetNSGLContext_impl
typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*);
glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl;
#define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl
@@ -1876,16 +1884,20 @@ typedef int32_t (*glfwGetX11Window_func)(GLFWwindow*);
glfwGetX11Window_func glfwGetX11Window_impl;
#define glfwGetX11Window glfwGetX11Window_impl
typedef void (*glfwSetX11SelectionString_func)(const char*);
glfwSetX11SelectionString_func glfwSetX11SelectionString_impl;
#define glfwSetX11SelectionString glfwSetX11SelectionString_impl
typedef void (*glfwSetPrimarySelectionString_func)(GLFWwindow*, const char*);
glfwSetPrimarySelectionString_func glfwSetPrimarySelectionString_impl;
#define glfwSetPrimarySelectionString glfwSetPrimarySelectionString_impl
typedef const char* (*glfwGetX11SelectionString_func)();
glfwGetX11SelectionString_func glfwGetX11SelectionString_impl;
#define glfwGetX11SelectionString glfwGetX11SelectionString_impl
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 void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc);
glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl;
#define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl
const char* load_glfw(const char* path);

View File

@@ -15,6 +15,7 @@ extern void cocoa_create_global_menu(void);
extern void cocoa_set_hide_from_tasks(void);
extern void cocoa_set_titlebar_color(void *w, color_type color);
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
#endif
@@ -26,9 +27,13 @@ static void set_os_window_dpi(OSWindow *w);
void
update_os_window_viewport(OSWindow *window, bool notify_boss) {
int w, h;
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
int w, h, fw, fh;
glfwGetFramebufferSize(window->handle, &fw, &fh);
glfwGetWindowSize(window->handle, &w, &h);
if (fw == window->viewport_width && fh == window->viewport_height && w == window->window_width && h == window->window_height) {
return; // no change, ignore
}
window->viewport_width = fw; window->viewport_height = fh;
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
window->viewport_x_ratio = (double)window->viewport_width / (double)w;
window->viewport_y_ratio = (double)window->viewport_height / (double)h;
@@ -44,6 +49,11 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
window->window_height = MAX(h, 100);
if (notify_boss) {
call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
if (dpi_changed && global_state.is_wayland) {
// Fake resize event needed on weston to ensure surface is
// positioned correctly after DPI change
glfwSetWindowSize(window->handle, window->window_width, window->window_height);
}
}
}
@@ -300,6 +310,7 @@ make_os_window_context_current(OSWindow *w) {
}
#ifndef __APPLE__
static GLFWmonitor*
current_monitor(GLFWwindow *window) {
// Find the monitor that has the maximum overlap with this window
@@ -334,14 +345,15 @@ current_monitor(GLFWwindow *window) {
return bestmonitor;
}
#endif
static inline void
get_window_dpi(GLFWwindow *w, double *x, double *y) {
GLFWmonitor *monitor = NULL;
if (w) monitor = current_monitor(w);
if (monitor == NULL) { PyErr_Print(); monitor = glfwGetPrimaryMonitor(); }
float xscale = 1, yscale = 1;
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
if (w) glfwGetWindowContentScale(w, &xscale, &yscale);
else glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale);
if (!xscale) xscale = 1.0;
if (!yscale) yscale = 1.0;
#ifdef __APPLE__
double factor = 72.0;
#else
@@ -463,9 +475,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwWindowHint(GLFW_DEPTH_BITS, 0);
glfwWindowHint(GLFW_STENCIL_BITS, 0);
#ifdef __APPLE__
if (OPT(macos_hide_titlebar)) glfwWindowHint(GLFW_DECORATED, false);
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);
glfwSetApplicationShouldHandleReopen(on_application_reopen);
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
#endif
}
@@ -474,9 +486,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
if (OPT(x11_hide_window_decorations)) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
}
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
#endif
if (global_state.num_os_windows >= MAX_CHILDREN) {
@@ -541,6 +551,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
}}
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
#undef CC
if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window);
is_first_window = false;
}
OSWindow *w = add_os_window();
@@ -555,7 +566,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
w->fonts_data = fonts_data;
w->shown_once = true;
push_focus_history(w);
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
#ifdef __APPLE__
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
@@ -806,9 +817,15 @@ hide_mouse(OSWindow *w) {
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
bool
is_mouse_hidden(OSWindow *w) {
return w->handle && glfwGetInputMode(w->handle, GLFW_CURSOR) == GLFW_CURSOR_HIDDEN;
}
void
swap_window_buffers(OSWindow *w) {
glfwSwapBuffers(w->handle);
swap_window_buffers(OSWindow *os_window) {
glfwSwapBuffers(os_window->handle);
}
void
@@ -878,9 +895,10 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
static PyObject*
get_primary_selection(PYNOARG) {
if (glfwGetX11SelectionString) {
return Py_BuildValue("y", glfwGetX11SelectionString());
} else log_error("Failed to load glfwGetX11SelectionString");
if (glfwGetPrimarySelectionString) {
OSWindow *w = current_os_window();
if (w) return Py_BuildValue("y", glfwGetPrimarySelectionString(w->handle));
} else log_error("Failed to load glfwGetPrimarySelectionString");
Py_RETURN_NONE;
}
@@ -888,8 +906,11 @@ static PyObject*
set_primary_selection(PyObject UNUSED *self, PyObject *args) {
char *text;
if (!PyArg_ParseTuple(args, "s", &text)) return NULL;
if (glfwSetX11SelectionString) glfwSetX11SelectionString(text);
else log_error("Failed to load glfwSetX11SelectionString");
if (glfwSetPrimarySelectionString) {
OSWindow *w = current_os_window();
if (w) glfwSetPrimarySelectionString(w->handle, text);
}
else log_error("Failed to load glfwSetPrimarySelectionString");
Py_RETURN_NONE;
}
@@ -972,6 +993,23 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
}
#endif
void
wayland_frame_request_callback(id_type os_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
if (global_state.os_windows[i].id == os_window_id) {
global_state.os_windows[i].wayland_render_state = RENDER_FRAME_READY;
break;
}
}
}
void
wayland_request_frame_render(OSWindow *w) {
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
w->wayland_render_state = RENDER_FRAME_REQUESTED;
}
// Boilerplate {{{
static PyMethodDef module_methods[] = {

View File

@@ -54,12 +54,35 @@ attrptr(HistoryBuf *self, index_type y) {
seg_ptr(line_attrs, 1);
}
static inline PagerHistoryBuf*
alloc_pagerhist(unsigned int pagerhist_sz) {
PagerHistoryBuf *ph;
if (!pagerhist_sz) return NULL;
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
ph->maxsz = pagerhist_sz / sizeof(Py_UCS4);
ph->bufsize = 1024*1024 / sizeof(Py_UCS4);
ph->buffer = PyMem_RawMalloc(1024*1024);
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
return ph;
}
static inline bool
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
if (ph->bufsize >= ph->maxsz) return false;
size_t newsz = ph->bufsize + MAX(1024 * 1024, minsz);
void *newbuf = PyMem_Realloc(ph->buffer, newsz * sizeof(Py_UCS4));
if (!newbuf) return false;
ph->buffer = newbuf;
ph->bufsize = newsz;
return true;
}
static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
HistoryBuf *self;
unsigned int xnum = 1, ynum = 1;
unsigned int xnum = 1, ynum = 1, pagerhist_sz = 0;
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
if (!PyArg_ParseTuple(args, "II|I", &ynum, &xnum, &pagerhist_sz)) return NULL;
if (xnum == 0 || ynum == 0) {
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
@@ -74,6 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
add_segment(self);
self->line = alloc_line();
self->line->xnum = xnum;
self->pagerhist = alloc_pagerhist(pagerhist_sz);
}
return (PyObject*)self;
@@ -88,6 +112,8 @@ dealloc(HistoryBuf* self) {
PyMem_Free(self->segments[i].line_attrs);
}
PyMem_Free(self->segments);
if (self->pagerhist) PyMem_Free(self->pagerhist->buffer);
PyMem_Free(self->pagerhist);
Py_TYPE(self)->tp_free((PyObject*)self);
}
@@ -132,12 +158,50 @@ historybuf_clear(HistoryBuf *self) {
self->start_of_data = 0;
}
static inline void
pagerhist_push(HistoryBuf *self) {
PagerHistoryBuf *ph = self->pagerhist;
if (!ph) return;
bool truncated;
Line l = {.xnum=self->xnum};
init_line(self, self->start_of_data, &l);
#define EXPAND_IF_FULL(sz) { \
if (ph->bufsize - ph->end < sz && !pagerhist_extend(ph, sz)) { \
ph->bufend = ph->end; ph->end = 0; \
} \
}
size_t sz = MAX(1024, ph->bufsize - ph->end);
sz = MAX(sz, self->xnum + self->xnum);
EXPAND_IF_FULL(sz);
if (ph->start != ph->end && !l.continued) {
ph->buffer[ph->end++] = '\n';
}
while(sz < ph->maxsz - 2) {
size_t num = line_as_ansi(&l, ph->buffer + ph->end, ph->bufsize - ph->end - 2, &truncated);
if (!truncated) {
ph->end += num;
ph->buffer[ph->end++] = '\r';
if (ph->bufend) {
ph->start = ph->end + 1 < ph->bufend ? ph->end + 1 : 0;
}
break;
}
// check if sz is too large too fit in buffer
if (ph->bufsize > ph->maxsz && !ph->end) break;
sz *= 2;
EXPAND_IF_FULL(sz);
}
#undef EXPAND_IF_FULL
}
static inline index_type
historybuf_push(HistoryBuf *self) {
index_type idx = (self->start_of_data + self->count) % self->ynum;
init_line(self, idx, self->line);
if (self->count == self->ynum) self->start_of_data = (self->start_of_data + 1) % self->ynum;
else self->count++;
if (self->count == self->ynum) {
pagerhist_push(self);
self->start_of_data = (self->start_of_data + 1) % self->ynum;
} else self->count++;
return idx;
}
@@ -189,12 +253,13 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
#define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line."
static Py_UCS4 t[5120];
Line l = {.xnum=self->xnum};
bool truncated;
for(unsigned int i = 0; i < self->count; i++) {
init_line(self, i, &l);
if (i < self->count - 1) {
l.continued = *attrptr(self, index_of(self, i + 1)) & CONTINUED_MASK;
} else l.continued = false;
index_type num = line_as_ansi(&l, t, 5120);
index_type num = line_as_ansi(&l, t, 5120, &truncated);
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
if (ans == NULL) return PyErr_NoMemory();
@@ -209,6 +274,93 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
static inline Line*
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
static void
pagerhist_rewrap(PagerHistoryBuf *ph, index_type xnum) {
Py_UCS4 *buf = PyMem_RawMalloc(ph->bufsize * sizeof(Py_UCS4));
if (!buf) return;
index_type s = ph->start, i = s, dest = 0, dest_bufend = 0, x = 0;
index_type end = ph->bufend ? ph->bufend : ph->end;
index_type lastmod_s = 0, lastmod_len = 0;
#define CPY(_s, _l) { if (dest + (_l) >= ph->bufsize - 1) { dest_bufend = dest; dest = 0; } \
memcpy(buf + dest, ph->buffer + (_s), (_l) * sizeof(Py_UCS4)); dest += (_l); }
while (i < end) {
switch (ph->buffer[i]) {
case '\n':
CPY(s, i - s + 1);
x = 0; s = i + 1; lastmod_len = 0;
break;
case '\r':
CPY(s, i - s);
if (!memcmp(ph->buffer + lastmod_s, ph->buffer + i + 1, lastmod_len * sizeof(Py_UCS4)))
i += lastmod_len;
s = i + 1;
break;
case '\x1b':
if (ph->buffer[i+1] != '[') break;
lastmod_s = i;
while (ph->buffer[++i] != 'm');
lastmod_len = i - lastmod_s + 1;
break;
default:
x++; break;
}
i++;
if (ph->bufend && i == ph->bufend) {
if (s != i) CPY(s, i - s);
end = ph->end; i = s = 0;
}
if (x == xnum) {
CPY(s, i - s); buf[dest++] = '\r'; s = i; x = 0;
if (!(ph->buffer[i] == '\x1b' && ph->buffer[i+1] == '[') && lastmod_len)
CPY(lastmod_s, lastmod_len);
}
}
#undef CPY
PyMem_Free(ph->buffer);
ph->buffer = buf;
ph->end = dest; ph->bufend = dest_bufend;
ph->start = dest_bufend ? dest + 1 : 0;
ph->rewrap_needed = false;
}
static PyObject *
pagerhist_as_text(HistoryBuf *self, PyObject *callback) {
PagerHistoryBuf *ph = self->pagerhist;
PyObject *ret = NULL, *t = NULL;
Py_UCS4 *buf = NULL;
index_type num;
if (!ph) Py_RETURN_NONE;
if (ph->rewrap_needed) pagerhist_rewrap(ph, self->xnum);
#define CALLBACK { \
if (t == NULL) goto end; \
ret = PyObject_CallFunctionObjArgs(callback, t, NULL); \
Py_DECREF(t); \
if (ret == NULL) goto end; \
Py_DECREF(ret); \
}
num = (ph->bufend ? ph->bufend : ph->end) - ph->start;
buf = ph->buffer + ph->start;
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
CALLBACK;
if (ph->bufend) {
num = ph->end; buf = ph->buffer;
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
CALLBACK;
}
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
if (!l.continued) {
t = PyUnicode_FromString("\n");
CALLBACK;
}
#undef CALLBACK
end:
if (PyErr_Occurred()) return NULL;
Py_RETURN_NONE;
}
static PyObject*
as_text(HistoryBuf *self, PyObject *args) {
Line l = {.xnum=self->xnum};
@@ -238,6 +390,7 @@ static PyObject* rewrap(HistoryBuf *self, PyObject *args);
static PyMethodDef methods[] = {
METHOD(line, METH_O)
METHOD(as_ansi, METH_O)
METHODB(pagerhist_as_text, METH_O),
METHODB(as_text, METH_VARARGS),
METHOD(dirty_lines, METH_NOARGS)
METHOD(push, METH_VARARGS)
@@ -267,8 +420,8 @@ PyTypeObject HistoryBuf_Type = {
INIT_TYPE(HistoryBuf)
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("II", lines, columns), NULL);
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned int pagerhist_sz) {
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("III", lines, columns, pagerhist_sz), NULL);
}
// }}}
@@ -298,6 +451,8 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
other->count = self->count; other->start_of_data = self->start_of_data;
return;
}
if (other->pagerhist && other->xnum != self->xnum && other->pagerhist->end != other->pagerhist->start)
other->pagerhist->rewrap_needed = true;
other->count = 0; other->start_of_data = 0;
index_type x = 0, y = 0;
if (self->count > 0) {

480
kitty/keys.h generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,11 +17,13 @@ def modify_key_bytes(keybytes, amt):
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
if ans[1] == ord('O'):
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
raise ValueError('Unknown key type')
raise ValueError('Unknown key type in key: {!r}'.format(keybytes))
def modify_complex_key(name, amt):
return modify_key_bytes(key_as_bytes(name), amt)
if not isinstance(name, bytes):
name = key_as_bytes(name)
return modify_key_bytes(name, amt)
control_codes = {
@@ -39,6 +41,7 @@ shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt')
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL
ctrl_alt_shift_mod = ctrl_alt_mod | defines.GLFW_MOD_SHIFT
SHIFTED_KEYS = {
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
@@ -54,6 +57,32 @@ SHIFTED_KEYS = {
control_alt_codes = {
defines.GLFW_KEY_SPACE: b'\x1b\0',
}
control_alt_shift_codes = {}
ASCII_C0_SHIFTED = {
# ^@
'2': b'\x00',
# ^^
'6': b'\x1e',
# ^_
'MINUS': b'\x1f',
# ^?
'SLASH': b'\x7f',
}
control_shift_keys = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
def create_modifier_variants(keycode, terminfo_name_or_bytes, add_shifted_key=True):
kn = terminfo_name_or_bytes
smkx_key_map[keycode] = kn if isinstance(kn, bytes) else key_as_bytes(kn)
if add_shifted_key:
SHIFTED_KEYS[keycode] = modify_complex_key(kn, 2)
alt_codes[keycode] = modify_complex_key(kn, 3)
shift_alt_codes[keycode] = modify_complex_key(kn, 4)
control_codes[keycode] = modify_complex_key(kn, 5)
control_shift_keys[keycode] = modify_complex_key(kn, 6)
control_alt_codes[keycode] = modify_complex_key(kn, 7)
control_alt_shift_codes[keycode] = modify_complex_key(kn, 8)
for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1',
@@ -67,24 +96,16 @@ for kf, kn in {
defines.GLFW_KEY_PAGE_UP: 'kpp',
defines.GLFW_KEY_PAGE_DOWN: 'knp',
}.items():
smkx_key_map[kf] = key_as_bytes(kn)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
control_alt_codes[kf] = modify_complex_key(kn, 7)
create_modifier_variants(kf, kn, add_shifted_key=False)
for f in range(1, 13):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
SHIFTED_KEYS[kf] = modify_complex_key(kn, 2)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
control_alt_codes[kf] = modify_complex_key(kn, 7)
create_modifier_variants(kf, kn)
for f in range(13, 26):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
create_modifier_variants(defines.GLFW_KEY_MENU, b'\x1b[29~')
f = {k: k for k in '0123456789'}
f.update({
'COMMA': ',',
@@ -215,17 +236,6 @@ SHIFTED_PRINTABLE.update(pmap(
"{|}~"
))
ASCII_C0_SHIFTED = {
# ^@
'2': b'\x00',
# ^^
'6': b'\x1e',
# ^_
'MINUS': b'\x1f',
# ^?
'SLASH': b'\x7f',
}
CTRL_SHIFT_KEYS = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase}
all_control_alt_keys = set(CTRL_ALT_KEYS) | set(control_alt_codes)
@@ -237,8 +247,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
# Map Ctrl-key to ascii control code
data.extend(control_codes[key])
elif mods == ctrl_shift_mod and key in CTRL_SHIFT_KEYS:
data.extend(CTRL_SHIFT_KEYS[key])
elif mods == ctrl_shift_mod and key in control_shift_keys:
data.extend(control_shift_keys[key])
elif mods in alt_mods:
if key in alt_codes:
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
@@ -251,6 +261,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
data.append(0x1b), data.extend(control_codes[key])
else:
data.extend(control_alt_codes[key])
elif mods == ctrl_alt_shift_mod and key in control_alt_shift_codes:
data.extend(control_alt_shift_codes[key])
else:
key_map = cursor_key_mode_map[smkx]
x = key_map.get(key)

View File

@@ -721,7 +721,7 @@ class Grid(Layout): # {{{
def neighbors_for_window(self, window, windows):
n = len(windows)
if n < 4:
return Tall.neighbors_for_window(window, windows)
return Tall.neighbors_for_window(self, window, windows)
try:
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
except Exception:

View File

@@ -397,16 +397,17 @@ as_ansi(LineBuf *self, PyObject *callback) {
Line l = {.xnum=self->xnum};
// remove trailing empty lines
index_type ylimit = self->ynum - 1;
bool truncated;
do {
init_line(self, (&l), self->line_map[ylimit]);
if (line_as_ansi(&l, t, 5120) != 0) break;
if (line_as_ansi(&l, t, 5120, &truncated) != 0) break;
ylimit--;
} while(ylimit > 0);
for(index_type i = 0; i <= ylimit; i++) {
l.continued = ((i < self->ynum - 1) ? self->line_attrs[i+1] : self->line_attrs[i]) & CONTINUED_MASK;
init_line(self, (&l), self->line_map[i]);
index_type num = line_as_ansi(&l, t, 5120);
index_type num = line_as_ansi(&l, t, 5120, &truncated);
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
if (ans == NULL) return PyErr_NoMemory();

View File

@@ -50,18 +50,25 @@ cell_text(CPUCell *cell) {
static const char* url_prefixes[4] = {"https", "http", "file", "ftp"};
static size_t url_prefix_lengths[sizeof(url_prefixes)/sizeof(url_prefixes[0])] = {0};
typedef enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH} URL_PARSER_STATE;
static inline index_type
find_colon_slash(Line *self, index_type x, index_type limit) {
// Find :// at or before x
index_type pos = x;
URL_PARSER_STATE state = ANY;
enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH};
enum URL_PARSER_STATES state = ANY;
limit = MAX(2, limit);
if (pos < limit) return 0;
do {
char_type ch = self->cpu_cells[pos].ch;
if (!is_url_char(ch)) return false;
if (pos == x) {
if (ch == ':') {
if (pos + 2 < self->xnum && self->cpu_cells[pos+1].ch == '/' && self->cpu_cells[pos + 2].ch == '/') state = SECOND_SLASH;
} else if (ch == '/') {
if (pos + 1 < self->xnum && self->cpu_cells[pos+1].ch == '/') state = FIRST_SLASH;
}
}
switch(state) {
case ANY:
if (ch == '/') state = FIRST_SLASH;
@@ -227,50 +234,53 @@ write_sgr(const char *val, Py_UCS4 *buf, index_type buflen, index_type *i) {
}
index_type
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) return i; }
#define WRITE_CH(val) if (i > buflen - 1) return i; buf[i++] = val;
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool *truncated) {
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) { *truncated = true; return i; } }
#define WRITE_CH(val) if (i > buflen - 1) { *truncated = true; return i; } buf[i++] = val;
index_type limit = xlimit_for_line(self), i=0;
*truncated = false;
if (limit == 0) return 0;
char_type previous_width = 0;
WRITE_SGR("0");
Cursor c1 = {{0}}, c2 = {{0}};
Cursor *cursor = &c1, *prev_cursor = &c2, *t;
GPUCell blank_cell = { 0 };
GPUCell *cell, *prev_cell = &blank_cell;
for (index_type pos=0; pos < limit; pos++) {
char_type attrs = self->gpu_cells[pos].attrs, ch = self->cpu_cells[pos].ch;
char_type ch = self->cpu_cells[pos].ch;
if (ch == 0) {
if (previous_width == 2) { previous_width = 0; continue; }
ch = ' ';
}
ATTRS_TO_CURSOR(attrs, cursor);
cursor->fg = self->gpu_cells[pos].fg; cursor->bg = self->gpu_cells[pos].bg;
cursor->decoration_fg = self->gpu_cells[pos].decoration_fg & COL_MASK;
const char *sgr = cursor_as_sgr(cursor, prev_cursor);
t = prev_cursor; prev_cursor = cursor; cursor = t;
if (*sgr) WRITE_SGR(sgr);
cell = &self->gpu_cells[pos];
#define CMP_ATTRS (cell->attrs & ATTRS_MASK_WITHOUT_WIDTH) != (prev_cell->attrs & ATTRS_MASK_WITHOUT_WIDTH)
#define CMP(x) cell->x != prev_cell->x
if (CMP_ATTRS || CMP(fg) || CMP(bg) || CMP(decoration_fg)) {
const char *sgr = cell_as_sgr(cell, prev_cell);
if (*sgr) WRITE_SGR(sgr);
}
prev_cell = cell;
WRITE_CH(ch);
for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) {
WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c]));
}
previous_width = attrs & WIDTH_MASK;
previous_width = cell->attrs & WIDTH_MASK;
}
return i;
#undef CHECK_BOOL
#undef CHECK_COLOR
#undef CMP_ATTRS
#undef CMP
#undef WRITE_SGR
#undef WRITE_CH
#undef WRITE_COLOR
}
static PyObject*
as_ansi(Line* self, PyObject *a UNUSED) {
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
static Py_UCS4 t[5120] = {0};
index_type num = line_as_ansi(self, t, 5120);
bool truncated;
index_type num = line_as_ansi(self, t, 5120, &truncated);
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
return ans;
}
@@ -527,6 +537,73 @@ set_attribute(Line *self, PyObject *args) {
Py_RETURN_NONE;
}
static inline int
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
switch(val & 0xff) {
case 1:
val >>= 8;
if (val < 16 && simple_code) {
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
}
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
case 2:
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
default:
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
}
}
static inline const char*
decoration_as_sgr(uint8_t decoration) {
switch(decoration) {
case 1: return "4;";
case 2: return "4:2;";
case 3: return "4:3;";
default: return "24;";
}
}
const char*
cell_as_sgr(GPUCell *cell, GPUCell *prev) {
static char buf[128];
#define SZ sizeof(buf) - (p - buf) - 2
#define P(s) { size_t len = strlen(s); if (SZ > len) { memcpy(p, s, len); p += len; } }
char *p = buf;
#define CMP(attr) (attr(cell) != attr(prev))
#define BOLD(cell) (cell->attrs & (1 << BOLD_SHIFT))
#define DIM(cell) (cell->attrs & (1 << DIM_SHIFT))
#define ITALIC(cell) (cell->attrs & (1 << ITALIC_SHIFT))
#define REVERSE(cell) (cell->attrs & (1 << REVERSE_SHIFT))
#define STRIKETHROUGH(cell) (cell->attrs & (1 << STRIKE_SHIFT))
#define DECORATION(cell) (cell->attrs & (DECORATION_MASK << DECORATION_SHIFT))
bool intensity_differs = CMP(BOLD) || CMP(DIM);
if (intensity_differs) {
if (!BOLD(cell) && !DIM(cell)) { P("22;"); }
else { if (BOLD(cell)) P("1;"); if (DIM(cell)) P("2;"); }
}
if (CMP(ITALIC)) P(ITALIC(cell) ? "3;" : "23;");
if (CMP(REVERSE)) P(REVERSE(cell) ? "7;" : "27;");
if (CMP(STRIKETHROUGH)) P(STRIKETHROUGH(cell) ? "9;" : "29;");
if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38);
if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48);
if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE);
if (CMP(DECORATION)) P(decoration_as_sgr((cell->attrs >> DECORATION_SHIFT) & DECORATION_MASK));
#undef CMP
#undef BOLD
#undef DIM
#undef ITALIC
#undef REVERSE
#undef STRIKETHROUGH
#undef DECORATION
#undef P
#undef SZ
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
*p = 0; // ensure string is null-terminated
return buf;
}
static Py_ssize_t
__len__(PyObject *self) {
return (Py_ssize_t)(((Line*)self)->xnum);

View File

@@ -37,6 +37,7 @@ xlimit_for_line(Line *line) {
index_type xlimit = line->xnum;
if (BLANK_CHAR == 0) {
while (xlimit > 0 && (line->cpu_cells[xlimit - 1].ch) == BLANK_CHAR) xlimit--;
if (xlimit < line->xnum && (line->gpu_cells[xlimit > 0 ? xlimit - 1 : xlimit].attrs & WIDTH_MASK) == 2) xlimit++;
}
return xlimit;
}
@@ -60,7 +61,7 @@ void line_right_shift(Line *, unsigned int , unsigned int );
void line_add_combining_char(Line *, uint32_t , unsigned int );
index_type line_url_start_at(Line *self, index_type x);
index_type line_url_end_at(Line *self, index_type x, bool, char_type);
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool*);
unsigned int line_length(Line *self);
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_utf8(CPUCell *cell, bool include_cc, char *buf, char_type);
@@ -110,7 +111,8 @@ void historybuf_clear(HistoryBuf *self);
Py_CLEAR(ret); \
} \
if (as_ansi) { \
index_type num = line_as_ansi(line, buf, columns * 100 - 2); \
bool truncated; \
index_type num = line_as_ansi(line, buf, columns * 100 - 2, &truncated); \
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num); \
} else { \
t = line_as_unicode(line); \

View File

@@ -85,7 +85,7 @@ encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_proto
static int
encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
unsigned int x = w->mouse_cell_x + 1, y = w->mouse_cell_y + 1; // 1 based indexing
unsigned int x = w->mouse_pos.cell_x + 1, y = w->mouse_pos.cell_y + 1; // 1 based indexing
Screen *screen = w->render_data.screen;
return encode_mouse_event_impl(x, y, screen->modes.mouse_tracking_protocol, button, action, mods);
@@ -142,6 +142,7 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, OSWindow *os_window) {
mouse_x = MIN(MAX(mouse_x, left), right);
mouse_y = MIN(MAX(mouse_y, top), bottom);
}
w->mouse_pos.x = mouse_x - left; w->mouse_pos.y = mouse_y - top;
if (mouse_x < left || mouse_y < top || mouse_x > right || mouse_y > bottom) return false;
if (mouse_x >= g->right) qx = screen->columns - 1;
else if (mouse_x >= g->left) qx = (unsigned int)((double)(mouse_x - g->left) / os_window->fonts_data->cell_width);
@@ -164,14 +165,14 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
global_state.active_drag_in_window = 0;
w->last_drag_scroll_at = 0;
if (screen->selection.in_progress)
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true);
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, true);
}
else {
global_state.active_drag_in_window = w->id;
screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, modifiers == (int)OPT(rectangle_select_modifiers) || modifiers == ((int)OPT(rectangle_select_modifiers) | GLFW_MOD_SHIFT), EXTEND_CELL);
}
} else if (screen->selection.in_progress) {
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, false);
}
}
@@ -201,7 +202,7 @@ static inline void
extend_selection(Window *w) {
Screen *screen = w->render_data.screen;
if (screen_has_selection(screen)) {
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, false);
}
}
@@ -276,8 +277,8 @@ HANDLER(handle_move_event) {
if (!cell_for_pos(w, &x, &y, global_state.callback_os_window)) return;
Screen *screen = w->render_data.screen;
detect_url(screen, x, y);
bool mouse_cell_changed = x != w->mouse_cell_x || y != w->mouse_cell_y;
w->mouse_cell_x = x; w->mouse_cell_y = y;
bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y;
w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y;
bool handle_in_kitty = (
(screen->modes.mouse_tracking_mode == ANY_MODE ||
(screen->modes.mouse_tracking_mode == MOTION_MODE && button >= 0)) &&
@@ -304,14 +305,14 @@ multi_click(Window *w, unsigned int count) {
index_type start, end;
bool found_selection = false;
SelectionExtendMode mode = EXTEND_CELL;
unsigned int y1 = w->mouse_cell_y, y2 = w->mouse_cell_y;
unsigned int y1 = w->mouse_pos.cell_y, y2 = w->mouse_pos.cell_y;
switch(count) {
case 2:
found_selection = screen_selection_range_for_word(screen, w->mouse_cell_x, &y1, &y2, &start, &end);
found_selection = screen_selection_range_for_word(screen, w->mouse_pos.cell_x, &y1, &y2, &start, &end);
mode = EXTEND_WORD;
break;
case 3:
found_selection = screen_selection_range_for_line(screen, w->mouse_cell_y, &start, &end);
found_selection = screen_selection_range_for_line(screen, w->mouse_pos.cell_y, &start, &end);
mode = EXTEND_LINE;
break;
default:
@@ -323,19 +324,39 @@ multi_click(Window *w, unsigned int count) {
}
}
static inline double
distance(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - 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();
#define N(n) (q->clicks[q->length - n])
N(0).at = now; N(0).button = button; N(0).modifiers = modifiers;
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++;
double multi_click_allowed_radius = 1.2 * global_state.callback_os_window->fonts_data->cell_height;
// Now dispatch the multi-click if any
if (q->length > 2 && N(1).at - N(3).at <= 2 * OPT(click_interval)) {
multi_click(w, 3);
q->length = 0;
} else if (q->length > 1 && N(1).at - N(2).at <= OPT(click_interval)) {
multi_click(w, 2);
if (q->length > 2) {
// possible triple-click
if (
N(1).at - N(3).at <= 2 * OPT(click_interval) &&
distance(N(1).x, N(1).y, N(3).x, N(3).y) <= multi_click_allowed_radius
) {
multi_click(w, 3);
q->length = 0;
}
}
if (q->length > 1) {
// possible double-click
if (
N(1).at - N(2).at <= OPT(click_interval) &&
distance(N(1).x, N(1).y, N(2).x, N(2).y) <= multi_click_allowed_radius
) {
multi_click(w, 2);
}
}
#undef N
}
@@ -343,6 +364,7 @@ HANDLER(add_click) {
static inline void
open_url(Window *w) {
Screen *screen = w->render_data.screen;
detect_url(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y);
screen_open_url(screen);
}
@@ -549,6 +571,7 @@ scroll_event(double UNUSED xoffset, double yoffset, int flags) {
int s;
bool is_high_resolution = flags & 1;
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
}

View File

@@ -599,6 +599,13 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
} \
break;
#define NO_MODIFIERS(modifier, special, special_msg) { \
if (start_modifier || end_modifier) { \
if (special && modifier == special) { REPORT_ERROR(special_msg); } \
else { REPORT_ERROR("CSI code 0x%x has unsupported start modifier: 0x%x or end modifier: 0x%x", code, start_modifier, end_modifier);} \
break; \
}}
char start_modifier = 0, end_modifier = 0;
uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos];
unsigned int num = screen->parser_buf_pos, start, i, num_params=0, p1, p2;
@@ -641,8 +648,10 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (i > start) params[num_params++] = utoi(buf + start, i - start);
switch(code) {
case ICH:
NO_MODIFIERS(end_modifier, ' ', "Shift left escape code not implemented");
CALL_CSI_HANDLER1(screen_insert_characters, 1);
case CUU:
NO_MODIFIERS(end_modifier, ' ', "Shift right escape code not implemented");
CALL_CSI_HANDLER1(screen_cursor_up2, 1);
case CUD:
case VPR:
@@ -758,6 +767,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case DECSCUSR:
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
case SU:
NO_MODIFIERS(end_modifier, ' ', "Select presentation directions escape code not implemented");
CALL_CSI_HANDLER1(screen_scroll, 1);
case SD:
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);

View File

@@ -16,7 +16,7 @@
#endif
#ifndef init_dest_line
#define init_dest_line(dest_y) init_line(dest, dest->line, dest->line_map[dest_y]);
#define init_dest_line(dest_y) init_line(dest, dest->line, dest->line_map[dest_y]); dest->line->continued = dest->line_attrs[dest_y];
#endif
#ifndef first_dest_line

View File

@@ -107,7 +107,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->color_profile = alloc_color_profile();
self->main_linebuf = alloc_linebuf(lines, columns); self->alt_linebuf = alloc_linebuf(lines, columns);
self->linebuf = self->main_linebuf;
self->historybuf = alloc_historybuf(MAX(scrollback, lines), columns);
self->historybuf = alloc_historybuf(MAX(scrollback, lines), columns, OPT(scrollback_pager_history_size));
self->main_grman = grman_alloc();
self->alt_grman = grman_alloc();
self->grman = self->main_grman;
@@ -165,8 +165,9 @@ screen_dirty_sprite_positions(Screen *self) {
static inline HistoryBuf*
realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns) {
HistoryBuf *ans = alloc_historybuf(lines, columns);
HistoryBuf *ans = alloc_historybuf(lines, columns, 0);
if (ans == NULL) { PyErr_NoMemory(); return NULL; }
ans->pagerhist = old->pagerhist; old->pagerhist = NULL;
historybuf_rewrap(old, ans);
return ans;
}
@@ -365,6 +366,7 @@ draw_combining_char(Screen *self, char_type ch) {
CPUCell *cpu_cell = self->linebuf->line->cpu_cells + xpos;
GPUCell *gpu_cell = self->linebuf->line->gpu_cells + xpos;
if ((gpu_cell->attrs & WIDTH_MASK) != 2 && cpu_cell->cc_idx[0] == VS16 && is_emoji_presentation_base(cpu_cell->ch)) {
if (self->cursor->x <= self->columns - 1) line_set_char(self->linebuf->line, self->cursor->x, 0, 0, self->cursor, true);
gpu_cell->attrs = (gpu_cell->attrs & !WIDTH_MASK) | 2;
if (xpos == self->columns - 1) move_widened_char(self, cpu_cell, gpu_cell, xpos, ypos);
else self->cursor->x++;
@@ -974,9 +976,9 @@ screen_restore_cursor(Screen *self) {
void
screen_restore_modes(Screen *self) {
ScreenModes *m;
const ScreenModes *m;
buffer_pop(&self->modes_savepoints, m);
if (m == NULL) *m = empty_modes;
if (m == NULL) m = &empty_modes;
#define S(name) set_mode_from_const(self, name, m->m##name)
S(DECTCEM); S(DECSCNM); S(DECSCNM); S(DECOM); S(DECAWM); S(DECARM); S(DECCKM);
S(BRACKETED_PASTE); S(FOCUS_TRACKING); S(EXTENDED_KEYBOARD);
@@ -1126,6 +1128,15 @@ screen_insert_lines(Screen *self, unsigned int count) {
}
}
void
screen_scroll_until_cursor(Screen *self) {
unsigned int num_lines_to_scroll = MIN(self->margin_bottom, self->cursor->y + 1);
index_type y = self->cursor->y;
self->cursor->y = self->margin_bottom;
while (num_lines_to_scroll--) screen_index(self);
self->cursor->y = y;
}
void
screen_delete_lines(Screen *self, unsigned int count) {
unsigned int top = self->margin_top, bottom = self->margin_bottom;
@@ -1405,7 +1416,6 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
static char buf[128];
int shape = 0;
const char *query;
Cursor blank_cursor = {{0}};
switch(c) {
case '+':
CALLBACK("request_capabilities", "O", q);
@@ -1429,7 +1439,7 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
shape = snprintf(buf, sizeof(buf), "1$r%d q", shape);
} else if (strcmp("m", query) == 0) {
// SGR
shape = snprintf(buf, sizeof(buf), "1$r%sm", cursor_as_sgr(self->cursor, &blank_cursor));
shape = snprintf(buf, sizeof(buf), "1$r%sm", cursor_as_sgr(self->cursor));
} else if (strcmp("r", query) == 0) {
shape = snprintf(buf, sizeof(buf), "1$r%u;%ur", self->margin_top + 1, self->margin_bottom + 1);
} else {
@@ -1863,6 +1873,7 @@ is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) {
WRAP1E(cursor_back, 1, -1)
WRAP1B(erase_in_line, 0)
WRAP1B(erase_in_display, 0)
WRAP0(scroll_until_cursor)
#define MODE_GETSET(name, uname) \
static PyObject* name##_get(Screen *self, void UNUSED *closure) { PyObject *ans = self->modes.m##uname ? Py_True : Py_False; Py_INCREF(ans); return ans; } \
@@ -2148,7 +2159,7 @@ COUNT_WRAP(cursor_forward)
static PyObject*
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromUnsignedLong(wcwidth_std(PyLong_AsLong(chr)));
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
}
@@ -2169,6 +2180,7 @@ static PyMethodDef methods[] = {
MND(cursor_back, METH_VARARGS)
MND(erase_in_line, METH_VARARGS)
MND(erase_in_display, METH_VARARGS)
MND(scroll_until_cursor, METH_NOARGS)
METHOD(current_char_width, METH_NOARGS)
MND(insert_lines, METH_VARARGS)
MND(delete_lines, METH_VARARGS)

View File

@@ -437,11 +437,6 @@ blank_os_window(OSWindow *os_window) {
bool
send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) {
bool changed = false;
if (os_window->clear_count < 2) {
os_window->clear_count++;
blank_os_window(os_window);
changed = true;
}
if (os_window->fonts_data) {
if (cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true;
}

View File

@@ -249,7 +249,7 @@ add_borders_rect(id_type os_window_id, id_type tab_id, uint32_t left, uint32_t t
void
os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
if (os_window->num_tabs > 1) {
if (!global_state.tab_bar_hidden && os_window->num_tabs > 1) {
switch(OPT(tab_bar_edge)) {
case TOP_EDGE:
central->left = 0; central->top = os_window->fonts_data->cell_height; central->right = os_window->viewport_width - 1;
@@ -354,9 +354,10 @@ PYWRAP1(set_options) {
global_state.debug_gl = debug_gl ? true : false;
global_state.debug_font_fallback = debug_font_fallback ? true : false;
#define GA(name) ret = PyObject_GetAttrString(opts, #name); if (ret == NULL) return NULL;
#define S(name, convert) { GA(name); global_state.opts.name = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
GA(kitty_mod);
kitty_mod = PyLong_AsLong(ret); Py_CLEAR(ret); if (PyErr_Occurred()) return NULL;
#define SS(name, dest, convert) { GA(name); dest = convert(ret); Py_DECREF(ret); if (PyErr_Occurred()) return NULL; }
#define S(name, convert) SS(name, global_state.opts.name, convert)
SS(kitty_mod, kitty_mod, PyLong_AsLong);
S(hide_window_decorations, PyObject_IsTrue);
S(visual_bell_duration, PyFloat_AsDouble);
S(enable_audio_bell, PyObject_IsTrue);
S(focus_follows_mouse, PyObject_IsTrue);
@@ -367,11 +368,13 @@ PYWRAP1(set_options) {
S(dynamic_background_opacity, PyObject_IsTrue);
S(inactive_text_alpha, PyFloat_AsDouble);
S(window_padding_width, PyFloat_AsDouble);
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(wheel_scroll_multiplier, PyFloat_AsDouble);
S(touch_scroll_multiplier, PyFloat_AsDouble);
S(open_url_modifiers, convert_mods);
S(rectangle_select_modifiers, convert_mods);
S(click_interval, PyFloat_AsDouble);
@@ -387,13 +390,16 @@ PYWRAP1(set_options) {
S(window_alert_on_bell, PyObject_IsTrue);
S(macos_option_as_alt, PyObject_IsTrue);
S(macos_traditional_fullscreen, PyObject_IsTrue);
S(macos_hide_titlebar, PyObject_IsTrue);
S(macos_quit_when_last_window_closed, PyObject_IsTrue);
S(macos_window_resizable, PyObject_IsTrue);
S(x11_hide_window_decorations, PyObject_IsTrue);
S(macos_hide_from_tasks, PyObject_IsTrue);
S(macos_thicken_font, PyFloat_AsDouble);
GA(tab_bar_style);
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
Py_CLEAR(ret);
if (PyErr_Occurred()) return NULL;
PyObject *chars = PyObject_GetAttrString(opts, "select_by_word_characters");
if (chars == NULL) return NULL;
for (size_t i = 0; i < MIN((size_t)PyUnicode_GET_LENGTH(chars), sizeof(OPT(select_by_word_characters))/sizeof(OPT(select_by_word_characters[0]))); i++) {
@@ -422,6 +428,7 @@ PYWRAP1(set_options) {
read_adjust(adjust_column_width);
#undef read_adjust
#undef S
#undef SS
Py_RETURN_NONE;
}

View File

@@ -13,17 +13,18 @@
typedef enum { LEFT_EDGE, TOP_EDGE, RIGHT_EDGE, BOTTOM_EDGE } Edge;
typedef struct {
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier;
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier, touch_scroll_multiplier;
bool enable_audio_bell;
CursorShape cursor_shape;
unsigned int open_url_modifiers;
unsigned int rectangle_select_modifiers;
unsigned int url_style;
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, active_border_color, inactive_border_color, bell_border_color;
double repaint_delay, input_delay;
bool focus_follows_mouse;
bool macos_option_as_alt, macos_hide_titlebar, macos_hide_from_tasks, x11_hide_window_decorations, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
bool focus_follows_mouse, hide_window_decorations;
bool macos_option_as_alt, macos_hide_from_tasks, macos_quit_when_last_window_closed, macos_window_resizable, macos_traditional_fullscreen;
float macos_thicken_font;
int adjust_line_height_px, adjust_column_width_px;
float adjust_line_height_frac, adjust_column_width_frac;
@@ -51,6 +52,7 @@ typedef struct {
typedef struct {
double at;
int button, modifiers;
double x, y;
} Click;
#define CLICK_QUEUE_SZ 3
@@ -66,7 +68,10 @@ typedef struct {
CursorShape last_cursor_shape;
PyObject *title;
ScreenRenderData render_data;
unsigned int mouse_cell_x, mouse_cell_y;
struct {
unsigned int cell_x, cell_y;
double x, y;
} mouse_pos;
WindowGeometry geometry;
ClickQueue click_queue;
double last_drag_scroll_at;
@@ -97,6 +102,7 @@ typedef struct {
bool is_set;
} OSWindowGeometry;
enum WAYLAND_RENDER_STATE { RENDER_FRAME_NOT_REQUESTED, RENDER_FRAME_REQUESTED, RENDER_FRAME_READY };
typedef struct {
void *handle;
@@ -126,6 +132,7 @@ typedef struct {
FONTS_DATA_HANDLE fonts_data;
id_type temp_font_group_id;
double pending_scroll_pixels;
enum WAYLAND_RENDER_STATE wayland_render_state;
} OSWindow;
@@ -142,6 +149,7 @@ typedef struct {
bool debug_gl, debug_font_fallback;
bool has_pending_resizes;
bool in_sequence_mode;
bool tab_bar_hidden;
double font_sz_in_pts;
struct { double x, y; } default_dpi;
id_type active_drag_in_window;
@@ -171,6 +179,7 @@ void event_loop_wait(double timeout);
void swap_window_buffers(OSWindow *w);
void make_window_context_current(OSWindow *w);
void hide_mouse(OSWindow *w);
bool is_mouse_hidden(OSWindow *w);
void destroy_os_window(OSWindow *w);
void focus_os_window(OSWindow *w, bool also_raise);
void set_os_window_title(OSWindow *w, const char *title);
@@ -201,3 +210,4 @@ typedef enum {
void set_cocoa_pending_action(CocoaPendingAction action);
bool application_quit_requested();
#endif
void wayland_request_frame_render(OSWindow *w);

View File

@@ -27,6 +27,16 @@ def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None,
return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd, overlay_for, env)
def add_active_id_to_history(items, item_id, maxlen=64):
try:
items.remove(item_id)
except ValueError:
pass
items.append(item_id)
if len(items) > maxlen:
items.popleft()
class Tab: # {{{
def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None):
@@ -34,6 +44,7 @@ class Tab: # {{{
self.tab_manager_ref = weakref.ref(tab_manager)
self.os_window_id = tab_manager.os_window_id
self.id = add_tab(self.os_window_id)
self.active_window_history = deque()
if not self.id:
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
self.opts, self.args = tab_manager.opts, tab_manager.args
@@ -84,6 +95,9 @@ class Tab: # {{{
old_active_window = self.windows[self._active_window_idx]
except Exception:
old_active_window = None
else:
wid = old_active_window.id if old_active_window.overlay_for is None else old_active_window.overlay_for
add_active_id_to_history(self.active_window_history, wid)
self._active_window_idx = max(0, min(val, len(self.windows) - 1))
try:
new_active_window = self.windows[self._active_window_idx]
@@ -255,7 +269,19 @@ class Tab: # {{{
def nth_window(self, num=0):
if self.windows:
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
if num < 0:
try:
old_window_id = self.active_window_history[num]
except IndexError:
return
for idx, w in enumerate(self.windows):
if w.id == old_window_id:
self.active_window_idx = self.current_layout.set_active_window(self.windows, idx)
break
else:
return
else:
self.active_window_idx = self.current_layout.nth_window(self.windows, num)
self.relayout_borders()
glfw_post_empty_event()
@@ -330,7 +356,9 @@ class TabManager: # {{{
def __init__(self, os_window_id, opts, args, startup_session):
self.os_window_id = os_window_id
self.last_active_tab_id = None
self.opts, self.args = opts, args
self.tab_bar_hidden = self.opts.tab_bar_style == 'hidden'
self.tabs = []
self.active_tab_history = deque()
self.tab_bar = TabBar(self.os_window_id, opts)
@@ -348,11 +376,10 @@ class TabManager: # {{{
def active_tab_idx(self, val):
try:
old_active_tab = self.tabs[self._active_tab_idx]
self.active_tab_history.append(old_active_tab.id)
if len(self.active_tab_history) > 64:
self.active_tab_history.popleft()
except Exception:
old_active_tab = None
else:
add_active_id_to_history(self.active_tab_history, old_active_tab.id)
self._active_tab_idx = max(0, min(val, len(self.tabs) - 1))
try:
new_active_tab = self.tabs[self._active_tab_idx]
@@ -369,7 +396,8 @@ class TabManager: # {{{
w.focus_changed(True)
def refresh_sprite_positions(self):
self.tab_bar.screen.refresh_sprite_positions()
if not self.tab_bar_hidden:
self.tab_bar.screen.refresh_sprite_positions()
def _add_tab(self, tab):
before = len(self.tabs)
@@ -389,12 +417,13 @@ class TabManager: # {{{
set_active_tab(self.os_window_id, idx)
def tabbar_visibility_changed(self):
self.tab_bar.layout()
self.resize(only_tabs=True)
glfw_post_empty_event()
if not self.tab_bar_hidden:
self.tab_bar.layout()
self.resize(only_tabs=True)
glfw_post_empty_event()
def mark_tab_bar_dirty(self):
if len(self.tabs) > 1:
if len(self.tabs) > 1 and not self.tab_bar_hidden:
mark_tab_bar_dirty(self.os_window_id)
def update_tab_bar_data(self):
@@ -402,8 +431,9 @@ class TabManager: # {{{
def resize(self, only_tabs=False):
if not only_tabs:
self.tab_bar.layout()
self.mark_tab_bar_dirty()
if not self.tab_bar_hidden:
self.tab_bar.layout()
self.mark_tab_bar_dirty()
for tab in self.tabs:
tab.relayout()
@@ -426,6 +456,15 @@ class TabManager: # {{{
def goto_tab(self, tab_num):
if tab_num < len(self.tabs) and 0 <= tab_num:
self.set_active_tab_idx(tab_num)
elif tab_num < 0:
try:
old_active_tab_id = self.active_tab_history[tab_num]
except IndexError:
return
for idx, tab in enumerate(self.tabs):
if tab.id == old_active_tab_id:
self.set_active_tab_idx(idx)
break
def __iter__(self):
return iter(self.tabs)
@@ -439,6 +478,7 @@ class TabManager: # {{{
'id': tab.id,
'is_focused': tab is active_tab,
'title': tab.name or tab.title,
'layout': tab.current_layout.name,
'windows': list(tab.list_windows(active_window)),
}

View File

@@ -37,6 +37,10 @@ bool_capabilities = {
# described at:
# https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc
'Su',
# Indicates support for full keyboard mode (non-standard) as
# described at:
# https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc
'fullkbd',
# The following are entries that we don't use
# # background color erase
@@ -223,8 +227,8 @@ string_capabilities = {
'rmso': r'\E[27m',
# Exit underline mode
'rmul': r'\E[24m',
# Reset string1
'rs1': r'\Ec',
# Reset string1 (empty OSC sequence to exit OSC/OTH modes, and regular reset)
'rs1': r'\E]\E\\\Ec',
# Save cursor
'sc': r'\E7',
# Set background color
@@ -284,10 +288,6 @@ string_capabilities = {
'setrgbb': r'\E[48:2:%p1%d:%p2%d:%p3%dm',
# The following are entries that we don't use
# # display status line
# 'dsl': r'\E]2;\007',
# # return from status line
# 'fsl': r'^G',
# # turn on blank mode, (characters invisible)
# 'invis': r'\E[8m',
# # init2 string
@@ -436,7 +436,7 @@ termcap_aliases.update({
queryable_capabilities = numeric_capabilities.copy()
queryable_capabilities.update(string_capabilities)
extra = (bool_capabilities | numeric_capabilities.keys() | string_capabilities.keys()) - set(termcap_aliases.values())
no_termcap_for = frozenset('Su Tc setrgbf setrgbb'.split())
no_termcap_for = frozenset('Su Tc setrgbf setrgbb fullkbd'.split())
if extra - no_termcap_for:
raise Exception('Termcap aliases not complete, missing: {}'.format(extra - no_termcap_for))
del extra

View File

@@ -61,6 +61,9 @@ def color_from_int(val):
def parse_color_set(raw):
parts = raw.split(';')
lp = len(parts)
if lp % 2 != 0:
return
for c, spec in [parts[i:i + 2] for i in range(0, len(parts), 2)]:
try:
c = int(c)
@@ -419,7 +422,7 @@ def get_editor():
import shlex
ans = os.environ.get('EDITOR')
if not ans or not exe_exists(shlex.split(ans)[0]):
for q in ('vim', 'vi', 'emacs', 'micro', 'nano'):
for q in ('vim', 'nvim', 'vi', 'emacs', 'micro', 'nano', 'vis'):
r = exe_exists(q)
if r:
ans = r

View File

@@ -8,6 +8,7 @@ import sys
import weakref
from collections import deque
from enum import IntEnum
from itertools import chain
from .child import cwd_of_process
from .config import build_ansi_color_table
@@ -98,6 +99,24 @@ def setup_colors(screen, opts):
opts.foreground, opts.background, opts.cursor, opts.selection_foreground, opts.selection_background)))
def text_sanitizer(as_ansi, add_wrap_markers):
import re
pat = re.compile(r'\033\[.+?m')
def remove_wrap_markers(line):
return line.replace('\r', '')
def remove_sgr(line):
return pat.sub('', line)
def remove_both(line):
return pat.sub('', line.replace('\r', ''))
if as_ansi:
return remove_both if add_wrap_markers else remove_sgr
return remove_wrap_markers
class Window:
def __init__(self, tab, child, opts, args, override_title=None):
@@ -248,7 +267,7 @@ class Window:
# screen callbacks {{{
def use_utf8(self, on):
get_boss().child_monitor.set_iutf8(self.window_id, on)
get_boss().child_monitor.set_iutf8(self.id, on)
def focus_changed(self, focused):
if focused:
@@ -376,6 +395,8 @@ class Window:
def clipboard_control(self, data):
where, text = data.partition(';')[::2]
if not where:
where = 's0'
if text == '?':
response = None
if 's' in where or 'c' in where:
@@ -438,7 +459,7 @@ class Window:
def as_text(self, as_ansi=False, add_history=False, add_wrap_markers=False, alternate_screen=False):
lines = []
add_history = add_history and not self.screen.is_using_alternate_linebuf() and not alternate_screen
add_history = add_history and not (self.screen.is_using_alternate_linebuf() ^ alternate_screen)
if alternate_screen:
f = self.screen.as_text_alternate
else:
@@ -446,8 +467,12 @@ class Window:
f(lines.append, as_ansi, add_wrap_markers)
if add_history:
h = []
self.screen.historybuf.pagerhist_as_text(h.append)
if not as_ansi or not add_wrap_markers:
sanitizer = text_sanitizer(as_ansi, add_wrap_markers)
h = list(map(sanitizer, h))
self.screen.historybuf.as_text(h.append, as_ansi, add_wrap_markers)
lines = h + lines
lines = chain(h, lines)
return ''.join(lines)
@property
@@ -458,15 +483,26 @@ class Window:
if pid is not None:
return cwd_of_process(pid) or None
def pipe_data(self, text, has_wrap_markers=False):
text = text or ''
if has_wrap_markers:
text = text.replace('\r\n', '\n').replace('\r', '\n')
lines = text.count('\n')
input_line_number = (lines - (self.screen.lines - 1) - self.screen.scrolled_by)
return {
'input_line_number': input_line_number, 'scrolled_by': self.screen.scrolled_by,
'cursor_x': self.screen.cursor.x + 1, 'cursor_y': self.screen.cursor.y + 1,
'lines': self.screen.lines, 'columns': self.screen.columns,
'text': text
}
# actions {{{
def show_scrollback(self):
data = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
data = data.replace('\r\n', '\n').replace('\r', '\n')
lines = data.count('\n')
input_line_number = (lines - (self.screen.lines - 1) - self.screen.scrolled_by)
cmd = [x.replace('INPUT_LINE_NUMBER', str(input_line_number)) for x in self.opts.scrollback_pager]
get_boss().display_scrollback(self, data, cmd)
text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
data = self.pipe_data(text, has_wrap_markers=True)
cmd = [x.replace('INPUT_LINE_NUMBER', str(data['input_line_number'])) for x in self.opts.scrollback_pager]
get_boss().display_scrollback(self, data['text'], cmd)
def paste(self, text):
if text and not self.destroyed:

View File

@@ -4,6 +4,8 @@
from unittest import TestCase
from kitty.config import Options, defaults, merge_configs
from kitty.fast_data_types import set_options
from kitty.fast_data_types import LineBuf, Cursor, Screen, HistoryBuf
@@ -72,7 +74,11 @@ class BaseTest(TestCase):
ae = TestCase.assertEqual
maxDiff = 2000
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20):
def create_screen(self, cols=5, lines=5, scrollback=5, cell_width=10, cell_height=20, options=None):
if options is None:
options = {'scrollback_pager_history_size': 1024}
options = Options(merge_configs(defaults._asdict(), options))
set_options(options)
c = Callbacks()
return Screen(c, lines, cols, scrollback, cell_width, cell_height, 0, c)

64
kitty_tests/bench_scrollback.py Executable file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
from time import clock_gettime, CLOCK_MONOTONIC, sleep
from argparse import ArgumentParser
from random import Random
from string import printable
import sys
def main():
parser = ArgumentParser(description='Generate text')
parser.add_argument('--freq', default=10000, type=int, help='Number of lines to try to write per second. Will warn if not attained.')
parser.add_argument('--color', action='store_true', help='Add color to the output')
parser.add_argument('--unicode', action='store_true', help='Mix in some unicode characters')
parser.add_argument('--length', default=50, type=int, help='Average line length')
parser.add_argument('--lengthvar', default=0.3, type=float, help='Variation for line length, in ratio of line length')
parser.add_argument('--emptylines', default=0.1, type=float, help='ratio of empty lines')
parser.add_argument('--linesperwrite', default=1, type=int, help='number of lines to repeat/write at a time')
parser.add_argument('--patterns', default=1000, type=int, help='number of different pattern to alternate')
parser.add_argument('--seed', default=sys.argv[0], type=str, help='seed to get different output')
args = parser.parse_args()
rng = Random()
rng.seed(args.seed)
characters = [c for c in printable if c not in '\r\n\x0b\x0c']
if args.color:
characters += ['\x1b[91m', '\x1b[0m', '\x1b[1;32m', '\x1b[22m', '\x1b[35m']
if args.unicode:
characters += [u'', u'', u'💜', u'', u'🎩', u'🍀', u'']
patterns = []
for _ in range(0, args.patterns):
s = ""
for _ in range(0, args.linesperwrite):
cnt = int(rng.gauss(args.length, args.length * args.lengthvar))
if cnt < 0 or rng.random() < args.emptylines:
cnt = 0
s += "".join(rng.choices(characters, k=cnt)) + '\n'
patterns += [s]
time_per_print = args.linesperwrite / args.freq
t1 = clock_gettime(CLOCK_MONOTONIC)
cnt = 0
while True:
sys.stdout.write(patterns[rng.randrange(0, args.patterns)])
sys.stdout.flush()
cnt += 1
t2 = clock_gettime(CLOCK_MONOTONIC)
if t2 - t1 < cnt * time_per_print:
sleep(cnt * time_per_print - (t2 - t1))
t1 = t2
cnt = 0
elif cnt >= 100:
print("Cannot print fast enough, printed %d lines in %f seconds instead of %f seconds target" %
(cnt * args.linesperwrite, t2 - t1, cnt * time_per_print))
break
else:
cnt += 1
if __name__ == '__main__':
main()

View File

@@ -242,6 +242,9 @@ class TestDataTypes(BaseTest):
self.ae(l0.url_end_at(0), len(l0) - 1)
l2 = create("http://-abcd] ")
self.ae(l2.url_end_at(0), len(l2) - 3)
l3 = create("http://ab.de ")
self.ae(l3.url_start_at(4), 0)
self.ae(l3.url_start_at(5), 0)
def lspace_test(n, scheme='http'):
lf = create(' ' * n + scheme + '://acme.com')
@@ -447,10 +450,10 @@ class TestDataTypes(BaseTest):
def test_ansi_repr(self):
lb = filled_line_buf()
l0 = lb.line(0)
self.ae(l0.as_ansi(), '\x1b[0m00000')
self.ae(l0.as_ansi(), '00000')
a = []
lb.as_ansi(a.append)
self.ae(a, ['\x1b[0m' + str(lb.line(i)) + '\n' for i in range(lb.ynum)])
self.ae(a, [str(lb.line(i)) + '\n' for i in range(lb.ynum)])
l2 = lb.line(0)
c = C()
c.bold = c.italic = c.reverse = c.strikethrough = c.dim = True
@@ -458,15 +461,15 @@ class TestDataTypes(BaseTest):
c.bg = (1 << 24) | (2 << 16) | (3 << 8) | 2
c.decoration_fg = (5 << 8) | 1
l2.set_text('1', 0, 1, c)
self.ae(l2.as_ansi(), '\x1b[0m\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1'
self.ae(l2.as_ansi(), '\x1b[1;2;3;7;9;34;48:2:1:2:3;58:5:5m' '1'
'\x1b[22;23;27;29;39;49;59m' '0000')
lb = filled_line_buf()
for i in range(lb.ynum):
lb.set_continued(i, True)
a = []
lb.as_ansi(a.append)
self.ae(a, ['\x1b[0m' + str(lb.line(i)) for i in range(lb.ynum)])
self.ae(a, [str(lb.line(i)) for i in range(lb.ynum)])
hb = filled_history_buf(5, 5)
a = []
hb.as_ansi(a.append)
self.ae(a, ['\x1b[0m' + str(hb.line(i)) + '\n' for i in range(hb.count - 1, -1, -1)])
self.ae(a, [str(hb.line(i)) + '\n' for i in range(hb.count - 1, -1, -1)])

View File

@@ -98,7 +98,7 @@ class TestParser(BaseTest):
pb('x\033[2;7@y', 'x', ('screen_insert_characters', 2), 'y')
pb('x\033[@y', 'x', ('screen_insert_characters', 1), 'y')
pb('x\033[345@y', 'x', ('screen_insert_characters', 345), 'y')
pb('x\033[345;@y', 'x', ('screen_insert_characters', 345), 'y')
pb('x\033[345;@y', 'x', ('CSI code 0x40 has unsupported start modifier: 0x0 or end modifier: 0x3b',), 'y')
pb('\033[H', ('screen_cursor_position', 1, 1))
self.ae(s.cursor.x, 0), self.ae(s.cursor.y, 0)
pb('\033[4H', ('screen_cursor_position', 4, 1))
@@ -171,6 +171,11 @@ class TestParser(BaseTest):
self.assertTrue(s.cursor.blink)
self.ae(s.cursor.shape, CURSOR_BLOCK)
s.reset()
pb('\033[3 @', ('Shift left escape code not implemented',))
pb('\033[3 A', ('Shift right escape code not implemented',))
pb('\033[3;4 S', ('Select presentation directions escape code not implemented',))
def test_osc_codes(self):
s = self.create_screen()
pb = partial(self.parse_bytes_dump, s)

View File

@@ -441,4 +441,4 @@ class TestScreen(BaseTest):
return ''.join(d)
self.ae(as_text(), 'ababababab\nc\n\n')
self.ae(as_text(True), '\x1b[0mababa\x1b[0mbabab\n\x1b[0mc\n\n')
self.ae(as_text(True), 'ababababab\nc\n\n')

View File

@@ -20,3 +20,4 @@ class TestUnicodeInput(BaseTest):
self.ae(matches('horiz', 'ell'), {0x2026, 0x22ef, 0x2b2c, 0x2b2d, 0xfe19})
self.ae(matches('horizontal', 'ell'), {0x2026, 0x22ef, 0x2b2c, 0x2b2d, 0xfe19})
self.assertFalse(matches('sfgsfgsfgfgsdg'))
self.assertIn(0x1f41d, matches('bee'))

View File

@@ -13,7 +13,6 @@ import shutil
import subprocess
import sys
import sysconfig
import tempfile
import time
base = os.path.dirname(os.path.abspath(__file__))
@@ -141,11 +140,13 @@ def get_sanitize_args(cc, ccver):
def test_compile(cc, *cflags, src=None):
with tempfile.NamedTemporaryFile(suffix='.c') as f:
src = src or 'int main(void) { return 0; }'
f.write(src.encode('utf-8'))
f.flush()
return subprocess.Popen([cc] + list(cflags) + [f.name, '-o', os.devnull]).wait() == 0
src = src or 'int main(void) { return 0; }'
p = subprocess.Popen([cc] + list(cflags) + ['-x', 'c', '-o', os.devnull, '-'], stdin=subprocess.PIPE)
try:
p.stdin.write(src.encode('utf-8')), p.stdin.close()
except BrokenPipeError:
return False
return p.wait() == 0
def first_successful_compile(cc, *cflags, src=None):
@@ -423,7 +424,7 @@ def compile_glfw(incremental, compilation_database, all_keys):
modules = 'cocoa' if is_macos else 'x11 wayland'
for module in modules.split():
try:
genv = glfw.init_env(env, pkg_config, at_least_version, module)
genv = glfw.init_env(env, pkg_config, at_least_version, test_compile, module)
except SystemExit as err:
if module != 'wayland':
raise
@@ -575,6 +576,7 @@ make && make docs
def compile_python(base_path):
import compileall
import py_compile
try:
from multiprocessing import cpu_count
num_workers = max(1, cpu_count())
@@ -584,7 +586,10 @@ def compile_python(base_path):
for f in files:
if f.rpartition('.')[-1] in ('pyc', 'pyo'):
os.remove(os.path.join(root, f))
compileall.compile_dir(base_path, ddir='', force=True, optimize=1, quiet=1, workers=num_workers)
kwargs = dict(ddir='', force=True, optimize=1, quiet=1, workers=num_workers)
if hasattr(py_compile, 'PycInvalidationMode'):
kwargs['invalidation_mode'] = py_compile.PycInvalidationMode.UNCHECKED_HASH
compileall.compile_dir(base_path, **kwargs)
def package(args, for_bundle=False, sh_launcher=False):
@@ -598,7 +603,11 @@ def package(args, for_bundle=False, sh_launcher=False):
for x in (libdir, os.path.join(ddir, 'share')):
odir = os.path.join(x, 'terminfo')
safe_makedirs(odir)
subprocess.check_call(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'])
proc = subprocess.run(['tic', '-x', '-o' + odir, 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
regex = '^"terminfo/kitty.terminfo", line [0-9]+, col [0-9]+, terminal \'xterm-kitty\': older tic versions may treat the description field as an alias$'
for error in proc.stderr.decode('utf-8').splitlines():
if not re.match(regex, error):
print(error, file=sys.stderr)
if not glob.glob(os.path.join(odir, '*/xterm-kitty')):
raise SystemExit('tic failed to output the compiled kitty terminfo file')
shutil.copy2('__main__.py', libdir)

Binary file not shown.

View File

@@ -3,6 +3,7 @@ xterm-kitty|KovIdTTY,
Tc,
am,
ccc,
fullkbd,
hs,
km,
mc5i,
@@ -120,7 +121,7 @@ xterm-kitty|KovIdTTY,
rmkx=\E[?1l,
rmso=\E[27m,
rmul=\E[24m,
rs1=\Ec,
rs1=\E]\E\\\Ec,
sc=\E7,
setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,

Binary file not shown.