Compare commits

...

284 Commits

Author SHA1 Message Date
Kovid Goyal
4cb99be8e6 version 0.8.0 2018-02-24 11:11:56 +05:30
Kovid Goyal
8cf9fd3cdb ... 2018-02-23 09:38:07 +05:30
Kovid Goyal
b22950bb0a More work on README 2018-02-23 09:36:46 +05:30
Kovid Goyal
f87d62571b Add links to relevant parts of the README in the major features list 2018-02-23 09:13:20 +05:30
Kovid Goyal
42afc56b65 Update CHANGELOG with entry for macos_titlebar_color
Fixes #341
2018-02-22 21:21:33 +05:30
Splinter Suidman
10c3c7c41f Implement setting title bar color. 2018-02-22 21:19:55 +05:30
Kovid Goyal
e236539e3a Option for changing macOS titlebar color
Note that the actual implementation is still needed, this is just the
option definition/parsing code.
2018-02-22 15:07:24 +05:30
Kovid Goyal
1fc605cbc0 Move needs_special_handling definition into keys.c 2018-02-22 14:37:51 +05:30
Kovid Goyal
1e4963e727 Revert change to make alt+bs emit werase
Most linux terminals emit ESC+BS instead. And iTerm2 in Esc+ mode also
emits this. So go with the majority for greatest ecosystem
compatibility. Fixes #264
2018-02-22 13:51:14 +05:30
Kovid Goyal
95384d437a Use the nice array based interface for fcntl.ioctl 2018-02-20 17:01:14 +05:30
Kovid Goyal
e44910212c Sample code to use TIOCGWINSZ in python 2018-02-20 16:47:06 +05:30
Kovid Goyal
36b8802f43 Add a link to the graphics protocol page in the README 2018-02-20 16:32:57 +05:30
Kovid Goyal
a6b0554edc Merge branch 'master' of https://github.com/Polynomial-C/kitty 2018-02-20 15:42:23 +05:30
Lars Wendler
c1df5eac57 Added Gentoo to the list of Linux packagers. 2018-02-20 11:09:43 +01:00
Kovid Goyal
68bf1be8d0 render on cursor shape change as well 2018-02-20 11:54:35 +05:30
Kovid Goyal
d5edea3c7f render on cursor position change as well 2018-02-20 11:27:39 +05:30
Kovid Goyal
eb580f5ead Update CHANGELOG for padding color 2018-02-20 10:10:28 +05:30
Kovid Goyal
c99588082b Fix incorrect bottom blank rects in vertical and tall layouts 2018-02-20 10:06:37 +05:30
Kovid Goyal
96d241e9f3 Fix enabled_layouts in session files not being respected 2018-02-20 10:01:41 +05:30
Kovid Goyal
8f423c6d8f Fix padding color not tracking active window bg color 2018-02-20 09:52:19 +05:30
Kovid Goyal
f25ecedcab Always set the padding color to current window background 2018-02-20 09:43:59 +05:30
Kovid Goyal
2913c1aedc Change the color of the padding and margins to match the current background color of the window, when only a single window is visible.
Fixes #323
2018-02-20 09:43:49 +05:30
Kovid Goyal
5a15f7e8ad Sync to monitor refreshes by default
Prevents tearing while scrolling. See #318

Reduce draw calls while processing mouse move events to make latency
while selecting with the mouse acceptable. Also add a config option to
turn off monitor syncing, so people with keyboard repeat rates higher
than their monitor's refresh rate can use it.
2018-02-20 09:38:00 +05:30
Kovid Goyal
e6cb64baa5 Revert "Enable syncing of redraws to monitor refresh rate"
This reverts commit 6a51096304.
Turns out that dynamically changing the swap interval causes bad flicker
when rendering on X11 in a fullscreen window (or more generally, when
only a single X11 window is visible).
2018-02-20 08:32:33 +05:30
Kovid Goyal
537023ef5a Fix simple_render_screen() broken by multiple OSwindow draw calls optimization 2018-02-20 05:52:32 +05:30
Kovid Goyal
70125e0de5 Only build asan-launcher if --sanitize is given
Fixes #328
2018-02-16 08:56:30 +05:30
Kovid Goyal
8c5646b81b Merge branch 'build-package-fix' of https://github.com/13k/kitty 2018-02-16 08:22:30 +05:30
Kiyoshi '13k' Murata
bd741365cb Fix packaging trying to modify unrelated files
`package()` method in `setup.py` tries to compile and chmod all files
under the directory given by `--prefix PREFIX`.

If PREFIX is a directory containing other distributions (like
`/usr/local` or `$HOME/.local`), then it would try to compile - using
possibly the wrong python version - and chmod all files within PREFIX.
2018-02-15 19:46:51 -02:00
Kovid Goyal
efd9843920 Update CHANGELOG 2018-02-15 17:42:32 +05:30
Kovid Goyal
0bb8ec61e5 Fix regression caused by overlay window support code
Closing a window would not update the window layout, depending on the
position of the window in the current layout.
2018-02-15 09:16:00 +05:30
Kovid Goyal
6a51096304 Enable syncing of redraws to monitor refresh rate
Now, when large changes, such as scrolling occur, the OpenGL back buffer
swapping is synchronized to the monitor refresh rate (if supported by
the OS). This eliminates tearing artifacts when scrolling the screen fast.
Fixes #318
2018-02-14 20:59:58 +05:30
Kovid Goyal
807c8f9005 Do not send history buf in alternate screen mode 2018-02-14 20:46:12 +05:30
Kovid Goyal
9c21610215 Use a single generic function to get text and ANSI representations of all buffers 2018-02-14 20:38:53 +05:30
Kovid Goyal
97b4256ba4 Remove unused code 2018-02-14 18:50:52 +05:30
Kovid Goyal
e4b643fdaf Show a nice message when no URLs are found 2018-02-14 18:36:57 +05:30
Kovid Goyal
0bda7b3679 When running kittens respect current screen scroll 2018-02-14 18:35:32 +05:30
Kovid Goyal
51e3e9997c Allow clicking URLs via keybaord hints by pressing ctrl+shift+e 2018-02-14 17:18:57 +05:30
Kovid Goyal
9f52d427e5 http->https in readme 2018-02-14 17:08:09 +05:30
Kovid Goyal
ad706f9924 Make the list of URL prefixes configurable 2018-02-14 12:48:44 +05:30
Kovid Goyal
57cf789c75 Finish up the URL hints kitten 2018-02-14 12:34:01 +05:30
Kovid Goyal
62174fdbcc DRYer 2018-02-14 11:19:58 +05:30
Kovid Goyal
dff7ddea39 Make the default intense black a little lighter
Brings it closer to the color definitions at:
https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
2018-02-14 11:17:07 +05:30
Kovid Goyal
0c3257b6b9 Start work on url hints kitten 2018-02-14 11:01:17 +05:30
Kovid Goyal
4bbf5c0bc9 Allow choosing a match by index in the search by name panel as well 2018-02-13 11:25:45 +05:30
Kovid Goyal
b0820f6e13 Use . instead of r for the index char 2018-02-13 11:10:35 +05:30
Kovid Goyal
b0f5d8dfdc Fix table row generation for non-name unicode input panels 2018-02-13 10:50:06 +05:30
Kovid Goyal
67e0d3723a Implement favorites for unicode input 2018-02-13 10:33:55 +05:30
Kovid Goyal
a36f4a4670 Add a Emoji panel to the unicode input kitten 2018-02-13 09:06:39 +05:30
Kovid Goyal
dab852c412 Implement searching for unicode characters by name 2018-02-12 14:08:54 +05:30
Kovid Goyal
193ac6cd81 Forgot to remove from recent when adding to top of list 2018-02-12 12:39:02 +05:30
Kovid Goyal
34ca8419bb Finish chose unicode symbol by recently used 2018-02-12 10:19:29 +05:30
Kovid Goyal
6094aabe26 ... 2018-02-12 10:19:29 +05:30
Kovid Goyal
19648257f2 Have wcwidth() ignore SGR 2018-02-12 10:19:29 +05:30
Kovid Goyal
3363a9459b Implement drawing of titlebar, remembering of mode and fix various bugs in tui 2018-02-12 10:19:29 +05:30
Kovid Goyal
4d92f4871a Mark names.h as being generated 2018-02-12 10:19:29 +05:30
Kovid Goyal
f0d4ad16d2 Work on search by name in unicode input 2018-02-12 10:19:29 +05:30
Kovid Goyal
2cc8402458 A FAQ entry about terminfo over SSH 2018-02-12 10:19:12 +05:30
Kovid Goyal
1c79320133 Fix #319 2018-02-11 13:03:20 +05:30
Kovid Goyal
8b099d969f oops 2018-02-11 11:37:05 +05:30
Kovid Goyal
61157e8763 Remove the passing of swap_interval as a constructor parameter
It was unused and makes it more complicated to override via an option,
if needed.
2018-02-11 11:14:48 +05:30
Kovid Goyal
f97d078f81 Fix kitty saving incorrect window heights
Typo caused saved window height to be actually the window width
2018-02-10 10:00:54 +05:30
Kovid Goyal
a14cf81422 Store cached window sizes in the cache dir rather than the config dir
A side-effect is that on the very next start kitty wont remember window
sizes, apologies for the on convenience.
2018-02-10 10:00:47 +05:30
Kovid Goyal
feeba9b5f5 Add a note about where kitty @ messaging works 2018-02-10 07:35:36 +05:30
Kovid Goyal
b1b76ca250 Redraw windows when the window manager tells us they have been damaged
Needed on systems with non-compositing window managers. Fix #317
2018-02-10 06:54:30 +05:30
Kovid Goyal
e119ed0daa Ignore harmless KeyError while modifying selectors 2018-02-09 20:20:18 +05:30
Kovid Goyal
8c18486836 Module with all the data for unicode entry by character name 2018-02-09 19:56:25 +05:30
Kovid Goyal
b6ed3951bc Add the kittens dir when building the package 2018-02-09 16:59:15 +05:30
Kovid Goyal
ea178ea689 Forgot to marcj launcher script executable 2018-02-09 16:48:04 +05:30
Kovid Goyal
973bbf3f8a Fix #316 2018-02-09 16:44:52 +05:30
Kovid Goyal
3068846d8f Implement using Ctrl+Shift+u to input unicode
Fixes #302
2018-02-08 22:47:17 +05:30
Kovid Goyal
194454715f Basic unicode input works 2018-02-08 22:16:39 +05:30
Kovid Goyal
ac02e053ee Dont save/restore DECCOLM since it is actually unused in kitty 2018-02-08 22:12:51 +05:30
Kovid Goyal
75db360929 ... 2018-02-08 12:57:57 +05:30
Kovid Goyal
f9141120aa A few fixes for the extended key protocol 2018-02-08 12:57:16 +05:30
Kovid Goyal
d19f9e5b96 Fix error handling in tui.loop 2018-02-08 12:33:27 +05:30
Kovid Goyal
213a223861 Typo in incremental decoder init 2018-02-08 11:56:34 +05:30
Kovid Goyal
ef9a24932f Start work on unicode input kitten 2018-02-08 11:00:57 +05:30
Kovid Goyal
bf5b0070c1 Add note about terminfo property used for styled underlines 2018-02-08 10:29:37 +05:30
Kovid Goyal
2ab6f57159 Add non-standard terminfo capabilities
Advertises support for truecolor and styled underlines.
The truecolor properties are used by at least tmux and neovim.
2018-02-08 10:26:13 +05:30
Kovid Goyal
77d46630d7 Update bundled glfw 2018-02-08 09:15:40 +05:30
Kovid Goyal
b78612aebd Entry point to run python on cmd line and from scripts 2018-02-08 09:11:13 +05:30
Kovid Goyal
0f63d1bfa7 ... 2018-02-07 22:41:03 +05:30
Kovid Goyal
66a897cc80 Add interrupt and eot callbacks 2018-02-07 19:46:53 +05:30
Kovid Goyal
3828511051 forgot to include in last commit 2018-02-07 14:40:23 +05:30
Kovid Goyal
226a89c184 Add JSON definition of key encoding 2018-02-07 14:39:11 +05:30
Kovid Goyal
e3b4919c17 Finish up tui.Loop 2018-02-07 13:30:34 +05:30
Kovid Goyal
168bc58635 The kittens learn about mice 2018-02-07 12:40:04 +05:30
Kovid Goyal
f4f0b8fd5f Kittens learn about bracketed paste 2018-02-07 10:39:05 +05:30
Kovid Goyal
13c034c613 Add link to ncurses FAQ on bce as well 2018-02-07 08:06:24 +05:30
Kovid Goyal
35f1658f4a Key and text handler for the kittens 2018-02-06 20:58:46 +05:30
Kovid Goyal
328f22adab Let update_cell_emtrics() know if it is being called in response to a DPI change 2018-02-06 20:12:57 +05:30
Kovid Goyal
22cf67b407 More work on tui.loop 2018-02-06 20:08:28 +05:30
Kovid Goyal
0c7d51dd9d Avoid unnecessary draw calls with multiple OS Windows
Do not ask the GPU to draw and swap buffers for OS Windows that are not
dirtied. See #313
2018-02-06 19:32:24 +05:30
Kovid Goyal
fa87ce72a8 A terminal input parse helper for the kittens 2018-02-06 17:29:05 +05:30
Kovid Goyal
ff2e5b3966 Avoid unnecessary calls to mark_for_codepoint 2018-02-06 11:23:39 +05:30
Kovid Goyal
96c93fa252 Add support for emoji presentation when choosing fallback fonts 2018-02-06 11:11:22 +05:30
Kovid Goyal
e830b7edf7 Add emoji presentation variation selector support to wcswidth() as well 2018-02-06 10:16:23 +05:30
Kovid Goyal
9c874f6685 Add support for the unicode emoji presentation variation selector 2018-02-06 09:52:55 +05:30
Kovid Goyal
b8ab15bea8 Fix incorrect window border drawing for overlay windows 2018-02-06 09:41:19 +05:30
Kovid Goyal
6c7247f726 Disable display_scrollback for overlay windows 2018-02-06 09:36:36 +05:30
Kovid Goyal
7674aa4e7c Add FAQ entry about bce in vim 2018-02-06 08:55:42 +05:30
Kovid Goyal
ebe97bf492 Get rid of safe_wcwidth
Since wcwidth is now in our control, we dont need to wrap it.
2018-02-05 10:11:30 +05:30
Kovid Goyal
fbe4d036d8 Have wcwidth() return 0 for marks instead of -1
Since kitty always treats marks as combinig chars, this allows us to
remove a few unnecessary branches
2018-02-05 10:06:05 +05:30
Kovid Goyal
c572b8bb1a Refactor screen_draw()
Make it a little faster and also clearer, splitting out
the logic for combining chars into a separate function.
2018-02-05 09:55:17 +05:30
Kovid Goyal
59a5e23466 Almost always resize glyphs that are too wide
When using FreeType, rescale glyphs that dont fit if they are more than
1px wider than the cell rather than 25% of the cell width. This matches
the behavior of CoreText and is needed for rendering the various Emoji
with default text presentation (i.e. emoji that have wcwidth() == 1)
2018-02-05 09:21:14 +05:30
Kovid Goyal
b9857f9499 Only consider emoji characters with emoji presentation to have width two
Fixes #308
2018-02-05 09:21:13 +05:30
Kovid Goyal
b667e3a577 DRYer 2018-02-05 09:19:12 +05:30
Kovid Goyal
240b1475c7 Fix regression that caused a few ligatures to not render correctly in rare circumstances
This was caused by cache corruption. is_special_glyph() and
is_empty_glyph() were treading over each other if called in
the right sequence. Fixes #303
2018-02-05 00:04:05 +05:30
Kovid Goyal
3f24e5b571 A few more places to remove wcwidth from 2018-02-04 22:47:02 +05:30
Kovid Goyal
fc7ec1d3f7 Get rid of the option to use the system wcwidth
The system wcwidth() is often wrong. Not to mention that if you SSH into
a different machine, then you have a potentially different wcwidth. The
only sane way to deal with this is to use the unicode standard.
2018-02-04 21:02:30 +05:30
Kovid Goyal
452ff02b15 Oops added extra file in previous commit 2018-02-04 20:34:02 +05:30
Kovid Goyal
c2cb43cc07 Framework for kittens 2018-02-04 20:23:52 +05:30
Kovid Goyal
6736fd3835 Get rid of the query escape code for styled underlines 2018-02-04 16:40:48 +05:30
Kovid Goyal
a1bd69a9c2 Fix incorrect handling of CSI s/r/u 2018-02-04 16:16:57 +05:30
Kovid Goyal
561e63ee2d Use an overlay window for browsing the scrollback buffer in less 2018-02-04 11:28:13 +05:30
Kovid Goyal
647b95631f Add tests for layout operations with an overlay window 2018-02-04 11:28:13 +05:30
Kovid Goyal
aa518b2520 Add tests for basic layout operations 2018-02-04 11:28:13 +05:30
Kovid Goyal
51c0f01539 Support for overlay windows in the layout manager 2018-02-04 11:28:02 +05:30
Kovid Goyal
cc9a19b303 Move set_active_window into base class
It was only used in a single sub-class anyway
2018-02-02 13:56:25 +05:30
Kovid Goyal
2ee9844c2b Track cursor position explicitly during rewrap
Instead of using heuristics to position the cursor after a resize, track
the position during re-wrapping and place the cursor at the re-wrapped
position. Fixes #242 (I hope)
2018-02-02 13:06:18 +05:30
Kovid Goyal
4c53a74fa9 Fix resize debounce implementation 2018-02-02 13:06:18 +05:30
Kovid Goyal
9b0ffdbf5a A spot of refactoring 2018-02-02 11:19:42 +05:30
Kovid Goyal
2777b89e45 Update bundled glfw 2018-02-01 13:03:26 +05:30
Kovid Goyal
7f90430414 Designating charsets should not switch to them
Fixes #301
2018-02-01 12:05:07 +05:30
Kovid Goyal
ee4e56edf9 ... 2018-02-01 10:26:17 +05:30
Kovid Goyal
93c83d03c0 More robust private mode restore 2018-02-01 09:45:34 +05:30
Kovid Goyal
855b3de473 Implement support for save/restore of DEC private modes (CSI ? s/r) 2018-02-01 09:19:43 +05:30
Kovid Goyal
a9be05c885 ... 2018-01-31 18:13:36 +05:30
Kovid Goyal
9f3dfc27da version 0.7.1 2018-01-31 10:37:58 +05:30
Kovid Goyal
4b4f6bc928 Update CHANGELOG 2018-01-31 10:34:54 +05:30
Kovid Goyal
273735f9dc ... 2018-01-31 10:14:08 +05:30
Kovid Goyal
b95e0fb80e Shorten build/run commands 2018-01-30 15:59:31 +05:30
Kovid Goyal
6ad21fc98f Update bundled glfw 2018-01-29 12:30:05 +05:30
Kovid Goyal
bff9b03935 ... 2018-01-28 10:18:02 +05:30
Kovid Goyal
edf9413356 Add a setting to adjust the width of cells, similar to the existing setting to adjust the height of cells 2018-01-28 10:16:59 +05:30
Kovid Goyal
c74f4a8165 Fix extra columns cut-off from the right edge of italic characters that are too wide to fit in a cell 2018-01-28 10:06:39 +05:30
Kovid Goyal
8201cd3288 Add a Makefile target for building kitty-profile 2018-01-26 21:35:48 +05:30
Kovid Goyal
1603310b86 Fix off-by-one when iterating over selections that stretch into the history buf
Fixes #295
2018-01-25 20:50:34 +05:30
Kovid Goyal
627b91ffba version 0.7.0 2018-01-24 15:33:16 +05:30
Kovid Goyal
9ad65f2e1d Cleanup layouts patch
Fix bottom rect in horizontal layout not covering all windows. Fix pep8
issues. Add README and CHANGELOG entries.
2018-01-23 19:35:30 +05:30
Guybrush Threepwood
a6a606ff3d Added vertical and horizontal layouts 2018-01-23 19:30:41 +05:30
Kovid Goyal
63317f9c78 macOS: Fix alt+arrow keys not working when disabling the macos_option_as_alt config option. 2018-01-21 15:41:56 +05:30
Kovid Goyal
46d9dfb614 ... 2018-01-20 17:33:55 +05:30
Kovid Goyal
be3b33af06 Update CHANGELOG 2018-01-19 17:08:21 +05:30
Kovid Goyal
04e1b8b1ae tab bar blank rects dont extend down enough 2018-01-19 17:05:18 +05:30
Kovid Goyal
213768b6e0 Option to place tab bar at the top edge
Fixes #288
2018-01-19 17:04:58 +05:30
Kovid Goyal
aff1abdb26 Fix top line of hollow block cursor 1px too high 2018-01-19 16:23:47 +05:30
Kovid Goyal
44a413df81 Fix blank rects on the sides of the tab bar not being drawn 2018-01-19 16:02:07 +05:30
Kovid Goyal
4f12c91fff Use the new region function everywhere
Centralizes window layout calculation and no longer assumes
tab bar geometry
2018-01-19 15:56:07 +05:30
Kovid Goyal
b6188bf436 Move window region calculations into one place 2018-01-19 11:31:44 +05:30
Kovid Goyal
b9a14e0e4a A Makefile target for building kitty.app 2018-01-19 10:42:12 +05:30
Kovid Goyal
6882c23f43 Update CHANGELOG 2018-01-18 16:26:33 +05:30
Kovid Goyal
80301d465b Handle non-BMP combining characters
Use a level of indirection to store combining characters. This allows
combining characters to be stored using only two bytes, even if they are
after USHORT_MAX
2018-01-18 16:25:42 +05:30
Kovid Goyal
32632264ee Mapping that can be used to store unicode mark symbols in only two bytes 2018-01-18 16:06:07 +05:30
Kovid Goyal
409bd37db5 Store combining chars as an array in the Cell
Makes it easier to increase the number of combining chars if needed,
rather than storing them as a mask.
2018-01-18 14:10:58 +05:30
Kovid Goyal
5fd4bc965f Annotate Cell structure
Make it clear what parts are needed only on the CPU
2018-01-18 12:47:49 +05:30
Kovid Goyal
e86e5ce1d8 ... 2018-01-18 00:15:39 +05:30
Kovid Goyal
5faa649452 Drop the dependency on libunistring 2018-01-18 00:09:40 +05:30
Kovid Goyal
2ddc7e8c80 Have all marks return a wcwidth of -1
Mirrors recent change to onsidering all marks combining chars instead of
only the marks with a non-zero combining class
2018-01-17 23:25:59 +05:30
Kovid Goyal
ed700ff830 ... 2018-01-17 21:59:10 +05:30
Kovid Goyal
33ed873997 Remove unnecessary extra test for combinig characters
There are not combining characters with a non-zero combining class that
are not in the marks category.
2018-01-17 21:56:30 +05:30
Kovid Goyal
9125c00f96 Update CHANGELOG 2018-01-17 21:50:00 +05:30
Kovid Goyal
73fe3293be Fix copying from selection being restricted to only the visible part of the selection. Fixes #287 2018-01-17 21:39:24 +05:30
Kovid Goyal
804c4fbe19 Recognize characters from the unicode Mark categories as combining characters, even if they do not have a combining class (i.e. are not re-ordered). Fixes #286 2018-01-15 11:24:11 +05:30
Kovid Goyal
d6c82ba21c oops 2018-01-14 11:18:44 +05:30
Kovid Goyal
67308f2ca1 Add some documentation for the shaping algorithm 2018-01-14 11:18:44 +05:30
Kovid Goyal
4a17a946fb Ensure groups dont have too many cells 2018-01-14 11:18:44 +05:30
Kovid Goyal
1ae7ae4a1d Fix consecutive separate ligatures being rendered in the same group
Rendering multiple ligatures in the smae group is bad for performance
and also causes incorrect rendering if the last ligature in the group
does not fit into the number of cells in the group.
2018-01-14 11:18:44 +05:30
Kovid Goyal
404ca23de3 Make handling of last glyph a little more intelligent 2018-01-14 11:18:44 +05:30
Kovid Goyal
d385952fef Add a bounds check 2018-01-14 11:18:44 +05:30
Kovid Goyal
f03cac8a9d Allow storing more flags in the special glyph cache 2018-01-14 11:18:44 +05:30
Kovid Goyal
4b501fd694 Fix rendering of ligatures with ligature fonts that dont use dummy glyphs 2018-01-14 11:18:43 +05:30
Kovid Goyal
872d39eb2c Have the ctrl+shift+<key> generate the ASCII C0 control codes for the few shifted control codes there are.
See #285
2018-01-14 11:18:30 +05:30
Kovid Goyal
0341cae922 Fix unused function warning 2018-01-13 15:49:44 +05:30
Kovid Goyal
5357a9a1cc Merge branch 'us/master' of https://github.com/koekeishiya/kitty 2018-01-13 07:33:30 +05:30
koekeishiya
3695538a11 fix mac os_window focus issue 2018-01-12 20:24:11 +01:00
Kovid Goyal
7780d52930 DRYer 2018-01-12 11:27:23 +05:30
Kovid Goyal
6a23b4ee47 Allow kitty @xxx in addition to kitty @ xxx 2018-01-12 11:23:34 +05:30
Kovid Goyal
f48b770036 macOS: Fix focus not being transferred on OSWindow close
Fixes #280
2018-01-12 08:05:12 +05:30
Kovid Goyal
81b5d2c4f2 Option to copy to clipboard on mouse select 2018-01-12 07:33:21 +05:30
Kovid Goyal
d43d6ab0a9 Update bundled glfw 2018-01-12 05:34:53 +05:30
Kovid Goyal
5a4e883360 Merge branch 'macos-logo-make' of https://github.com/zchee/kitty 2018-01-11 22:31:18 +05:30
Koichi Shiraishi
eb93cd4048 Add missing 64 size because doesn't create icon_32x32@2x.png 2018-01-12 00:59:19 +09:00
Koichi Shiraishi
d83e3fddb0 Add install 'optipng' description because logo/make.py depends 'optipng' 2018-01-12 00:58:02 +09:00
Kovid Goyal
80ca25e879 ... 2018-01-11 16:03:23 +05:30
Kovid Goyal
c86b34aacd Move to_color into config_utils 2018-01-11 16:02:52 +05:30
Kovid Goyal
8da426039b Make the config parsing code more re-useable 2018-01-11 12:17:18 +05:30
Kovid Goyal
9184e9a28f Update CHANGELOG 2018-01-11 10:46:50 +05:30
Kovid Goyal
578d537d5f kitty icat: Fix some PNG images not being displayed
Work around for bug in ImageMagick where it converts images
into rgba data with fewer rows than specified. Fixes #276
2018-01-11 09:56:40 +05:30
Kovid Goyal
236c92b2b4 No longer use a shell script for the wrapper .app 2018-01-11 08:40:35 +05:30
Kovid Goyal
66a46279f4 ... 2018-01-11 00:26:29 +05:30
Kovid Goyal
eb35edbe20 kitty icat: Fix typo that broke the SIGWNCH signal handler 2018-01-11 00:24:26 +05:30
Kovid Goyal
7cf2404331 Update CHANGELOG 2018-01-10 15:29:58 +05:30
Kovid Goyal
9ccc6bf835 Fix incorrect handling of zero extra pixel offset 2018-01-10 13:54:51 +05:30
Kovid Goyal
025a46bb35 Fix the in cell offset calculations 2018-01-10 13:50:44 +05:30
Kovid Goyal
677e8a1ae9 Make the signal handler a little simpler 2018-01-10 13:37:43 +05:30
Kovid Goyal
03702772d3 kitty icat: Add a --scale-up option to expand images when used with the --place operator 2018-01-10 13:32:05 +05:30
Kovid Goyal
3cd744b247 Implement @focus-window 2018-01-10 13:23:04 +05:30
Kovid Goyal
88523b2692 Add a note about @ls to the tutorial 2018-01-10 13:00:59 +05:30
Kovid Goyal
dcb39eb210 ... 2018-01-10 12:57:15 +05:30
Kovid Goyal
102823809b Add a tutorial for controlling kitty from scripts 2018-01-10 12:54:21 +05:30
Kovid Goyal
23f2b29069 Add --keep-focus for @new-window 2018-01-10 12:36:15 +05:30
Kovid Goyal
7b33a87725 @send-text allow sending from files and stdin 2018-01-10 10:37:43 +05:30
Kovid Goyal
6dde573ed9 Fix __name__ != __main__ when using runpy in the osx-bundle 2018-01-10 08:31:22 +05:30
Kovid Goyal
566e641be1 Allow easily building a kitty.app wrapper to run kitty from source 2018-01-10 07:44:16 +05:30
Kovid Goyal
08d5448523 Move creation of Info.plist and kitty.icns into osx-bundle 2018-01-10 06:58:57 +05:30
Kovid Goyal
c11a20f1ec Add --new-tab to @new-window 2018-01-09 23:09:57 +05:30
Kovid Goyal
e5b9bd2e5a Implement @get-text 2018-01-09 22:48:24 +05:30
Kovid Goyal
6ad49bd7fb Add an option to override the default shell 2018-01-09 16:28:46 +05:30
Kovid Goyal
5d082d8d5a Implement @new-window 2018-01-09 16:13:28 +05:30
Kovid Goyal
977575da73 Implement @close-tab 2018-01-09 15:30:46 +05:30
Kovid Goyal
bced6f31b1 Implement @close-window 2018-01-09 15:27:12 +05:30
Kovid Goyal
6bf04e22cc Fix send_text parsing choking on ''' 2018-01-09 13:10:28 +05:30
Kovid Goyal
2f9784809d Chunk up overly large send text inputs 2018-01-09 02:51:22 +05:30
Kovid Goyal
3e85497c2c Implement @set-tab-title 2018-01-08 16:13:38 +05:30
Kovid Goyal
3b551a1dee Implement @set-window-title 2018-01-08 14:04:27 +05:30
Kovid Goyal
d2fdb1c1eb DRYer 2018-01-08 13:57:43 +05:30
Kovid Goyal
db92af68e9 Fix a couple of typos 2018-01-08 13:54:08 +05:30
Kovid Goyal
815087460c ... 2018-01-08 13:25:25 +05:30
Kovid Goyal
b4cb9d7c4d ... 2018-01-08 13:07:47 +05:30
Kovid Goyal
c6d10c6a23 Use the launch time argv for cmdline on macOS 2018-01-08 13:07:06 +05:30
Kovid Goyal
464c7a80e2 Implement window matching for remote control 2018-01-08 13:01:57 +05:30
Kovid Goyal
4f56cce727 Fix client eating up send-text when seding to same window 2018-01-08 12:48:15 +05:30
Kovid Goyal
7dea6e0ef4 Initial implementation of send-text remote cmd 2018-01-08 12:48:15 +05:30
Kovid Goyal
8c143db381 Add documentation for the ls command 2018-01-08 12:48:15 +05:30
Kovid Goyal
0cea6d5a1b Implement window listing 2018-01-08 12:48:15 +05:30
Kovid Goyal
eff881f4bb Allow sending arbitrary sized escpae codes to children 2018-01-08 12:48:14 +05:30
Kovid Goyal
c64fd1760c More work on remote control 2018-01-08 12:48:14 +05:30
Kovid Goyal
9e361700d2 Handle reading of RC response via stdin 2018-01-08 12:48:14 +05:30
Kovid Goyal
1f976644bf Refactor read with timeout logic into its own function 2018-01-08 12:48:14 +05:30
Kovid Goyal
f3cb68ee40 Framework for remote control 2018-01-08 12:48:14 +05:30
Kovid Goyal
85a3da057f Session file: Allow setting the title for windows 2018-01-08 12:47:47 +05:30
Kovid Goyal
c551384369 Session file: Allow specifying multiple cd directives per tab to have different windows launched with different working directories 2018-01-08 12:38:01 +05:30
Kovid Goyal
ce230b071b Add tests for SGR protocol encoding 2018-01-07 23:48:16 +05:30
Kovid Goyal
13d5f04ad9 A spot of refactoring 2018-01-07 21:26:28 +05:30
Daniels Kursits
3b861de3d2 Fix mouse move events being reported as drag events
when using the SGR mouse reporting protocol

Fixes #271
2018-01-07 21:12:50 +05:30
Kovid Goyal
1ef010689d Add support for clearing the scrollback buffer
kitty now supports using \E3J to clear the screen and scrollback buffer.
See #268
2018-01-06 23:55:49 +05:30
Kovid Goyal
3523ab283f More trailing whitespace 2018-01-06 09:12:23 +05:30
Kovid Goyal
5b2a9b101d Fix trailing whitespace 2018-01-05 21:27:24 +05:30
Kovid Goyal
fdcfcfa1b9 Make alt+backspace delete the previous word
i.e. alt+backspace now sends ^W (werase). THis matches the behavior of
terminal.app and gnome-terminal. Fixes #264
2018-01-05 21:08:42 +05:30
Kovid Goyal
88e9c21a3b Ensure a kitty executable is on PATH even when running from source 2018-01-05 14:56:01 +05:30
Kovid Goyal
973352a98d Allow + prefixing of special invocations 2018-01-05 14:36:31 +05:30
Kovid Goyal
f06f871dfc Handle fork() failures more gracefully 2018-01-05 13:57:49 +05:30
Kovid Goyal
e03c713294 Remove a few more signal unsafe function calls between fork() and exec() 2018-01-05 00:24:15 +05:30
Kovid Goyal
aa107956f9 ... 2018-01-04 23:55:03 +05:30
Kovid Goyal
d725578e1b Silence spurious compiler warning 2018-01-04 23:54:35 +05:30
Kovid Goyal
239eb8202b Move fork()+exec() of child processes into C
By avoiding python in the child process before exec we ensure that
malloc and other unsafe to use after fork functions are not used.
Should also mean that less pages will need to be copied into thec hild
process, leading to marginally faster startups.
2018-01-04 23:19:09 +05:30
Kovid Goyal
58d7439719 Add a note on installing dev packages for X11 on linux
Fixes #260
2018-01-04 18:06:11 +05:30
Kovid Goyal
cc145ec3dd DRYer 2018-01-04 12:38:35 +05:30
Kovid Goyal
847ce37160 Take the mouse wheel multiplier into account when generating keypresses for mouse wheel events. Fixes #262 2018-01-04 12:34:31 +05:30
Kovid Goyal
0305009766 Remove --without-cairo from brew install since it is ignored anyway 2018-01-03 09:22:23 +05:30
Kovid Goyal
fe1b056371 Allow using macOS specific modifier key names in the config file
cmd and option can now be used instead of super and alt respectively.
2018-01-03 08:59:36 +05:30
Kovid Goyal
0c4804db79 DRYer 2017-12-31 09:04:22 +05:30
Kovid Goyal
f7e84df332 Add an explicit OpenGL version test
Apparently there are some pre 3.3 drivers that include the
texture_storage extension. Fixes #258
2017-12-31 08:36:50 +05:30
Kovid Goyal
bd654d22f4 Read the required wayland protocols version from upstream instead of hardcoding it 2017-12-28 14:12:46 +05:30
Kovid Goyal
115de36599 version 0.6.1 2017-12-28 10:40:42 +05:30
Kovid Goyal
b01c3483de Add an option to fade text in inactive windows
Fixes #256
2017-12-28 10:36:48 +05:30
Kovid Goyal
db7f566a0c Update Changelog 2017-12-27 10:17:34 +05:30
Kovid Goyal
9f806e76e7 Fix incorrect condition for aborting cursor geometry calculation 2017-12-27 10:07:21 +05:30
Kovid Goyal
12bdd4dddd Bump min version of wayland-protocols to match upstream 2017-12-27 07:24:38 +05:30
Kovid Goyal
72a1eb237d Fix regression that broke drawing of hollow cursor when window loses focus
Fixes #255
2017-12-27 07:15:48 +05:30
Kovid Goyal
6a89289907 Merge branch 'recmod' of https://github.com/grimpy/kitty 2017-12-27 06:40:53 +05:30
Kovid Goyal
7ed835cf13 Fix a crash when detecting URLs continued onto multiple lines
Fixes #244
2017-12-27 06:35:09 +05:30
Kovid Goyal
4621ff41d9 Skip building the wayland backend on systems with missing wayland protocol definitions
Fixes #254
2017-12-27 06:22:36 +05:30
Jo De Boeck
60e17d5224 Make it possible to modify rectangle selection modifiers
Signed-off-by: Jo De Boeck <deboeck.jo@gmail.com>
2017-12-24 18:40:25 +02:00
Kovid Goyal
1e2f7c705d ... 2017-12-22 12:36:40 +05:30
Kovid Goyal
1ebf0b95c4 Implement reading of cwd of a process on macOS as well 2017-12-21 12:30:34 +05:30
Kovid Goyal
c5acd94456 Add new actions to open tabs/windows with the working directory set to the working directory of the current window
Fixes #237
2017-12-21 11:29:21 +05:30
Kovid Goyal
448ffe01c3 Fix new window being created with different DPI not changing global DPI 2017-12-21 09:06:07 +05:30
Kovid Goyal
5c882f3290 Fix Travis flake failure on gen-wcwidth.py because of old python version 2017-12-20 23:38:16 +05:30
Kovid Goyal
70a4720bf9 Fix for window being opened on secondary monitor using primary monitor DPI 2017-12-20 23:01:06 +05:30
Kovid Goyal
c8aee8c881 Automatically adjust cell height when DPI changes
DPI changes are detected as a change in the ration of the window size to
the framebuffer size. I dont know how reliable that is. Possiblly Fix #77
2017-12-20 22:55:05 +05:30
Kovid Goyal
c2ad0607b8 Fix trailing whitespace in generated unicde data files 2017-12-20 22:44:20 +05:30
Kovid Goyal
d1282b9f55 Add a script to generate wcwidth as well
Generated function is more efficient than the implementation from
wcwidth9 and also makes it easy to update when the unicode standard
changes.
2017-12-20 16:18:05 +05:30
Kovid Goyal
11ee317884 Fix some emoji glyphs not colored on Linux
Linux has a plethora of fonts that contain some emoji glyphs. If one of
those fonts is used as a fallback font for a non-emoji character, it
also gets used for emojis -- leading to non-colored emoji. Fix by making
sure the first fallback font is a dedicated emoji font.
2017-12-20 10:40:27 +05:30
Kovid Goyal
e69de2f968 Use the new glfw API for setting WM_CLASS 2017-12-20 09:18:29 +05:30
Kovid Goyal
2f4f3e3331 Update bundled glfw 2017-12-20 09:07:06 +05:30
Kovid Goyal
020f8a2047 Add trailing space test to travis 2017-12-20 08:52:06 +05:30
Kovid Goyal
b8faba2a16 More trailing space removal 2017-12-20 08:50:09 +05:30
Kovid Goyal
0fcce6ec58 Remove trailing whitespace from native code files 2017-12-20 08:44:47 +05:30
Kovid Goyal
15f5ea92b8 Merge branch 'master' of https://github.com/FabioLolix/kitty 2017-12-20 08:24:20 +05:30
FabioLolix
2bbb5d25eb Updated readme 2017-12-20 00:12:58 +01:00
Kovid Goyal
8d94285a5d Ensure underlines are rendered even for fonts with very poor metrics
Fixes #236
2017-12-19 00:54:04 +05:30
Kovid Goyal
eed11361c5 Fix typo in config directory override env var 2017-12-18 06:59:19 +05:30
141 changed files with 77847 additions and 5651 deletions

4
.gitattributes vendored
View File

@@ -1,11 +1,13 @@
kitty/wcwidth9.h linguist-generated=true kitty/wcwidth-std.h linguist-generated=true
kitty/emoji.h linguist-generated=true kitty/emoji.h linguist-generated=true
kitty/keys.h linguist-generated=true kitty/keys.h linguist-generated=true
kitty/charsets.c linguist-generated=true kitty/charsets.c linguist-generated=true
kitty/key_encoding.py linguist-generated=true kitty/key_encoding.py linguist-generated=true
kitty/unicode-data.c
kitty/rgb.py linguist-generated=true kitty/rgb.py linguist-generated=true
kitty/gl-wrapper.* linguist-generated=true kitty/gl-wrapper.* linguist-generated=true
kitty/khrplatform.h linguist-generated=true kitty/khrplatform.h linguist-generated=true
kitty/glfw-wrapper.* linguist-generated=true kitty/glfw-wrapper.* linguist-generated=true
glfw/*.c linguist-vendored=true glfw/*.c linguist-vendored=true
glfw/*.h linguist-vendored=true glfw/*.h linguist-vendored=true
kittens/unicode_input/names.h linguist-generated=true

View File

@@ -56,7 +56,7 @@ matrix:
env: env:
- RUN_FLAKE=1 BUILD_PKG=1 - RUN_FLAKE=1 BUILD_PKG=1
language: python language: python
python: "3.5" python: "3.6"
addons: addons:
apt: apt:
packages: packages:
@@ -95,7 +95,7 @@ install: |
if [[ "$USE_BREW" == "1" ]]; then if [[ "$USE_BREW" == "1" ]]; then
brew update; brew update;
brew install python3; brew install python3;
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype --without-cairo; brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype;
else else
mkdir -p $SW; mkdir -p $SW;
curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW; curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW;
@@ -123,6 +123,7 @@ before_script:
- $PYTHON setup.py build --debug --verbose $SANITIZE_ARG; - $PYTHON setup.py build --debug --verbose $SANITIZE_ARG;
script: script:
- if grep -Inr '\s$' kitty kitty_tests *.py *.asciidoc .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
- if [[ -z $SANITIZE_ARG ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi - if [[ -z $SANITIZE_ARG ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi
- if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi - if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi
- if [[ "$BUILD_PKG" == "1" ]]; then $PYTHON setup.py linux-package; fi - if [[ "$BUILD_PKG" == "1" ]]; then $PYTHON setup.py linux-package; fi

View File

@@ -3,6 +3,137 @@ Changelog
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator. kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
version 0.8.0 [2018-02-24]
-----------------------------
- A framework for kittens, that is, small terminal programs designed to run
inside kitty and extend its capabilities. Examples include unicode input and
selecting URLs with the keyboard.
- Input arbitrary unicode characters by pressing Ctrl+Shift+u. You can choose
characters by name, by hex code, by recently used, etc. There is even and
editable Favorites list.
- Open URLs using only the keyboard. kitty has a new "hints mode". Press
Ctrl+Shift+e and all detected URLs on the screen are highlighted with a key
to press to open them. The facility is customizable so you can change
what is detected as a URL and which program is used to open it.
- Add an option to change the titlebar color of kitty windows on macOS
- Only consider Emoji characters with default Emoji presentation to be two
cells wide. This matches the standard. Also add support for the Unicode Emoji
variation presentation selector.
- Prevent video tearing during high speed scrolling by syncing draws
to the monitor's refresh rate. There is a new configuration option to
control this ``sync_to_monitor``.
- When displaying only a single window, use the default background color of the
window (which can be changed via escape codes) as the color for the margin
and padding of the window.
- Add some non standard terminfo capabilities used by neovim and tmux.
- Fix large drop in performance when using multiple top-level windows on macOS
- Fix save/restore of window sizes not working correctly.
- Remove option to use system wcwidth(). Now always use a wcwidth() based on
the Unicode standard. Only sane way.
- Fix a regression that caused a few ligature glyphs to not render correctly in
rare circumstances.
- Browsing the scrollback buffer now happens in an overlay window instead of a
new window/tab.
version 0.7.1 [2018-01-31]
---------------------------
- Add an option to adjust the width of character cells
- Fix selecting text with the mouse in the scrollback buffer selecting text
from the line above the actually selected line
- Fix some italic fonts having the right edge of characters cut-off,
unnecessarily
version 0.7.0 [2018-01-24]
---------------------------
- Allow controlling kitty from the shell prompt/scripts. You can
open/close/rename windows and tabs and even send input to specific windows.
See the README for details.
- Add option to put tab bar at the top instead of the bottom
- Add option to override the default shell
- Add "Horizontal" and "Vertical" window layouts
- Sessions: Allow setting titles and working directories for individual windows
- Option to copy to clipboard on mouse select
- Fix incorrect reporting of mouse move events when using the SGR protocol
- Make alt+backspace delete the previous word
- Take the mouse wheel multiplier option in to account when generating fake key
scroll events
- macOS: Fix closing top-level window does not transfer focus to other
top-level windows.
- macOS: Fix alt+arrow keys not working when disabling the macos_option_as_alt
config option.
- kitty icat: Workaround for bug in ImageMagick that would cause some images
to fail to display at certain sizes.
- Fix rendering of text with ligature fonts that do not use dummy glyphs
- Fix a regression that caused copying of the selection to clipboard to only
copy the visible part of the selection
- Fix incorrect handling of some unicode combining marks that are not re-ordered
- Fix handling on non-BMP combining characters
- Drop the dependency on libunistring
version 0.6.1 [2017-12-28]
---------------------------
- Add an option to fade the text in inactive windows
- Add new actions to open windows/tabs/etc. with the working directory set to
the working directory of the current window.
- Automatically adjust cell size when DPI changes, for example when kitty is
moved from one monitor to another with a different DPI
- Ensure underlines are rendered even for fonts with very poor metrics
- Fix some emoji glyphs not colored on Linux
- Internal wcwidth() implementation is now auto-generated from the unicode
standard database
- Allow configuring the modifiers to use for rectangular selection with the
mouse.
- Fix incorrect minimum wayland version in the build script
- Fix a crash when detecting a URL that ends at the end of the line
- Fix regression that broke drawing of hollow cursor when window loses focus
version 0.6.0 [2017-12-18] version 0.6.0 [2017-12-18]
--------------------------- ---------------------------

View File

@@ -21,3 +21,14 @@ debug:
# Build with the ASAN and UBSAN sanitizers # Build with the ASAN and UBSAN sanitizers
asan: asan:
python3 setup.py build $(VVAL) --debug --sanitize python3 setup.py build $(VVAL) --debug --sanitize
profile:
python3 setup.py build $(VVAL) --profile
logo/kitty.iconset/icon_256x256.png: logo/kitty.svg logo/make.py
logo/make.py
rendered_logo: logo/kitty.iconset/icon_256x256.png
app: rendered_logo
python3 setup.py app $(VVAL)

View File

@@ -11,6 +11,7 @@
:sc_first_window: pass:quotes[`ctrl+shift+1`] :sc_first_window: pass:quotes[`ctrl+shift+1`]
:sc_fourth_window: pass:quotes[`ctrl+shift+4`] :sc_fourth_window: pass:quotes[`ctrl+shift+4`]
:sc_increase_font_size: pass:quotes[`ctrl+shift+equal`] :sc_increase_font_size: pass:quotes[`ctrl+shift+equal`]
:sc_input_unicode_character: pass:quotes[`ctrl+shift+u`]
:sc_move_tab_backward: pass:quotes[`ctrl+shift+,`] :sc_move_tab_backward: pass:quotes[`ctrl+shift+,`]
:sc_move_tab_forward: pass:quotes[`ctrl+shift+.`] :sc_move_tab_forward: pass:quotes[`ctrl+shift+.`]
:sc_move_window_backward: pass:quotes[`ctrl+shift+b`] :sc_move_window_backward: pass:quotes[`ctrl+shift+b`]
@@ -29,6 +30,7 @@
:sc_previous_tab: pass:quotes[`ctrl+shift+left`] :sc_previous_tab: pass:quotes[`ctrl+shift+left`]
:sc_previous_window: pass:quotes[`ctrl+shift+[`] :sc_previous_window: pass:quotes[`ctrl+shift+[`]
:sc_restore_font_size: pass:quotes[`ctrl+shift+backspace`] :sc_restore_font_size: pass:quotes[`ctrl+shift+backspace`]
:sc_run_simple_kitten_text_url_hints: pass:quotes[`ctrl+shift+e`]
:sc_scroll_end: pass:quotes[`ctrl+shift+end`] :sc_scroll_end: pass:quotes[`ctrl+shift+end`]
:sc_scroll_home: pass:quotes[`ctrl+shift+home`] :sc_scroll_home: pass:quotes[`ctrl+shift+home`]
:sc_scroll_line_down: pass:quotes[`ctrl+shift+down` or `ctrl+shift+j`] :sc_scroll_line_down: pass:quotes[`ctrl+shift+down` or `ctrl+shift+j`]
@@ -51,30 +53,32 @@ image::https://travis-ci.org/kovidgoyal/kitty.svg?branch=master[Build status, li
== Major Features == Major Features
* Supports tiling multiple terminal windows side by side in different * Uses OpenGL for rendering, offloads rendering to the GPU for link:#performance[lower system
layouts without needing to use an extra program like tmux load] and buttery smooth scrolling. Uses threaded rendering to minimize input
latency.
* Supports all modern terminal features: graphics (images), unicode, * Supports all modern terminal features: link:graphics-protocol.asciidoc[graphics (images)], unicode,
true-color, OpenType ligatures, mouse protocol, focus tracking, bracketed paste and so on. true-color, OpenType ligatures, mouse protocol, focus tracking, bracketed paste and so on.
* Supports startup sessions which allow you to specify the window/tab layout, * Supports tiling multiple terminal windows side by side in different
working directories and programs to run on startup. link:#layouts[layouts] without needing to use an extra program like tmux
* Uses OpenGL for rendering, offloads rendering to the GPU for lower system * Can be link:remote-control.asciidoc[controlled from scripts or the shell prompt], even over SSH.
load and buttery smooth scrolling. Uses threaded rendering to minimize input
latency. * Has a framework for _kittens_, small terminal programs that can be used to extend kitty's functionality.
For example, they are used for link:#unicode-input[Unicode input] and link:#url-hints[URL hints].
* Supports link:#startup-sessions[startup sessions] which allow you to specify the window/tab layout,
working directories and programs to run on startup.
* Cross-platform support: kitty currently works on Linux and macOS, but because * Cross-platform support: kitty currently works on Linux and macOS, but because
it uses only OpenGL for rendering, it should be trivial to port to other it uses only OpenGL for rendering, it should be trivial to port to other
platforms. platforms.
* Allows you to open the scrollback buffer and window contents in a separate * Allows you to open link:#the-scrollback-buffer[the scrollback buffer] in a separate
window using arbitrary programs of your choice. This is useful for browsing window using arbitrary programs of your choice. This is useful for browsing
the history comfortably in a pager or editor and also for doing things like the history comfortably in a pager or editor.
opening URLs using only the keyboard.
* Easily hackable (UI layer written in python, inner loops in C for
speed). Less than fifteen thousand lines of code.
image::screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout] image::screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout]
@@ -89,8 +93,7 @@ the following dependencies are installed first.
=== Dependencies === Dependencies
* python >= 3.5 * python >= 3.5
* harfbuzz >= 1.5.0 * harfbuzz >= 1.5.0
* libunistring
* zlib * zlib
* libpng * libpng
* freetype (not needed on macOS) * freetype (not needed on macOS)
@@ -98,6 +101,7 @@ the following dependencies are installed first.
* ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal) * ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal)
* gcc or clang (required only for building) * gcc or clang (required only for building)
* pkg-config (required only for building) * pkg-config (required only for building)
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `xinerama` and `xkbcommon-x11`, if they are not already installed by your distro.
=== Install and run from source === Install and run from source
@@ -108,21 +112,24 @@ git clone https://github.com/kovidgoyal/kitty && cd kitty
Now build the C parts of kitty with the following command: Now build the C parts of kitty with the following command:
.... ....
python3 setup.py build make
.... ....
You can run kitty, as: You can run kitty, as:
.... ....
python3 /path/to/kitty/folder python3 .
.... ....
=== Linux packages === Linux packages
* Arch linux: AUR git package https://aur.archlinux.org/packages/kitty-git/ * Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
* Arch Linux: AUR stable package https://aur.archlinux.org/packages/kitty/
* NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix * NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix
* Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty
=== macOS packages === macOS packages
@@ -222,27 +229,51 @@ windows are:
|Increase font size | {sc_increase_font_size} |Increase font size | {sc_increase_font_size}
|Decrease font size | {sc_decrease_font_size} |Decrease font size | {sc_decrease_font_size}
|Restore font size | {sc_restore_font_size} |Restore font size | {sc_restore_font_size}
|Toggle fullscreen | {sc_toggle_fullscreen} |Toggle fullscreen | {sc_toggle_fullscreen}
|Input unicode character | {sc_input_unicode_character}
|Click URL using the keyboard | {sc_run_simple_kitten_text_url_hints}
|Pass current selection to program | {sc_pass_selection_to_program} |Pass current selection to program | {sc_pass_selection_to_program}
|=== |===
== Controlling kitty from scripts
You can control kitty from the command line/scripts by sending it messages.
You can tell kitty to open/close/rename tabs and windows. You can even send
arbitrary input/text to any specified window. Messages can be sent using
`kitty @`. Note that you must set `allow_remote_control yes` in your
kitty.conf to use this feature. It even works over SSH connections. This
feature is best illustrated with a link:remote-control.asciidoc[tutorial].
== The scrollback buffer == The scrollback buffer
kitty supports scrolling back to view history, just like most terminals. You kitty supports scrolling back to view history, just like most terminals. You
can use either the <<scrolling-shortcuts,keyboard shortcuts>> or the mouse can use either the <<scrolling-shortcuts,keyboard shortcuts>> or the mouse
scroll wheel to do so. However, kitty has an extra, neat feature. Sometimes you scroll wheel to do so. However, kitty has an extra, neat feature. Sometimes
need to explore the scrollback buffer in more detail, maybe search for some you need to explore the scrollback buffer in more detail, maybe search for some
text or refer to it side-by-side while typing in a follow-up command. kitty text or refer to it side-by-side while typing in a follow-up command. kitty
allows you to do this by pressing the {sc_show_scrollback} key-combination, allows you to do this by pressing the {sc_show_scrollback} key-combination,
which will open the scrollback buffer in a new window, using your favorite which will open the scrollback buffer in your favorite pager program (which is
pager program (which is `less` by default). You can then explore the scrollback `less` by default). Colors and text formatting are preserved. You can explore
buffer using whatever program you normally use. Colors and text formatting are the scrollback pager comfortably within the pager.
preserved.
== Unicode input
You can input unicode characters by name, hex code, recently used and even an editable favorites list.
Press {sc_input_unicode_character} to start the unicode input widget, shown below.
image::unicode.png?raw=true[Unicode input widget, showing selection of unicode characters by name]
== URL hints
kitty has a _hints mode_ to click URLs visible on the screen by using only the keyboard.
Press {sc_run_simple_kitten_text_url_hints} to activate hints mode, as shown below.
image::hints_mode.png?raw=true[URL hints mode]
== Miscellaneous features == Miscellaneous features
You can double click to select a word and triple click to select a line. You can also You can double click to select a word and triple click to select a line. You can also
@@ -251,11 +282,13 @@ hold down `ctrl+shift` and click on a URL to open it in a browser.
== Layouts == Layouts
Currently, there are three layouts available, Currently, there are five layouts available,
* Stack -- Only a single maximized window is shown at a time * Stack -- Only a single maximized window is shown at a time
* Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right. * Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
* Grid -- All windows are shown in a grid * Grid -- All windows are shown in a grid
* Horizontal -- All windows are shown side-by-side
* Vertical -- All windows are shown one below the other
You can switch between layouts using the {sc_next_layout} key combination. You can switch between layouts using the {sc_next_layout} key combination.
@@ -283,11 +316,13 @@ command line flag. For example:
.... ....
# Set the window layout for the current tab # Set the window layout for the current tab
layout tall layout tall
# Set the working directory for the current tab # Set the working directory for windows in the current tab
cd ~ cd ~
# Create a window and run the specified command in it # Create a window and run the specified command in it
launch zsh launch zsh
launch vim launch vim
# Set the title for the next window
title Chat with x
launch irssi --profile x launch irssi --profile x
# Create a new tab (the part after new_tab is the optional tab name which will # Create a new tab (the part after new_tab is the optional tab name which will
@@ -333,18 +368,20 @@ smoothness.
There are two parameters you can tune to adjust the performance. ``repaint_delay`` There are two parameters you can tune to adjust the performance. ``repaint_delay``
and ``input_delay``. These control the artificial delays introduced into the and ``input_delay``. These control the artificial delays introduced into the
render loop to reduce CPU usage. See the link:kitty/kitty.conf[config file] for details. render loop to reduce CPU usage. See the link:kitty/kitty.conf[config file] for details.
See also the ``sync_to_monitor`` option to further decrease latency at the cost
of some link:https://en.wikipedia.org/wiki/Screen_tearing[tearing] while scrolling.
You can generate detailed per-function performance data using You can generate detailed per-function performance data using
link:https://github.com/gperftools/gperftools[gperftools]. Build kitty with the link:https://github.com/gperftools/gperftools[gperftools]. Build kitty with
`--profile` flag which will create an executable called `kitty-profile`. Run `make profile` which will create an executable called `kitty-profile`. Run
that and perform the task you want to analyse, for example, scrolling a large that and perform the task you want to analyse, for example, scrolling a large
file with `less`. After you quit, function call statistics will be printed to file with `less`. After you quit, function call statistics will be printed to
`stdout` and you can use tools like *kcachegrind* for more detailed analysis. `stdout` and you can use tools like *kcachegrind* for more detailed analysis.
Here are some CPU usage numbers for the task of scrolling a file continuously in less. Here are some CPU usage numbers for the task of scrolling a file continuously in less.
The CPU usage is for the terminal process and X together and is measured using htop. The CPU usage is for the terminal process and X together and is measured using htop.
The measurements are taken at the same font and window size for all terminals on a The measurements are taken at the same font and window size for all terminals on a
`Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz` CPU with a `Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz` CPU with a
`Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde XT [Radeon HD 7770/8760 / R7 250X]` GPU. `Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde XT [Radeon HD 7770/8760 / R7 250X]` GPU.
|=== |===
@@ -393,76 +430,70 @@ brew or MacPorts as well.
=== Some special symbols are rendered small/truncated in kitty? === Some special symbols are rendered small/truncated in kitty?
The number of cells a unicode character takes up are controlled by the The number of cells a unicode character takes up are controlled by the unicode
`wcwidth()` system function. If wcwidth() returns 2 then kitty will render the standard. All characters are rendered in a single cell unless the unicode
character in two cells, otherwise it will render it in one cell. Often the standard says they should be rendered in two cells. When a symbol does not fit,
system `wcwidth()` is old/outdated. You can use the `use_system_wcwidth=no` it will either be rescaled to be smaller or truncated (depending on how much
setting in your kitty.conf to workaround this. But note that it might cause extra space it needs). This is often different from other terminals which just
other issues, since now kitty and the programs running inside it may not agree let the character overflow into neighboring cells, which is fine if the
on how wide characters should be. When a symbol does not fit, it will either be neighboring cell is empty, but looks terrible if it is not.
rescaled to be smaller or truncated (depending on how much extra space it
needs). This is often different from other terminals which just let the
character overflow into neighboring cells, leading to ugly artifacts.
In addition to the problem with `wcwidth()` above, some programs, like Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
powerline, vim with fancy gutter symbols/status-bar, etc. use unicode use unicode characters from the private use area to represent symbols. Often
characters from the private use area to represent symbols. Often these symbols these symbols are square and should be rendered in two cells. However, since
are square and should be rendered in two cells. However, since private use private use area symbols all have their width set to one in the unicode
area symbols all have `wcwdith() == 1` kitty renders them either smaller or standard, kitty renders them either smaller or truncated. The correct solution
truncated. The correct solution for this is to use either use different symbols for this is to use either use different symbols that are not square, or to use
that are not square, or to use a font that defines ligatures with the space a font that defines ligatures with the space character for these symbols. See
character for these symbols. See
link:https://github.com/kovidgoyal/kitty/issues/182[#182] for a discussion of link:https://github.com/kovidgoyal/kitty/issues/182[#182] for a discussion of
the approach using ligatures. the approach using ligatures.
=== How do I build kitty.app on macOS? === How do I build kitty.app on macOS?
You dont need to build kitty.app to run kitty from source. Simply install the dependencies Install `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
listed above using brew/MacPorts or similar and then compile and run with:
And run:
``` ```
make make app
python3 .
``` ```
You can also create a shell script to run kitty: This `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 kitty.dmg includes all dependencies, unlike the
#!/bin/sh `kitty.app` built above and is built automatically by using the `kitty` branch of
exec python3 /path/to/kitty/source/dir
```
And using something like link:https://mathiasbynens.be/notes/shell-script-mac-apps[app-wrapper-for-shell-script]
you can create a simple launcher app to run the shell script.
If you only want to make some small changes to a python file, you can do that
and simply replace the corresponding file inside an existing kitty.app. (Note
that kitty.app will contain `.pyo` files which are compiled versions of `.py`
files, delete the `.pyo` file and replace it with a `.py` file).
Finally, note that the released kitty.dmg is built automatically by using the
`kitty` branch of
link:https://github.com/kovidgoyal/build-calibre[build-calibre] however, that link:https://github.com/kovidgoyal/build-calibre[build-calibre] however, that
is designed to run on linux and is not for the faint of heart. is designed to run on Linux and is not for the faint of heart.
== A tribute
While over the decades I am sure many people have contributed to the === Using a color theme with a background color does not work well in vim?
development of the terminal emulator space, there is one individual in
particular I would like to thank. link:http://invisible-island.net[Thomas E.
Dickey], the creator of xterm. xterm is the most comprehensive and
feature-rich terminal emulator I have had the pleasure of using. As I worked on
kitty, I ran headlong into more and more gnarly corners of terminal behavior.
On all those occasions, either the excellent documentation at
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html[xterm-ctlseqs] or
investigating the behavior and code of xterm or vttest were invaluable tools to
aid my understanding.
My achievements, if any, in developing kitty would not have been possible without First make sure you have not changed the TERM environment variable, it should
his prior work and the generous sharing of knowledge accumulated over decades. be `xterm-kitty`. vim uses *background color erase* even if the terminfo file
does not contain the `bce` capability. This is a bug in vim. You can work around
it by adding the following to your vimrc:
Thank you. ....
let &t_ut=''
....
See link:https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc#setting-text-styles-colors-in-arbitrary-regions-of-the-screen[here]
for why kitty does not support background color erase.
== I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
This happens because the kitty terminfo files are not available on the server. Either install kitty on the server,
or simply copy over the terminfo files, using:
....
ssh myserver mkdir -p \~/.terminfo
infocmp xterm-kitty | ssh myserver "cat > /tmp/kitty.terminfo"
ssh myserver tic -x -o \~/.terminfo /tmp/kitty.terminfo
....
Really, the correct solution for this is to convince the OpenSSH maintainers to have ssh do this
automatically when connecting to a server.
== Resources on terminal behavior == Resources on terminal behavior
@@ -470,4 +501,4 @@ http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
https://en.wikipedia.org/wiki/C0_and_C1_control_codes https://en.wikipedia.org/wiki/C0_and_C1_control_codes
http://vt100.net/ https://vt100.net/

View File

@@ -4,12 +4,65 @@
import sys import sys
if len(sys.argv) > 1 and sys.argv[1] == 'icat':
def icat(args):
from kitty.icat import main from kitty.icat import main
main(sys.argv[1:]) main(args)
elif len(sys.argv) > 1 and sys.argv[1] == 'list-fonts':
def list_fonts(args):
from kitty.fonts.list import main from kitty.fonts.list import main
main(sys.argv[1:]) main(args)
else:
from kitty.main import main
def remote_control(args):
from kitty.remote_control import main
main(args)
def runpy(args):
sys.argv = ['kitty'] + args[2:]
exec(args[1])
def launch(args):
import runpy
sys.argv = args[1:]
runpy.run_path(args[1], run_name='__main__')
def namespaced(args):
func = namespaced_entry_points[args[1]]
func(args[1:])
entry_points = {
# These two are here for backwards compat
'icat': icat,
'list-fonts': list_fonts,
'runpy': runpy,
'launch': launch,
'@': remote_control,
'+': namespaced,
}
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
def main():
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
func = entry_points.get(first_arg)
if func is None:
if first_arg.startswith('@'):
remote_control(['@', first_arg[1:]] + sys.argv[2:])
elif first_arg.startswith('+'):
namespaced(['+', first_arg[1:]] + sys.argv[2:])
else:
from kitty.main import main
main()
else:
func(sys.argv[1:])
if __name__ == '__main__':
main() main()

View File

@@ -15,7 +15,7 @@ with open('terminfo/kitty.terminfo', 'w') as f:
f.write(generate_terminfo()) f.write(generate_terminfo())
os.environ['TERMINFO'] = os.path.join(base, 'terminfo') os.environ['TERMINFO'] = os.path.join(base, 'terminfo')
subprocess.check_call(['tic', 'terminfo/kitty.terminfo']) subprocess.check_call(['tic', '-x', 'terminfo/kitty.terminfo'])
# On OS X tic puts the compiled database into a different directory # On OS X tic puts the compiled database into a different directory
try: try:
os.mkdir('terminfo/78') os.mkdir('terminfo/78')

View File

@@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
cloc --exclude-list-file <(echo -e 'kitty/wcwidth9.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty

View File

@@ -1,75 +0,0 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
from collections import defaultdict
from functools import partial
from itertools import groupby
from operator import itemgetter
from urllib.request import urlopen
os.chdir(os.path.dirname(os.path.abspath(__file__)))
raw = urlopen('http://unicode.org/Public/emoji/5.0/emoji-data.txt').read().decode('utf-8')
seen = set()
cmap = defaultdict(set)
for line in raw.splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
spec, rest = line.partition(';')[::2]
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
if '.' in spec:
spec = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
spec = set(range(spec[0], spec[1] + 1))
else:
spec = {int(spec, 16)}
cmap[rest] |= spec
seen |= spec
items = list(seen)
def get_ranges(items):
items.sort()
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
group = tuple(map(itemgetter(1), g))
a, b = group[0], group[-1]
if a == b:
yield a
else:
yield a, b
def write_case(spec, p):
if isinstance(spec, tuple):
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
else:
p('\t\tcase 0x{:x}:'.format(spec))
with open('kitty/emoji.h', 'w') as f:
p = partial(print, file=f)
p('#pragma once')
p('#include "data-types.h"\n')
p('START_ALLOW_CASE_RANGE')
p('static inline bool is_emoji(uint32_t code) {')
p('\tswitch(code) {')
for spec in get_ranges(items):
last = spec[1] if isinstance(spec, tuple) else spec
if last < 0x231a:
continue
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false; \n}')
p('static inline bool is_emoji_modifier(uint32_t code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(cmap['Emoji_Modifier'])):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false; \n}')
p('END_ALLOW_CASE_RANGE')

354
gen-wcwidth.py Executable file
View File

@@ -0,0 +1,354 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from collections import defaultdict
from contextlib import contextmanager
from datetime import date
from functools import partial
from html.entities import html5
from itertools import groupby
from operator import itemgetter
from urllib.request import urlopen
os.chdir(os.path.dirname(os.path.abspath(__file__)))
def get_data(fname, folder='UCD'):
url = f'https://www.unicode.org/Public/{folder}/latest/{fname}'
bn = os.path.basename(url)
local = os.path.join('/tmp', bn)
if os.path.exists(local):
data = open(local, 'rb').read()
else:
data = urlopen(url).read()
open(local, 'wb').write(data)
for line in data.decode('utf-8').splitlines():
line = line.strip()
if line and not line.startswith('#'):
yield line
# Map of class names to set of codepoints in class
class_maps = {}
name_map = {}
word_search_map = defaultdict(set)
marks = set()
not_assigned = set(range(0, sys.maxunicode))
def parse_ucd():
def add_word(w, c):
if c <= 32 or c == 127 or 128 <= c <= 159:
return
word_search_map[w.lower()].add(c)
first = None
for word, c in html5.items():
if len(c) == 1:
add_word(word.rstrip(';'), ord(c))
word_search_map['nnbsp'].add(0x202f)
for line in get_data('ucd/UnicodeData.txt'):
parts = [x.strip() for x in line.split(';')]
codepoint = int(parts[0], 16)
name = parts[1]
if name:
name_map[codepoint] = name
for word in name.lower().split():
add_word(word, codepoint)
category = parts[2]
s = class_maps.setdefault(category, set())
desc = parts[1]
codepoints = (codepoint,)
if first is None:
if desc.endswith(', First>'):
first = codepoint
continue
else:
codepoints = range(first, codepoint + 1)
first = None
for codepoint in codepoints:
s.add(codepoint)
not_assigned.discard(codepoint)
if category.startswith('M'):
marks.add(codepoint)
def split_two(line):
spec, rest = line.split(';', 1)
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
if '..' in spec:
chars = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
chars = set(range(chars[0], chars[1] + 1))
else:
chars = {int(spec, 16)}
return chars, rest
all_emoji = set()
emoji_categories = {}
emoji_presentation_bases = set()
def parse_emoji():
for line in get_data('emoji-data.txt', 'emoji'):
chars, rest = split_two(line)
s = emoji_categories.setdefault(rest, set())
s.update(chars)
all_emoji.update(chars)
for line in get_data('emoji-variation-sequences.txt', 'emoji'):
base, var, *rest = line.split()
if base.startswith('#'):
continue
base = int(base, 16)
if var.upper() == 'FE0F':
emoji_presentation_bases.add(base)
doublewidth, ambiguous = set(), set()
def parse_eaw():
global doublewidth, ambiguous
seen = set()
for line in get_data('ucd/EastAsianWidth.txt'):
chars, eaw = split_two(line)
if eaw == 'A':
ambiguous |= chars
seen |= chars
elif eaw == 'W' or eaw == 'F':
doublewidth |= chars
seen |= chars
doublewidth |= set(range(0x3400, 0x4DBF + 1)) - seen
doublewidth |= set(range(0x4E00, 0x9FFF + 1)) - seen
doublewidth |= set(range(0xF900, 0xFAFF + 1)) - seen
doublewidth |= set(range(0x20000, 0x2FFFD + 1)) - seen
doublewidth |= set(range(0x30000, 0x3FFFD + 1)) - seen
def get_ranges(items):
items.sort()
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
group = tuple(map(itemgetter(1), g))
a, b = group[0], group[-1]
if a == b:
yield a
else:
yield a, b
def write_case(spec, p):
if isinstance(spec, tuple):
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
else:
p('\t\tcase 0x{:x}:'.format(spec))
@contextmanager
def create_header(path, include_data_types=True):
f = open(path, 'w')
p = partial(print, file=f)
p('// unicode data, built from the unicode standard on:', date.today())
p('// see gen-wcwidth.py')
if path.endswith('.h'):
p('#pragma once')
if include_data_types:
p('#include "data-types.h"\n')
p('START_ALLOW_CASE_RANGE')
p()
yield p
p()
if include_data_types:
p('END_ALLOW_CASE_RANGE')
f.close()
def gen_emoji():
with create_header('kitty/emoji.h') as p:
p('static inline bool\nis_emoji(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(all_emoji)):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
p('static inline bool\nis_emoji_modifier(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
def category_test(name, p, classes, comment, static=False):
static = 'static inline ' if static else ''
chars = set()
for c in classes:
chars |= class_maps[c]
p(f'{static}bool\n{name}(char_type code) {{')
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
p('\tswitch(code) {')
for spec in get_ranges(list(chars)):
write_case(spec, p)
p(f'\t\t\treturn true;')
p('\t} // }}}\n')
p('\treturn false;\n}\n')
def codepoint_to_mark_map(p, mark_map):
p('\tswitch(c) { // {{{')
rmap = {c: m for m, c in enumerate(mark_map)}
for spec in get_ranges(mark_map):
if isinstance(spec, tuple):
s = rmap[spec[0]]
p(f'\t\tcase {spec[0]} ... {spec[1]}: return {s} + c - {spec[0]};')
else:
p(f'\t\tcase {spec}: return {rmap[spec]};')
p('default: return 0;')
p('\t} // }}}')
return rmap
def classes_to_regex(classes):
chars = set()
for c in classes:
chars |= class_maps[c]
def as_string(codepoint):
if codepoint < 256:
return r'\x{:02x}'.format(codepoint)
if codepoint <= 0xffff:
return r'\u{:04x}'.format(codepoint)
return r'\U{:08x}'.format(codepoint)
for spec in get_ranges(list(chars)):
if isinstance(spec, tuple):
yield '{}-{}'.format(*map(as_string, (spec[0], spec[1])))
else:
yield as_string(spec)
def gen_ucd():
cz = {c for c in class_maps if c[0] in 'CZ'}
with create_header('kitty/unicode-data.c') as p:
p('#include "unicode-data.h"')
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters (Cc Cf Cs)')
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
category_test('is_CZ_category', p, cz, 'C and Z categories')
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
mark_map = [0] + list(sorted(marks))
p('char_type codepoint_for_mark(combining_type m) {')
p(f'\tstatic char_type map[{len(mark_map)}] =', '{', ', '.join(map(str, mark_map)), '}; // {{{ mapping }}}')
p('\tif (m < arraysz(map)) return map[m];')
p('\treturn 0;')
p('}\n')
p('combining_type mark_for_codepoint(char_type c) {')
rmap = codepoint_to_mark_map(p, mark_map)
p('}\n')
if rmap[0xfe0e] != 1275:
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
rmap[0xfe0e], rmap[0xfe0f]
))
with open('kittens/url_hints/url_regex.py', 'w') as f:
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz))))
def gen_names():
words = tuple(sorted(word_search_map))
with create_header('kittens/unicode_input/names.h') as p:
cp_map = list(sorted(name_map))
p(f'static const char* name_map[{len(cp_map)}] = {{' ' // {{{')
for cp in cp_map:
w = name_map[cp].replace('"', '\\"')
p(f'\t"{w}",')
p("}; // }}}\n")
p(f'static const char* idx_to_word[{len(words)}] = ' '{ // {{{')
for s in words:
s = s.replace('"', '\\"')
p(f'\t"{s}",')
p("}; // }}}\n")
first_letters = {ord(w[0]) for w in words if ord(w[0]) < 256}
wmap = {w: i for i, w in enumerate(words)}
p(f'static const unsigned short* words_for_first_letter[256] = ' '{ // {{{')
for fl in range(0, 256):
if fl in first_letters:
winds = [str(wmap[w]) for w in words if w.startswith(chr(fl))]
p(f'\t(const unsigned short[{len(winds) + 1}]){{{len(winds)}, ', ', '.join(winds), '},')
else:
p('NULL,')
p("}; // }}}\n")
p(f'static const char_type* codepoints_for_word_idx[{len(words)}] = ' '{ // {{{')
for s in words:
cps = word_search_map[s]
a = ', '.join(map(str, cps))
p(f'\t(const char_type[{len(cps) + 1}]){{{len(cps)}, ', a, '},')
p("}; // }}}\n")
p('static char_type mark_for_codepoint(char_type c) {')
codepoint_to_mark_map(p, cp_map)
p('}\n')
p('static inline const char* name_for_codepoint(char_type cp) {')
p('\tchar_type m = mark_for_codepoint(cp); if (m == 0) return NULL;')
p('\treturn name_map[m];')
p('}\n')
def gen_wcwidth():
seen = set()
def add(p, comment, chars_, ret):
chars = chars_ - seen
seen.update(chars)
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
for spec in get_ranges(list(chars)):
write_case(spec, p)
p(f'\t\t\treturn {ret};')
p('\t\t// }}}\n')
with create_header('kitty/wcwidth-std.h') as p:
p('static int\nwcwidth_std(int32_t code) {')
p('\tswitch(code) {')
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
add(p, 'Marks', marks | {0}, 0)
add(p, 'Non-printing characters', non_printing, -1)
add(p, 'Private use', class_maps['Co'], -3)
add(p, 'Text Presentation', emoji_categories['Emoji'] - emoji_categories['Emoji_Presentation'], 1)
add(p, 'East Asian ambiguous width', ambiguous, -2)
add(p, 'East Asian double width', doublewidth, 2)
add(p, 'Emoji Presentation', emoji_categories['Emoji_Presentation'], 2)
add(p, 'Not assigned in the unicode character database', not_assigned, -1)
p('\t\tdefault: return 1;')
p('\t}')
p('\treturn 1;\n}')
p('static bool\nis_emoji_presentation_base(uint32_t code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(emoji_presentation_bases)):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn 1;\n}')
parse_ucd()
parse_emoji()
parse_eaw()
gen_ucd()
gen_wcwidth()
gen_emoji()
gen_names()

View File

@@ -264,7 +264,7 @@ void _glfwPollMonitorsNS(void)
const CGSize size = CGDisplayScreenSize(displays[i]); const CGSize size = CGDisplayScreenSize(displays[i]);
char* name = getDisplayName(displays[i]); char* name = getDisplayName(displays[i]);
if (!name) if (!name)
name = strdup("Unknown"); name = _glfw_strdup("Unknown");
monitor = _glfwAllocMonitor(name, size.width, size.height); monitor = _glfwAllocMonitor(name, size.width, size.height);
monitor->ns.displayID = displays[i]; monitor->ns.displayID = displays[i];
@@ -287,7 +287,7 @@ void _glfwPollMonitorsNS(void)
// Change the current video mode // Change the current video mode
// //
GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired) void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
{ {
CFArrayRef modes; CFArrayRef modes;
CFIndex count, i; CFIndex count, i;
@@ -299,7 +299,7 @@ GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
best = _glfwChooseVideoMode(monitor, desired); best = _glfwChooseVideoMode(monitor, desired);
_glfwPlatformGetVideoMode(monitor, &current); _glfwPlatformGetVideoMode(monitor, &current);
if (_glfwCompareVideoModes(&current, best) == 0) if (_glfwCompareVideoModes(&current, best) == 0)
return GLFW_TRUE; return;
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link); CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
@@ -332,15 +332,6 @@ GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
CFRelease(modes); CFRelease(modes);
CVDisplayLinkRelease(link); CVDisplayLinkRelease(link);
if (!native)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Monitor mode list changed");
return GLFW_FALSE;
}
return GLFW_TRUE;
} }
// Restore the previously saved (original) video mode // Restore the previously saved (original) video mode

View File

@@ -88,9 +88,10 @@ typedef struct _GLFWwindowNS
GLFWbool maximized; GLFWbool maximized;
// Cached window and framebuffer sizes used to filter out duplicate events // Cached window properties to filter out duplicate events
int width, height; int width, height;
int fbWidth, fbHeight; int fbWidth, fbHeight;
float xscale, yscale;
// The total sum of the distances the cursor has been warped // The total sum of the distances the cursor has been warped
// since the last cursor motion event was processed // since the last cursor motion event was processed
@@ -163,6 +164,6 @@ typedef struct _GLFWtimerNS
void _glfwInitTimerNS(void); void _glfwInitTimerNS(void);
void _glfwPollMonitorsNS(void); void _glfwPollMonitorsNS(void);
GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor); void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);

View File

@@ -43,6 +43,7 @@
#define NSEventModifierFlagControl NSControlKeyMask #define NSEventModifierFlagControl NSControlKeyMask
#define NSEventModifierFlagOption NSAlternateKeyMask #define NSEventModifierFlagOption NSAlternateKeyMask
#define NSEventModifierFlagShift NSShiftKeyMask #define NSEventModifierFlagShift NSShiftKeyMask
#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask #define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
#define NSEventMaskAny NSAnyEventMask #define NSEventMaskAny NSAnyEventMask
#define NSEventTypeApplicationDefined NSApplicationDefined #define NSEventTypeApplicationDefined NSApplicationDefined
@@ -137,9 +138,9 @@ static float transformY(float y)
// Make the specified window and its video mode active on its monitor // Make the specified window and its video mode active on its monitor
// //
static GLFWbool acquireMonitor(_GLFWwindow* window) static void acquireMonitor(_GLFWwindow* window)
{ {
const GLFWbool status = _glfwSetVideoModeNS(window->monitor, &window->videoMode); _glfwSetVideoModeNS(window->monitor, &window->videoMode);
const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID); const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID);
const NSRect frame = NSMakeRect(bounds.origin.x, const NSRect frame = NSMakeRect(bounds.origin.x,
transformY(bounds.origin.y + bounds.size.height), transformY(bounds.origin.y + bounds.size.height),
@@ -149,7 +150,6 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
[window->ns.object setFrame:frame display:YES]; [window->ns.object setFrame:frame display:YES];
_glfwInputMonitorWindow(window->monitor, window); _glfwInputMonitorWindow(window->monitor, window);
return status;
} }
// Remove the window and restore the original video mode // Remove the window and restore the original video mode
@@ -177,6 +177,8 @@ static int translateFlags(NSUInteger flags)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if (flags & NSEventModifierFlagCommand) if (flags & NSEventModifierFlagCommand)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (flags & NSEventModifierFlagCapsLock)
mods |= GLFW_MOD_CAPS_LOCK;
return mods; return mods;
} }
@@ -571,6 +573,16 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
window->ns.fbHeight = fbRect.size.height; window->ns.fbHeight = fbRect.size.height;
_glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
} }
const float xscale = fbRect.size.width / contentRect.size.width;
const float yscale = fbRect.size.height / contentRect.size.height;
if (xscale != window->ns.xscale || yscale != window->ns.yscale)
{
window->ns.xscale = xscale;
window->ns.yscale = yscale;
_glfwInputWindowContentScale(window, xscale, yscale);
}
} }
- (void)drawRect:(NSRect)rect - (void)drawRect:(NSRect)rect
@@ -694,7 +706,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
NSUInteger i; NSUInteger i;
for (i = 0; i < count; i++) for (i = 0; i < count; i++)
paths[i] = strdup([[e nextObject] UTF8String]); paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
_glfwInputDrop(window, (int) count, (const char**) paths); _glfwInputDrop(window, (int) count, (const char**) paths);
@@ -1087,8 +1099,8 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
[window->ns.object zoom:nil]; [window->ns.object zoom:nil];
} }
if (wndconfig->ns.frame) if (strlen(wndconfig->ns.frameName))
[window->ns.object setFrameAutosaveName:[NSString stringWithUTF8String:wndconfig->title]]; [window->ns.object setFrameAutosaveName:[NSString stringWithUTF8String:wndconfig->ns.frameName]];
window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
@@ -1159,11 +1171,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
{ {
_glfwPlatformShowWindow(window); _glfwPlatformShowWindow(window);
_glfwPlatformFocusWindow(window); _glfwPlatformFocusWindow(window);
if (!acquireMonitor(window)) acquireMonitor(window);
return GLFW_FALSE;
if (wndconfig->centerCursor)
centerCursor(window);
} }
return GLFW_TRUE; return GLFW_TRUE;
@@ -1405,6 +1413,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
const NSUInteger styleMask = getStyleMask(window); const NSUInteger styleMask = getStyleMask(window);
[window->ns.object setStyleMask:styleMask]; [window->ns.object setStyleMask:styleMask];
// HACK: Changing the style mask can cause the first responder to be cleared
[window->ns.object makeFirstResponder:window->ns.view]; [window->ns.object makeFirstResponder:window->ns.view];
if (monitor) if (monitor)
@@ -1475,6 +1484,20 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return [window->ns.object isZoomed]; return [window->ns.object isZoomed];
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
const NSPoint point = [NSEvent mouseLocation];
if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
[window->ns.object windowNumber])
{
return GLFW_FALSE;
}
return NSPointInRect(point,
[window->ns.object convertRectToScreen:[window->ns.view bounds]]);
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];
@@ -1786,7 +1809,7 @@ const char* _glfwPlatformGetClipboardString(void)
} }
free(_glfw.ns.clipboardString); free(_glfw.ns.clipboardString);
_glfw.ns.clipboardString = strdup([object UTF8String]); _glfw.ns.clipboardString = _glfw_strdup([object UTF8String]);
return _glfw.ns.clipboardString; return _glfw.ns.clipboardString;
} }

55
glfw/context.c vendored
View File

@@ -38,8 +38,24 @@
////// GLFW internal API ////// ////// GLFW internal API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Checks whether the desired context attributes are valid
//
// This function checks things like whether the specified client API version
// exists and whether all relevant options have supported and non-conflicting
// values
//
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig) GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
{ {
if (ctxconfig->share)
{
if (ctxconfig->client == GLFW_NO_API ||
ctxconfig->share->context.client == GLFW_NO_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return GLFW_FALSE;
}
}
if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API && if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API &&
ctxconfig->source != GLFW_EGL_CONTEXT_API && ctxconfig->source != GLFW_EGL_CONTEXT_API &&
ctxconfig->source != GLFW_OSMESA_CONTEXT_API) ctxconfig->source != GLFW_OSMESA_CONTEXT_API)
@@ -155,6 +171,8 @@ GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
return GLFW_TRUE; return GLFW_TRUE;
} }
// Chooses the framebuffer config that best matches the desired one
//
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired, const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
const _GLFWfbconfig* alternatives, const _GLFWfbconfig* alternatives,
unsigned int count) unsigned int count)
@@ -321,10 +339,13 @@ const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
return closest; return closest;
} }
GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig) // Retrieves the attributes of the current context
//
GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig)
{ {
int i; int i;
_GLFWwindow* window; _GLFWwindow* previous;
const char* version; const char* version;
const char* prefixes[] = const char* prefixes[] =
{ {
@@ -334,11 +355,12 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
NULL NULL
}; };
window = _glfwPlatformGetTls(&_glfw.contextSlot);
window->context.source = ctxconfig->source; window->context.source = ctxconfig->source;
window->context.client = GLFW_OPENGL_API; window->context.client = GLFW_OPENGL_API;
previous = _glfwPlatformGetTls(&_glfw.contextSlot);;
glfwMakeContextCurrent((GLFWwindow*) window);
window->context.GetIntegerv = (PFNGLGETINTEGERVPROC) window->context.GetIntegerv = (PFNGLGETINTEGERVPROC)
window->context.getProcAddress("glGetIntegerv"); window->context.getProcAddress("glGetIntegerv");
window->context.GetString = (PFNGLGETSTRINGPROC) window->context.GetString = (PFNGLGETSTRINGPROC)
@@ -346,6 +368,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
if (!window->context.GetIntegerv || !window->context.GetString) if (!window->context.GetIntegerv || !window->context.GetString)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken"); _glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -363,6 +386,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
"OpenGL ES version string retrieval is broken"); "OpenGL ES version string retrieval is broken");
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -394,6 +418,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
"No version found in OpenGL ES version string"); "No version found in OpenGL ES version string");
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -423,6 +448,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
window->context.major, window->context.minor); window->context.major, window->context.minor);
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -438,6 +464,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Entry point retrieval is broken"); "Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
} }
@@ -544,9 +571,12 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
window->context.swapBuffers(window); window->context.swapBuffers(window);
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_TRUE; return GLFW_TRUE;
} }
// Searches an extension string for the specified extension
//
GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions) GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions)
{ {
const char* start = extensions; const char* start = extensions;
@@ -587,7 +617,8 @@ GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle)
if (window && window->context.client == GLFW_NO_API) if (window && window->context.client == GLFW_NO_API)
{ {
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); _glfwInputError(GLFW_NO_WINDOW_CONTEXT,
"Cannot make current with a window that has no OpenGL or OpenGL ES context");
return; return;
} }
@@ -616,7 +647,8 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
if (window->context.client == GLFW_NO_API) if (window->context.client == GLFW_NO_API)
{ {
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); _glfwInputError(GLFW_NO_WINDOW_CONTEXT,
"Cannot swap buffers of a window that has no OpenGL or OpenGL ES context");
return; return;
} }
@@ -632,7 +664,8 @@ GLFWAPI void glfwSwapInterval(int interval)
window = _glfwPlatformGetTls(&_glfw.contextSlot); window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window) if (!window)
{ {
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL); _glfwInputError(GLFW_NO_CURRENT_CONTEXT,
"Cannot set swap interval without a current OpenGL or OpenGL ES context");
return; return;
} }
@@ -649,13 +682,14 @@ GLFWAPI int glfwExtensionSupported(const char* extension)
window = _glfwPlatformGetTls(&_glfw.contextSlot); window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window) if (!window)
{ {
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL); _glfwInputError(GLFW_NO_CURRENT_CONTEXT,
"Cannot query extension without a current OpenGL or OpenGL ES context");
return GLFW_FALSE; return GLFW_FALSE;
} }
if (*extension == '\0') if (*extension == '\0')
{ {
_glfwInputError(GLFW_INVALID_VALUE, "Extension name is empty string"); _glfwInputError(GLFW_INVALID_VALUE, "Extension name cannot be an empty string");
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -714,7 +748,8 @@ GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname)
window = _glfwPlatformGetTls(&_glfw.contextSlot); window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window) if (!window)
{ {
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL); _glfwInputError(GLFW_NO_CURRENT_CONTEXT,
"Cannot query entry point without a current OpenGL or OpenGL ES context");
return NULL; return NULL;
} }

View File

@@ -52,7 +52,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
ans.ldpaths.extend(('-framework', f)) ans.ldpaths.extend(('-framework', f))
elif module == 'wayland': elif module == 'wayland':
at_least_version('wayland-protocols', 1, 1) at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0]) ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0]) ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
ans.wayland_protocols = tuple(sinfo[module]['protocols']) ans.wayland_protocols = tuple(sinfo[module]['protocols'])
@@ -69,6 +69,8 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir): def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
for protocol in env.wayland_protocols: for protocol in env.wayland_protocols:
src = os.path.join(env.wayland_packagedir, protocol) src = os.path.join(env.wayland_packagedir, protocol)
if not os.path.exists(src):
raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol))
for ext in 'hc': for ext in 'hc':
dest = wayland_protocol_file_name(src, ext) dest = wayland_protocol_file_name(src, ext)
dest = os.path.join(dest_dir, dest) dest = os.path.join(dest_dir, dest)
@@ -80,6 +82,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
def collect_source_information(): def collect_source_information():
raw = open('src/CMakeLists.txt').read() raw = open('src/CMakeLists.txt').read()
mraw = open('CMakeLists.txt').read()
def extract_sources(group, start_pos=0): def extract_sources(group, start_pos=0):
for which in 'HEADERS SOURCES'.split(): for which in 'HEADERS SOURCES'.split():
@@ -91,8 +94,11 @@ def collect_source_information():
).group(1).strip().split() ).group(1).strip().split()
) )
wayland_protocols = re.search('WaylandProtocols\s+(\S+)\s+', mraw).group(1)
wayland_protocols = list(map(int, wayland_protocols.split('.')))
ans = { ans = {
'common': dict(extract_sources('common')), 'common': dict(extract_sources('common')),
'wayland_protocols': wayland_protocols,
} }
for group in 'cocoa win32 x11 wayland osmesa'.split(): for group in 'cocoa win32 x11 wayland osmesa'.split():
m = re.search('_GLFW_' + group.upper(), raw) m = re.search('_GLFW_' + group.upper(), raw)
@@ -242,9 +248,10 @@ def main():
sinfo = collect_source_information() sinfo = collect_source_information()
files_to_copy = set() files_to_copy = set()
for x in sinfo.values(): for x in sinfo.values():
headers, sources = x['headers'], x['sources'] if isinstance(x, dict):
for name in headers + sources: headers, sources = x['headers'], x['sources']
files_to_copy.add(os.path.abspath(os.path.join('src', name))) 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_header = os.path.abspath('include/GLFW/glfw3.h')
glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h') glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h')
os.chdir(base) os.chdir(base)

353
glfw/glfw3.h vendored
View File

@@ -493,17 +493,37 @@ extern "C" {
* @{ */ * @{ */
/*! @brief If this bit is set one or more Shift keys were held down. /*! @brief If this bit is set one or more Shift keys were held down.
*
* If this bit is set one or more Shift keys were held down.
*/ */
#define GLFW_MOD_SHIFT 0x0001 #define GLFW_MOD_SHIFT 0x0001
/*! @brief If this bit is set one or more Control keys were held down. /*! @brief If this bit is set one or more Control keys were held down.
*
* If this bit is set one or more Control keys were held down.
*/ */
#define GLFW_MOD_CONTROL 0x0002 #define GLFW_MOD_CONTROL 0x0002
/*! @brief If this bit is set one or more Alt keys were held down. /*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/ */
#define GLFW_MOD_ALT 0x0004 #define GLFW_MOD_ALT 0x0004
/*! @brief If this bit is set one or more Super keys were held down. /*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.
*/ */
#define GLFW_MOD_SUPER 0x0008 #define GLFW_MOD_SUPER 0x0008
/*! @brief If this bit is set the Caps Lock key is enabled.
*
* If this bit is set the Caps Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_CAPS_LOCK 0x0010
/*! @brief If this bit is set the Num Lock key is enabled.
*
* If this bit is set the Num Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_NUM_LOCK 0x0020
/*! @} */ /*! @} */
@@ -794,6 +814,11 @@ extern "C" {
* [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib). * [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
*/ */
#define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
/*! @brief Mouse cursor hover window attribute.
*
* Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib).
*/
#define GLFW_HOVERED 0x0002000B
/*! @brief Framebuffer bit depth hint. /*! @brief Framebuffer bit depth hint.
* *
@@ -944,8 +969,11 @@ extern "C" {
#define GLFW_CONTEXT_CREATION_API 0x0002200B #define GLFW_CONTEXT_CREATION_API 0x0002200B
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
#define GLFW_COCOA_FRAME_AUTOSAVE 0x00023002 #define GLFW_COCOA_FRAME_NAME 0x00023002
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003 #define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
#define GLFW_X11_CLASS_NAME 0x00024001
#define GLFW_X11_INSTANCE_NAME 0x00024002
/*! @} */ /*! @} */
#define GLFW_NO_API 0 #define GLFW_NO_API 0
@@ -963,6 +991,7 @@ extern "C" {
#define GLFW_CURSOR 0x00033001 #define GLFW_CURSOR 0x00033001
#define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_HIDDEN 0x00034002
@@ -1025,9 +1054,6 @@ extern "C" {
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001 #define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
#define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_COCOA_MENUBAR 0x00051002
#define GLFW_X11_WM_CLASS_NAME 0x00052001
#define GLFW_X11_WM_CLASS_CLASS 0x00052002
/*! @} */ /*! @} */
#define GLFW_DONT_CARE -1 #define GLFW_DONT_CARE -1
@@ -1046,7 +1072,7 @@ extern "C" {
* @sa @ref glfwGetProcAddress * @sa @ref glfwGetProcAddress
* *
* @since Added in version 3.0. * @since Added in version 3.0.
*
* @ingroup context * @ingroup context
*/ */
typedef void (*GLFWglproc)(void); typedef void (*GLFWglproc)(void);
@@ -1257,6 +1283,24 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
*/ */
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
/*! @brief The function signature for window content scale callbacks.
*
* This is the function signature for window content scale callback
* functions.
*
* @param[in] window The window whose content scale changed.
* @param[in] xscale The new x-axis content scale of the window.
* @param[in] yscale The new y-axis content scale of the window.
*
* @sa @ref window_scale
* @sa @ref glfwSetWindowContentScaleCallback
*
* @since Added in version 3.3.
*
* @ingroup window
*/
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
/*! @brief The function signature for mouse button callbacks. /*! @brief The function signature for mouse button callbacks.
* *
* This is the function signature for mouse button callback functions. * This is the function signature for mouse button callback functions.
@@ -1414,7 +1458,8 @@ typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**);
* This is the function signature for monitor configuration callback functions. * This is the function signature for monitor configuration callback functions.
* *
* @param[in] monitor The monitor that was connected or disconnected. * @param[in] monitor The monitor that was connected or disconnected.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
* values reserved for future use.
* *
* @sa @ref monitor_event * @sa @ref monitor_event
* @sa @ref glfwSetMonitorCallback * @sa @ref glfwSetMonitorCallback
@@ -1431,7 +1476,8 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int);
* functions. * functions.
* *
* @param[in] jid The joystick that was connected or disconnected. * @param[in] jid The joystick that was connected or disconnected.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
* values reserved for future use.
* *
* @sa @ref joystick_event * @sa @ref joystick_event
* @sa @ref glfwSetJoystickCallback * @sa @ref glfwSetJoystickCallback
@@ -1623,8 +1669,7 @@ GLFWAPI void glfwTerminate(void);
/*! @brief Sets the specified init hint to the desired value. /*! @brief Sets the specified init hint to the desired value.
* *
* This function sets hints for the next initialization of GLFW. Only integer * This function sets hints for the next initialization of GLFW.
* type hints can be set with this function.
* *
* The values you set hints to are never reset by GLFW, but they only take * The values you set hints to are never reset by GLFW, but they only take
* effect during initialization. Once GLFW has been initialized, any values * effect during initialization. Once GLFW has been initialized, any values
@@ -1633,7 +1678,7 @@ GLFWAPI void glfwTerminate(void);
* *
* Some hints are platform specific. These may be set on any platform but they * Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them. * will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions. * Setting these hints requires no platform specific headers or functions.
* *
* @param[in] hint The [init hint](@ref init_hints) to set. * @param[in] hint The [init hint](@ref init_hints) to set.
* @param[in] value The new value of the init hint. * @param[in] value The new value of the init hint.
@@ -1647,7 +1692,6 @@ GLFWAPI void glfwTerminate(void);
* *
* @sa init_hints * @sa init_hints
* @sa glfwInit * @sa glfwInit
* @sa glfwInitHintString
* *
* @since Added in version 3.3. * @since Added in version 3.3.
* *
@@ -1655,40 +1699,6 @@ GLFWAPI void glfwTerminate(void);
*/ */
GLFWAPI void glfwInitHint(int hint, int value); GLFWAPI void glfwInitHint(int hint, int value);
/*! @brief Sets the specified init hint to the desired value.
*
* This function sets hints for the next initialization of GLFW. Only string
* type hints can be set with this function.
*
* The values you set hints to are never reset by GLFW, but they only take
* effect during initialization. Once GLFW has been initialized, any values
* you set will be ignored until the library is terminated and initialized
* again.
*
* Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions.
*
* @param[in] hint The [init hint](@ref init_hints) to set.
* @param[in] value The new value of the init hint.
*
* @errors Possible errors include @ref GLFW_INVALID_ENUM and @ref
* GLFW_INVALID_VALUE.
*
* @remarks This function may be called before @ref glfwInit.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa init_hints
* @sa glfwInit
* @sa glfwInitHint
*
* @since Added in version 3.3.
*
* @ingroup init
*/
GLFWAPI void glfwInitHintString(int hint, const char* value);
/*! @brief Retrieves the version of the GLFW library. /*! @brief Retrieves the version of the GLFW library.
* *
* This function retrieves the major, minor and revision numbers of the GLFW * This function retrieves the major, minor and revision numbers of the GLFW
@@ -1985,6 +1995,56 @@ GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, flo
*/ */
GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor); GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor);
/*! @brief Sets the user pointer of the specified monitor.
*
* This function sets the user-defined pointer of the specified monitor. The
* current value is retained until the monitor is disconnected. The initial
* value is `NULL`.
*
* This function may be called from the monitor callback, even for a monitor
* that is being disconnected.
*
* @param[in] monitor The monitor whose pointer to set.
* @param[in] pointer The new value.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref monitor_userptr
* @sa @ref glfwGetMonitorUserPointer
*
* @since Added in version 3.3.
*
* @ingroup monitor
*/
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* monitor, void* pointer);
/*! @brief Returns the user pointer of the specified monitor.
*
* This function returns the current value of the user-defined pointer of the
* specified monitor. The initial value is `NULL`.
*
* This function may be called from the monitor callback, even for a monitor
* that is being disconnected.
*
* @param[in] monitor The monitor whose pointer to return.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref monitor_userptr
* @sa @ref glfwSetMonitorUserPointer
*
* @since Added in version 3.3.
*
* @ingroup monitor
*/
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* monitor);
/*! @brief Sets the monitor configuration callback. /*! @brief Sets the monitor configuration callback.
* *
* This function sets the monitor configuration callback, or removes the * This function sets the monitor configuration callback, or removes the
@@ -2184,6 +2244,7 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* monitor, const GLFWgammaramp* ramp);
* *
* @sa @ref window_hints * @sa @ref window_hints
* @sa @ref glfwWindowHint * @sa @ref glfwWindowHint
* @sa @ref glfwWindowHintString
* *
* @since Added in version 3.0. * @since Added in version 3.0.
* *
@@ -2194,14 +2255,20 @@ GLFWAPI void glfwDefaultWindowHints(void);
/*! @brief Sets the specified window hint to the desired value. /*! @brief Sets the specified window hint to the desired value.
* *
* This function sets hints for the next call to @ref glfwCreateWindow. The * This function sets hints for the next call to @ref glfwCreateWindow. The
* hints, once set, retain their values until changed by a call to @ref * hints, once set, retain their values until changed by a call to this
* glfwWindowHint or @ref glfwDefaultWindowHints, or until the library is * function or @ref glfwDefaultWindowHints, or until the library is terminated.
* terminated. *
* Only integer value hints can be set with this function. String value hints
* are set with @ref glfwWindowHintString.
* *
* This function does not check whether the specified hint values are valid. * This function does not check whether the specified hint values are valid.
* If you set hints to invalid values this will instead be reported by the next * If you set hints to invalid values this will instead be reported by the next
* call to @ref glfwCreateWindow. * call to @ref glfwCreateWindow.
* *
* Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions.
*
* @param[in] hint The [window hint](@ref window_hints) to set. * @param[in] hint The [window hint](@ref window_hints) to set.
* @param[in] value The new value of the window hint. * @param[in] value The new value of the window hint.
* *
@@ -2211,6 +2278,7 @@ GLFWAPI void glfwDefaultWindowHints(void);
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
* @sa @ref window_hints * @sa @ref window_hints
* @sa @ref glfwWindowHintString
* @sa @ref glfwDefaultWindowHints * @sa @ref glfwDefaultWindowHints
* *
* @since Added in version 3.0. Replaces `glfwOpenWindowHint`. * @since Added in version 3.0. Replaces `glfwOpenWindowHint`.
@@ -2219,6 +2287,44 @@ GLFWAPI void glfwDefaultWindowHints(void);
*/ */
GLFWAPI void glfwWindowHint(int hint, int value); GLFWAPI void glfwWindowHint(int hint, int value);
/*! @brief Sets the specified window hint to the desired value.
*
* This function sets hints for the next call to @ref glfwCreateWindow. The
* hints, once set, retain their values until changed by a call to this
* function or @ref glfwDefaultWindowHints, or until the library is terminated.
*
* Only string type hints can be set with this function. Integer value hints
* are set with @ref glfwWindowHint.
*
* This function does not check whether the specified hint values are valid.
* If you set hints to invalid values this will instead be reported by the next
* call to @ref glfwCreateWindow.
*
* Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions.
*
* @param[in] hint The [window hint](@ref window_hints) to set.
* @param[in] value The new value of the window hint.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
*
* @pointer_lifetime The specified string is copied before this function
* returns.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_hints
* @sa @ref glfwWindowHint
* @sa @ref glfwDefaultWindowHints
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI void glfwWindowHintString(int hint, const char* value);
/*! @brief Creates a window and its associated context. /*! @brief Creates a window and its associated context.
* *
* This function creates a window and its associated OpenGL or OpenGL ES * This function creates a window and its associated OpenGL or OpenGL ES
@@ -2329,9 +2435,8 @@ GLFWAPI void glfwWindowHint(int hint, int value);
* `CMake/MacOSXBundleInfo.plist.in` in the source tree. * `CMake/MacOSXBundleInfo.plist.in` in the source tree.
* *
* @remark @macos When activating frame autosaving with * @remark @macos When activating frame autosaving with
* [GLFW_COCOA_FRAME_AUTOSAVE](@ref GLFW_COCOA_FRAME_AUTOSAVE_hint), the * [GLFW_COCOA_FRAME_NAME](@ref GLFW_COCOA_FRAME_NAME_hint), the specified
* specified window size may be overriden by a previously saved size and * window size and position may be overriden by previously saved values.
* position.
* *
* @remark @x11 Some window managers will not respect the placement of * @remark @x11 Some window managers will not respect the placement of
* initially hidden windows. * initially hidden windows.
@@ -2341,15 +2446,12 @@ GLFWAPI void glfwWindowHint(int hint, int value);
* query the final size, position or other attributes directly after window * query the final size, position or other attributes directly after window
* creation. * creation.
* *
* @remark @x11 The name and class of the `WM_CLASS` window property will by * @remark @x11 The class part of the `WM_CLASS` window property will by
* default be set to the window title passed to this function. Set the @ref * default be set to the window title passed to this function. The instance
* GLFW_X11_WM_CLASS_NAME and @ref GLFW_X11_WM_CLASS_CLASS init hints before * part will use the contents of the `RESOURCE_NAME` environment variable, if
* initialization to override this. You can also set the title int he following * present and not empty, or fall back to the window title. Set the @ref
* special format, which allows setting the two parts of the WM_CLASS property * GLFW_X11_CLASS_NAME and @ref GLFW_X11_INSTANCE_NAME window hints to override
* and the window title independently: * this.
* <01> WM_CLASS name <30> WM_CLASS class <30> title
* Here <01> refers to the byte value 01 (ASCII start-of-header) and <30> refers
* to the byte value 30 (ASCII record separator).
* *
* @remark @wayland The window frame is currently unimplemented, as if * @remark @wayland The window frame is currently unimplemented, as if
* [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`. * [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`.
@@ -2359,10 +2461,6 @@ GLFWAPI void glfwWindowHint(int hint, int value);
* @remark @wayland A full screen window will not attempt to change the mode, * @remark @wayland A full screen window will not attempt to change the mode,
* no matter what the requested size or refresh rate. * no matter what the requested size or refresh rate.
* *
* @remark @wayland The wl_shell protocol does not support window
* icons, the window will inherit the one defined in the application's
* desktop file, so this function emits @ref GLFW_PLATFORM_ERROR.
*
* @remark @wayland Screensaver inhibition requires the idle-inhibit protocol * @remark @wayland Screensaver inhibition requires the idle-inhibit protocol
* to be implemented in the user's compositor. * to be implemented in the user's compositor.
* *
@@ -2506,9 +2604,9 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/) * [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
* in the Mac Developer Library. * in the Mac Developer Library.
* *
* @remark @wayland The wl_shell protocol does not support icons, the window * @remark @wayland There is no existing protocol to change an icon, the
* will inherit the one defined in the application's desktop file, so this * window will thus inherit the one defined in the application's desktop file.
* function emits @ref GLFW_PLATFORM_ERROR. * This function always emits @ref GLFW_PLATFORM_ERROR.
* *
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
@@ -2836,6 +2934,7 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
* @sa @ref window_scale * @sa @ref window_scale
* @sa @ref glfwSetWindowContentScaleCallback
* @sa @ref glfwGetMonitorContentScale * @sa @ref glfwGetMonitorContentScale
* *
* @since Added in version 3.3. * @since Added in version 3.3.
@@ -2915,7 +3014,8 @@ GLFWAPI void glfwSetWindowOpacity(GLFWwindow* window, float opacity);
* GLFW_PLATFORM_ERROR. * GLFW_PLATFORM_ERROR.
* *
* @remark @wayland There is no concept of iconification in wl_shell, this * @remark @wayland There is no concept of iconification in wl_shell, this
* function will always emit @ref GLFW_PLATFORM_ERROR. * function will emit @ref GLFW_PLATFORM_ERROR when using this deprecated
* protocol.
* *
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
@@ -3237,7 +3337,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
* The supported attributes are [GLFW_DECORATED](@ref GLFW_DECORATED_attrib), * The supported attributes are [GLFW_DECORATED](@ref GLFW_DECORATED_attrib),
* [GLFW_RESIZABLE](@ref GLFW_RESIZABLE_attrib), * [GLFW_RESIZABLE](@ref GLFW_RESIZABLE_attrib),
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib) and * [GLFW_FLOATING](@ref GLFW_FLOATING_attrib) and
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib). * [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib).
* *
* Some of these attributes are ignored for full screen windows. The new * Some of these attributes are ignored for full screen windows. The new
* value will take effect if the window is later made windowed. * value will take effect if the window is later made windowed.
@@ -3467,7 +3567,7 @@ GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwi
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
* *
* @remark @wayland The wl_shell protocol has no concept of iconification, * @remark @wayland The wl_shell protocol has no concept of iconification,
* this callback will never be called. * this callback will never be called when using this deprecated protocol.
* *
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
@@ -3525,6 +3625,30 @@ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window,
*/ */
GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun); GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun);
/*! @brief Sets the window content scale callback for the specified window.
*
* This function sets the window content scale callback of the specified window,
* which is called when the content scale of the specified window changes.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or the
* library had not been [initialized](@ref intro_init).
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_scale
* @sa @ref glfwGetWindowContentScale
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun cbfun);
/*! @brief Processes all pending events. /*! @brief Processes all pending events.
* *
* This function processes only those events that are already in the event * This function processes only those events that are already in the event
@@ -3689,12 +3813,12 @@ GLFWAPI void glfwPostEmptyEvent(void);
/*! @brief Returns the value of an input option for the specified window. /*! @brief Returns the value of an input option for the specified window.
* *
* This function returns the value of an input option for the specified window. * This function returns the value of an input option for the specified window.
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS or * The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS. * @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
* *
* @param[in] window The window to query. * @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`. * `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM. * GLFW_INVALID_ENUM.
@@ -3712,8 +3836,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
/*! @brief Sets an input option for the specified window. /*! @brief Sets an input option for the specified window.
* *
* This function sets an input mode option for the specified window. The mode * This function sets an input mode option for the specified window. The mode
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS or * must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS. * @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
* *
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes: * modes:
@@ -3739,9 +3863,15 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* you are only interested in whether mouse buttons have been pressed but not * you are only interested in whether mouse buttons have been pressed but not
* when or in which order. * when or in which order.
* *
* If the mode is `GLFW_LOCK_KEY_MODS`, the value must be either `GLFW_TRUE` to
* enable lock key modifier bits, or `GLFW_FALSE` to disable them. If enabled,
* callbacks that receive modifier bits will also have the @ref
* GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on,
* and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on.
*
* @param[in] window The window whose input mode to set. * @param[in] window The window whose input mode to set.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`. * `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
* @param[in] value The new value of the specified input mode. * @param[in] value The new value of the specified input mode.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
@@ -4481,10 +4611,10 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count);
* it with the corresponding direction. * it with the corresponding direction.
* *
* @code * @code
* if (hats[2] & GLFW_HAT_RIGHT) * if (hats[2] & GLFW_HAT_RIGHT)
* { * {
* // State of hat 2 could be right-up, right or right-down * // State of hat 2 could be right-up, right or right-down
* } * }
* @endcode * @endcode
* *
* If the specified joystick is not present this function will return `NULL` * If the specified joystick is not present this function will return `NULL`
@@ -4588,6 +4718,56 @@ GLFWAPI const char* glfwGetJoystickName(int jid);
*/ */
GLFWAPI const char* glfwGetJoystickGUID(int jid); GLFWAPI const char* glfwGetJoystickGUID(int jid);
/*! @brief Sets the user pointer of the specified joystick.
*
* This function sets the user-defined pointer of the specified joystick. The
* current value is retained until the joystick is disconnected. The initial
* value is `NULL`.
*
* This function may be called from the joystick callback, even for a joystick
* that is being disconnected.
*
* @param[in] joystick The joystick whose pointer to set.
* @param[in] pointer The new value.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref joystick_userptr
* @sa @ref glfwGetJoystickUserPointer
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer);
/*! @brief Returns the user pointer of the specified joystick.
*
* This function returns the current value of the user-defined pointer of the
* specified joystick. The initial value is `NULL`.
*
* This function may be called from the joystick callback, even for a joystick
* that is being disconnected.
*
* @param[in] joystick The joystick whose pointer to return.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref joystick_userptr
* @sa @ref glfwSetJoystickUserPointer
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI void* glfwGetJoystickUserPointer(int jid);
/*! @brief Returns whether the specified joystick has a gamepad mapping. /*! @brief Returns whether the specified joystick has a gamepad mapping.
* *
* This function returns whether the specified joystick is both present and has * This function returns whether the specified joystick is both present and has
@@ -4864,7 +5044,7 @@ GLFWAPI void glfwSetTime(double time);
* 1&nbsp;/&nbsp;frequency seconds. To get the frequency, call @ref * 1&nbsp;/&nbsp;frequency seconds. To get the frequency, call @ref
* glfwGetTimerFrequency. * glfwGetTimerFrequency.
* *
* @return The value of the timer, or zero if an * @return The value of the timer, or zero if an
* [error](@ref error_handling) occurred. * [error](@ref error_handling) occurred.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED. * @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
@@ -5290,6 +5470,11 @@ GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhys
* glfwGetRequiredInstanceExtensions to check what instance extensions are * glfwGetRequiredInstanceExtensions to check what instance extensions are
* required. * required.
* *
* The window surface cannot be shared with another API so the window must
* have been created with the [client api hint](@ref GLFW_CLIENT_API_attrib)
* set to `GLFW_NO_API` otherwise it generates a @ref GLFW_INVALID_VALUE error
* and returns `VK_ERROR_NATIVE_WINDOW_IN_USE_KHR`.
*
* The window surface must be destroyed before the specified Vulkan instance. * The window surface must be destroyed before the specified Vulkan instance.
* It is the responsibility of the caller to destroy the window surface. GLFW * It is the responsibility of the caller to destroy the window surface. GLFW
* does not destroy it for you. Call `vkDestroySurfaceKHR` to destroy the * does not destroy it for you. Call `vkDestroySurfaceKHR` to destroy the
@@ -5305,7 +5490,7 @@ GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhys
* [error](@ref error_handling) occurred. * [error](@ref error_handling) occurred.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
* GLFW_API_UNAVAILABLE and @ref GLFW_PLATFORM_ERROR. * GLFW_API_UNAVAILABLE, @ref GLFW_PLATFORM_ERROR and @ref GLFW_INVALID_VALUE
* *
* @remark If an error occurs before the creation call is made, GLFW returns * @remark If an error occurs before the creation call is made, GLFW returns
* the Vulkan error code most appropriate for the error. Appropriate use of * the Vulkan error code most appropriate for the error. Appropriate use of

36
glfw/glx_context.c vendored
View File

@@ -223,7 +223,7 @@ static GLFWglproc getProcAddressGLX(const char* procname)
else if (_glfw.glx.GetProcAddressARB) else if (_glfw.glx.GetProcAddressARB)
return _glfw.glx.GetProcAddressARB((const GLubyte*) procname); return _glfw.glx.GetProcAddressARB((const GLubyte*) procname);
else else
return dlsym(_glfw.glx.handle, procname); return _glfw_dlsym(_glfw.glx.handle, procname);
} }
// Destroy the OpenGL context // Destroy the OpenGL context
@@ -271,7 +271,7 @@ GLFWbool _glfwInitGLX(void)
for (i = 0; sonames[i]; i++) for (i = 0; sonames[i]; i++)
{ {
_glfw.glx.handle = dlopen(sonames[i], RTLD_LAZY | RTLD_GLOBAL); _glfw.glx.handle = _glfw_dlopen(sonames[i]);
if (_glfw.glx.handle) if (_glfw.glx.handle)
break; break;
} }
@@ -283,35 +283,35 @@ GLFWbool _glfwInitGLX(void)
} }
_glfw.glx.GetFBConfigs = _glfw.glx.GetFBConfigs =
dlsym(_glfw.glx.handle, "glXGetFBConfigs"); _glfw_dlsym(_glfw.glx.handle, "glXGetFBConfigs");
_glfw.glx.GetFBConfigAttrib = _glfw.glx.GetFBConfigAttrib =
dlsym(_glfw.glx.handle, "glXGetFBConfigAttrib"); _glfw_dlsym(_glfw.glx.handle, "glXGetFBConfigAttrib");
_glfw.glx.GetClientString = _glfw.glx.GetClientString =
dlsym(_glfw.glx.handle, "glXGetClientString"); _glfw_dlsym(_glfw.glx.handle, "glXGetClientString");
_glfw.glx.QueryExtension = _glfw.glx.QueryExtension =
dlsym(_glfw.glx.handle, "glXQueryExtension"); _glfw_dlsym(_glfw.glx.handle, "glXQueryExtension");
_glfw.glx.QueryVersion = _glfw.glx.QueryVersion =
dlsym(_glfw.glx.handle, "glXQueryVersion"); _glfw_dlsym(_glfw.glx.handle, "glXQueryVersion");
_glfw.glx.DestroyContext = _glfw.glx.DestroyContext =
dlsym(_glfw.glx.handle, "glXDestroyContext"); _glfw_dlsym(_glfw.glx.handle, "glXDestroyContext");
_glfw.glx.MakeCurrent = _glfw.glx.MakeCurrent =
dlsym(_glfw.glx.handle, "glXMakeCurrent"); _glfw_dlsym(_glfw.glx.handle, "glXMakeCurrent");
_glfw.glx.SwapBuffers = _glfw.glx.SwapBuffers =
dlsym(_glfw.glx.handle, "glXSwapBuffers"); _glfw_dlsym(_glfw.glx.handle, "glXSwapBuffers");
_glfw.glx.QueryExtensionsString = _glfw.glx.QueryExtensionsString =
dlsym(_glfw.glx.handle, "glXQueryExtensionsString"); _glfw_dlsym(_glfw.glx.handle, "glXQueryExtensionsString");
_glfw.glx.CreateNewContext = _glfw.glx.CreateNewContext =
dlsym(_glfw.glx.handle, "glXCreateNewContext"); _glfw_dlsym(_glfw.glx.handle, "glXCreateNewContext");
_glfw.glx.CreateWindow = _glfw.glx.CreateWindow =
dlsym(_glfw.glx.handle, "glXCreateWindow"); _glfw_dlsym(_glfw.glx.handle, "glXCreateWindow");
_glfw.glx.DestroyWindow = _glfw.glx.DestroyWindow =
dlsym(_glfw.glx.handle, "glXDestroyWindow"); _glfw_dlsym(_glfw.glx.handle, "glXDestroyWindow");
_glfw.glx.GetProcAddress = _glfw.glx.GetProcAddress =
dlsym(_glfw.glx.handle, "glXGetProcAddress"); _glfw_dlsym(_glfw.glx.handle, "glXGetProcAddress");
_glfw.glx.GetProcAddressARB = _glfw.glx.GetProcAddressARB =
dlsym(_glfw.glx.handle, "glXGetProcAddressARB"); _glfw_dlsym(_glfw.glx.handle, "glXGetProcAddressARB");
_glfw.glx.GetVisualFromFBConfig = _glfw.glx.GetVisualFromFBConfig =
dlsym(_glfw.glx.handle, "glXGetVisualFromFBConfig"); _glfw_dlsym(_glfw.glx.handle, "glXGetVisualFromFBConfig");
if (!_glfw.glx.GetFBConfigs || if (!_glfw.glx.GetFBConfigs ||
!_glfw.glx.GetFBConfigAttrib || !_glfw.glx.GetFBConfigAttrib ||
@@ -428,7 +428,7 @@ void _glfwTerminateGLX(void)
if (_glfw.glx.handle) if (_glfw.glx.handle)
{ {
dlclose(_glfw.glx.handle); _glfw_dlclose(_glfw.glx.handle);
_glfw.glx.handle = NULL; _glfw.glx.handle = NULL;
} }
} }

46
glfw/init.c vendored
View File

@@ -35,8 +35,9 @@
#include <assert.h> #include <assert.h>
// The global variables below comprise all global data in GLFW. // The global variables below comprise all mutable global data in GLFW
// Any other global variable is a bug. //
// Any other global variable is a bug
// Global state shared between compilation units of GLFW // Global state shared between compilation units of GLFW
// //
@@ -53,10 +54,6 @@ static _GLFWinitconfig _glfwInitHints =
{ {
GLFW_TRUE, // macOS menu bar GLFW_TRUE, // macOS menu bar
GLFW_TRUE // macOS bundle chdir GLFW_TRUE // macOS bundle chdir
},
{
"", // X11 WM_CLASS name
"" // X11 WM_CLASS class
} }
}; };
@@ -141,10 +138,25 @@ static void terminate(void)
} }
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
char* _glfw_strdup(const char* source)
{
const size_t length = strlen(source);
char* result = calloc(length + 1, 1);
strcpy(result, source);
return result;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW event API ////// ////// GLFW event API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Notifies shared code of an error
//
void _glfwInputError(int code, const char* format, ...) void _glfwInputError(int code, const char* format, ...)
{ {
_GLFWerror* error; _GLFWerror* error;
@@ -260,27 +272,7 @@ GLFWAPI void glfwInitHint(int hint, int value)
} }
_glfwInputError(GLFW_INVALID_ENUM, _glfwInputError(GLFW_INVALID_ENUM,
"Invalid integer type init hint 0x%08X", hint); "Invalid init hint 0x%08X", hint);
}
GLFWAPI void glfwInitHintString(int hint, const char* value)
{
assert(value != NULL);
switch (hint)
{
case GLFW_X11_WM_CLASS_NAME:
strncpy(_glfwInitHints.x11.className, value,
sizeof(_glfwInitHints.x11.className) - 1);
return;
case GLFW_X11_WM_CLASS_CLASS:
strncpy(_glfwInitHints.x11.classClass, value,
sizeof(_glfwInitHints.x11.classClass) - 1);
return;
}
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid string type init hint 0x%08X", hint);
} }
GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev) GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev)

141
glfw/input.c vendored
View File

@@ -57,6 +57,58 @@ static _GLFWmapping* findMapping(const char* guid)
return NULL; return NULL;
} }
// Checks whether a gamepad mapping element is present in the hardware
//
static GLFWbool isValidElementForJoystick(const _GLFWmapelement* e,
const _GLFWjoystick* js)
{
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->value >> 4) >= js->hatCount)
return GLFW_FALSE;
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->value >= js->buttonCount)
return GLFW_FALSE;
else if (e->type == _GLFW_JOYSTICK_AXIS && e->value >= js->axisCount)
return GLFW_FALSE;
return GLFW_TRUE;
}
// Finds a mapping based on joystick GUID and verifies element indices
//
static _GLFWmapping* findValidMapping(const _GLFWjoystick* js)
{
_GLFWmapping* mapping = findMapping(js->guid);
if (mapping)
{
int i;
for (i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++)
{
if (!isValidElementForJoystick(mapping->buttons + i, js))
{
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid button in gamepad mapping %s (%s)",
mapping->guid,
mapping->name);
return NULL;
}
}
for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++)
{
if (!isValidElementForJoystick(mapping->axes + i, js))
{
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid axis in gamepad mapping %s (%s)",
mapping->guid,
mapping->name);
return NULL;
}
}
}
return mapping;
}
// Parses an SDL_GameControllerDB line and adds it to the mapping list // Parses an SDL_GameControllerDB line and adds it to the mapping list
// //
static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string) static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
@@ -172,6 +224,8 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
////// GLFW event API ////// ////// GLFW event API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Notifies shared code of a physical key event
//
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods) void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods)
{ {
if (key >= 0 && key <= GLFW_KEY_LAST) if (key >= 0 && key <= GLFW_KEY_LAST)
@@ -193,15 +247,24 @@ void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int m
action = GLFW_REPEAT; action = GLFW_REPEAT;
} }
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.key) if (window->callbacks.key)
window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods); window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods);
} }
// Notifies shared code of a Unicode codepoint input event
// The 'plain' parameter determines whether to emit a regular character event
//
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain) void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain)
{ {
if (codepoint < 32 || (codepoint > 126 && codepoint < 160)) if (codepoint < 32 || (codepoint > 126 && codepoint < 160))
return; return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.charmods) if (window->callbacks.charmods)
window->callbacks.charmods((GLFWwindow*) window, codepoint, mods); window->callbacks.charmods((GLFWwindow*) window, codepoint, mods);
@@ -212,17 +275,24 @@ void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWb
} }
} }
// Notifies shared code of a scroll event
//
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset) void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
{ {
if (window->callbacks.scroll) if (window->callbacks.scroll)
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset); window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
} }
// Notifies shared code of a mouse button click event
//
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods) void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods)
{ {
if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST) if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST)
return; return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (action == GLFW_RELEASE && window->stickyMouseButtons) if (action == GLFW_RELEASE && window->stickyMouseButtons)
window->mouseButtons[button] = _GLFW_STICK; window->mouseButtons[button] = _GLFW_STICK;
else else
@@ -232,6 +302,9 @@ void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods)
window->callbacks.mouseButton((GLFWwindow*) window, button, action, mods); window->callbacks.mouseButton((GLFWwindow*) window, button, action, mods);
} }
// Notifies shared code of a cursor motion event
// The position is specified in client-area relative screen coordinates
//
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos) void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos)
{ {
if (window->virtualCursorPosX == xpos && window->virtualCursorPosY == ypos) if (window->virtualCursorPosX == xpos && window->virtualCursorPosY == ypos)
@@ -244,18 +317,24 @@ void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos)
window->callbacks.cursorPos((GLFWwindow*) window, xpos, ypos); window->callbacks.cursorPos((GLFWwindow*) window, xpos, ypos);
} }
// Notifies shared code of a cursor enter/leave event
//
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered) void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered)
{ {
if (window->callbacks.cursorEnter) if (window->callbacks.cursorEnter)
window->callbacks.cursorEnter((GLFWwindow*) window, entered); window->callbacks.cursorEnter((GLFWwindow*) window, entered);
} }
// Notifies shared code of files or directories dropped on a window
//
void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths) void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths)
{ {
if (window->callbacks.drop) if (window->callbacks.drop)
window->callbacks.drop((GLFWwindow*) window, count, paths); window->callbacks.drop((GLFWwindow*) window, count, paths);
} }
// Notifies shared code of a joystick connection or disconnection
//
void _glfwInputJoystick(_GLFWjoystick* js, int event) void _glfwInputJoystick(_GLFWjoystick* js, int event)
{ {
const int jid = (int) (js - _glfw.joysticks); const int jid = (int) (js - _glfw.joysticks);
@@ -264,16 +343,22 @@ void _glfwInputJoystick(_GLFWjoystick* js, int event)
_glfw.callbacks.joystick(jid, event); _glfw.callbacks.joystick(jid, event);
} }
// Notifies shared code of the new value of a joystick axis
//
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value) void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value)
{ {
js->axes[axis] = value; js->axes[axis] = value;
} }
// Notifies shared code of the new value of a joystick button
//
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value) void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value)
{ {
js->buttons[button] = value; js->buttons[button] = value;
} }
// Notifies shared code of the new value of a joystick hat
//
void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value) void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value)
{ {
const int base = js->buttonCount + hat * 4; const int base = js->buttonCount + hat * 4;
@@ -291,6 +376,8 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value)
////// GLFW internal API ////// ////// GLFW internal API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Returns an available joystick object with arrays and name allocated
//
_GLFWjoystick* _glfwAllocJoystick(const char* name, _GLFWjoystick* _glfwAllocJoystick(const char* name,
const char* guid, const char* guid,
int axisCount, int axisCount,
@@ -311,20 +398,22 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name,
js = _glfw.joysticks + jid; js = _glfw.joysticks + jid;
js->present = GLFW_TRUE; js->present = GLFW_TRUE;
js->name = strdup(name); js->name = _glfw_strdup(name);
js->axes = calloc(axisCount, sizeof(float)); js->axes = calloc(axisCount, sizeof(float));
js->buttons = calloc(buttonCount + hatCount * 4, 1); js->buttons = calloc(buttonCount + hatCount * 4, 1);
js->hats = calloc(hatCount, 1); js->hats = calloc(hatCount, 1);
js->axisCount = axisCount; js->axisCount = axisCount;
js->buttonCount = buttonCount; js->buttonCount = buttonCount;
js->hatCount = hatCount; js->hatCount = hatCount;
js->mapping = findMapping(guid);
strcpy(js->guid, guid); strcpy(js->guid, guid);
js->mapping = findValidMapping(js);
return js; return js;
} }
// Frees arrays and name and flags the joystick object as unused
//
void _glfwFreeJoystick(_GLFWjoystick* js) void _glfwFreeJoystick(_GLFWjoystick* js)
{ {
free(js->name); free(js->name);
@@ -354,6 +443,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
return window->stickyKeys; return window->stickyKeys;
case GLFW_STICKY_MOUSE_BUTTONS: case GLFW_STICKY_MOUSE_BUTTONS:
return window->stickyMouseButtons; return window->stickyMouseButtons;
case GLFW_LOCK_KEY_MODS:
return window->lockKeyMods;
} }
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
@@ -409,7 +500,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
} }
} }
window->stickyKeys = value ? GLFW_TRUE : GLFW_FALSE; window->stickyKeys = value;
} }
else if (mode == GLFW_STICKY_MOUSE_BUTTONS) else if (mode == GLFW_STICKY_MOUSE_BUTTONS)
{ {
@@ -429,8 +520,10 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
} }
} }
window->stickyMouseButtons = value ? GLFW_TRUE : GLFW_FALSE; window->stickyMouseButtons = value;
} }
else if (mode == GLFW_LOCK_KEY_MODS)
window->lockKeyMods = value ? GLFW_TRUE : GLFW_FALSE;
else else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
} }
@@ -914,6 +1007,38 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
return js->guid; return js->guid;
} }
GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer)
{
_GLFWjoystick* js;
assert(jid >= GLFW_JOYSTICK_1);
assert(jid <= GLFW_JOYSTICK_LAST);
_GLFW_REQUIRE_INIT();
js = _glfw.joysticks + jid;
if (!js->present)
return;
js->userPointer = pointer;
}
GLFWAPI void* glfwGetJoystickUserPointer(int jid)
{
_GLFWjoystick* js;
assert(jid >= GLFW_JOYSTICK_1);
assert(jid <= GLFW_JOYSTICK_LAST);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
return js->userPointer;
}
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
{ {
_GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -973,7 +1098,7 @@ GLFWAPI int glfwUpdateGamepadMappings(const char* string)
{ {
_GLFWjoystick* js = _glfw.joysticks + jid; _GLFWjoystick* js = _glfw.joysticks + jid;
if (js->present) if (js->present)
js->mapping = findMapping(js->guid); js->mapping = findValidMapping(js);
} }
return GLFW_TRUE; return GLFW_TRUE;
@@ -1065,7 +1190,7 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
{ {
if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_AXIS) if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_AXIS)
{ {
if (fabs(js->axes[js->mapping->buttons[i].value]) > 0.5) if (fabsf(js->axes[js->mapping->buttons[i].value]) > 0.5f)
state->buttons[i] = GLFW_PRESS; state->buttons[i] = GLFW_PRESS;
} }
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT) else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
@@ -1085,8 +1210,8 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
state->axes[i] = js->axes[js->mapping->axes[i].value]; state->axes[i] = js->axes[js->mapping->axes[i].value];
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT) else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
{ {
const unsigned int hat = js->mapping->buttons[i].value >> 4; const unsigned int hat = js->mapping->axes[i].value >> 4;
const unsigned int bit = js->mapping->buttons[i].value & 0xf; const unsigned int bit = js->mapping->axes[i].value & 0xf;
if (js->hats[hat] & bit) if (js->hats[hat] & bit)
state->axes[i] = 1.f; state->axes[i] = 1.f;
} }

476
glfw/internal.h vendored
View File

@@ -196,38 +196,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
#error "No supported window creation API selected" #error "No supported window creation API selected"
#endif #endif
//========================================================================
// Doxygen group definitions
//========================================================================
/*! @defgroup platform Platform interface
* @brief The interface implemented by the platform-specific code.
*
* The platform API is the interface exposed by the platform-specific code for
* each platform and is called by the shared code of the public API It mirrors
* the public API except it uses objects instead of handles.
*/
/*! @defgroup event Event interface
* @brief The interface used by the platform-specific code to report events.
*
* The event API is used by the platform-specific code to notify the shared
* code of events that can be translated into state changes and/or callback
* calls.
*/
/*! @defgroup utility Utility functions
* @brief Various utility functions for internal use.
*
* These functions are shared code and may be used by any part of GLFW
* Each platform may add its own utility functions, but those must only be
* called by the platform-specific code
*/
//========================================================================
// Helper macros
//========================================================================
// Constructs a version number string from the public header macros // Constructs a version number string from the public header macros
#define _GLFW_CONCAT_VERSION(m, n, r) #m "." #n "." #r #define _GLFW_CONCAT_VERSION(m, n, r) #m "." #n "." #r
#define _GLFW_MAKE_VERSION(m, n, r) _GLFW_CONCAT_VERSION(m, n, r) #define _GLFW_MAKE_VERSION(m, n, r) _GLFW_CONCAT_VERSION(m, n, r)
@@ -258,11 +226,8 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
y = t; \ y = t; \
} }
// Per-thread error structure
//======================================================================== //
// Platform-independent structures
//========================================================================
struct _GLFWerror struct _GLFWerror
{ {
_GLFWerror* next; _GLFWerror* next;
@@ -270,10 +235,10 @@ struct _GLFWerror
char description[_GLFW_MESSAGE_SIZE]; char description[_GLFW_MESSAGE_SIZE];
}; };
/*! @brief Initialization configuration. // Initialization configuration
* //
* Parameters relating to the initialization of the library. // Parameters relating to the initialization of the library
*/ //
struct _GLFWinitconfig struct _GLFWinitconfig
{ {
GLFWbool hatButtons; GLFWbool hatButtons;
@@ -281,18 +246,14 @@ struct _GLFWinitconfig
GLFWbool menubar; GLFWbool menubar;
GLFWbool chdir; GLFWbool chdir;
} ns; } ns;
struct {
char className[256];
char classClass[256];
} x11;
}; };
/*! @brief Window configuration. // Window configuration
* //
* Parameters relating to the creation of the window but not directly related // Parameters relating to the creation of the window but not directly related
* to the framebuffer. This is used to pass window creation parameters from // to the framebuffer. This is used to pass window creation parameters from
* shared code to the platform API. // shared code to the platform API.
*/ //
struct _GLFWwndconfig struct _GLFWwndconfig
{ {
int width; int width;
@@ -308,16 +269,20 @@ struct _GLFWwndconfig
GLFWbool centerCursor; GLFWbool centerCursor;
struct { struct {
GLFWbool retina; GLFWbool retina;
GLFWbool frame; char frameName[256];
} ns; } ns;
struct {
char className[256];
char instanceName[256];
} x11;
}; };
/*! @brief Context configuration. // Context configuration
* //
* Parameters relating to the creation of the context but not directly related // Parameters relating to the creation of the context but not directly related
* to the framebuffer. This is used to pass context creation parameters from // to the framebuffer. This is used to pass context creation parameters from
* shared code to the platform API. // shared code to the platform API.
*/ //
struct _GLFWctxconfig struct _GLFWctxconfig
{ {
int client; int client;
@@ -336,14 +301,14 @@ struct _GLFWctxconfig
} nsgl; } nsgl;
}; };
/*! @brief Framebuffer configuration. // Framebuffer configuration
* //
* This describes buffers and their sizes. It also contains // This describes buffers and their sizes. It also contains
* a platform-specific ID used to map back to the backend API object. // a platform-specific ID used to map back to the backend API object.
* //
* It is used to pass framebuffer parameters from shared code to the platform // It is used to pass framebuffer parameters from shared code to the platform
* API and also to enumerate and select available framebuffer configs. // API and also to enumerate and select available framebuffer configs.
*/ //
struct _GLFWfbconfig struct _GLFWfbconfig
{ {
int redBits; int redBits;
@@ -365,8 +330,8 @@ struct _GLFWfbconfig
uintptr_t handle; uintptr_t handle;
}; };
/*! @brief Context structure. // Context structure
*/ //
struct _GLFWcontext struct _GLFWcontext
{ {
int client; int client;
@@ -396,8 +361,8 @@ struct _GLFWcontext
_GLFW_OSMESA_CONTEXT_STATE; _GLFW_OSMESA_CONTEXT_STATE;
}; };
/*! @brief Window and context structure. // Window and context structure
*/ //
struct _GLFWwindow struct _GLFWwindow
{ {
struct _GLFWwindow* next; struct _GLFWwindow* next;
@@ -419,6 +384,7 @@ struct _GLFWwindow
GLFWbool stickyKeys; GLFWbool stickyKeys;
GLFWbool stickyMouseButtons; GLFWbool stickyMouseButtons;
GLFWbool lockKeyMods;
int cursorMode; int cursorMode;
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1]; char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
char keys[GLFW_KEY_LAST + 1]; char keys[GLFW_KEY_LAST + 1];
@@ -436,6 +402,7 @@ struct _GLFWwindow
GLFWwindowiconifyfun iconify; GLFWwindowiconifyfun iconify;
GLFWwindowmaximizefun maximize; GLFWwindowmaximizefun maximize;
GLFWframebuffersizefun fbsize; GLFWframebuffersizefun fbsize;
GLFWwindowcontentscalefun scale;
GLFWmousebuttonfun mouseButton; GLFWmousebuttonfun mouseButton;
GLFWcursorposfun cursorPos; GLFWcursorposfun cursorPos;
GLFWcursorenterfun cursorEnter; GLFWcursorenterfun cursorEnter;
@@ -450,11 +417,12 @@ struct _GLFWwindow
_GLFW_PLATFORM_WINDOW_STATE; _GLFW_PLATFORM_WINDOW_STATE;
}; };
/*! @brief Monitor structure. // Monitor structure
*/ //
struct _GLFWmonitor struct _GLFWmonitor
{ {
char* name; char* name;
void* userPointer;
// Physical dimensions in millimeters. // Physical dimensions in millimeters.
int widthMM, heightMM; int widthMM, heightMM;
@@ -473,8 +441,8 @@ struct _GLFWmonitor
_GLFW_PLATFORM_MONITOR_STATE; _GLFW_PLATFORM_MONITOR_STATE;
}; };
/*! @brief Cursor structure // Cursor structure
*/ //
struct _GLFWcursor struct _GLFWcursor
{ {
_GLFWcursor* next; _GLFWcursor* next;
@@ -483,16 +451,16 @@ struct _GLFWcursor
_GLFW_PLATFORM_CURSOR_STATE; _GLFW_PLATFORM_CURSOR_STATE;
}; };
/*! @brief Gamepad mapping element structure // Gamepad mapping element structure
*/ //
struct _GLFWmapelement struct _GLFWmapelement
{ {
uint8_t type; uint8_t type;
uint8_t value; uint8_t value;
}; };
/*! @brief Gamepad mapping structure // Gamepad mapping structure
*/ //
struct _GLFWmapping struct _GLFWmapping
{ {
char name[128]; char name[128];
@@ -501,8 +469,8 @@ struct _GLFWmapping
_GLFWmapelement axes[6]; _GLFWmapelement axes[6];
}; };
/*! @brief Joystick structure // Joystick structure
*/ //
struct _GLFWjoystick struct _GLFWjoystick
{ {
GLFWbool present; GLFWbool present;
@@ -513,6 +481,7 @@ struct _GLFWjoystick
unsigned char* hats; unsigned char* hats;
int hatCount; int hatCount;
char* name; char* name;
void* userPointer;
char guid[33]; char guid[33];
_GLFWmapping* mapping; _GLFWmapping* mapping;
@@ -520,24 +489,24 @@ struct _GLFWjoystick
_GLFW_PLATFORM_JOYSTICK_STATE; _GLFW_PLATFORM_JOYSTICK_STATE;
}; };
/*! @brief Thread local storage structure. // Thread local storage structure
*/ //
struct _GLFWtls struct _GLFWtls
{ {
// This is defined in the platform's thread.h // This is defined in the platform's thread.h
_GLFW_PLATFORM_TLS_STATE; _GLFW_PLATFORM_TLS_STATE;
}; };
/*! @brief Mutex structure. // Mutex structure
*/ //
struct _GLFWmutex struct _GLFWmutex
{ {
// This is defined in the platform's thread.h // This is defined in the platform's thread.h
_GLFW_PLATFORM_MUTEX_STATE; _GLFW_PLATFORM_MUTEX_STATE;
}; };
/*! @brief Library global data. // Library global data
*/ //
struct _GLFWlibrary struct _GLFWlibrary
{ {
GLFWbool initialized; GLFWbool initialized;
@@ -611,21 +580,14 @@ struct _GLFWlibrary
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE; _GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
}; };
//========================================================================
// Global state shared between compilation units of GLFW // Global state shared between compilation units of GLFW
//======================================================================== //
/*! @brief All global data shared between compilation units.
*/
extern _GLFWlibrary _glfw; extern _GLFWlibrary _glfw;
//======================================================================== //////////////////////////////////////////////////////////////////////////
// Platform API functions ////// GLFW platform API //////
//======================================================================== //////////////////////////////////////////////////////////////////////////
/*! @addtogroup platform @{ */
int _glfwPlatformInit(void); int _glfwPlatformInit(void);
void _glfwPlatformTerminate(void); void _glfwPlatformTerminate(void);
@@ -634,7 +596,8 @@ const char* _glfwPlatformGetVersionString(void);
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos); void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode); void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
int _glfwPlatformCreateCursor(_GLFWcursor* cursor, const GLFWimage* image, int xhot, int yhot); int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, int xhot, int yhot);
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape); int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor); void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor); void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
@@ -643,7 +606,8 @@ const char* _glfwPlatformGetScancodeName(int scancode);
int _glfwPlatformGetKeyScancode(int key); int _glfwPlatformGetKeyScancode(int key);
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos); void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, float* xscale, float* yscale); void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale);
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count); GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode); void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp); void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
@@ -664,16 +628,22 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWfbconfig* fbconfig); const _GLFWfbconfig* fbconfig);
void _glfwPlatformDestroyWindow(_GLFWwindow* window); void _glfwPlatformDestroyWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title); void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title);
void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images); void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
int count, const GLFWimage* images);
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos); void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos);
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos); void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos);
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height); void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height);
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height); void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height);
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight); void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
int minwidth, int minheight,
int maxwidth, int maxheight);
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom); void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height); void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom); void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale); int* left, int* top,
int* right, int* bottom);
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale);
void _glfwPlatformIconifyWindow(_GLFWwindow* window); void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window); void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window); void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
@@ -682,11 +652,14 @@ void _glfwPlatformHideWindow(_GLFWwindow* window);
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window); void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t); int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t);
void _glfwPlatformFocusWindow(_GLFWwindow* window); void _glfwPlatformFocusWindow(_GLFWwindow* window);
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate); void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor,
int xpos, int ypos, int width, int height,
int refreshRate);
int _glfwPlatformWindowFocused(_GLFWwindow* window); int _glfwPlatformWindowFocused(_GLFWwindow* window);
int _glfwPlatformWindowIconified(_GLFWwindow* window); int _glfwPlatformWindowIconified(_GLFWwindow* window);
int _glfwPlatformWindowVisible(_GLFWwindow* window); int _glfwPlatformWindowVisible(_GLFWwindow* window);
int _glfwPlatformWindowMaximized(_GLFWwindow* window); int _glfwPlatformWindowMaximized(_GLFWwindow* window);
int _glfwPlatformWindowHovered(_GLFWwindow* window);
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window); int _glfwPlatformFramebufferTransparent(_GLFWwindow* window);
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window); float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
@@ -700,8 +673,13 @@ void _glfwPlatformWaitEventsTimeout(double timeout);
void _glfwPlatformPostEmptyEvent(void); void _glfwPlatformPostEmptyEvent(void);
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions); void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily); int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); VkPhysicalDevice device,
uint32_t queuefamily);
VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
_GLFWwindow* window,
const VkAllocationCallbacks* allocator,
VkSurfaceKHR* surface);
GLFWbool _glfwPlatformCreateTls(_GLFWtls* tls); GLFWbool _glfwPlatformCreateTls(_GLFWtls* tls);
void _glfwPlatformDestroyTls(_GLFWtls* tls); void _glfwPlatformDestroyTls(_GLFWtls* tls);
@@ -713,307 +691,79 @@ void _glfwPlatformDestroyMutex(_GLFWmutex* mutex);
void _glfwPlatformLockMutex(_GLFWmutex* mutex); void _glfwPlatformLockMutex(_GLFWmutex* mutex);
void _glfwPlatformUnlockMutex(_GLFWmutex* mutex); void _glfwPlatformUnlockMutex(_GLFWmutex* mutex);
/*! @} */
//////////////////////////////////////////////////////////////////////////
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
//========================================================================
// Event API functions
//========================================================================
/*! @brief Notifies shared code that a window has lost or received input focus.
* @param[in] window The window that received the event.
* @param[in] focused `GLFW_TRUE` if the window received focus, or `GLFW_FALSE`
* if it lost focus.
* @ingroup event
*/
void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused); void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused);
/*! @brief Notifies shared code that a window has moved.
* @param[in] window The window that received the event.
* @param[in] xpos The new x-coordinate of the client area of the window.
* @param[in] ypos The new y-coordinate of the client area of the window.
* @ingroup event
*/
void _glfwInputWindowPos(_GLFWwindow* window, int xpos, int ypos); void _glfwInputWindowPos(_GLFWwindow* window, int xpos, int ypos);
/*! @brief Notifies shared code that a window has been resized.
* @param[in] window The window that received the event.
* @param[in] width The new width of the client area of the window.
* @param[in] height The new height of the client area of the window.
* @ingroup event
*/
void _glfwInputWindowSize(_GLFWwindow* window, int width, int height); void _glfwInputWindowSize(_GLFWwindow* window, int width, int height);
/*! @brief Notifies shared code that a window framebuffer has been resized.
* @param[in] window The window that received the event.
* @param[in] width The new width, in pixels, of the framebuffer.
* @param[in] height The new height, in pixels, of the framebuffer.
* @ingroup event
*/
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height); void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height);
void _glfwInputWindowContentScale(_GLFWwindow* window,
/*! @brief Notifies shared code that a window has been iconified or restored. float xscale, float yscale);
* @param[in] window The window that received the event.
* @param[in] iconified `GLFW_TRUE` if the window was iconified, or
* `GLFW_FALSE` if it was restored.
* @ingroup event
*/
void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified); void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified);
/*! @brief Notifies shared code that a window has been maximized or restored.
* @param[in] window The window that received the event.
* @param[in] maximized `GLFW_TRUE` if the window was maximized, or
* `GLFW_FALSE` if it was restored.
* @ingroup event
*/
void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized); void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized);
/*! @brief Notifies shared code that a window's contents needs updating.
* @param[in] window The window that received the event.
*/
void _glfwInputWindowDamage(_GLFWwindow* window); void _glfwInputWindowDamage(_GLFWwindow* window);
/*! @brief Notifies shared code that the user wishes to close a window.
* @param[in] window The window that received the event.
* @ingroup event
*/
void _glfwInputWindowCloseRequest(_GLFWwindow* window); void _glfwInputWindowCloseRequest(_GLFWwindow* window);
/*! @brief Notifies shared code that a window has changed its desired monitor.
* @param[in] window The window that received the event.
* @param[in] monitor The new desired monitor, or `NULL`.
* @ingroup event
*/
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor); void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
/*! @brief Notifies shared code of a physical key event. void _glfwInputKey(_GLFWwindow* window,
* @param[in] window The window that received the event. int key, int scancode, int action, int mods);
* @param[in] key The key that was pressed or released. void _glfwInputChar(_GLFWwindow* window,
* @param[in] scancode The system-specific scan code of the key. unsigned int codepoint, int mods, GLFWbool plain);
* @param[in] action @ref GLFW_PRESS or @ref GLFW_RELEASE.
* @param[in] mods The modifiers pressed when the event was generated.
* @ingroup event
*/
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods);
/*! @brief Notifies shared code of a Unicode character input event.
* @param[in] window The window that received the event.
* @param[in] codepoint The Unicode code point of the input character.
* @param[in] mods Bit field describing which modifier keys were held down.
* @param[in] plain `GLFW_TRUE` if the character is regular text input, or
* `GLFW_FALSE` otherwise.
* @ingroup event
*/
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain);
/*! @brief Notifies shared code of a scroll event.
* @param[in] window The window that received the event.
* @param[in] xoffset The scroll offset along the x-axis.
* @param[in] yoffset The scroll offset along the y-axis.
* @ingroup event
*/
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
/*! @brief Notifies shared code of a mouse button click event.
* @param[in] window The window that received the event.
* @param[in] button The button that was pressed or released.
* @param[in] action @ref GLFW_PRESS or @ref GLFW_RELEASE.
* @param[in] mods The modifiers pressed when the event was generated.
* @ingroup event
*/
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
/*! @brief Notifies shared code of a cursor motion event.
* @param[in] window The window that received the event.
* @param[in] xpos The new x-coordinate of the cursor, relative to the left
* edge of the client area of the window.
* @param[in] ypos The new y-coordinate of the cursor, relative to the top edge
* of the client area of the window.
* @ingroup event
*/
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
/*! @brief Notifies shared code of a cursor enter/leave event.
* @param[in] window The window that received the event.
* @param[in] entered `GLFW_TRUE` if the cursor entered the client area of the
* window, or `GLFW_FALSE` if it left it.
* @ingroup event
*/
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered); void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
void _glfwInputDrop(_GLFWwindow* window, int count, const char** names);
void _glfwInputJoystick(_GLFWjoystick* js, int event);
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);
void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value);
/*! @brief Notifies shared code of a monitor connection or disconnection.
* @param[in] monitor The monitor that was connected or disconnected.
* @param[in] action One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`.
* @param[in] placement `_GLFW_INSERT_FIRST` or `_GLFW_INSERT_LAST`.
* @ingroup event
*/
void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement); void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement);
/*! @brief Notifies shared code that a full screen window has acquired or
* released a monitor.
* @param[in] monitor The monitor that was acquired or released.
* @param[in] window The window that acquired the monitor, or `NULL`.
* @ingroup event
*/
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window); void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window);
/*! @brief Notifies shared code of an error.
* @param[in] code The error code most suitable for the error.
* @param[in] format The `printf` style format string of the error
* description.
* @ingroup event
*/
#if defined(__GNUC__) #if defined(__GNUC__)
void _glfwInputError(int code, const char* format, ...) __attribute__((format(printf, 2, 3))); void _glfwInputError(int code, const char* format, ...)
__attribute__((format(printf, 2, 3)));
#else #else
void _glfwInputError(int code, const char* format, ...); void _glfwInputError(int code, const char* format, ...);
#endif #endif
/*! @brief Notifies shared code of files or directories dropped on a window.
* @param[in] window The window that received the event.
* @param[in] count The number of dropped objects.
* @param[in] names The names of the dropped objects.
* @ingroup event
*/
void _glfwInputDrop(_GLFWwindow* window, int count, const char** names);
/*! @brief Notifies shared code of a joystick connection or disconnection. //////////////////////////////////////////////////////////////////////////
* @param[in] js The joystick that was connected or disconnected. ////// GLFW internal API //////
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. //////////////////////////////////////////////////////////////////////////
* @ingroup event
*/
void _glfwInputJoystick(_GLFWjoystick* js, int event);
/*! @brief Notifies shared code of the new value of a joystick axis.
* @param[in] js The joystick whose axis to update.
* @param[in] axis The index of the axis to update.
* @param[in] value The new value of the axis.
*/
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
/*! @brief Notifies shared code of the new value of a joystick button.
* @param[in] js The joystick whose button to update.
* @param[in] button The index of the button to update.
* @param[in] value The new value of the button.
*/
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);
/*! @brief Notifies shared code of the new value of a joystick hat.
* @param[in] js The joystick whose hat to update.
* @param[in] button The index of the hat to update.
* @param[in] value The new value of the hat.
*/
void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value);
//========================================================================
// Utility functions
//========================================================================
/*! @brief Chooses the video mode most closely matching the desired one.
* @ingroup utility
*/
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
const GLFWvidmode* desired);
/*! @brief Performs lexical comparison between two @ref GLFWvidmode structures.
* @ingroup utility
*/
int _glfwCompareVideoModes(const GLFWvidmode* first, const GLFWvidmode* second);
/*! @brief Splits a color depth into red, green and blue bit depths.
* @ingroup utility
*/
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue);
/*! @brief Searches an extension string for the specified extension.
* @param[in] string The extension string to search.
* @param[in] extensions The extension to search for.
* @return `GLFW_TRUE` if the extension was found, or `GLFW_FALSE` otherwise.
* @ingroup utility
*/
GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions); GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions);
/*! @brief Chooses the framebuffer config that best matches the desired one.
* @param[in] desired The desired framebuffer config.
* @param[in] alternatives The framebuffer configs supported by the system.
* @param[in] count The number of entries in the alternatives array.
* @return The framebuffer config most closely matching the desired one, or @c
* NULL if none fulfilled the hard constraints of the desired values.
* @ingroup utility
*/
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired, const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
const _GLFWfbconfig* alternatives, const _GLFWfbconfig* alternatives,
unsigned int count); unsigned int count);
GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
/*! @brief Retrieves the attributes of the current context. const _GLFWctxconfig* ctxconfig);
* @param[in] ctxconfig The desired context attributes.
* @return `GLFW_TRUE` if successful, or `GLFW_FALSE` if the context is
* unusable.
* @ingroup utility
*/
GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig);
/*! @brief Checks whether the desired context attributes are valid.
* @param[in] ctxconfig The context attributes to check.
* @return `GLFW_TRUE` if the context attributes are valid, or `GLFW_FALSE`
* otherwise.
* @ingroup utility
*
* This function checks things like whether the specified client API version
* exists and whether all relevant options have supported and non-conflicting
* values.
*/
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig); GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig);
/*! @brief Allocates red, green and blue value arrays of the specified size. const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
* @ingroup utility const GLFWvidmode* desired);
*/ int _glfwCompareVideoModes(const GLFWvidmode* first, const GLFWvidmode* second);
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size);
/*! @brief Frees the red, green and blue value arrays and clears the struct.
* @ingroup utility
*/
void _glfwFreeGammaArrays(GLFWgammaramp* ramp);
/*! @brief Allocates and returns a monitor object with the specified name
* and dimensions.
* @param[in] name The name of the monitor.
* @param[in] widthMM The width, in mm, of the monitor's display area.
* @param[in] heightMM The height, in mm, of the monitor's display area.
* @return The newly created object.
* @ingroup utility
*/
_GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM); _GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM);
/*! @brief Frees a monitor object and any data associated with it.
* @ingroup utility
*/
void _glfwFreeMonitor(_GLFWmonitor* monitor); void _glfwFreeMonitor(_GLFWmonitor* monitor);
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size);
void _glfwFreeGammaArrays(GLFWgammaramp* ramp);
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue);
/*! @brief Returns an available joystick object with arrays and name allocated.
* @ingroup utility
*/
_GLFWjoystick* _glfwAllocJoystick(const char* name, _GLFWjoystick* _glfwAllocJoystick(const char* name,
const char* guid, const char* guid,
int axisCount, int axisCount,
int buttonCount, int buttonCount,
int hatCount); int hatCount);
/*! @brief Frees arrays and name and flags the joystick object as unused.
* @ingroup utility
*/
void _glfwFreeJoystick(_GLFWjoystick* js); void _glfwFreeJoystick(_GLFWjoystick* js);
/*! @ingroup utility
*/
GLFWbool _glfwInitVulkan(int mode); GLFWbool _glfwInitVulkan(int mode);
/*! @ingroup utility
*/
void _glfwTerminateVulkan(void); void _glfwTerminateVulkan(void);
/*! @ingroup utility
*/
const char* _glfwGetVulkanResultString(VkResult result); const char* _glfwGetVulkanResultString(VkResult result);
char* _glfw_strdup(const char* source);

View File

@@ -38,6 +38,11 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#ifndef SYN_DROPPED // < v2.6.39 kernel headers
// Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32
#define SYN_DROPPED 3
#endif
// Apply an EV_KEY event to the specified joystick // Apply an EV_KEY event to the specified joystick
// //
static void handleKeyEvent(_GLFWjoystick* js, int code, int value) static void handleKeyEvent(_GLFWjoystick* js, int code, int value)

2
glfw/mappings.h vendored
View File

@@ -51,7 +51,7 @@
// 1. The origin of this software must not be misrepresented; you must not // 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software // claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would // in a product, an acknowledgment in the product documentation would
// be appreciated but is not required. // be appreciated but is not required.
// //
// 2. Altered source versions must be plainly marked as such, and must not be // 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software. // misrepresented as being the original software.

49
glfw/monitor.c vendored
View File

@@ -86,6 +86,8 @@ static GLFWbool refreshVideoModes(_GLFWmonitor* monitor)
////// GLFW event API ////// ////// GLFW event API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Notifies shared code of a monitor connection or disconnection
//
void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement) void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement)
{ {
if (action == GLFW_CONNECTED) if (action == GLFW_CONNECTED)
@@ -141,6 +143,9 @@ void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement)
_glfwFreeMonitor(monitor); _glfwFreeMonitor(monitor);
} }
// Notifies shared code that a full screen window has acquired or released
// a monitor
//
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window) void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window)
{ {
monitor->window = window; monitor->window = window;
@@ -151,6 +156,8 @@ void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window)
////// GLFW internal API ////// ////// GLFW internal API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Allocates and returns a monitor object with the specified name and dimensions
//
_GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM) _GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM)
{ {
_GLFWmonitor* monitor = calloc(1, sizeof(_GLFWmonitor)); _GLFWmonitor* monitor = calloc(1, sizeof(_GLFWmonitor));
@@ -158,11 +165,13 @@ _GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM)
monitor->heightMM = heightMM; monitor->heightMM = heightMM;
if (name) if (name)
monitor->name = strdup(name); monitor->name = _glfw_strdup(name);
return monitor; return monitor;
} }
// Frees a monitor object and any data associated with it
//
void _glfwFreeMonitor(_GLFWmonitor* monitor) void _glfwFreeMonitor(_GLFWmonitor* monitor)
{ {
if (monitor == NULL) if (monitor == NULL)
@@ -176,6 +185,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor)
free(monitor); free(monitor);
} }
// Allocates red, green and blue value arrays of the specified size
//
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size) void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size)
{ {
ramp->red = calloc(size, sizeof(unsigned short)); ramp->red = calloc(size, sizeof(unsigned short));
@@ -184,6 +195,8 @@ void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size)
ramp->size = size; ramp->size = size;
} }
// Frees the red, green and blue value arrays and clears the struct
//
void _glfwFreeGammaArrays(GLFWgammaramp* ramp) void _glfwFreeGammaArrays(GLFWgammaramp* ramp)
{ {
free(ramp->red); free(ramp->red);
@@ -193,6 +206,8 @@ void _glfwFreeGammaArrays(GLFWgammaramp* ramp)
memset(ramp, 0, sizeof(GLFWgammaramp)); memset(ramp, 0, sizeof(GLFWgammaramp));
} }
// Chooses the video mode most closely matching the desired one
//
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor, const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
const GLFWvidmode* desired) const GLFWvidmode* desired)
{ {
@@ -243,11 +258,15 @@ const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
return closest; return closest;
} }
// Performs lexical comparison between two @ref GLFWvidmode structures
//
int _glfwCompareVideoModes(const GLFWvidmode* fm, const GLFWvidmode* sm) int _glfwCompareVideoModes(const GLFWvidmode* fm, const GLFWvidmode* sm)
{ {
return compareVideoModes(fm, sm); return compareVideoModes(fm, sm);
} }
// Splits a color depth into red, green and blue bit depths
//
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue) void _glfwSplitBPP(int bpp, int* red, int* green, int* blue)
{ {
int delta; int delta;
@@ -351,6 +370,24 @@ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle)
return monitor->name; return monitor->name;
} }
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT();
monitor->userPointer = pointer;
}
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->userPointer;
}
GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun) GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun)
{ {
_GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -406,16 +443,16 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
for (i = 0; i < 256; i++) for (i = 0; i < 256; i++)
{ {
double value; float value;
// Calculate intensity // Calculate intensity
value = i / 255.0; value = i / 255.f;
// Apply gamma curve // Apply gamma curve
value = pow(value, 1.0 / gamma) * 65535.0 + 0.5; value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
// Clamp to value range // Clamp to value range
if (value > 65535.0) if (value > 65535.f)
value = 65535.0; value = 65535.f;
values[i] = (unsigned short) value; values[i] = (unsigned short) value;
} }

5
glfw/null_window.c vendored
View File

@@ -165,6 +165,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return GLFW_FALSE; return GLFW_FALSE;
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return GLFW_FALSE;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return GLFW_FALSE; return GLFW_FALSE;

View File

@@ -63,6 +63,7 @@
"osmesa_context.h" "osmesa_context.h"
], ],
"protocols": [ "protocols": [
"stable/xdg-shell/xdg-shell.xml",
"unstable/relative-pointer/relative-pointer-unstable-v1.xml", "unstable/relative-pointer/relative-pointer-unstable-v1.xml",
"unstable/pointer-constraints/pointer-constraints-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"
@@ -79,6 +80,10 @@
"osmesa_context.c" "osmesa_context.c"
] ]
}, },
"wayland_protocols": [
1,
12
],
"win32": { "win32": {
"headers": [ "headers": [
"win32_platform.h", "win32_platform.h",

7
glfw/vulkan.c vendored
View File

@@ -317,6 +317,13 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
return VK_ERROR_EXTENSION_NOT_PRESENT; return VK_ERROR_EXTENSION_NOT_PRESENT;
} }
if (window->context.client != GLFW_NO_API)
{
_glfwInputError(GLFW_INVALID_VALUE,
"Vulkan: Window surface creation requires the window to have the client API set to GLFW_NO_API");
return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR;
}
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface); return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
} }

91
glfw/wgl_context.c vendored
View File

@@ -329,21 +329,52 @@ static void destroyContextWGL(_GLFWwindow* window)
} }
} }
// Initialize WGL-specific extensions
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize WGL
// //
static void loadWGLExtensions(void) GLFWbool _glfwInitWGL(void)
{ {
PIXELFORMATDESCRIPTOR pfd; PIXELFORMATDESCRIPTOR pfd;
HGLRC rc; HGLRC prc, rc;
HDC dc = GetDC(_glfw.win32.helperWindowHandle);; HDC pdc, dc;
_glfw.wgl.extensionsLoaded = GLFW_TRUE; if (_glfw.wgl.instance)
return GLFW_TRUE;
_glfw.wgl.instance = LoadLibraryA("opengl32.dll");
if (!_glfw.wgl.instance)
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to load opengl32.dll");
return GLFW_FALSE;
}
_glfw.wgl.CreateContext = (PFN_wglCreateContext)
GetProcAddress(_glfw.wgl.instance, "wglCreateContext");
_glfw.wgl.DeleteContext = (PFN_wglDeleteContext)
GetProcAddress(_glfw.wgl.instance, "wglDeleteContext");
_glfw.wgl.GetProcAddress = (PFN_wglGetProcAddress)
GetProcAddress(_glfw.wgl.instance, "wglGetProcAddress");
_glfw.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentDC");
_glfw.wgl.GetCurrentContext = (PFN_wglGetCurrentContext)
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentContext");
_glfw.wgl.MakeCurrent = (PFN_wglMakeCurrent)
GetProcAddress(_glfw.wgl.instance, "wglMakeCurrent");
_glfw.wgl.ShareLists = (PFN_wglShareLists)
GetProcAddress(_glfw.wgl.instance, "wglShareLists");
// NOTE: A dummy context has to be created for opengl32.dll to load the // NOTE: A dummy context has to be created for opengl32.dll to load the
// OpenGL ICD, from which we can then query WGL extensions // OpenGL ICD, from which we can then query WGL extensions
// NOTE: This code will accept the Microsoft GDI ICD; accelerated context // NOTE: This code will accept the Microsoft GDI ICD; accelerated context
// creation failure occurs during manual pixel format enumeration // creation failure occurs during manual pixel format enumeration
dc = GetDC(_glfw.win32.helperWindowHandle);;
ZeroMemory(&pfd, sizeof(pfd)); ZeroMemory(&pfd, sizeof(pfd));
pfd.nSize = sizeof(pfd); pfd.nSize = sizeof(pfd);
pfd.nVersion = 1; pfd.nVersion = 1;
@@ -355,7 +386,7 @@ static void loadWGLExtensions(void)
{ {
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR, _glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to set pixel format for dummy context"); "WGL: Failed to set pixel format for dummy context");
return; return GLFW_FALSE;
} }
rc = wglCreateContext(dc); rc = wglCreateContext(dc);
@@ -363,15 +394,19 @@ static void loadWGLExtensions(void)
{ {
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR, _glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to create dummy context"); "WGL: Failed to create dummy context");
return; return GLFW_FALSE;
} }
pdc = wglGetCurrentDC();
prc = wglGetCurrentContext();
if (!wglMakeCurrent(dc, rc)) if (!wglMakeCurrent(dc, rc))
{ {
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR, _glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to make dummy context current"); "WGL: Failed to make dummy context current");
wglMakeCurrent(pdc, prc);
wglDeleteContext(rc); wglDeleteContext(rc);
return; return GLFW_FALSE;
} }
// NOTE: Functions must be loaded first as they're needed to retrieve the // NOTE: Functions must be loaded first as they're needed to retrieve the
@@ -414,43 +449,8 @@ static void loadWGLExtensions(void)
_glfw.wgl.ARB_context_flush_control = _glfw.wgl.ARB_context_flush_control =
extensionSupportedWGL("WGL_ARB_context_flush_control"); extensionSupportedWGL("WGL_ARB_context_flush_control");
wglMakeCurrent(dc, NULL); wglMakeCurrent(pdc, prc);
wglDeleteContext(rc); wglDeleteContext(rc);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize WGL
//
GLFWbool _glfwInitWGL(void)
{
if (_glfw.wgl.instance)
return GLFW_TRUE;
_glfw.wgl.instance = LoadLibraryA("opengl32.dll");
if (!_glfw.wgl.instance)
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to load opengl32.dll");
return GLFW_FALSE;
}
_glfw.wgl.CreateContext = (PFN_wglCreateContext)
GetProcAddress(_glfw.wgl.instance, "wglCreateContext");
_glfw.wgl.DeleteContext = (PFN_wglDeleteContext)
GetProcAddress(_glfw.wgl.instance, "wglDeleteContext");
_glfw.wgl.GetProcAddress = (PFN_wglGetProcAddress)
GetProcAddress(_glfw.wgl.instance, "wglGetProcAddress");
_glfw.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentDC");
_glfw.wgl.MakeCurrent = (PFN_wglMakeCurrent)
GetProcAddress(_glfw.wgl.instance, "wglMakeCurrent");
_glfw.wgl.ShareLists = (PFN_wglShareLists)
GetProcAddress(_glfw.wgl.instance, "wglShareLists");
return GLFW_TRUE; return GLFW_TRUE;
} }
@@ -480,9 +480,6 @@ GLFWbool _glfwCreateContextWGL(_GLFWwindow* window,
PIXELFORMATDESCRIPTOR pfd; PIXELFORMATDESCRIPTOR pfd;
HGLRC share = NULL; HGLRC share = NULL;
if (!_glfw.wgl.extensionsLoaded)
loadWGLExtensions();
if (ctxconfig->share) if (ctxconfig->share)
share = ctxconfig->share->context.wgl.handle; share = ctxconfig->share->context.wgl.handle;

5
glfw/wgl_context.h vendored
View File

@@ -86,6 +86,7 @@ typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC);
typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC);
typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR);
typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void);
typedef HGLRC (WINAPI * PFN_wglGetCurrentContext)(void);
typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC);
typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC); typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
@@ -94,6 +95,7 @@ typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
#define wglDeleteContext _glfw.wgl.DeleteContext #define wglDeleteContext _glfw.wgl.DeleteContext
#define wglGetProcAddress _glfw.wgl.GetProcAddress #define wglGetProcAddress _glfw.wgl.GetProcAddress
#define wglGetCurrentDC _glfw.wgl.GetCurrentDC #define wglGetCurrentDC _glfw.wgl.GetCurrentDC
#define wglGetCurrentContext _glfw.wgl.GetCurrentContext
#define wglMakeCurrent _glfw.wgl.MakeCurrent #define wglMakeCurrent _glfw.wgl.MakeCurrent
#define wglShareLists _glfw.wgl.ShareLists #define wglShareLists _glfw.wgl.ShareLists
@@ -124,11 +126,10 @@ typedef struct _GLFWlibraryWGL
PFN_wglDeleteContext DeleteContext; PFN_wglDeleteContext DeleteContext;
PFN_wglGetProcAddress GetProcAddress; PFN_wglGetProcAddress GetProcAddress;
PFN_wglGetCurrentDC GetCurrentDC; PFN_wglGetCurrentDC GetCurrentDC;
PFN_wglGetCurrentContext GetCurrentContext;
PFN_wglMakeCurrent MakeCurrent; PFN_wglMakeCurrent MakeCurrent;
PFN_wglShareLists ShareLists; PFN_wglShareLists ShareLists;
GLFWbool extensionsLoaded;
PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT;
PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB;
PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT;

10
glfw/win32_init.c vendored
View File

@@ -351,9 +351,10 @@ static HWND createHelperWindow(void)
dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
dbi.dbcc_classguid = GUID_DEVINTERFACE_HID; dbi.dbcc_classguid = GUID_DEVINTERFACE_HID;
RegisterDeviceNotificationW(window, _glfw.win32.deviceNotificationHandle =
(DEV_BROADCAST_HDR*) &dbi, RegisterDeviceNotificationW(window,
DEVICE_NOTIFY_WINDOW_HANDLE); (DEV_BROADCAST_HDR*) &dbi,
DEVICE_NOTIFY_WINDOW_HANDLE);
} }
while (PeekMessageW(&msg, _glfw.win32.helperWindowHandle, 0, 0, PM_REMOVE)) while (PeekMessageW(&msg, _glfw.win32.helperWindowHandle, 0, 0, PM_REMOVE))
@@ -543,6 +544,9 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void) void _glfwPlatformTerminate(void)
{ {
if (_glfw.win32.deviceNotificationHandle)
UnregisterDeviceNotification(_glfw.win32.deviceNotificationHandle);
if (_glfw.win32.helperWindowHandle) if (_glfw.win32.helperWindowHandle)
DestroyWindow(_glfw.win32.helperWindowHandle); DestroyWindow(_glfw.win32.helperWindowHandle);

13
glfw/win32_monitor.c vendored
View File

@@ -241,7 +241,7 @@ void _glfwPollMonitorsWin32(void)
// Change the current video mode // Change the current video mode
// //
GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired) void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired)
{ {
GLFWvidmode current; GLFWvidmode current;
const GLFWvidmode* best; const GLFWvidmode* best;
@@ -251,7 +251,7 @@ GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desire
best = _glfwChooseVideoMode(monitor, desired); best = _glfwChooseVideoMode(monitor, desired);
_glfwPlatformGetVideoMode(monitor, &current); _glfwPlatformGetVideoMode(monitor, &current);
if (_glfwCompareVideoModes(&current, best) == 0) if (_glfwCompareVideoModes(&current, best) == 0)
return GLFW_TRUE; return;
ZeroMemory(&dm, sizeof(dm)); ZeroMemory(&dm, sizeof(dm));
dm.dmSize = sizeof(dm); dm.dmSize = sizeof(dm);
@@ -270,7 +270,9 @@ GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desire
NULL, NULL,
CDS_FULLSCREEN, CDS_FULLSCREEN,
NULL); NULL);
if (result != DISP_CHANGE_SUCCESSFUL) if (result == DISP_CHANGE_SUCCESSFUL)
monitor->win32.modeChanged = GLFW_TRUE;
else
{ {
const char* description = "Unknown error"; const char* description = "Unknown error";
@@ -292,12 +294,7 @@ GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desire
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Win32: Failed to set video mode: %s", "Win32: Failed to set video mode: %s",
description); description);
return GLFW_FALSE;
} }
monitor->win32.modeChanged = GLFW_TRUE;
return GLFW_TRUE;
} }
// Restore the previously saved (original) video mode // Restore the previously saved (original) video mode

19
glfw/win32_platform.h vendored
View File

@@ -67,11 +67,6 @@
#include <xinput.h> #include <xinput.h>
#include <dbt.h> #include <dbt.h>
#if defined(_MSC_VER)
#include <malloc.h>
#define strdup _strdup
#endif
// HACK: Define macros that some windows.h variants don't // HACK: Define macros that some windows.h variants don't
#ifndef WM_MOUSEHWHEEL #ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x020E #define WM_MOUSEHWHEEL 0x020E
@@ -105,12 +100,11 @@
#endif #endif
#if WINVER < 0x0601 #if WINVER < 0x0601
typedef struct tagCHANGEFILTERSTRUCT typedef struct
{ {
DWORD cbSize; DWORD cbSize;
DWORD ExtStatus; DWORD ExtStatus;
} CHANGEFILTERSTRUCT;
} CHANGEFILTERSTRUCT, *PCHANGEFILTERSTRUCT;
#ifndef MSGFLT_ALLOW #ifndef MSGFLT_ALLOW
#define MSGFLT_ALLOW 1 #define MSGFLT_ALLOW 1
#endif #endif
@@ -129,13 +123,13 @@ typedef struct
#endif /*Windows Vista*/ #endif /*Windows Vista*/
#ifndef DPI_ENUMS_DECLARED #ifndef DPI_ENUMS_DECLARED
typedef enum PROCESS_DPI_AWARENESS typedef enum
{ {
PROCESS_DPI_UNAWARE = 0, PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2 PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS; } PROCESS_DPI_AWARENESS;
typedef enum MONITOR_DPI_TYPE typedef enum
{ {
MDT_EFFECTIVE_DPI = 0, MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1, MDT_ANGULAR_DPI = 1,
@@ -209,7 +203,7 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
// user32.dll function pointer typedefs // user32.dll function pointer typedefs
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void); typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,PCHANGEFILTERSTRUCT); typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_ #define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_ #define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
@@ -289,6 +283,7 @@ typedef struct _GLFWwindowWin32
typedef struct _GLFWlibraryWin32 typedef struct _GLFWlibraryWin32
{ {
HWND helperWindowHandle; HWND helperWindowHandle;
HDEVNOTIFY deviceNotificationHandle;
DWORD foregroundLockTimeout; DWORD foregroundLockTimeout;
int acquiredMonitorCount; int acquiredMonitorCount;
char* clipboardString; char* clipboardString;
@@ -403,7 +398,7 @@ void _glfwUpdateKeyNamesWin32(void);
void _glfwInitTimerWin32(void); void _glfwInitTimerWin32(void);
void _glfwPollMonitorsWin32(void); void _glfwPollMonitorsWin32(void);
GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor); void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale); void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);

101
glfw/win32_window.c vendored
View File

@@ -384,14 +384,18 @@ static int getKeyMods(void)
{ {
int mods = 0; int mods = 0;
if (GetKeyState(VK_SHIFT) & (1 << 31)) if (GetKeyState(VK_SHIFT) & 0x8000)
mods |= GLFW_MOD_SHIFT; mods |= GLFW_MOD_SHIFT;
if (GetKeyState(VK_CONTROL) & (1 << 31)) if (GetKeyState(VK_CONTROL) & 0x8000)
mods |= GLFW_MOD_CONTROL; mods |= GLFW_MOD_CONTROL;
if (GetKeyState(VK_MENU) & (1 << 31)) if (GetKeyState(VK_MENU) & 0x8000)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1 << 31)) if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (GetKeyState(VK_CAPITAL) & 1)
mods |= GLFW_MOD_CAPS_LOCK;
if (GetKeyState(VK_NUMLOCK) & 1)
mods |= GLFW_MOD_NUM_LOCK;
return mods; return mods;
} }
@@ -402,14 +406,18 @@ static int getAsyncKeyMods(void)
{ {
int mods = 0; int mods = 0;
if (GetAsyncKeyState(VK_SHIFT) & (1 << 31)) if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
mods |= GLFW_MOD_SHIFT; mods |= GLFW_MOD_SHIFT;
if (GetAsyncKeyState(VK_CONTROL) & (1 << 31)) if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
mods |= GLFW_MOD_CONTROL; mods |= GLFW_MOD_CONTROL;
if (GetAsyncKeyState(VK_MENU) & (1 << 31)) if (GetAsyncKeyState(VK_MENU) & 0x8000)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & (1 << 31)) if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & 0x8000)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (GetAsyncKeyState(VK_CAPITAL) & 1)
mods |= GLFW_MOD_CAPS_LOCK;
if (GetAsyncKeyState(VK_NUMLOCK) & 1)
mods |= GLFW_MOD_NUM_LOCK;
return mods; return mods;
} }
@@ -463,30 +471,29 @@ static int translateKey(WPARAM wParam, LPARAM lParam)
return _glfw.win32.keycodes[HIWORD(lParam) & 0x1FF]; return _glfw.win32.keycodes[HIWORD(lParam) & 0x1FF];
} }
static void fitToMonitor(_GLFWwindow* window)
{
MONITORINFO mi = { sizeof(mi) };
GetMonitorInfo(window->monitor->win32.handle, &mi);
SetWindowPos(window->win32.handle, HWND_TOPMOST,
mi.rcMonitor.left,
mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS);
}
// Make the specified window and its video mode active on its monitor // Make the specified window and its video mode active on its monitor
// //
static GLFWbool acquireMonitor(_GLFWwindow* window) static void acquireMonitor(_GLFWwindow* window)
{ {
GLFWvidmode mode;
GLFWbool status;
int xpos, ypos;
if (!_glfw.win32.acquiredMonitorCount) if (!_glfw.win32.acquiredMonitorCount)
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
if (!window->monitor->window) if (!window->monitor->window)
_glfw.win32.acquiredMonitorCount++; _glfw.win32.acquiredMonitorCount++;
status = _glfwSetVideoModeWin32(window->monitor, &window->videoMode); _glfwSetVideoModeWin32(window->monitor, &window->videoMode);
_glfwPlatformGetVideoMode(window->monitor, &mode);
_glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos);
SetWindowPos(window->win32.handle, HWND_TOPMOST,
xpos, ypos, mode.width, mode.height,
SWP_NOACTIVATE | SWP_NOCOPYBITS);
_glfwInputMonitorWindow(window->monitor, window); _glfwInputMonitorWindow(window->monitor, window);
return status;
} }
// Remove the window and restore the original video mode // Remove the window and restore the original video mode
@@ -893,7 +900,10 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
if (iconified) if (iconified)
releaseMonitor(window); releaseMonitor(window);
else else
{
acquireMonitor(window); acquireMonitor(window);
fitToMonitor(window);
}
} }
window->win32.iconified = iconified; window->win32.iconified = iconified;
@@ -988,6 +998,14 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
return 0; return 0;
} }
case WM_DPICHANGED:
{
const float xscale = HIWORD(wParam) / 96.f;
const float yscale = LOWORD(wParam) / 96.f;
_glfwInputWindowContentScale(window, xscale, yscale);
break;
}
case WM_SETCURSOR: case WM_SETCURSOR:
{ {
if (LOWORD(lParam) == HTCLIENT) if (LOWORD(lParam) == HTCLIENT)
@@ -1224,11 +1242,8 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
{ {
_glfwPlatformShowWindow(window); _glfwPlatformShowWindow(window);
_glfwPlatformFocusWindow(window); _glfwPlatformFocusWindow(window);
if (!acquireMonitor(window)) acquireMonitor(window);
return GLFW_FALSE; fitToMonitor(window);
if (wndconfig->centerCursor)
centerCursor(window);
} }
return GLFW_TRUE; return GLFW_TRUE;
@@ -1344,7 +1359,10 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
if (window->monitor) if (window->monitor)
{ {
if (window->monitor->window == window) if (window->monitor->window == window)
{
acquireMonitor(window); acquireMonitor(window);
fitToMonitor(window);
}
} }
else else
{ {
@@ -1443,7 +1461,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
void _glfwPlatformShowWindow(_GLFWwindow* window) void _glfwPlatformShowWindow(_GLFWwindow* window)
{ {
ShowWindow(window->win32.handle, SW_SHOW); ShowWindow(window->win32.handle, SW_SHOWNA);
} }
void _glfwPlatformHideWindow(_GLFWwindow* window) void _glfwPlatformHideWindow(_GLFWwindow* window)
@@ -1479,7 +1497,10 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
if (monitor) if (monitor)
{ {
if (monitor->window == window) if (monitor->window == window)
{
acquireMonitor(window); acquireMonitor(window);
fitToMonitor(window);
}
} }
else else
{ {
@@ -1502,20 +1523,27 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
if (monitor) if (monitor)
{ {
MONITORINFO mi = { sizeof(mi) };
UINT flags = SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOCOPYBITS;
if (window->decorated) if (window->decorated)
{ {
DWORD style = GetWindowLongW(window->win32.handle, GWL_STYLE); DWORD style = GetWindowLongW(window->win32.handle, GWL_STYLE);
UINT flags = SWP_FRAMECHANGED | SWP_SHOWWINDOW |
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE;
style &= ~WS_OVERLAPPEDWINDOW; style &= ~WS_OVERLAPPEDWINDOW;
style |= getWindowStyle(window); style |= getWindowStyle(window);
SetWindowLongW(window->win32.handle, GWL_STYLE, style); SetWindowLongW(window->win32.handle, GWL_STYLE, style);
SetWindowPos(window->win32.handle, HWND_TOPMOST, 0, 0, 0, 0, flags); flags |= SWP_FRAMECHANGED;
} }
acquireMonitor(window); acquireMonitor(window);
GetMonitorInfo(window->monitor->win32.handle, &mi);
SetWindowPos(window->win32.handle, HWND_TOPMOST,
mi.rcMonitor.left,
mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
flags);
} }
else else
{ {
@@ -1567,6 +1595,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return IsZoomed(window->win32.handle); return IsZoomed(window->win32.handle);
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return cursorInClientArea(window);
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return window->win32.transparent && _glfwIsCompositionEnabledWin32(); return window->win32.transparent && _glfwIsCompositionEnabledWin32();

108
glfw/window.c vendored
View File

@@ -38,6 +38,8 @@
////// GLFW event API ////// ////// GLFW event API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Notifies shared code that a window has lost or received input focus
//
void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused) void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
{ {
if (window->callbacks.focus) if (window->callbacks.focus)
@@ -64,42 +66,68 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
} }
} }
// Notifies shared code that a window has moved
// The position is specified in client-area relative screen coordinates
//
void _glfwInputWindowPos(_GLFWwindow* window, int x, int y) void _glfwInputWindowPos(_GLFWwindow* window, int x, int y)
{ {
if (window->callbacks.pos) if (window->callbacks.pos)
window->callbacks.pos((GLFWwindow*) window, x, y); window->callbacks.pos((GLFWwindow*) window, x, y);
} }
// Notifies shared code that a window has been resized
// The size is specified in screen coordinates
//
void _glfwInputWindowSize(_GLFWwindow* window, int width, int height) void _glfwInputWindowSize(_GLFWwindow* window, int width, int height)
{ {
if (window->callbacks.size) if (window->callbacks.size)
window->callbacks.size((GLFWwindow*) window, width, height); window->callbacks.size((GLFWwindow*) window, width, height);
} }
// Notifies shared code that a window has been iconified or restored
//
void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified) void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified)
{ {
if (window->callbacks.iconify) if (window->callbacks.iconify)
window->callbacks.iconify((GLFWwindow*) window, iconified); window->callbacks.iconify((GLFWwindow*) window, iconified);
} }
// Notifies shared code that a window has been maximized or restored
//
void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized) void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized)
{ {
if (window->callbacks.maximize) if (window->callbacks.maximize)
window->callbacks.maximize((GLFWwindow*) window, maximized); window->callbacks.maximize((GLFWwindow*) window, maximized);
} }
// Notifies shared code that a window framebuffer has been resized
// The size is specified in pixels
//
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height) void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height)
{ {
if (window->callbacks.fbsize) if (window->callbacks.fbsize)
window->callbacks.fbsize((GLFWwindow*) window, width, height); window->callbacks.fbsize((GLFWwindow*) window, width, height);
} }
// Notifies shared code that a window content scale has changed
// The scale is specified as the ratio between the current and default DPI
//
void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale)
{
if (window->callbacks.scale)
window->callbacks.scale((GLFWwindow*) window, xscale, yscale);
}
// Notifies shared code that the window contents needs updating
//
void _glfwInputWindowDamage(_GLFWwindow* window) void _glfwInputWindowDamage(_GLFWwindow* window)
{ {
if (window->callbacks.refresh) if (window->callbacks.refresh)
window->callbacks.refresh((GLFWwindow*) window); window->callbacks.refresh((GLFWwindow*) window);
} }
// Notifies shared code that the user wishes to close a window
//
void _glfwInputWindowCloseRequest(_GLFWwindow* window) void _glfwInputWindowCloseRequest(_GLFWwindow* window)
{ {
window->shouldClose = GLFW_TRUE; window->shouldClose = GLFW_TRUE;
@@ -108,6 +136,8 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window)
window->callbacks.close((GLFWwindow*) window); window->callbacks.close((GLFWwindow*) window);
} }
// Notifies shared code that a window has changed its desired monitor
//
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor) void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor)
{ {
window->monitor = monitor; window->monitor = monitor;
@@ -127,7 +157,6 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
_GLFWctxconfig ctxconfig; _GLFWctxconfig ctxconfig;
_GLFWwndconfig wndconfig; _GLFWwndconfig wndconfig;
_GLFWwindow* window; _GLFWwindow* window;
_GLFWwindow* previous;
assert(title != NULL); assert(title != NULL);
assert(width >= 0); assert(width >= 0);
@@ -153,16 +182,6 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
wndconfig.title = title; wndconfig.title = title;
ctxconfig.share = (_GLFWwindow*) share; ctxconfig.share = (_GLFWwindow*) share;
if (ctxconfig.share)
{
if (ctxconfig.client == GLFW_NO_API ||
ctxconfig.share->context.client == GLFW_NO_API)
{
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
return NULL;
}
}
if (!_glfwIsValidContextConfig(&ctxconfig)) if (!_glfwIsValidContextConfig(&ctxconfig))
return NULL; return NULL;
@@ -191,36 +210,32 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->numer = GLFW_DONT_CARE; window->numer = GLFW_DONT_CARE;
window->denom = GLFW_DONT_CARE; window->denom = GLFW_DONT_CARE;
// Save the currently current context so it can be restored later
previous = _glfwPlatformGetTls(&_glfw.contextSlot);
if (ctxconfig.client != GLFW_NO_API)
glfwMakeContextCurrent(NULL);
// Open the actual window and create its context // Open the actual window and create its context
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig)) if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
{ {
glfwMakeContextCurrent((GLFWwindow*) previous);
glfwDestroyWindow((GLFWwindow*) window); glfwDestroyWindow((GLFWwindow*) window);
return NULL; return NULL;
} }
if (ctxconfig.client != GLFW_NO_API) if (ctxconfig.client != GLFW_NO_API)
{ {
window->context.makeCurrent(window); if (!_glfwRefreshContextAttribs(window, &ctxconfig))
// Retrieve the actual (as opposed to requested) context attributes
if (!_glfwRefreshContextAttribs(&ctxconfig))
{ {
glfwMakeContextCurrent((GLFWwindow*) previous);
glfwDestroyWindow((GLFWwindow*) window); glfwDestroyWindow((GLFWwindow*) window);
return NULL; return NULL;
} }
// Restore the previously current context (or NULL)
glfwMakeContextCurrent((GLFWwindow*) previous);
} }
if (!window->monitor) if (window->monitor)
{
if (wndconfig.centerCursor)
{
int width, height;
_glfwPlatformGetWindowSize(window, &width, &height);
_glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0);
}
}
else
{ {
if (wndconfig.visible) if (wndconfig.visible)
{ {
@@ -349,9 +364,6 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_COCOA_RETINA_FRAMEBUFFER: case GLFW_COCOA_RETINA_FRAMEBUFFER:
_glfw.hints.window.ns.retina = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.window.ns.retina = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
case GLFW_COCOA_FRAME_AUTOSAVE:
_glfw.hints.window.ns.frame = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_COCOA_GRAPHICS_SWITCHING: case GLFW_COCOA_GRAPHICS_SWITCHING:
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
@@ -396,6 +408,31 @@ GLFWAPI void glfwWindowHint(int hint, int value)
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint); _glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint);
} }
GLFWAPI void glfwWindowHintString(int hint, const char* value)
{
assert(value != NULL);
_GLFW_REQUIRE_INIT();
switch (hint)
{
case GLFW_COCOA_FRAME_NAME:
strncpy(_glfw.hints.window.ns.frameName, value,
sizeof(_glfw.hints.window.ns.frameName) - 1);
return;
case GLFW_X11_CLASS_NAME:
strncpy(_glfw.hints.window.x11.className, value,
sizeof(_glfw.hints.window.x11.className) - 1);
return;
case GLFW_X11_INSTANCE_NAME:
strncpy(_glfw.hints.window.x11.instanceName, value,
sizeof(_glfw.hints.window.x11.instanceName) - 1);
return;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
}
GLFWAPI void glfwDestroyWindow(GLFWwindow* handle) GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
{ {
_GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* window = (_GLFWwindow*) handle;
@@ -781,6 +818,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return _glfwPlatformWindowVisible(window); return _glfwPlatformWindowVisible(window);
case GLFW_MAXIMIZED: case GLFW_MAXIMIZED:
return _glfwPlatformWindowMaximized(window); return _glfwPlatformWindowMaximized(window);
case GLFW_HOVERED:
return _glfwPlatformWindowHovered(window);
case GLFW_TRANSPARENT_FRAMEBUFFER: case GLFW_TRANSPARENT_FRAMEBUFFER:
return _glfwPlatformFramebufferTransparent(window); return _glfwPlatformFramebufferTransparent(window);
case GLFW_RESIZABLE: case GLFW_RESIZABLE:
@@ -1015,6 +1054,17 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* handle
return cbfun; return cbfun;
} }
GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* handle,
GLFWwindowcontentscalefun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.scale, cbfun);
return cbfun;
}
GLFWAPI void glfwPollEvents(void) GLFWAPI void glfwPollEvents(void)
{ {
_GLFW_REQUIRE_INIT(); _GLFW_REQUIRE_INIT();

175
glfw/wl_init.c vendored
View File

@@ -34,7 +34,6 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <wayland-cursor.h>
static inline int min(int n1, int n2) static inline int min(int n1, int n2)
@@ -49,11 +48,17 @@ static void pointerHandleEnter(void* data,
wl_fixed_t sx, wl_fixed_t sx,
wl_fixed_t sy) wl_fixed_t sy)
{ {
// Happens in the case we just destroyed the surface.
if (!surface)
return;
_GLFWwindow* window = wl_surface_get_user_data(surface); _GLFWwindow* window = wl_surface_get_user_data(surface);
_glfw.wl.pointerSerial = serial; _glfw.wl.pointerSerial = serial;
_glfw.wl.pointerFocus = window; _glfw.wl.pointerFocus = window;
window->wl.hovered = GLFW_TRUE;
_glfwPlatformSetCursor(window, window->wl.currentCursor); _glfwPlatformSetCursor(window, window->wl.currentCursor);
_glfwInputCursorEnter(window, GLFW_TRUE); _glfwInputCursorEnter(window, GLFW_TRUE);
} }
@@ -68,6 +73,8 @@ static void pointerHandleLeave(void* data,
if (!window) if (!window)
return; return;
window->wl.hovered = GLFW_FALSE;
_glfw.wl.pointerSerial = serial; _glfw.wl.pointerSerial = serial;
_glfw.wl.pointerFocus = NULL; _glfw.wl.pointerFocus = NULL;
_glfwInputCursorEnter(window, GLFW_FALSE); _glfwInputCursorEnter(window, GLFW_FALSE);
@@ -131,31 +138,22 @@ static void pointerHandleAxis(void* data,
wl_fixed_t value) wl_fixed_t value)
{ {
_GLFWwindow* window = _glfw.wl.pointerFocus; _GLFWwindow* window = _glfw.wl.pointerFocus;
double scrollFactor;
double x = 0.0, y = 0.0; double x = 0.0, y = 0.0;
// Wayland scroll events are in pointer motion coordinate space (think two
// finger scroll). The factor 10 is commonly used to convert to "scroll
// step means 1.0.
const double scrollFactor = 1.0 / 10.0;
if (!window) if (!window)
return; return;
/* Wayland scroll events are in pointer motion coordinate space (think assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
* two finger scroll). The factor 10 is commonly used to convert to axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
* "scroll step means 1.0. */
scrollFactor = 1.0/10.0;
switch (axis) if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
{ x = wl_fixed_to_double(value) * scrollFactor;
case WL_POINTER_AXIS_HORIZONTAL_SCROLL: else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
x = wl_fixed_to_double(value) * scrollFactor; y = wl_fixed_to_double(value) * scrollFactor;
y = 0.0;
break;
case WL_POINTER_AXIS_VERTICAL_SCROLL:
x = 0.0;
y = wl_fixed_to_double(value) * scrollFactor;
break;
default:
assert(GLFW_FALSE);
break;
}
_glfwInputScroll(window, x, y); _glfwInputScroll(window, x, y);
} }
@@ -264,6 +262,10 @@ static void keyboardHandleKeymap(void* data,
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift"); 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift");
_glfw.wl.xkb.superMask = _glfw.wl.xkb.superMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4"); 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4");
_glfw.wl.xkb.capsLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock");
_glfw.wl.xkb.numLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2");
} }
static void keyboardHandleEnter(void* data, static void keyboardHandleEnter(void* data,
@@ -272,6 +274,10 @@ static void keyboardHandleEnter(void* data,
struct wl_surface* surface, struct wl_surface* surface,
struct wl_array* keys) struct wl_array* keys)
{ {
// Happens in the case we just destroyed the surface.
if (!surface)
return;
_GLFWwindow* window = wl_surface_get_user_data(surface); _GLFWwindow* window = wl_surface_get_user_data(surface);
_glfw.wl.keyboardFocus = window; _glfw.wl.keyboardFocus = window;
@@ -409,6 +415,10 @@ static void keyboardHandleModifiers(void* data,
modifiers |= GLFW_MOD_SHIFT; modifiers |= GLFW_MOD_SHIFT;
if (mask & _glfw.wl.xkb.superMask) if (mask & _glfw.wl.xkb.superMask)
modifiers |= GLFW_MOD_SUPER; modifiers |= GLFW_MOD_SUPER;
if (mask & _glfw.wl.xkb.capsLockMask)
modifiers |= GLFW_MOD_CAPS_LOCK;
if (mask & _glfw.wl.xkb.numLockMask)
modifiers |= GLFW_MOD_NUM_LOCK;
_glfw.wl.xkb.modifiers = modifiers; _glfw.wl.xkb.modifiers = modifiers;
} }
@@ -451,6 +461,17 @@ static const struct wl_seat_listener seatListener = {
seatHandleCapabilities seatHandleCapabilities
}; };
static void wmBaseHandlePing(void* data,
struct xdg_wm_base* wmBase,
uint32_t serial)
{
xdg_wm_base_pong(wmBase, serial);
}
static const struct xdg_wm_base_listener wmBaseListener = {
wmBaseHandlePing
};
static void registryHandleGlobal(void* data, static void registryHandleGlobal(void* data,
struct wl_registry* registry, struct wl_registry* registry,
uint32_t name, uint32_t name,
@@ -487,6 +508,12 @@ static void registryHandleGlobal(void* data,
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL); wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
} }
} }
else if (strcmp(interface, "xdg_wm_base") == 0)
{
_glfw.wl.wmBase =
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
}
else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0)
{ {
_glfw.wl.relativePointerManager = _glfw.wl.relativePointerManager =
@@ -663,50 +690,82 @@ static void createKeyTables(void)
int _glfwPlatformInit(void) int _glfwPlatformInit(void)
{ {
_glfw.wl.xkb.handle = dlopen("libxkbcommon.so.0", RTLD_LAZY | RTLD_GLOBAL); _glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
if (!_glfw.wl.cursor.handle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to open libwayland-cursor");
return GLFW_FALSE;
}
_glfw.wl.cursor.theme_load = (PFN_wl_cursor_theme_load)
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_theme_load");
_glfw.wl.cursor.theme_destroy = (PFN_wl_cursor_theme_destroy)
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_theme_destroy");
_glfw.wl.cursor.theme_get_cursor = (PFN_wl_cursor_theme_get_cursor)
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_theme_get_cursor");
_glfw.wl.cursor.image_get_buffer = (PFN_wl_cursor_image_get_buffer)
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_image_get_buffer");
_glfw.wl.egl.handle = _glfw_dlopen("libwayland-egl.so.1");
if (!_glfw.wl.egl.handle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to open libwayland-egl");
return GLFW_FALSE;
}
_glfw.wl.egl.window_create = (PFN_wl_egl_window_create)
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_create");
_glfw.wl.egl.window_destroy = (PFN_wl_egl_window_destroy)
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_destroy");
_glfw.wl.egl.window_resize = (PFN_wl_egl_window_resize)
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_resize");
_glfw.wl.xkb.handle = _glfw_dlopen("libxkbcommon.so.0");
if (!_glfw.wl.xkb.handle) if (!_glfw.wl.xkb.handle)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to open libxkbcommon."); "Wayland: Failed to open libxkbcommon");
return GLFW_FALSE; return GLFW_FALSE;
} }
_glfw.wl.xkb.context_new = (PFN_xkb_context_new) _glfw.wl.xkb.context_new = (PFN_xkb_context_new)
dlsym(_glfw.wl.xkb.handle, "xkb_context_new"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_new");
_glfw.wl.xkb.context_unref = (PFN_xkb_context_unref) _glfw.wl.xkb.context_unref = (PFN_xkb_context_unref)
dlsym(_glfw.wl.xkb.handle, "xkb_context_unref"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_unref");
_glfw.wl.xkb.keymap_new_from_string = (PFN_xkb_keymap_new_from_string) _glfw.wl.xkb.keymap_new_from_string = (PFN_xkb_keymap_new_from_string)
dlsym(_glfw.wl.xkb.handle, "xkb_keymap_new_from_string"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_new_from_string");
_glfw.wl.xkb.keymap_unref = (PFN_xkb_keymap_unref) _glfw.wl.xkb.keymap_unref = (PFN_xkb_keymap_unref)
dlsym(_glfw.wl.xkb.handle, "xkb_keymap_unref"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_unref");
_glfw.wl.xkb.keymap_mod_get_index = (PFN_xkb_keymap_mod_get_index) _glfw.wl.xkb.keymap_mod_get_index = (PFN_xkb_keymap_mod_get_index)
dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index");
_glfw.wl.xkb.state_new = (PFN_xkb_state_new) _glfw.wl.xkb.state_new = (PFN_xkb_state_new)
dlsym(_glfw.wl.xkb.handle, "xkb_state_new"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_new");
_glfw.wl.xkb.state_unref = (PFN_xkb_state_unref) _glfw.wl.xkb.state_unref = (PFN_xkb_state_unref)
dlsym(_glfw.wl.xkb.handle, "xkb_state_unref"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_unref");
_glfw.wl.xkb.state_key_get_syms = (PFN_xkb_state_key_get_syms) _glfw.wl.xkb.state_key_get_syms = (PFN_xkb_state_key_get_syms)
dlsym(_glfw.wl.xkb.handle, "xkb_state_key_get_syms"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_key_get_syms");
_glfw.wl.xkb.state_update_mask = (PFN_xkb_state_update_mask) _glfw.wl.xkb.state_update_mask = (PFN_xkb_state_update_mask)
dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask");
_glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods) _glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods)
dlsym(_glfw.wl.xkb.handle, "xkb_state_serialize_mods"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_serialize_mods");
#ifdef HAVE_XKBCOMMON_COMPOSE_H #ifdef HAVE_XKBCOMMON_COMPOSE_H
_glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale) _glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_new_from_locale"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_new_from_locale");
_glfw.wl.xkb.compose_table_unref = (PFN_xkb_compose_table_unref) _glfw.wl.xkb.compose_table_unref = (PFN_xkb_compose_table_unref)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_unref"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_unref");
_glfw.wl.xkb.compose_state_new = (PFN_xkb_compose_state_new) _glfw.wl.xkb.compose_state_new = (PFN_xkb_compose_state_new)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_new"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_new");
_glfw.wl.xkb.compose_state_unref = (PFN_xkb_compose_state_unref) _glfw.wl.xkb.compose_state_unref = (PFN_xkb_compose_state_unref)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_unref"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_unref");
_glfw.wl.xkb.compose_state_feed = (PFN_xkb_compose_state_feed) _glfw.wl.xkb.compose_state_feed = (PFN_xkb_compose_state_feed)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_feed"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_feed");
_glfw.wl.xkb.compose_state_get_status = (PFN_xkb_compose_state_get_status) _glfw.wl.xkb.compose_state_get_status = (PFN_xkb_compose_state_get_status)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_status"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_status");
_glfw.wl.xkb.compose_state_get_one_sym = (PFN_xkb_compose_state_get_one_sym) _glfw.wl.xkb.compose_state_get_one_sym = (PFN_xkb_compose_state_get_one_sym)
dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_one_sym"); _glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_one_sym");
#endif #endif
_glfw.wl.display = wl_display_connect(NULL); _glfw.wl.display = wl_display_connect(NULL);
@@ -747,7 +806,7 @@ int _glfwPlatformInit(void)
if (!_glfw.wl.cursorTheme) if (!_glfw.wl.cursorTheme)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Unable to load default cursor theme\n"); "Wayland: Unable to load default cursor theme");
return GLFW_FALSE; return GLFW_FALSE;
} }
_glfw.wl.cursorSurface = _glfw.wl.cursorSurface =
@@ -759,22 +818,38 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void) void _glfwPlatformTerminate(void)
{ {
_glfwTerminateEGL();
_glfwTerminateJoysticksLinux(); _glfwTerminateJoysticksLinux();
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
_glfw_dlclose(_glfw.wl.egl.handle);
_glfw.wl.egl.handle = NULL;
}
#ifdef HAVE_XKBCOMMON_COMPOSE_H #ifdef HAVE_XKBCOMMON_COMPOSE_H
xkb_compose_state_unref(_glfw.wl.xkb.composeState); if (_glfw.wl.xkb.composeState)
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
#endif #endif
if (_glfw.wl.xkb.keymap)
xkb_keymap_unref(_glfw.wl.xkb.keymap); xkb_keymap_unref(_glfw.wl.xkb.keymap);
xkb_state_unref(_glfw.wl.xkb.state); if (_glfw.wl.xkb.state)
xkb_context_unref(_glfw.wl.xkb.context); xkb_state_unref(_glfw.wl.xkb.state);
if (_glfw.wl.xkb.context)
dlclose(_glfw.wl.xkb.handle); xkb_context_unref(_glfw.wl.xkb.context);
_glfw.wl.xkb.handle = NULL; if (_glfw.wl.xkb.handle)
{
_glfw_dlclose(_glfw.wl.xkb.handle);
_glfw.wl.xkb.handle = NULL;
}
if (_glfw.wl.cursorTheme) if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme); wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
if (_glfw.wl.cursor.handle)
{
_glfw_dlclose(_glfw.wl.cursor.handle);
_glfw.wl.cursor.handle = NULL;
}
if (_glfw.wl.cursorSurface) if (_glfw.wl.cursorSurface)
wl_surface_destroy(_glfw.wl.cursorSurface); wl_surface_destroy(_glfw.wl.cursorSurface);
if (_glfw.wl.compositor) if (_glfw.wl.compositor)
@@ -783,6 +858,8 @@ void _glfwPlatformTerminate(void)
wl_shm_destroy(_glfw.wl.shm); wl_shm_destroy(_glfw.wl.shm);
if (_glfw.wl.shell) if (_glfw.wl.shell)
wl_shell_destroy(_glfw.wl.shell); wl_shell_destroy(_glfw.wl.shell);
if (_glfw.wl.wmBase)
xdg_wm_base_destroy(_glfw.wl.wmBase);
if (_glfw.wl.pointer) if (_glfw.wl.pointer)
wl_pointer_destroy(_glfw.wl.pointer); wl_pointer_destroy(_glfw.wl.pointer);
if (_glfw.wl.keyboard) if (_glfw.wl.keyboard)

2
glfw/wl_monitor.c vendored
View File

@@ -52,7 +52,7 @@ static void geometry(void* data,
monitor->heightMM = physicalHeight; monitor->heightMM = physicalHeight;
snprintf(name, sizeof(name), "%s %s", make, model); snprintf(name, sizeof(name), "%s %s", make, model);
monitor->name = strdup(name); monitor->name = _glfw_strdup(name);
} }
static void mode(void* data, static void mode(void* data,

58
glfw/wl_platform.h vendored
View File

@@ -52,6 +52,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "egl_context.h" #include "egl_context.h"
#include "osmesa_context.h" #include "osmesa_context.h"
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-relative-pointer-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-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
@@ -71,6 +72,34 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#define _GLFW_PLATFORM_CONTEXT_STATE #define _GLFW_PLATFORM_CONTEXT_STATE
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE #define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
struct wl_cursor_image {
uint32_t width;
uint32_t height;
uint32_t hotspot_x;
uint32_t hotspot_y;
uint32_t delay;
};
struct wl_cursor {
unsigned int image_count;
struct wl_cursor_image** images;
char* name;
};
typedef struct wl_cursor_theme* (* PFN_wl_cursor_theme_load)(const char*, int, struct wl_shm*);
typedef void (* PFN_wl_cursor_theme_destroy)(struct wl_cursor_theme*);
typedef struct wl_cursor* (* PFN_wl_cursor_theme_get_cursor)(struct wl_cursor_theme*, const char*);
typedef struct wl_buffer* (* PFN_wl_cursor_image_get_buffer)(struct wl_cursor_image*);
#define wl_cursor_theme_load _glfw.wl.cursor.theme_load
#define wl_cursor_theme_destroy _glfw.wl.cursor.theme_destroy
#define wl_cursor_theme_get_cursor _glfw.wl.cursor.theme_get_cursor
#define wl_cursor_image_get_buffer _glfw.wl.cursor.image_get_buffer
typedef struct wl_egl_window* (* PFN_wl_egl_window_create)(struct wl_surface*, int, int);
typedef void (* PFN_wl_egl_window_destroy)(struct wl_egl_window*);
typedef void (* PFN_wl_egl_window_resize)(struct wl_egl_window*, int, int, int, int);
#define wl_egl_window_create _glfw.wl.egl.window_create
#define wl_egl_window_destroy _glfw.wl.egl.window_destroy
#define wl_egl_window_resize _glfw.wl.egl.window_resize
typedef struct xkb_context* (* PFN_xkb_context_new)(enum xkb_context_flags); typedef struct xkb_context* (* PFN_xkb_context_new)(enum xkb_context_flags);
typedef void (* PFN_xkb_context_unref)(struct xkb_context*); typedef void (* PFN_xkb_context_unref)(struct xkb_context*);
typedef struct xkb_keymap* (* PFN_xkb_keymap_new_from_string)(struct xkb_context*, const char*, enum xkb_keymap_format, enum xkb_keymap_compile_flags); typedef struct xkb_keymap* (* PFN_xkb_keymap_new_from_string)(struct xkb_context*, const char*, enum xkb_keymap_format, enum xkb_keymap_compile_flags);
@@ -117,12 +146,18 @@ typedef struct _GLFWwindowWayland
int width, height; int width, height;
GLFWbool visible; GLFWbool visible;
GLFWbool maximized; GLFWbool maximized;
GLFWbool hovered;
GLFWbool transparent; GLFWbool transparent;
struct wl_surface* surface; struct wl_surface* surface;
struct wl_egl_window* native; struct wl_egl_window* native;
struct wl_shell_surface* shellSurface; struct wl_shell_surface* shellSurface;
struct wl_callback* callback; struct wl_callback* callback;
struct {
struct xdg_surface* surface;
struct xdg_toplevel* toplevel;
} xdg;
_GLFWcursor* currentCursor; _GLFWcursor* currentCursor;
double cursorPosX, cursorPosY; double cursorPosX, cursorPosY;
@@ -142,6 +177,9 @@ typedef struct _GLFWwindowWayland
struct zwp_idle_inhibitor_v1* idleInhibitor; struct zwp_idle_inhibitor_v1* idleInhibitor;
// This is a hack to prevent auto-iconification on creation.
GLFWbool justCreated;
} _GLFWwindowWayland; } _GLFWwindowWayland;
// Wayland-specific global data // Wayland-specific global data
@@ -156,6 +194,7 @@ typedef struct _GLFWlibraryWayland
struct wl_seat* seat; struct wl_seat* seat;
struct wl_pointer* pointer; struct wl_pointer* pointer;
struct wl_keyboard* keyboard; struct wl_keyboard* keyboard;
struct xdg_wm_base* wmBase;
struct zwp_relative_pointer_manager_v1* relativePointerManager; struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints; struct zwp_pointer_constraints_v1* pointerConstraints;
struct zwp_idle_inhibit_manager_v1* idleInhibitManager; struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
@@ -183,6 +222,8 @@ typedef struct _GLFWlibraryWayland
xkb_mod_mask_t altMask; xkb_mod_mask_t altMask;
xkb_mod_mask_t shiftMask; xkb_mod_mask_t shiftMask;
xkb_mod_mask_t superMask; xkb_mod_mask_t superMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
unsigned int modifiers; unsigned int modifiers;
PFN_xkb_context_new context_new; PFN_xkb_context_new context_new;
@@ -210,6 +251,23 @@ typedef struct _GLFWlibraryWayland
_GLFWwindow* pointerFocus; _GLFWwindow* pointerFocus;
_GLFWwindow* keyboardFocus; _GLFWwindow* keyboardFocus;
struct {
void* handle;
PFN_wl_cursor_theme_load theme_load;
PFN_wl_cursor_theme_destroy theme_destroy;
PFN_wl_cursor_theme_get_cursor theme_get_cursor;
PFN_wl_cursor_image_get_buffer image_get_buffer;
} cursor;
struct {
void* handle;
PFN_wl_egl_window_create window_create;
PFN_wl_egl_window_destroy window_destroy;
PFN_wl_egl_window_resize window_resize;
} egl;
} _GLFWlibraryWayland; } _GLFWlibraryWayland;
// Wayland-specific per-monitor data // Wayland-specific per-monitor data

316
glfw/wl_window.c vendored
View File

@@ -37,9 +37,6 @@
#include <sys/mman.h> #include <sys/mman.h>
#include <poll.h> #include <poll.h>
#include <wayland-egl.h>
#include <wayland-cursor.h>
static void handlePing(void* data, static void handlePing(void* data,
struct wl_shell_surface* shellSurface, struct wl_shell_surface* shellSurface,
@@ -125,6 +122,7 @@ static void checkScaleChange(_GLFWwindow* window)
wl_surface_set_buffer_scale(window->wl.surface, scale); wl_surface_set_buffer_scale(window->wl.surface, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0); wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight); _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
_glfwInputWindowContentScale(window, scale, scale);
} }
} }
@@ -238,10 +236,21 @@ static GLFWbool createSurface(_GLFWwindow* window,
static GLFWbool createShellSurface(_GLFWwindow* window) static GLFWbool createShellSurface(_GLFWwindow* window)
{ {
if (!_glfw.wl.shell)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: wl_shell protocol not available");
return GLFW_FALSE;
}
window->wl.shellSurface = wl_shell_get_shell_surface(_glfw.wl.shell, window->wl.shellSurface = wl_shell_get_shell_surface(_glfw.wl.shell,
window->wl.surface); window->wl.surface);
if (!window->wl.shellSurface) if (!window->wl.shellSurface)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Shell surface creation failed");
return GLFW_FALSE; return GLFW_FALSE;
}
wl_shell_surface_add_listener(window->wl.shellSurface, wl_shell_surface_add_listener(window->wl.shellSurface,
&shellSurfaceListener, &shellSurfaceListener,
@@ -275,6 +284,146 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
return GLFW_TRUE; return GLFW_TRUE;
} }
static void xdgToplevelHandleConfigure(void* data,
struct xdg_toplevel* toplevel,
int32_t width,
int32_t height,
struct wl_array* states)
{
_GLFWwindow* window = data;
float aspectRatio;
float targetRatio;
uint32_t* state;
GLFWbool maximized = GLFW_FALSE;
GLFWbool fullscreen = GLFW_FALSE;
GLFWbool activated = GLFW_FALSE;
wl_array_for_each(state, states)
{
switch (*state)
{
case XDG_TOPLEVEL_STATE_MAXIMIZED:
maximized = GLFW_TRUE;
break;
case XDG_TOPLEVEL_STATE_FULLSCREEN:
fullscreen = GLFW_TRUE;
break;
case XDG_TOPLEVEL_STATE_RESIZING:
break;
case XDG_TOPLEVEL_STATE_ACTIVATED:
activated = GLFW_TRUE;
break;
}
}
if (width != 0 && height != 0)
{
if (!maximized && !fullscreen)
{
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
{
aspectRatio = (float)width / (float)height;
targetRatio = (float)window->numer / (float)window->denom;
if (aspectRatio < targetRatio)
height = width / targetRatio;
else if (aspectRatio > targetRatio)
width = height * targetRatio;
}
}
_glfwInputWindowSize(window, width, height);
_glfwPlatformSetWindowSize(window, width, height);
_glfwInputWindowDamage(window);
}
if (!window->wl.justCreated && !activated && window->autoIconify)
_glfwPlatformIconifyWindow(window);
_glfwInputWindowFocus(window, activated);
window->wl.justCreated = GLFW_FALSE;
}
static void xdgToplevelHandleClose(void* data,
struct xdg_toplevel* toplevel)
{
_GLFWwindow* window = data;
_glfwInputWindowCloseRequest(window);
}
static const struct xdg_toplevel_listener xdgToplevelListener = {
xdgToplevelHandleConfigure,
xdgToplevelHandleClose
};
static void xdgSurfaceHandleConfigure(void* data,
struct xdg_surface* surface,
uint32_t serial)
{
xdg_surface_ack_configure(surface, serial);
}
static const struct xdg_surface_listener xdgSurfaceListener = {
xdgSurfaceHandleConfigure
};
static GLFWbool createXdgSurface(_GLFWwindow* window)
{
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
window->wl.surface);
if (!window->wl.xdg.surface)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: xdg-surface creation failed");
return GLFW_FALSE;
}
xdg_surface_add_listener(window->wl.xdg.surface,
&xdgSurfaceListener,
window);
window->wl.xdg.toplevel = xdg_surface_get_toplevel(window->wl.xdg.surface);
if (!window->wl.xdg.toplevel)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: xdg-toplevel creation failed");
return GLFW_FALSE;
}
xdg_toplevel_add_listener(window->wl.xdg.toplevel,
&xdgToplevelListener,
window);
if (window->wl.title)
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE)
xdg_toplevel_set_min_size(window->wl.xdg.toplevel,
window->minwidth, window->minheight);
if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE)
xdg_toplevel_set_max_size(window->wl.xdg.toplevel,
window->maxwidth, window->maxheight);
if (window->monitor)
{
xdg_toplevel_set_fullscreen(window->wl.xdg.toplevel,
window->monitor->wl.output);
setIdleInhibitor(window, GLFW_TRUE);
}
else if (window->wl.maximized)
{
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
setIdleInhibitor(window, GLFW_FALSE);
}
else
{
setIdleInhibitor(window, GLFW_FALSE);
}
wl_surface_commit(window->wl.surface);
wl_display_roundtrip(_glfw.wl.display);
return GLFW_TRUE;
}
static int static int
createTmpfileCloexec(char* tmpname) createTmpfileCloexec(char* tmpname)
{ {
@@ -411,6 +560,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig, const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig) const _GLFWfbconfig* fbconfig)
{ {
window->wl.justCreated = GLFW_TRUE;
window->wl.transparent = fbconfig->transparent; window->wl.transparent = fbconfig->transparent;
if (!createSurface(window, wndconfig)) if (!createSurface(window, wndconfig))
@@ -436,17 +586,27 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
} }
if (wndconfig->title) if (wndconfig->title)
window->wl.title = strdup(wndconfig->title); window->wl.title = _glfw_strdup(wndconfig->title);
if (wndconfig->visible) if (wndconfig->visible)
{ {
if (!createShellSurface(window)) if (_glfw.wl.wmBase)
return GLFW_FALSE; {
if (!createXdgSurface(window))
return GLFW_FALSE;
}
else
{
if (!createShellSurface(window))
return GLFW_FALSE;
}
window->wl.visible = GLFW_TRUE; window->wl.visible = GLFW_TRUE;
} }
else else
{ {
window->wl.xdg.surface = NULL;
window->wl.xdg.toplevel = NULL;
window->wl.shellSurface = NULL; window->wl.shellSurface = NULL;
window->wl.visible = GLFW_FALSE; window->wl.visible = GLFW_FALSE;
} }
@@ -485,6 +645,12 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (window->wl.shellSurface) if (window->wl.shellSurface)
wl_shell_surface_destroy(window->wl.shellSurface); wl_shell_surface_destroy(window->wl.shellSurface);
if (window->wl.xdg.toplevel)
xdg_toplevel_destroy(window->wl.xdg.toplevel);
if (window->wl.xdg.surface)
xdg_surface_destroy(window->wl.xdg.surface);
if (window->wl.surface) if (window->wl.surface)
wl_surface_destroy(window->wl.surface); wl_surface_destroy(window->wl.surface);
@@ -496,8 +662,10 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
{ {
if (window->wl.title) if (window->wl.title)
free(window->wl.title); free(window->wl.title);
window->wl.title = strdup(title); window->wl.title = _glfw_strdup(title);
if (window->wl.shellSurface) if (window->wl.xdg.toplevel)
xdg_toplevel_set_title(window->wl.xdg.toplevel, title);
else if (window->wl.shellSurface)
wl_shell_surface_set_title(window->wl.shellSurface, title); wl_shell_surface_set_title(window->wl.shellSurface, title);
} }
@@ -549,8 +717,24 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
int minwidth, int minheight, int minwidth, int minheight,
int maxwidth, int maxheight) int maxwidth, int maxheight)
{ {
// TODO: find out how to trigger a resize. if (_glfw.wl.wmBase)
// The actual limits are checked in the wl_shell_surface::configure handler. {
if (window->wl.xdg.toplevel)
{
if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
minwidth = minheight = 0;
if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
maxwidth = maxheight = 0;
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
wl_surface_commit(window->wl.surface);
}
}
else
{
// TODO: find out how to trigger a resize.
// The actual limits are checked in the wl_shell_surface::configure handler.
}
} }
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom) void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
@@ -585,41 +769,59 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
void _glfwPlatformIconifyWindow(_GLFWwindow* window) void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{ {
// TODO: move to xdg_shell instead of wl_shell. if (_glfw.wl.wmBase)
_glfwInputError(GLFW_PLATFORM_ERROR, {
"Wayland: Iconify window not supported"); if (window->wl.xdg.toplevel)
xdg_toplevel_set_minimized(window->wl.xdg.toplevel);
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Iconify window not supported on wl_shell");
}
} }
void _glfwPlatformRestoreWindow(_GLFWwindow* window) void _glfwPlatformRestoreWindow(_GLFWwindow* window)
{ {
// TODO: also do the same for iconified. if (window->wl.xdg.toplevel)
if (window->monitor || window->wl.maximized)
{ {
if (window->wl.shellSurface) if (window->monitor)
wl_shell_surface_set_toplevel(window->wl.shellSurface); xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
if (window->wl.maximized)
window->wl.maximized = GLFW_FALSE; xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
// There is no way to unset minimized, or even to know if we are
// minimized, so there is nothing to do here.
} }
else if (window->wl.shellSurface)
{
if (window->monitor || window->wl.maximized)
wl_shell_surface_set_toplevel(window->wl.shellSurface);
}
_glfwInputWindowMonitor(window, NULL);
window->wl.maximized = GLFW_FALSE;
} }
void _glfwPlatformMaximizeWindow(_GLFWwindow* window) void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
{ {
if (!window->monitor && !window->wl.maximized) if (window->wl.xdg.toplevel)
{ {
if (window->wl.shellSurface) xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
{
// Let the compositor select the best output.
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
}
window->wl.maximized = GLFW_TRUE;
} }
else if (window->wl.shellSurface)
{
// Let the compositor select the best output.
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
}
window->wl.maximized = GLFW_TRUE;
} }
void _glfwPlatformShowWindow(_GLFWwindow* window) void _glfwPlatformShowWindow(_GLFWwindow* window)
{ {
if (!window->monitor) if (!window->wl.visible)
{ {
if (!window->wl.shellSurface) if (_glfw.wl.wmBase)
createXdgSurface(window);
else if (!window->wl.shellSurface)
createShellSurface(window); createShellSurface(window);
window->wl.visible = GLFW_TRUE; window->wl.visible = GLFW_TRUE;
} }
@@ -627,12 +829,19 @@ void _glfwPlatformShowWindow(_GLFWwindow* window)
void _glfwPlatformHideWindow(_GLFWwindow* window) void _glfwPlatformHideWindow(_GLFWwindow* window)
{ {
if (!window->monitor) if (window->wl.xdg.toplevel)
{ {
if (window->wl.shellSurface) xdg_toplevel_destroy(window->wl.xdg.toplevel);
wl_shell_surface_destroy(window->wl.shellSurface); xdg_surface_destroy(window->wl.xdg.surface);
window->wl.visible = GLFW_FALSE; window->wl.xdg.toplevel = NULL;
window->wl.xdg.surface = NULL;
} }
else if (window->wl.shellSurface)
{
wl_shell_surface_destroy(window->wl.shellSurface);
window->wl.shellSurface = NULL;
}
window->wl.visible = GLFW_FALSE;
} }
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
@@ -662,20 +871,35 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
int width, int height, int width, int height,
int refreshRate) int refreshRate)
{ {
if (monitor) if (window->wl.xdg.toplevel)
{ {
wl_shell_surface_set_fullscreen( if (monitor)
window->wl.shellSurface, {
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT, xdg_toplevel_set_fullscreen(
refreshRate * 1000, // Convert Hz to mHz. window->wl.xdg.toplevel,
monitor->wl.output); monitor->wl.output);
setIdleInhibitor(window, GLFW_TRUE); }
else
{
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
}
} }
else else if (window->wl.shellSurface)
{ {
wl_shell_surface_set_toplevel(window->wl.shellSurface); if (monitor)
setIdleInhibitor(window, GLFW_FALSE); {
wl_shell_surface_set_fullscreen(
window->wl.shellSurface,
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
refreshRate * 1000, // Convert Hz to mHz.
monitor->wl.output);
}
else
{
wl_shell_surface_set_toplevel(window->wl.shellSurface);
}
} }
setIdleInhibitor(window, monitor ? GLFW_TRUE : GLFW_FALSE);
_glfwInputWindowMonitor(window, monitor); _glfwInputWindowMonitor(window, monitor);
} }
@@ -686,7 +910,8 @@ int _glfwPlatformWindowFocused(_GLFWwindow* window)
int _glfwPlatformWindowIconified(_GLFWwindow* window) int _glfwPlatformWindowIconified(_GLFWwindow* window)
{ {
// TODO: move to xdg_shell, wl_shell doesn't have any iconified concept. // wl_shell doesn't have any iconified concept, and xdg-shell doesnt give
// any way to request whether a surface is iconified.
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -700,6 +925,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return window->wl.maximized; return window->wl.maximized;
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return window->wl.hovered;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return window->wl.transparent; return window->wl.transparent;

90
glfw/x11_init.c vendored
View File

@@ -461,17 +461,17 @@ static void detectEWMH(void)
// //
static GLFWbool initExtensions(void) static GLFWbool initExtensions(void)
{ {
_glfw.x11.vidmode.handle = dlopen("libXxf86vm.so.1", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.vidmode.handle = _glfw_dlopen("libXxf86vm.so.1");
if (_glfw.x11.vidmode.handle) if (_glfw.x11.vidmode.handle)
{ {
_glfw.x11.vidmode.QueryExtension = (PFN_XF86VidModeQueryExtension) _glfw.x11.vidmode.QueryExtension = (PFN_XF86VidModeQueryExtension)
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeQueryExtension"); _glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeQueryExtension");
_glfw.x11.vidmode.GetGammaRamp = (PFN_XF86VidModeGetGammaRamp) _glfw.x11.vidmode.GetGammaRamp = (PFN_XF86VidModeGetGammaRamp)
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRamp"); _glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRamp");
_glfw.x11.vidmode.SetGammaRamp = (PFN_XF86VidModeSetGammaRamp) _glfw.x11.vidmode.SetGammaRamp = (PFN_XF86VidModeSetGammaRamp)
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeSetGammaRamp"); _glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeSetGammaRamp");
_glfw.x11.vidmode.GetGammaRampSize = (PFN_XF86VidModeGetGammaRampSize) _glfw.x11.vidmode.GetGammaRampSize = (PFN_XF86VidModeGetGammaRampSize)
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRampSize"); _glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRampSize");
_glfw.x11.vidmode.available = _glfw.x11.vidmode.available =
XF86VidModeQueryExtension(_glfw.x11.display, XF86VidModeQueryExtension(_glfw.x11.display,
@@ -479,13 +479,13 @@ static GLFWbool initExtensions(void)
&_glfw.x11.vidmode.errorBase); &_glfw.x11.vidmode.errorBase);
} }
_glfw.x11.xi.handle = dlopen("libXi.so.6", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.xi.handle = _glfw_dlopen("libXi.so.6");
if (_glfw.x11.xi.handle) if (_glfw.x11.xi.handle)
{ {
_glfw.x11.xi.QueryVersion = (PFN_XIQueryVersion) _glfw.x11.xi.QueryVersion = (PFN_XIQueryVersion)
dlsym(_glfw.x11.xi.handle, "XIQueryVersion"); _glfw_dlsym(_glfw.x11.xi.handle, "XIQueryVersion");
_glfw.x11.xi.SelectEvents = (PFN_XISelectEvents) _glfw.x11.xi.SelectEvents = (PFN_XISelectEvents)
dlsym(_glfw.x11.xi.handle, "XISelectEvents"); _glfw_dlsym(_glfw.x11.xi.handle, "XISelectEvents");
if (XQueryExtension(_glfw.x11.display, if (XQueryExtension(_glfw.x11.display,
"XInputExtension", "XInputExtension",
@@ -505,45 +505,45 @@ static GLFWbool initExtensions(void)
} }
} }
_glfw.x11.randr.handle = dlopen("libXrandr.so.2", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.randr.handle = _glfw_dlopen("libXrandr.so.2");
if (_glfw.x11.randr.handle) if (_glfw.x11.randr.handle)
{ {
_glfw.x11.randr.AllocGamma = (PFN_XRRAllocGamma) _glfw.x11.randr.AllocGamma = (PFN_XRRAllocGamma)
dlsym(_glfw.x11.randr.handle, "XRRAllocGamma"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRAllocGamma");
_glfw.x11.randr.FreeGamma = (PFN_XRRFreeGamma) _glfw.x11.randr.FreeGamma = (PFN_XRRFreeGamma)
dlsym(_glfw.x11.randr.handle, "XRRFreeGamma"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeGamma");
_glfw.x11.randr.FreeCrtcInfo = (PFN_XRRFreeCrtcInfo) _glfw.x11.randr.FreeCrtcInfo = (PFN_XRRFreeCrtcInfo)
dlsym(_glfw.x11.randr.handle, "XRRFreeCrtcInfo"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeCrtcInfo");
_glfw.x11.randr.FreeGamma = (PFN_XRRFreeGamma) _glfw.x11.randr.FreeGamma = (PFN_XRRFreeGamma)
dlsym(_glfw.x11.randr.handle, "XRRFreeGamma"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeGamma");
_glfw.x11.randr.FreeOutputInfo = (PFN_XRRFreeOutputInfo) _glfw.x11.randr.FreeOutputInfo = (PFN_XRRFreeOutputInfo)
dlsym(_glfw.x11.randr.handle, "XRRFreeOutputInfo"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeOutputInfo");
_glfw.x11.randr.FreeScreenResources = (PFN_XRRFreeScreenResources) _glfw.x11.randr.FreeScreenResources = (PFN_XRRFreeScreenResources)
dlsym(_glfw.x11.randr.handle, "XRRFreeScreenResources"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeScreenResources");
_glfw.x11.randr.GetCrtcGamma = (PFN_XRRGetCrtcGamma) _glfw.x11.randr.GetCrtcGamma = (PFN_XRRGetCrtcGamma)
dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGamma"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGamma");
_glfw.x11.randr.GetCrtcGammaSize = (PFN_XRRGetCrtcGammaSize) _glfw.x11.randr.GetCrtcGammaSize = (PFN_XRRGetCrtcGammaSize)
dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGammaSize"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGammaSize");
_glfw.x11.randr.GetCrtcInfo = (PFN_XRRGetCrtcInfo) _glfw.x11.randr.GetCrtcInfo = (PFN_XRRGetCrtcInfo)
dlsym(_glfw.x11.randr.handle, "XRRGetCrtcInfo"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRGetCrtcInfo");
_glfw.x11.randr.GetOutputInfo = (PFN_XRRGetOutputInfo) _glfw.x11.randr.GetOutputInfo = (PFN_XRRGetOutputInfo)
dlsym(_glfw.x11.randr.handle, "XRRGetOutputInfo"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRGetOutputInfo");
_glfw.x11.randr.GetOutputPrimary = (PFN_XRRGetOutputPrimary) _glfw.x11.randr.GetOutputPrimary = (PFN_XRRGetOutputPrimary)
dlsym(_glfw.x11.randr.handle, "XRRGetOutputPrimary"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRGetOutputPrimary");
_glfw.x11.randr.GetScreenResourcesCurrent = (PFN_XRRGetScreenResourcesCurrent) _glfw.x11.randr.GetScreenResourcesCurrent = (PFN_XRRGetScreenResourcesCurrent)
dlsym(_glfw.x11.randr.handle, "XRRGetScreenResourcesCurrent"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRGetScreenResourcesCurrent");
_glfw.x11.randr.QueryExtension = (PFN_XRRQueryExtension) _glfw.x11.randr.QueryExtension = (PFN_XRRQueryExtension)
dlsym(_glfw.x11.randr.handle, "XRRQueryExtension"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRQueryExtension");
_glfw.x11.randr.QueryVersion = (PFN_XRRQueryVersion) _glfw.x11.randr.QueryVersion = (PFN_XRRQueryVersion)
dlsym(_glfw.x11.randr.handle, "XRRQueryVersion"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRQueryVersion");
_glfw.x11.randr.SelectInput = (PFN_XRRSelectInput) _glfw.x11.randr.SelectInput = (PFN_XRRSelectInput)
dlsym(_glfw.x11.randr.handle, "XRRSelectInput"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRSelectInput");
_glfw.x11.randr.SetCrtcConfig = (PFN_XRRSetCrtcConfig) _glfw.x11.randr.SetCrtcConfig = (PFN_XRRSetCrtcConfig)
dlsym(_glfw.x11.randr.handle, "XRRSetCrtcConfig"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRSetCrtcConfig");
_glfw.x11.randr.SetCrtcGamma = (PFN_XRRSetCrtcGamma) _glfw.x11.randr.SetCrtcGamma = (PFN_XRRSetCrtcGamma)
dlsym(_glfw.x11.randr.handle, "XRRSetCrtcGamma"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRSetCrtcGamma");
_glfw.x11.randr.UpdateConfiguration = (PFN_XRRUpdateConfiguration) _glfw.x11.randr.UpdateConfiguration = (PFN_XRRUpdateConfiguration)
dlsym(_glfw.x11.randr.handle, "XRRUpdateConfiguration"); _glfw_dlsym(_glfw.x11.randr.handle, "XRRUpdateConfiguration");
if (XRRQueryExtension(_glfw.x11.display, if (XRRQueryExtension(_glfw.x11.display,
&_glfw.x11.randr.eventBase, &_glfw.x11.randr.eventBase,
@@ -593,26 +593,26 @@ static GLFWbool initExtensions(void)
RROutputChangeNotifyMask); RROutputChangeNotifyMask);
} }
_glfw.x11.xcursor.handle = dlopen("libXcursor.so.1", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor.so.1");
if (_glfw.x11.xcursor.handle) if (_glfw.x11.xcursor.handle)
{ {
_glfw.x11.xcursor.ImageCreate = (PFN_XcursorImageCreate) _glfw.x11.xcursor.ImageCreate = (PFN_XcursorImageCreate)
dlsym(_glfw.x11.xcursor.handle, "XcursorImageCreate"); _glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageCreate");
_glfw.x11.xcursor.ImageDestroy = (PFN_XcursorImageDestroy) _glfw.x11.xcursor.ImageDestroy = (PFN_XcursorImageDestroy)
dlsym(_glfw.x11.xcursor.handle, "XcursorImageDestroy"); _glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageDestroy");
_glfw.x11.xcursor.ImageLoadCursor = (PFN_XcursorImageLoadCursor) _glfw.x11.xcursor.ImageLoadCursor = (PFN_XcursorImageLoadCursor)
dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor"); _glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
} }
_glfw.x11.xinerama.handle = dlopen("libXinerama.so.1", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama.so.1");
if (_glfw.x11.xinerama.handle) if (_glfw.x11.xinerama.handle)
{ {
_glfw.x11.xinerama.IsActive = (PFN_XineramaIsActive) _glfw.x11.xinerama.IsActive = (PFN_XineramaIsActive)
dlsym(_glfw.x11.xinerama.handle, "XineramaIsActive"); _glfw_dlsym(_glfw.x11.xinerama.handle, "XineramaIsActive");
_glfw.x11.xinerama.QueryExtension = (PFN_XineramaQueryExtension) _glfw.x11.xinerama.QueryExtension = (PFN_XineramaQueryExtension)
dlsym(_glfw.x11.xinerama.handle, "XineramaQueryExtension"); _glfw_dlsym(_glfw.x11.xinerama.handle, "XineramaQueryExtension");
_glfw.x11.xinerama.QueryScreens = (PFN_XineramaQueryScreens) _glfw.x11.xinerama.QueryScreens = (PFN_XineramaQueryScreens)
dlsym(_glfw.x11.xinerama.handle, "XineramaQueryScreens"); _glfw_dlsym(_glfw.x11.xinerama.handle, "XineramaQueryScreens");
if (XineramaQueryExtension(_glfw.x11.display, if (XineramaQueryExtension(_glfw.x11.display,
&_glfw.x11.xinerama.major, &_glfw.x11.xinerama.major,
@@ -644,22 +644,22 @@ static GLFWbool initExtensions(void)
} }
} }
_glfw.x11.x11xcb.handle = dlopen("libX11-xcb.so.1", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.x11xcb.handle = _glfw_dlopen("libX11-xcb.so.1");
if (_glfw.x11.x11xcb.handle) if (_glfw.x11.x11xcb.handle)
{ {
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection) _glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection"); _glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
} }
_glfw.x11.xrender.handle = dlopen("libXrender.so.1", RTLD_LAZY | RTLD_GLOBAL); _glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1");
if (_glfw.x11.xrender.handle) if (_glfw.x11.xrender.handle)
{ {
_glfw.x11.xrender.QueryExtension = (PFN_XRenderQueryExtension) _glfw.x11.xrender.QueryExtension = (PFN_XRenderQueryExtension)
dlsym(_glfw.x11.xrender.handle, "XRenderQueryExtension"); _glfw_dlsym(_glfw.x11.xrender.handle, "XRenderQueryExtension");
_glfw.x11.xrender.QueryVersion = (PFN_XRenderQueryVersion) _glfw.x11.xrender.QueryVersion = (PFN_XRenderQueryVersion)
dlsym(_glfw.x11.xrender.handle, "XRenderQueryVersion"); _glfw_dlsym(_glfw.x11.xrender.handle, "XRenderQueryVersion");
_glfw.x11.xrender.FindVisualFormat = (PFN_XRenderFindVisualFormat) _glfw.x11.xrender.FindVisualFormat = (PFN_XRenderFindVisualFormat)
dlsym(_glfw.x11.xrender.handle, "XRenderFindVisualFormat"); _glfw_dlsym(_glfw.x11.xrender.handle, "XRenderFindVisualFormat");
if (XRenderQueryExtension(_glfw.x11.display, if (XRenderQueryExtension(_glfw.x11.display,
&_glfw.x11.xrender.errorBase, &_glfw.x11.xrender.errorBase,
@@ -1002,25 +1002,25 @@ void _glfwPlatformTerminate(void)
if (_glfw.x11.x11xcb.handle) if (_glfw.x11.x11xcb.handle)
{ {
dlclose(_glfw.x11.x11xcb.handle); _glfw_dlclose(_glfw.x11.x11xcb.handle);
_glfw.x11.x11xcb.handle = NULL; _glfw.x11.x11xcb.handle = NULL;
} }
if (_glfw.x11.xcursor.handle) if (_glfw.x11.xcursor.handle)
{ {
dlclose(_glfw.x11.xcursor.handle); _glfw_dlclose(_glfw.x11.xcursor.handle);
_glfw.x11.xcursor.handle = NULL; _glfw.x11.xcursor.handle = NULL;
} }
if (_glfw.x11.randr.handle) if (_glfw.x11.randr.handle)
{ {
dlclose(_glfw.x11.randr.handle); _glfw_dlclose(_glfw.x11.randr.handle);
_glfw.x11.randr.handle = NULL; _glfw.x11.randr.handle = NULL;
} }
if (_glfw.x11.xinerama.handle) if (_glfw.x11.xinerama.handle)
{ {
dlclose(_glfw.x11.xinerama.handle); _glfw_dlclose(_glfw.x11.xinerama.handle);
_glfw.x11.xinerama.handle = NULL; _glfw.x11.xinerama.handle = NULL;
} }

13
glfw/x11_monitor.c vendored
View File

@@ -216,7 +216,7 @@ void _glfwPollMonitorsX11(void)
// Set the current video mode for the specified monitor // Set the current video mode for the specified monitor
// //
GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired) void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
{ {
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken) if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
{ {
@@ -231,7 +231,7 @@ GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
best = _glfwChooseVideoMode(monitor, desired); best = _glfwChooseVideoMode(monitor, desired);
_glfwPlatformGetVideoMode(monitor, &current); _glfwPlatformGetVideoMode(monitor, &current);
if (_glfwCompareVideoModes(&current, best) == 0) if (_glfwCompareVideoModes(&current, best) == 0)
return GLFW_TRUE; return;
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root); sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc); ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
@@ -269,16 +269,7 @@ GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
XRRFreeOutputInfo(oi); XRRFreeOutputInfo(oi);
XRRFreeCrtcInfo(ci); XRRFreeCrtcInfo(ci);
XRRFreeScreenResources(sr); XRRFreeScreenResources(sr);
if (!native)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Monitor mode list changed");
return GLFW_FALSE;
}
} }
return GLFW_TRUE;
} }
// Restore the saved (original) video mode for the specified monitor // Restore the saved (original) video mode for the specified monitor

2
glfw/x11_platform.h vendored
View File

@@ -425,7 +425,7 @@ typedef struct _GLFWcursorX11
void _glfwPollMonitorsX11(void); void _glfwPollMonitorsX11(void);
GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired); void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor); void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor);
Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot); Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot);

95
glfw/x11_window.c vendored
View File

@@ -212,6 +212,10 @@ static int translateState(int state)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if (state & Mod4Mask) if (state & Mod4Mask)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (state & LockMask)
mods |= GLFW_MOD_CAPS_LOCK;
if (state & Mod2Mask)
mods |= GLFW_MOD_NUM_LOCK;
return mods; return mods;
} }
@@ -703,49 +707,34 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
updateNormalHints(window, wndconfig->width, wndconfig->height); updateNormalHints(window, wndconfig->width, wndconfig->height);
// Set ICCCM WM_CLASS property and window title // Set ICCCM WM_CLASS property
{ {
XClassHint* hint = XAllocClassHint(); XClassHint* hint = XAllocClassHint();
char *wm_cclass = NULL, *wm_cname = NULL;
const char *real_title = wndconfig->title;
if (strlen(_glfw.hints.init.x11.className) && if (strlen(wndconfig->x11.instanceName) &&
strlen(_glfw.hints.init.x11.classClass)) strlen(wndconfig->x11.className))
{ {
hint->res_name = (char*) _glfw.hints.init.x11.className; hint->res_name = (char*) wndconfig->x11.instanceName;
hint->res_class = (char*) _glfw.hints.init.x11.classClass; hint->res_class = (char*) wndconfig->x11.className;
}
else if (strlen(real_title))
{
if (*real_title == 1) {
char *p = strchr(real_title, 30);
if (p && p > real_title + 1) {
wm_cname = calloc(p - real_title + 1, 1);
if (wm_cname) memcpy(wm_cname, real_title + 1, p - real_title - 1);
hint->res_name = wm_cname;
char *q = strchr(p + 1, 30);
if (q && q > p + 1) {
wm_cclass = calloc(q - p + 1, 1);
if (wm_cclass) memcpy(wm_cclass, p + 1, q - p - 1);
hint->res_class = wm_cclass;
real_title = q + 1;
}
}
} else {
hint->res_name = (char*) real_title;
hint->res_class = (char*) real_title;
}
} }
else else
{ {
hint->res_name = (char*) "glfw-application"; const char* resourceName = getenv("RESOURCE_NAME");
hint->res_class = (char*) "GLFW-Application"; if (resourceName && strlen(resourceName))
hint->res_name = (char*) resourceName;
else if (strlen(wndconfig->title))
hint->res_name = (char*) wndconfig->title;
else
hint->res_name = (char*) "glfw-application";
if (strlen(wndconfig->title))
hint->res_class = (char*) wndconfig->title;
else
hint->res_class = (char*) "GLFW-Application";
} }
XSetClassHint(_glfw.x11.display, window->x11.handle, hint); XSetClassHint(_glfw.x11.display, window->x11.handle, hint);
XFree(hint); XFree(hint);
free(wm_cclass); free(wm_cname);
_glfwPlatformSetWindowTitle(window, real_title);
} }
// Announce support for Xdnd (drag and drop) // Announce support for Xdnd (drag and drop)
@@ -756,6 +745,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
PropModeReplace, (unsigned char*) &version, 1); PropModeReplace, (unsigned char*) &version, 1);
} }
_glfwPlatformSetWindowTitle(window, wndconfig->title);
if (_glfw.x11.im) if (_glfw.x11.im)
{ {
@@ -1066,7 +1056,7 @@ static const char* getSelectionString(Atom selection)
if (targets[i] == XA_STRING) if (targets[i] == XA_STRING)
*selectionString = convertLatin1toUTF8(data); *selectionString = convertLatin1toUTF8(data);
else else
*selectionString = strdup(data); *selectionString = _glfw_strdup(data);
} }
XFree(data); XFree(data);
@@ -1086,10 +1076,8 @@ static const char* getSelectionString(Atom selection)
// Make the specified window and its video mode active on its monitor // Make the specified window and its video mode active on its monitor
// //
static GLFWbool acquireMonitor(_GLFWwindow* window) static void acquireMonitor(_GLFWwindow* window)
{ {
GLFWbool status;
if (_glfw.x11.saver.count == 0) if (_glfw.x11.saver.count == 0)
{ {
// Remember old screen saver settings // Remember old screen saver settings
@@ -1107,7 +1095,7 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
if (!window->monitor->window) if (!window->monitor->window)
_glfw.x11.saver.count++; _glfw.x11.saver.count++;
status = _glfwSetVideoModeX11(window->monitor, &window->videoMode); _glfwSetVideoModeX11(window->monitor, &window->videoMode);
if (window->x11.overrideRedirect) if (window->x11.overrideRedirect)
{ {
@@ -1123,7 +1111,6 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
} }
_glfwInputMonitorWindow(window->monitor, window); _glfwInputMonitorWindow(window->monitor, window);
return status;
} }
// Remove the window and restore the original video mode // Remove the window and restore the original video mode
@@ -1987,11 +1974,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
{ {
_glfwPlatformShowWindow(window); _glfwPlatformShowWindow(window);
updateWindowMode(window); updateWindowMode(window);
if (!acquireMonitor(window)) acquireMonitor(window);
return GLFW_FALSE;
if (wndconfig->centerCursor)
centerCursor(window);
} }
XFlush(_glfw.x11.display); XFlush(_glfw.x11.display);
@@ -2468,6 +2451,28 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return maximized; return maximized;
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
Window w = _glfw.x11.root;
while (w)
{
Window root;
int rootX, rootY, childX, childY;
unsigned int mask;
if (!XQueryPointer(_glfw.x11.display, w,
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
{
return GLFW_FALSE;
}
if (w == window->x11.handle)
return GLFW_TRUE;
}
return GLFW_FALSE;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
if (!window->x11.transparent) if (!window->x11.transparent)
@@ -2827,7 +2832,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
void _glfwPlatformSetClipboardString(const char* string) void _glfwPlatformSetClipboardString(const char* string)
{ {
free(_glfw.x11.clipboardString); free(_glfw.x11.clipboardString);
_glfw.x11.clipboardString = strdup(string); _glfw.x11.clipboardString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display, XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.CLIPBOARD, _glfw.x11.CLIPBOARD,
@@ -3019,7 +3024,7 @@ GLFWAPI void glfwSetX11SelectionString(const char* string)
_GLFW_REQUIRE_INIT(); _GLFW_REQUIRE_INIT();
free(_glfw.x11.primarySelectionString); free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = strdup(string); _glfw.x11.primarySelectionString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display, XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.PRIMARY, _glfw.x11.PRIMARY,

View File

@@ -8,7 +8,7 @@ to render arbitrary pixel (raster) graphics to the screen of the terminal
emulator. The major design goals are emulator. The major design goals are
* Should not require terminal emulators to understand image formats. * Should not require terminal emulators to understand image formats.
* Should allow specifying graphics to be drawn at individual pixel positions. * Should allow specifying graphics to be drawn at individual pixel positions.
* The graphics should integrate with the text, in particular it should be possible to draw graphics * The graphics should integrate with the text, in particular it should be possible to draw graphics
below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically. below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically.
* Should use optimizations when the client is running on the same computer as the terminal emulator. * Should use optimizations when the client is running on the same computer as the terminal emulator.
@@ -21,23 +21,37 @@ To see a quick demo, inside a kitty terminal run:
kitty icat path/to/some/image.png kitty icat path/to/some/image.png
``` ```
You can also see a screenshot with more sophisticated features such as alpha-blending and text over graphics You can also see a screenshot with more sophisticated features such as alpha-blending and text over graphics
link:https://github.com/kovidgoyal/kitty/issues/33#issuecomment-334436100[here]. link:https://github.com/kovidgoyal/kitty/issues/33#issuecomment-334436100[here].
Some third-party programs that use the kitty graphics protocol:
* link:https://github.com/dsanson/termpdf[termdpf] - a terminal PDF/DJVU/CBR viewer
* link:https://github.com/ranger/ranger[ranger] - a terminal file manager, with image previews, see this link:https://github.com/ranger/ranger/pull/1077[PR]
toc::[] toc::[]
== Getting the window size == Getting the window size
In order to know what size of images to display and how to position them, the client must be able to get the In order to know what size of images to display and how to position them, the client must be able to get the
window size in pixels and the number of cells per row and column. This can be done by using the `TIOCGWINSZ` ioctl. window size in pixels and the number of cells per row and column. This can be done by using the `TIOCGWINSZ` ioctl.
Some C code to demonstrate its use Some code to demonstrate its use
In C:
```C ```C
struct ttysize ts; struct ttysize ts;
ioctl(0, TIOCGWINSZ, &ts); ioctl(0, TIOCGWINSZ, &ts);
printf("number of columns: %i, number of rows: %i, screen width: %i, screen height: %i\n", sz.ws_col, sz.ws_row, sz.ws_xpixel, sz.ws_ypixel); printf("number of columns: %i, number of rows: %i, screen width: %i, screen height: %i\n", sz.ws_col, sz.ws_row, sz.ws_xpixel, sz.ws_ypixel);
``` ```
In Python:
```py
import array, fcntl, termios
buf = array.array('H', [0, 0, 0, 0])
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
print('number of columns: {}, number of rows: {}, screen width: {}, screen height: {}'.format(*buf))
```
Note that some terminals return `0` for the width and height values. Such terminals should be modified to return the correct values. Note that some terminals return `0` for the width and height values. Such terminals should be modified to return the correct values.
Examples of terminals that return correct values: `kitty, xterm` Examples of terminals that return correct values: `kitty, xterm`
@@ -50,7 +64,7 @@ All graphics escape codes are of the form:
``` ```
This is a so-called _Application Programming Command (APC)_. Most terminal This is a so-called _Application Programming Command (APC)_. Most terminal
emulators ignore APC codes, making it safe to use. emulators ignore APC codes, making it safe to use.
The control data is a comma-separated list of `key=value` pairs. The payload The control data is a comma-separated list of `key=value` pairs. The payload
is arbitrary binary data, base64-encoded to prevent interoperation problems is arbitrary binary data, base64-encoded to prevent interoperation problems
@@ -124,12 +138,12 @@ The transmission medium is specified using the `t` key. The `t` key defaults to
and can take the values: and can take the values:
|=== |===
| Value of `t` | Meaning | Value of `t` | Meaning
| d | Direct (the data is transmitted within the escape code itself) | d | Direct (the data is transmitted within the escape code itself)
| f | A simple file | f | A simple file
| t | A temporary file, the terminal emulator will delete the file after reading the pixel data | t | A temporary file, the terminal emulator will delete the file after reading the pixel data
| s | A http://man7.org/linux/man-pages/man7/shm_overview.7.html[POSIX shared memory object]. The terminal emulator will delete it after reading the pixel data | s | A http://man7.org/linux/man-pages/man7/shm_overview.7.html[POSIX shared memory object]. The terminal emulator will delete it after reading the pixel data
|=== |===
==== Local client ==== Local client
@@ -175,9 +189,9 @@ if the data is split into three chunks, the client would send the following
sequence of escape codes to the terminal emulator: sequence of escape codes to the terminal emulator:
``` ```
<ESC>_Gs=100,v=30,m=1;<encoded pixel data first chunk><ESC>\ <ESC>_Gs=100,v=30,m=1;<encoded pixel data first chunk><ESC>\
<ESC>_Gm=1;<encoded pixel data second chunk><ESC>\ <ESC>_Gm=1;<encoded pixel data second chunk><ESC>\
<ESC>_Gm=0;<encoded pixel data last chunk><ESC>\ <ESC>_Gm=0;<encoded pixel data last chunk><ESC>\
``` ```
Note that only the first escape code needs to have the full set of control Note that only the first escape code needs to have the full set of control
@@ -189,7 +203,7 @@ before sending any other graphics related escape codes.
=== Detecting available transmission mediums === Detecting available transmission mediums
Since a client has no a-priori knowledge of whether it shares a filesystem/shared memory Since a client has no a-priori knowledge of whether it shares a filesystem/shared memory
with the terminal emulator, it can send an id with the control data, using the `i` key with the terminal emulator, it can send an id with the control data, using the `i` key
(which can be an arbitrary positive integer up to 4294967295, it must not be zero). (which can be an arbitrary positive integer up to 4294967295, it must not be zero).
If it does so, the terminal emulator will reply after trying to load the image, saying If it does so, the terminal emulator will reply after trying to load the image, saying
whether loading was successful or not. For example: whether loading was successful or not. For example:
@@ -207,7 +221,7 @@ to which the terminal emulator will reply (after trying to load the data):
Here the `i` value will be the same as was sent by the client in the original Here the `i` value will be the same as was sent by the client in the original
request. The message data will be a ASCII encoded string containing only request. The message data will be a ASCII encoded string containing only
printable characters and spaces. The string will be `OK` if reading the pixel printable characters and spaces. The string will be `OK` if reading the pixel
data succeeded or an error message. data succeeded or an error message.
Sometimes, using an id is not appropriate, for example, if you do not want to Sometimes, using an id is not appropriate, for example, if you do not want to
replace a previously sent image with the same id, or if you are sending a dummy replace a previously sent image with the same id, or if you are sending a dummy
@@ -245,7 +259,7 @@ be re-transmitted.
=== Controlling displayed image layout === Controlling displayed image layout
The image is rendered at the current cursor position, from the upper left corner of The image is rendered at the current cursor position, from the upper left corner of
the current cell. You can also specify extra `X=3` and `Y=4` pixel offsets to display from the current cell. You can also specify extra `X=3` and `Y=4` pixel offsets to display from
a different origin within the cell. Note that the offsets must be smaller that the size of the cell. a different origin within the cell. Note that the offsets must be smaller that the size of the cell.
By default, the entire image will be displayed (images wider than the available By default, the entire image will be displayed (images wider than the available
@@ -255,7 +269,7 @@ the top-left corner, width and height of the source rectangle.
You can also ask the terminal emulator to display the image in a specified rectangle You can also ask the terminal emulator to display the image in a specified rectangle
(num of columns / num of lines), using the control codes `c,r`. `c` is the number of columns (num of columns / num of lines), using the control codes `c,r`. `c` is the number of columns
and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit
the specified area. Note that if you specify a start cell offset via the `X,Y` keys, it is not the specified area. Note that if you specify a start cell offset via the `X,Y` keys, it is not
added to the number of rows/columns. added to the number of rows/columns.
@@ -269,7 +283,7 @@ allows rendering of text on top of images.
Images can be deleted by using the delete action `a=d`. If specified without any Images can be deleted by using the delete action `a=d`. If specified without any
other keys, it will delete all images visible on screen. To delete specific images, other keys, it will delete all images visible on screen. To delete specific images,
use the `d` key as described in the table below. Note that each value of d has use the `d` key as described in the table below. Note that each value of d has
both a lowercase and an uppercase variant. The lowercase variant only deletes the both a lowercase and an uppercase variant. The lowercase variant only deletes the
images without necessarily freeing up the stored image data, so that the images can be images without necessarily freeing up the stored image data, so that the images can be
re-displayed without needing to resend the data. The uppercase variants will delete re-displayed without needing to resend the data. The uppercase variants will delete
@@ -278,7 +292,7 @@ scrollback buffer. The values of the `x` and `y` keys are the same as cursor pos
x=1, y=1 is the top left cell). x=1, y=1 is the top left cell).
|=== |===
| Value of `d` | Meaning | Value of `d` | Meaning
| `a` or `A` | Delete all images visible on screen | `a` or `A` | Delete all images visible on screen
| `i` or `I` | Delete all images with the specified id, specified using the `i` key. | `i` or `I` | Delete all images with the specified id, specified using the `i` key.
@@ -313,7 +327,7 @@ delete older images to make space for the new one.
== Control data reference == Control data reference
The table below shows all the control data keys as well as what values they can The table below shows all the control data keys as well as what values they can
take, and the default value they take when missing. All integers are 32-bit. take, and the default value they take when missing. All integers are 32-bit.
[cols="^1,<3,^1,<6"] [cols="^1,<3,^1,<6"]
|=== |===

BIN
hints_mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -1,6 +1,7 @@
= Key encoding for extended keyboard protocol = Key encoding for extended keyboard protocol
See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension] See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension]
for more information and link:key_encoding.json[for this table in JSON format].
|=== |===
| Name | Encoded representation (base64) | Name | Encoded representation (base64)

121
key_encoding.json Normal file
View File

@@ -0,0 +1,121 @@
{
"0": "G",
"1": "H",
"2": "I",
"3": "J",
"4": "K",
"5": "L",
"6": "M",
"7": "N",
"8": "O",
"9": "P",
"A": "S",
"APOSTROPHE": "B",
"B": "T",
"BACKSLASH": "t",
"BACKSPACE": "1",
"C": "U",
"CAPS LOCK": ":",
"COMMA": "C",
"D": "V",
"DELETE": "3",
"DOWN": "6",
"E": "W",
"END": "-",
"ENTER": "z",
"EQUAL": "R",
"ESCAPE": "y",
"F": "X",
"F1": "/",
"F10": "]",
"F11": "{",
"F12": "}",
"F13": "@",
"F14": "%",
"F15": "$",
"F16": "#",
"F17": "BA",
"F18": "BB",
"F19": "BC",
"F2": "*",
"F20": "BD",
"F21": "BE",
"F22": "BF",
"F23": "BG",
"F24": "BH",
"F25": "BI",
"F3": "?",
"F4": "&",
"F5": "<",
"F6": ">",
"F7": "(",
"F8": ")",
"F9": "[",
"G": "Y",
"GRAVE ACCENT": "v",
"H": "Z",
"HOME": ".",
"I": "a",
"INSERT": "2",
"J": "b",
"K": "c",
"KP 0": "BJ",
"KP 1": "BK",
"KP 2": "BL",
"KP 3": "BM",
"KP 4": "BN",
"KP 5": "BO",
"KP 6": "BP",
"KP 7": "BQ",
"KP 8": "BR",
"KP 9": "BS",
"KP ADD": "BX",
"KP DECIMAL": "BT",
"KP DIVIDE": "BU",
"KP ENTER": "BY",
"KP EQUAL": "BZ",
"KP MULTIPLY": "BV",
"KP SUBTRACT": "BW",
"L": "d",
"LEFT": "5",
"LEFT ALT": "Bc",
"LEFT BRACKET": "s",
"LEFT CONTROL": "Bb",
"LEFT SHIFT": "Ba",
"LEFT SUPER": "Bd",
"M": "e",
"MINUS": "D",
"N": "f",
"NUM LOCK": "=",
"O": "g",
"P": "h",
"PAGE DOWN": "9",
"PAGE UP": "8",
"PAUSE": "!",
"PERIOD": "E",
"PRINT SCREEN": "^",
"Q": "i",
"R": "j",
"RIGHT": "4",
"RIGHT ALT": "Bg",
"RIGHT BRACKET": "u",
"RIGHT CONTROL": "Bf",
"RIGHT SHIFT": "Be",
"RIGHT SUPER": "Bh",
"S": "k",
"SCROLL LOCK": "+",
"SEMICOLON": "Q",
"SLASH": "F",
"SPACE": "A",
"T": "l",
"TAB": "0",
"U": "m",
"UP": "7",
"V": "n",
"W": "o",
"WORLD 1": "w",
"WORLD 2": "x",
"X": "p",
"Y": "q",
"Z": "r"
}

0
kittens/__init__.py Normal file
View File

0
kittens/tui/__init__.py Normal file
View File

43
kittens/tui/handler.py Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
class Handler:
def initialize(self, screen_size, quit_loop, wakeup):
self.screen_size, self.quit_loop = screen_size, quit_loop
self.wakeup = wakeup
def on_resize(self, screen_size):
self.screen_size = screen_size
def on_term(self):
self.quit_loop(1)
def on_text(self, text, in_bracketed_paste=False):
pass
def on_key(self, key_event):
pass
def on_mouse(self, mouse_event):
pass
def on_interrupt(self):
pass
def on_eot(self):
pass
def write(self, data):
if isinstance(data, str):
data = data.encode('utf-8')
self.write_buf.append(data)
def print(self, *args, sep=' ', end='\r\n'):
data = sep.join(map(str, args)) + end
self.write(data)
def suspend(self):
return self._term_manager.suspend()

369
kittens/tui/loop.py Normal file
View File

@@ -0,0 +1,369 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import codecs
import fcntl
import io
import os
import re
import selectors
import signal
import sys
import termios
import tty
from collections import namedtuple
from contextlib import closing, contextmanager
from functools import partial
from kitty.fast_data_types import parse_input_from_terminal
from kitty.icat import screen_size
from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
decode_key_event, enter_key
)
from .handler import Handler
from .operations import init_state, reset_state, clear_screen
def log(*a, **kw):
fd = getattr(log, 'fd', None)
if fd is None:
fd = log.fd = open('/tmp/kitten-debug', 'w')
kw['file'] = fd
print(*a, **kw)
fd.flush()
def write_all(fd, data):
if isinstance(data, str):
data = data.encode('utf-8')
while data:
n = os.write(fd, data)
if not n:
break
data = data[n:]
class TermManager:
def __init__(self, input_fd, output_fd):
self.input_fd = input_fd
self.output_fd = output_fd
self.original_fl = fcntl.fcntl(self.input_fd, fcntl.F_GETFL)
self.isatty = os.isatty(self.input_fd)
if self.isatty:
self.original_termios = termios.tcgetattr(self.input_fd)
def set_state_for_loop(self):
write_all(self.output_fd, init_state())
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl | os.O_NONBLOCK)
if self.isatty:
tty.setraw(self.input_fd)
def reset_state_to_original(self):
if self.isatty:
termios.tcsetattr(self.input_fd, termios.TCSADRAIN, self.original_termios)
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl)
write_all(self.output_fd, reset_state())
@contextmanager
def suspend(self):
self.reset_state_to_original()
yield self
self.set_state_for_loop()
def __enter__(self):
self.set_state_for_loop()
return self
def __exit__(self, *a):
self.reset_state_to_original()
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
DRAG = REPEAT
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
bmap = {0: LEFT, 1: MIDDLE, 2: RIGHT}
MOTION_INDICATOR = 1 << 5
EXTRA_BUTTON_INDICATOR = 1 << 6
SHIFT_INDICATOR = 1 << 2
ALT_INDICATOR = 1 << 3
CTRL_INDICATOR = 1 << 4
def decode_sgr_mouse(text):
cb, x, y = text.split(';')
m, y = y[-1], y[:-1]
cb, x, y = map(int, (cb, x, y))
typ = RELEASE if m == 'm' else (DRAG if cb & MOTION_INDICATOR else PRESS)
buttons = 0
cb3 = cb & 3
if cb3 != 3:
if cb & EXTRA_BUTTON_INDICATOR:
buttons |= FIFTH if cb3 & 1 else FOURTH
else:
buttons |= bmap[cb3]
mods = 0
if cb & SHIFT_INDICATOR:
mods |= SHIFT
if cb & ALT_INDICATOR:
mods |= ALT
if cb & CTRL_INDICATOR:
mods |= CTRL
return MouseEvent(x, y, typ, buttons, mods)
class UnhandledException(Handler):
def __init__(self, tb):
self.tb = tb
def initialize(self, screen_size, quit_loop, wakeup):
Handler.initialize(self, screen_size, quit_loop, wakeup)
self.write(clear_screen())
self.write(self.tb.replace('\n', '\r\n'))
self.write('\r\n')
self.write('Press the Enter key to quit')
def on_key(self, key_event):
if key_event is enter_key:
self.quit_loop(1)
def on_interrupt(self):
self.quit_loop(1)
def on_eot(self):
self.quit_loop(1)
class Loop:
def __init__(self, input_fd=None, output_fd=None,
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
self.input_fd = input_fd or sys.stdin.fileno()
self.output_fd = output_fd or sys.stdout.fileno()
self.wakeup_read_fd, self.wakeup_write_fd = os.pipe()
self.sel = s = selectors.DefaultSelector()
s.register(self.input_fd, selectors.EVENT_READ, self._read_ready)
s.register(
self.wakeup_read_fd, selectors.EVENT_READ, self._wakeup_ready
)
s.register(self.output_fd, selectors.EVENT_WRITE, self._write_ready)
self.return_code = 0
self.read_allowed = True
self.read_buf = ''
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
try:
self.iov_limit = os.sysconf('SC_IOV_MAX') - 1
except Exception:
self.iov_limit = 255
self.parse_input_from_terminal = partial(parse_input_from_terminal, self._on_text, self._on_dcs, self._on_csi, self._on_osc, self._on_pm, self._on_apc)
self.ebs_pat = re.compile('([\177\r\x03\x04])')
self.in_bracketed_paste = False
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
if self.sanitize_bracketed_paste:
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
def _read_ready(self, handler):
if not self.read_allowed:
return
data = os.read(self.input_fd, io.DEFAULT_BUFFER_SIZE)
if not data:
raise EOFError('The input stream is closed')
data = self.decoder.decode(data)
if self.read_buf:
data = self.read_buf + data
self.read_buf = data
self.handler = handler
try:
self.read_buf = self.parse_input_from_terminal(self.read_buf, self.in_bracketed_paste)
except Exception:
self.read_buf = ''
raise
finally:
del self.handler
def _on_text(self, text):
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
text = self.sanitize_ibp_pat.sub('', text)
for chunk in self.ebs_pat.split(text):
if len(chunk) == 1:
if chunk == '\r':
self.handler.on_key(enter_key)
elif chunk == '\177':
self.handler.on_key(backspace_key)
elif chunk == '\x03':
self.handler.on_interrupt()
elif chunk == '\x04':
self.handler.on_eot()
else:
self.handler.on_text(chunk, self.in_bracketed_paste)
else:
self.handler.on_text(chunk, self.in_bracketed_paste)
def _on_dcs(self, dcs):
pass
def _on_csi(self, csi):
q = csi[-1]
if q in 'mM':
if csi.startswith('<'):
# SGR mouse event
try:
ev = decode_sgr_mouse(csi[1:])
except Exception:
pass
else:
self.handler.on_mouse(ev)
elif q == '~':
if csi == '200~':
self.in_bracketed_paste = True
elif csi == '201~':
self.in_bracketed_paste = False
def _on_pm(self, pm):
pass
def _on_osc(self, osc):
pass
def _on_apc(self, apc):
if apc.startswith('K'):
try:
k = decode_key_event(apc)
except Exception:
pass
else:
if k.mods is CTRL and k.type is not RELEASE:
if k.key is C:
self.handler.on_interrupt()
return
if k.key is D:
self.handler.on_eot()
return
self.handler.on_key(k)
def _write_ready(self, handler):
if len(handler.write_buf) > self.iov_limit:
handler.write_buf[self.iov_limit - 1] = b''.join(handler.write_buf[self.iov_limit - 1:])
del handler.write_buf[self.iov_limit:]
sizes = tuple(map(len, handler.write_buf))
written = os.writev(self.output_fd, handler.write_buf)
if not written:
raise EOFError('The output stream is closed')
if written >= sum(sizes):
handler.write_buf = []
else:
consumed = 0
for i, buf in enumerate(handler.write_buf):
if not written:
break
if len(buf) <= written:
written -= len(buf)
consumed += 1
continue
handler.write_buf[i] = buf[written:]
break
del handler.write_buf[:consumed]
def _wakeup_ready(self, handler):
data = os.read(self.wakeup_read_fd, 1024)
if b'r' in data:
screen_size.changed = True
handler.on_resize(screen_size())
if b't' in data:
handler.on_term()
if b'i' in data:
handler.on_interrupt()
def _wakeup_write(self, val):
while not os.write(self.wakeup_write_fd, val):
pass
def _on_sigwinch(self, signum, frame):
self._wakeup_write(b'r')
def _on_sigterm(self, signum, frame):
self._wakeup_write(b't')
def _on_sigint(self, signum, frame):
self._wakeup_write(b'i')
def quit(self, return_code=None):
self.read_allowed = False
if return_code is not None:
self.return_code = return_code
def wakeup(self):
self._wakeup_write(b'1')
def _modify_output_selector(self, waiting_for_write):
if waiting_for_write:
try:
self.sel.register(self.output_fd, selectors.EVENT_WRITE, self._write_ready)
except KeyError:
pass
else:
try:
self.sel.unregister(self.output_fd)
except KeyError:
pass
def loop(self, handler):
select = self.sel.select
tb = None
waiting_for_write = True
with closing(self.sel), TermManager(self.input_fd, self.output_fd) as term_manager:
signal.signal(signal.SIGWINCH, self._on_sigwinch)
signal.signal(signal.SIGTERM, self._on_sigterm)
signal.signal(signal.SIGINT, self._on_sigint)
handler.write_buf = []
handler._term_manager = term_manager
keep_going = True
try:
handler.initialize(screen_size(), self.quit, self.wakeup)
except Exception:
import traceback
tb = traceback.format_exc()
self.return_code = 1
keep_going = False
while keep_going:
has_data_to_write = bool(handler.write_buf)
if not has_data_to_write and not self.read_allowed:
break
if has_data_to_write != waiting_for_write:
waiting_for_write = has_data_to_write
self._modify_output_selector(waiting_for_write)
events = select()
for key, mask in events:
try:
key.data(handler)
except Exception:
import traceback
tb = traceback.format_exc()
self.return_code = 1
keep_going = False
break
if tb is not None:
self._report_error_loop(tb, term_manager)
def _report_error_loop(self, tb, term_manager):
select = self.sel.select
waiting_for_write = False
handler = UnhandledException(tb)
handler.write_buf = []
handler._term_manager = term_manager
handler.initialize(screen_size(), self.quit, self.wakeup)
while True:
has_data_to_write = bool(handler.write_buf)
if not has_data_to_write and not self.read_allowed:
break
if has_data_to_write != waiting_for_write:
waiting_for_write = has_data_to_write
self._modify_output_selector(waiting_for_write)
events = select()
for key, mask in events:
key.data(handler)

153
kittens/tui/operations.py Normal file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from contextlib import contextmanager
from kitty.terminfo import string_capabilities
S7C1T = '\033 F'
SAVE_CURSOR = '\0337'
RESTORE_CURSOR = '\0338'
SAVE_PRIVATE_MODE_VALUES = '\033[?s'
RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
MODES = dict(
LNM=(20, ''),
IRM=(4, ''),
DECKM=(1, '?'),
DECSCNM=(5, '?'),
DECOM=(6, '?'),
DECAWM=(7, '?'),
DECARM=(8, '?'),
DECTCEM=(25, '?'),
MOUSE_BUTTON_TRACKING=(1000, '?'),
MOUSE_MOTION_TRACKING=(1002, '?'),
MOUSE_MOVE_TRACKING=(1003, '?'),
FOCUS_TRACKING=(1004, '?'),
MOUSE_UTF8_MODE=(1005, '?'),
MOUSE_SGR_MODE=(1006, '?'),
MOUSE_URXVT_MODE=(1015, '?'),
ALTERNATE_SCREEN=(1049, '?'),
BRACKETED_PASTE=(2004, '?'),
EXTENDED_KEYBOARD=(2017, '?'),
)
def set_mode(which, private=True):
num, private = MODES[which]
return '\033[{}{}h'.format(private, num)
def reset_mode(which):
num, private = MODES[which]
return '\033[{}{}l'.format(private, num)
def clear_screen():
return string_capabilities['clear'].replace(r'\E', '\033')
def set_window_title(value):
return ('\033]2;' + value.replace('\033', '').replace('\x9c', '') + '\033\\')
def set_line_wrapping(yes_or_no):
return (set_mode if yes_or_no else reset_mode)('DECAWM')
def set_cursor_visible(yes_or_no):
return (set_mode if yes_or_no else reset_mode)('DECTCEM')
STANDARD_COLORS = {name: i for i, name in enumerate(
'black red green yellow blue magenta cyan gray'.split())}
UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
'straight double curly'.split())}
def color_code(color, intense=False, base=30):
if isinstance(color, str):
e = str((base + 60 if intense else base) + STANDARD_COLORS[color])
elif isinstance(color, int):
e = '{}:5:{}'.format(base + 8, max(0, min(color, 255)))
else:
e = '{}:2:{}:{}:{}'.format(base + 8, *color)
return e
def sgr(*parts):
return '\033[{}m'.format(';'.join(parts))
def colored(text, color, intense=False, reset_to=None, reset_to_intense=False):
e = color_code(color, intense)
return '\033[{}m{}\033[{}m'.format(e, text, 39 if reset_to is None else color_code(reset_to, reset_to_intense))
def faint(text):
return colored(text, 'black', True)
def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=None, bold=None, underline=None, underline_color=None, reverse=None):
start, end = [], []
if fg is not None:
start.append(color_code(fg, fg_intense))
end.append('39')
if bg is not None:
start.append(color_code(bg, bg_intense, 40))
end.append('49')
if underline_color is not None:
if isinstance(underline_color, str):
underline_color = STANDARD_COLORS[underline_color]
start.append(color_code(underline_color, base=50))
end.append('59')
if underline is not None:
start.append('4:{}'.format(UNDERLINE_STYLES[underline]))
end.append('4:0')
if italic is not None:
s, e = (start, end) if italic else (end, start)
s.append('3'), e.append('23')
if bold is not None:
s, e = (start, end) if bold else (end, start)
s.append('1'), e.append('22')
if reverse is not None:
s, e = (start, end) if reverse else (end, start)
s.append('7'), e.append('27')
if not start:
return text
return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end))
def init_state(alternate_screen=True):
ans = (
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
set_mode('DECARM') + reset_mode('DECOM') + set_mode('DECAWM') +
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING')
+ reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
'\033[*x' # reset DECSACE to default region select
)
if alternate_screen:
ans += set_mode('ALTERNATE_SCREEN')
ans += clear_screen()
return ans
def reset_state(normal_screen=True):
ans = ''
if normal_screen:
ans += reset_mode('ALTERNATE_SCREEN')
ans += RESTORE_PRIVATE_MODE_VALUES
ans += RESTORE_CURSOR
return ans
@contextmanager
def cursor(write):
write(SAVE_CURSOR)
yield
write(RESTORE_CURSOR)

View File

View File

@@ -0,0 +1,470 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import shlex
import string
import subprocess
import sys
from functools import lru_cache
from gettext import gettext as _
from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import wcswidth
from kitty.key_encoding import (
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
backspace_key, enter_key
)
from ..tui.handler import Handler
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, color_code, colored, cursor, faint, set_line_wrapping,
set_window_title, sgr, styled
)
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
favorites_path = os.path.join(config_dir, 'unicode-input-favorites.conf')
INDEX_CHAR = '.'
DEFAULT_SET = tuple(map(
ord,
'‘’“”‹›«»‚„' '😀😛😇😈😉😍😎😮👍👎' '—–§¶†‡©®™' '→⇒•·°±−×÷¼½½¾'
'…µ¢£€¿¡¨´¸ˆ˜' 'ÀÁÂÃÄÅÆÇÈÉÊË' 'ÌÍÎÏÐÑÒÓÔÕÖØ' 'ŒŠÙÚÛÜÝŸÞßàá' 'âãäåæçèéêëìí'
'îïðñòóôõöøœš' 'ùúûüýÿþªºαΩ∞'
))
EMOTICONS_SET = tuple(range(0x1f600, 0x1f64f + 1))
def codepoint_ok(code):
return not (code <= 32 or code == 127 or 128 <= code <= 159 or 0xd800 <= code <= 0xdbff or 0xDC00 <= code <= 0xDFFF)
@lru_cache(maxsize=256)
def points_for_word(w):
from .unicode_names import codepoints_for_word
return codepoints_for_word(w)
@lru_cache(maxsize=4096)
def name(cp):
from .unicode_names import name_for_codepoint
if isinstance(cp, str):
cp = ord(cp[0])
return (name_for_codepoint(cp) or '').capitalize()
@lru_cache(maxsize=256)
def codepoints_matching_search(parts):
ans = []
if parts and parts[0]:
codepoints = points_for_word(parts[0])
for word in parts[1:]:
pts = points_for_word(word)
if pts:
intersection = codepoints & pts
if intersection:
codepoints = intersection
continue
codepoints = {c for c in codepoints if word in name(c).lower()}
if codepoints:
ans = list(sorted(codepoints))
return ans
def parse_favorites(raw):
for line in raw.splitlines():
line = line.strip()
if line.startswith('#') or not line:
continue
idx = line.find('#')
if idx > -1:
line = line[:idx]
code_text = line.partition(' ')[0]
try:
code = int(code_text, 16)
except Exception:
pass
else:
if codepoint_ok(code):
yield code
def serialize_favorites(favorites):
ans = '''\
# Favorite characters for unicode input
# Enter the hex code for each favorite character on a new line. Blank lines are
# ignored and anything after a # is considered a comment.
'''.splitlines()
for cp in favorites:
ans.append('{:x} # {} {}'.format(cp, chr(cp), name(cp)))
return '\n'.join(ans)
def load_favorites(refresh=False):
ans = getattr(load_favorites, 'ans', None)
if ans is None or refresh:
try:
with open(favorites_path, 'rb') as f:
raw = f.read().decode('utf-8')
ans = load_favorites.ans = list(parse_favorites(raw)) or list(DEFAULT_SET)
except FileNotFoundError:
ans = load_favorites.ans = list(DEFAULT_SET)
return ans
def encode_hint(num, digits=string.digits + string.ascii_lowercase):
res = ''
d = len(digits)
while not res or num > 0:
num, i = divmod(num, d)
res = digits[i] + res
return res
def decode_hint(x):
return int(x, 36)
class Table:
def __init__(self):
self.layout_dirty = True
self.last_rows = self.last_cols = -1
self.codepoints = []
self.current_idx = 0
self.text = ''
self.num_cols = 0
self.mode = HEX
@property
def current_codepoint(self):
if self.codepoints:
return self.codepoints[self.current_idx]
def set_codepoints(self, codepoints, mode=HEX):
self.codepoints = codepoints
self.mode = mode
self.layout_dirty = True
self.current_idx = 0
def codepoint_at_hint(self, hint):
return self.codepoints[decode_hint(hint)]
def layout(self, rows, cols):
if not self.layout_dirty and self.last_cols == cols and self.last_rows == rows:
return self.text
self.last_cols, self.last_rows = cols, rows
self.layout_dirty = False
if self.mode is NAME:
def as_parts(i, codepoint):
return encode_hint(i).ljust(idx_size), 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) + ' '
w = wcswidth(c)
if w < 2:
yield ' ' * (2 - w)
if len(desc) > space_for_desc:
desc = desc[:space_for_desc - 1] + ''
yield faint(desc)
extra = space_for_desc - len(desc)
if extra > 0:
yield ' ' * extra
if is_current:
yield sgr('49')
else:
def as_parts(i, codepoint):
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
def cell(i, idx, c, desc):
yield colored(idx, 'green') + ' '
yield colored(c, 'gray', True)
w = wcswidth(c)
if w < 2:
yield ' ' * (2 - w)
num = len(self.codepoints)
if num < 1:
self.text = ''
self.num_cols = 0
return self.text
idx_size = len(encode_hint(num - 1))
parts = [as_parts(i, c) for i, c in enumerate(self.codepoints)]
if self.mode is NAME:
sizes = [idx_size + 2 + len(p[2]) + 2 for p in parts]
else:
sizes = [idx_size + 3]
longest = max(sizes) if sizes else 0
col_width = longest + 2
col_width = min(col_width, 40)
space_for_desc = col_width - 2 - idx_size - 4
num_cols = self.num_cols = cols // col_width
buf = []
a = buf.append
rows_left = rows
for i, (idx, c, desc) in enumerate(parts):
if i > 0 and i % num_cols == 0:
rows_left -= 1
if rows_left == 0:
break
buf.append('\r\n')
buf.extend(cell(i, idx, c, desc))
a(' ')
self.text = ''.join(buf)
return self.text
def move_current(self, rows=0, cols=0):
if cols:
self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
self.layout_dirty = True
if rows:
amt = rows * self.num_cols
self.current_idx += amt
self.current_idx = max(0, min(self.current_idx, len(self.codepoints) - 1))
self.layout_dirty = True
def is_index(w):
try:
int(w.lstrip(INDEX_CHAR), 16)
return True
except Exception:
return False
class UnicodeInput(Handler):
def __init__(self, cached_values):
self.cached_values = cached_values
self.recent = list(self.cached_values.get('recent', DEFAULT_SET))
self.current_input = ''
self.current_char = None
self.prompt_template = '{}> '
self.last_updated_code_point_at = None
self.choice_line = ''
self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX')
self.table = Table()
self.update_prompt()
def update_codepoints(self):
codepoints = None
if self.mode is HEX:
q = self.mode, None
codepoints = self.recent
elif self.mode is EMOTICONS:
q = self.mode, None
codepoints = list(EMOTICONS_SET)
elif self.mode is FAVORITES:
codepoints = load_favorites()
q = self.mode, tuple(codepoints)
elif self.mode is NAME:
q = self.mode, self.current_input
if q != self.last_updated_code_point_at:
words = self.current_input.split()
words = [w for w in words if w != INDEX_CHAR]
index_words = [i for i, w in enumerate(words) if i > 0 and is_index(w)]
if index_words:
index_word = words[index_words[0]]
words = words[:index_words[0]]
codepoints = codepoints_matching_search(tuple(words))
if index_words:
index_word = int(index_word.lstrip(INDEX_CHAR), 16)
if index_word < len(codepoints):
codepoints = [codepoints[index_word]]
if q != self.last_updated_code_point_at:
self.last_updated_code_point_at = q
self.table.set_codepoints(codepoints, self.mode)
def update_current_char(self):
self.update_codepoints()
self.current_char = None
if self.mode is HEX:
try:
if self.current_input.startswith(INDEX_CHAR) and len(self.current_input) > 1:
self.current_char = chr(self.table.codepoint_at_hint(self.current_input[1:]))
else:
code = int(self.current_input, 16)
self.current_char = chr(code)
except Exception:
pass
elif self.mode is NAME:
cc = self.table.current_codepoint
if cc:
self.current_char = chr(cc)
else:
try:
if self.current_input:
self.current_char = chr(self.table.codepoint_at_hint(self.current_input.lstrip(INDEX_CHAR)))
except Exception:
pass
if self.current_char is not None:
code = ord(self.current_char)
if not codepoint_ok(code):
self.current_char = None
def update_prompt(self):
self.update_current_char()
if self.current_char is None:
c, color = '??', 'red'
self.choice_line = ''
else:
c, color = self.current_char, 'green'
self.choice_line = _('Chosen:') + ' {} U+{} {}'.format(
colored(c, 'green'), hex(ord(c))[2:], faint(styled(name(c) or '', italic=True)))
self.prompt = self.prompt_template.format(colored(c, color))
def init_terminal_state(self):
self.write(set_line_wrapping(False))
self.write(set_window_title(_('Unicode input')))
def initialize(self, *args):
Handler.initialize(self, *args)
self.init_terminal_state()
self.draw_screen()
def draw_title_bar(self):
entries = []
for name, key, mode in [
(_('Code'), 'F1', HEX),
(_('Name'), 'F2', NAME),
(_('Emoji'), 'F3', EMOTICONS),
(_('Favorites'), 'F4', FAVORITES),
]:
entry = ' {} ({}) '.format(name, key)
if mode is self.mode:
entry = styled(entry, reverse=False, bold=True)
entries.append(entry)
text = _('Search by:{}').format(' '.join(entries))
extra = self.screen_size.cols - wcswidth(text)
if extra > 0:
text += ' ' * extra
self.print(styled(text, reverse=True))
def draw_screen(self):
self.write(clear_screen())
self.draw_title_bar()
y = 1
def writeln(text=''):
nonlocal y
self.print(text)
y += 1
if self.mode is NAME:
writeln(_('Enter the hex code for the character'))
elif self.mode is HEX:
writeln(_('Enter words from the name of the character'))
else:
writeln(_('Enter the index for the character you want from the list below'))
self.write(self.prompt)
self.write(self.current_input)
with cursor(self.write):
writeln()
writeln(self.choice_line)
if self.mode is HEX:
writeln(faint(_('Type {} followed by the index for the recent entries below').format(INDEX_CHAR)))
elif self.mode is NAME:
writeln(faint(_('Use Tab or the arrow keys to choose a character from below')))
elif self.mode is FAVORITES:
writeln(faint(_('Press F12 to edit the list of favorites')))
self.table_at = y
self.write(self.table.layout(self.screen_size.rows - self.table_at, self.screen_size.cols))
def refresh(self):
self.update_prompt()
self.draw_screen()
def on_text(self, text, in_bracketed_paste):
self.current_input += text
self.refresh()
def on_key(self, key_event):
if key_event is backspace_key:
self.current_input = self.current_input[:-1]
self.refresh()
elif key_event is enter_key:
self.quit_loop(0)
elif key_event.type is RELEASE and not key_event.mods:
if key_event.key is ESCAPE:
self.quit_loop(1)
elif key_event.key is F1:
self.switch_mode(HEX)
elif key_event.key is F2:
self.switch_mode(NAME)
elif key_event.key is F3:
self.switch_mode(EMOTICONS)
elif key_event.key is F4:
self.switch_mode(FAVORITES)
elif key_event.key is F12 and self.mode is FAVORITES:
self.edit_favorites()
elif self.mode is NAME:
if key_event.key is TAB:
if key_event.mods == SHIFT:
self.table.move_current(cols=-1), self.refresh()
elif not key_event.mods:
self.table.move_current(cols=1), self.refresh()
elif key_event.key is LEFT and not key_event.mods:
self.table.move_current(cols=-1), self.refresh()
elif key_event.key is RIGHT and not key_event.mods:
self.table.move_current(cols=1), self.refresh()
elif key_event.key is UP and not key_event.mods:
self.table.move_current(rows=-1), self.refresh()
elif key_event.key is DOWN and not key_event.mods:
self.table.move_current(rows=1), self.refresh()
def edit_favorites(self):
if not os.path.exists(favorites_path):
with open(favorites_path, 'wb') as f:
f.write(serialize_favorites(load_favorites()).encode('utf-8'))
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
with self.suspend():
p = subprocess.Popen(editor + [favorites_path])
if p.wait() == 0:
load_favorites(refresh=True)
self.init_terminal_state()
self.refresh()
def switch_mode(self, mode):
if mode is not self.mode:
self.mode = mode
self.cached_values['mode'] = mode
self.current_input = ''
self.current_char = None
self.choice_line = ''
self.refresh()
def on_interrupt(self):
self.quit_loop(1)
def on_eot(self):
self.quit_loop(1)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.refresh()
def main(args=sys.argv):
loop = Loop()
with cached_values_for('unicode-input') as cached_values:
handler = UnicodeInput(cached_values)
loop.loop(handler)
if handler.current_char and loop.return_code == 0:
print('OK:', hex(ord(handler.current_char))[2:])
try:
handler.recent.remove(ord(handler.current_char))
except Exception:
pass
recent = [ord(handler.current_char)] + handler.recent
cached_values['recent'] = recent[:len(DEFAULT_SET)]
raise SystemExit(loop.return_code)

63052
kittens/unicode_input/names.h generated Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,81 @@
/*
* unicode_names.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "names.h"
static PyObject*
all_words(PyObject *self UNUSED) {
PyObject *ans = PyTuple_New(arraysz(idx_to_word));
if (!ans) return NULL;
for (size_t i = 0; i < arraysz(idx_to_word); i++) {
PyObject *w = PyUnicode_FromString(idx_to_word[i]);
if (w == NULL) { Py_DECREF(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, w);
}
return ans;
}
static inline PyObject*
codepoints_for_word(const char *word, size_t len) {
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
const unsigned short *words = words_for_first_letter[(unsigned)*word];
if (words == NULL) return ans;
for (unsigned short i = 1; i <= words[0]; i++) {
unsigned short word_idx = words[i];
const char *w = idx_to_word[word_idx];
if (strncmp(word, w, len) == 0 && strlen(w) == len) {
const char_type* codepoints = codepoints_for_word_idx[word_idx];
for (char_type i = 1; i <= codepoints[0]; i++) {
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
int ret = PySet_Add(ans, t); Py_DECREF(t); if (ret != 0) { Py_DECREF(ans); return NULL; }
}
break;
}
}
return ans;
}
static PyObject*
cfw(PyObject *self UNUSED, PyObject *args) {
const char *word;
if (!PyArg_ParseTuple(args, "s", &word)) return NULL;
return codepoints_for_word(word, strlen(word));
}
static PyObject*
nfc(PyObject *self UNUSED, PyObject *args) {
unsigned int cp;
if (!PyArg_ParseTuple(args, "I", &cp)) return NULL;
const char *n = name_for_codepoint(cp);
if (n == NULL) Py_RETURN_NONE;
return PyUnicode_FromString(n);
}
static PyMethodDef module_methods[] = {
METHODB(all_words, METH_NOARGS),
{"codepoints_for_word", (PyCFunction)cfw, METH_VARARGS, ""},
{"name_for_codepoint", (PyCFunction)nfc, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "unicode_names", /* name of module */
.m_doc = NULL,
.m_size = -1,
.m_methods = module_methods
};
EXPORTED PyMODINIT_FUNC
PyInit_unicode_names(void) {
PyObject *m;
m = PyModule_Create(&module);
if (m == NULL) return NULL;
return m;
}

View File

238
kittens/url_hints/main.py Normal file
View File

@@ -0,0 +1,238 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import re
import string
import subprocess
import sys
from collections import namedtuple
from functools import lru_cache, partial
from gettext import gettext as _
from kitty.cli import parse_args
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
from kitty.utils import command_for_open
from ..tui.handler import Handler
from ..tui.loop import Loop
from ..tui.operations import (
clear_screen, faint, set_cursor_visible, set_window_title, styled
)
Mark = namedtuple('Mark', 'index start end text')
URL_PREFIXES = 'http https file ftp'.split()
HINT_ALPHABET = string.digits + string.ascii_lowercase
@lru_cache(maxsize=2048)
def encode_hint(num):
res = ''
d = len(HINT_ALPHABET)
while not res or num > 0:
num, i = divmod(num, d)
res = HINT_ALPHABET[i] + res
return res
def decode_hint(x):
return int(x, 36)
def render(lines, current_input):
ans = []
def mark(m):
hint = encode_hint(m.index)
text = m.text
if current_input and not hint.startswith(current_input):
return faint(text)
hint = hint[len(current_input):] or ' '
text = text[len(hint):]
return styled(
hint,
fg='black',
bg='green',
bold=True
) + styled(
text, fg='gray', fg_intense=True, bold=True
)
for line, marks in lines:
if not marks:
ans.append(faint(line))
continue
buf = []
for i, m in enumerate(marks):
if i == 0 and m.start:
buf.append(faint(line[:m.start]))
buf.append(mark(m))
if m is not marks[-1]:
buf.append(faint(line[m.end:marks[i + 1].start]))
rest = line[marks[-1].end:]
if rest:
buf.append(faint(rest))
ans.append(''.join(buf))
return '\r\n'.join(ans)
class URLHints(Handler):
def __init__(self, lines, index_map):
self.lines, self.index_map = tuple(lines), index_map
self.current_input = ''
self.current_text = None
self.chosen = None
def init_terminal_state(self):
self.write(set_cursor_visible(False))
self.write(set_window_title(_('Choose URL')))
def initialize(self, *args):
Handler.initialize(self, *args)
self.init_terminal_state()
self.draw_screen()
def on_text(self, text, in_bracketed_paste):
changed = False
for c in text:
if c in HINT_ALPHABET:
self.current_input += c
changed = True
if changed:
matches = [
t for idx, t in self.index_map.items()
if encode_hint(idx).startswith(self.current_input)
]
if len(matches) == 1:
self.chosen = matches[0]
self.quit_loop(0)
return
self.current_text = None
self.draw_screen()
def on_key(self, key_event):
if key_event is backspace_key:
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event is enter_key and self.current_input:
idx = decode_hint(self.current_input)
self.chosen = self.index_map[idx]
self.quit_loop(0)
elif key_event.key is ESCAPE:
self.quit_loop(1)
def on_interrupt(self):
self.quit_loop(1)
def on_eot(self):
self.quit_loop(1)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.draw_screen()
def draw_screen(self):
if self.current_text is None:
self.current_text = render(self.lines, self.current_input)
self.write(clear_screen())
self.write(self.current_text)
def regex_finditer(pat, line):
for m in pat.finditer(line):
s, e = m.span()
if e - s > 2:
yield s, e
def find_urls(pat, line):
for m in pat.finditer(line):
s, e = m.span()
if s > 4 and line[s - 5:s] == 'link:': # asciidoc URLs
url = line[s:e]
idx = url.rfind('[')
if idx > -1:
e -= len(url) - idx
yield s, e
def mark(finditer, line, index_map):
marks = []
for s, e in finditer(line):
idx = len(index_map)
text = line[s:e]
marks.append(Mark(idx, s, e, text))
index_map[idx] = text
return line, marks
def run(args, source_file=None):
if source_file is None:
text = sys.stdin.buffer.read().decode('utf-8')
sys.stdin = open('/dev/tty')
else:
with open(source_file, 'r') as f:
text = f.read()
if args.regex is None:
from .url_regex import url_delimiters
url_pat = '(?:{})://[^{}]{{3,}}'.format(
'|'.join(args.url_prefixes.split(',')), url_delimiters
)
finditer = partial(find_urls, re.compile(url_pat))
else:
finditer = partial(regex_finditer, re.compile(args.regex))
lines = []
index_map = {}
for line in text.splitlines():
marked = mark(finditer, line, index_map)
lines.append(marked)
if not index_map:
input(_('No URLs found, press Enter to abort.'))
return
loop = Loop()
handler = URLHints(lines, index_map)
loop.loop(handler)
if handler.chosen and loop.return_code == 0:
cmd = command_for_open(args.program)
ret = subprocess.Popen(cmd + [handler.chosen]).wait()
if ret != 0:
print('URL handler "{}" failed with return code: {}'.format(' '.join(cmd), ret), file=sys.stderr)
input('Press Enter to quit')
loop.return_code = ret
raise SystemExit(loop.return_code)
OPTIONS = partial('''\
--program
default=default
What program to use to open matched URLs. Defaults
to the default URL open program for the operating system.
--regex
Instead of searching for URLs search for the specified regular
expression instead.
--url-prefixes
default={0}
Comma separeted list of recognized URL prefixes. Defaults to:
{0}
'''.format, ','.join(sorted(URL_PREFIXES)))
def main(args=sys.argv):
msg = 'Highlight URLs inside the specified text'
try:
args, items = parse_args(args[1:], OPTIONS, '[path to file or omit to use stdin]', msg, 'url_hints')
except SystemExit as e:
print(e.args[0], file=sys.stderr)
input('Press enter to quit...')
return 1
run(args, (items or [None])[0])

View File

@@ -0,0 +1 @@
url_delimiters = '\x00-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa

View File

@@ -5,10 +5,10 @@
#define bottom -1.0f #define bottom -1.0f
const vec2 pos_map[] = vec2[4]( const vec2 pos_map[] = vec2[4](
vec2(right, top), vec2(right, top),
vec2(right, bottom), vec2(right, bottom),
vec2(left, bottom), vec2(left, bottom),
vec2(left, top) vec2(left, top)
); );
out vec2 texcoord; out vec2 texcoord;

View File

@@ -1,5 +1,8 @@
#version GLSL_VERSION #version GLSL_VERSION
uniform uvec2 viewport; uniform uvec2 viewport;
uniform vec3 default_bg;
uniform vec3 active_border_color;
uniform vec3 inactive_border_color;
in uvec4 rect; // left, top, right, bottom in uvec4 rect; // left, top, right, bottom
in uint rect_color; in uint rect_color;
out vec3 color; out vec3 color;
@@ -9,12 +12,13 @@ const int LEFT = 0;
const int TOP = 1; const int TOP = 1;
const int RIGHT = 2; const int RIGHT = 2;
const int BOTTOM = 3; const int BOTTOM = 3;
const uint FF = uint(0xff);
const uvec2 pos_map[] = uvec2[4]( const uvec2 pos_map[] = uvec2[4](
uvec2(RIGHT, TOP), uvec2(RIGHT, TOP),
uvec2(RIGHT, BOTTOM), uvec2(RIGHT, BOTTOM),
uvec2(LEFT, BOTTOM), uvec2(LEFT, BOTTOM),
uvec2(LEFT, TOP) uvec2(LEFT, TOP)
); );
vec2 to_opengl(uint x, uint y) { vec2 to_opengl(uint x, uint y) {
@@ -25,11 +29,13 @@ vec2 to_opengl(uint x, uint y) {
} }
float to_color(uint c) { float to_color(uint c) {
return float(c & uint(0xff)) / 255.0; return float(c & FF) / 255.0;
} }
void main() { void main() {
uvec2 pos = pos_map[gl_VertexID]; uvec2 pos = pos_map[gl_VertexID];
gl_Position = vec4(to_opengl(rect[pos.x], rect[pos.y]), 0, 1); gl_Position = vec4(to_opengl(rect[pos.x], rect[pos.y]), 0, 1);
color = vec3(to_color(rect_color >> 16), to_color(rect_color >> 8), to_color(rect_color)); int rc = int(rect_color);
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
color = float(1 & rc) * default_bg + float((2 & rc) >> 1) * active_border_color + float((4 & rc) >> 2) * inactive_border_color + float((8 & rc) >> 3) * window_bg;
} }

View File

@@ -9,7 +9,7 @@ from .fast_data_types import (
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program, BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
pt_to_px pt_to_px
) )
from .utils import color_as_int, load_shaders from .utils import load_shaders
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left): def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
@@ -44,9 +44,6 @@ class Borders:
self.tab_id = tab_id self.tab_id = tab_id
self.border_width = pt_to_px(opts.window_border_width) self.border_width = pt_to_px(opts.window_border_width)
self.padding_width = pt_to_px(opts.window_padding_width) self.padding_width = pt_to_px(opts.window_padding_width)
self.background = color_as_int(opts.background)
self.active_border = color_as_int(opts.active_border_color)
self.inactive_border = color_as_int(opts.inactive_border_color)
def __call__( def __call__(
self, self,
@@ -56,9 +53,9 @@ class Borders:
extra_blank_rects, extra_blank_rects,
draw_window_borders=True draw_window_borders=True
): ):
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 0) add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 1)
for br in chain(current_layout.blank_rects, extra_blank_rects): for br in chain(current_layout.blank_rects, extra_blank_rects):
add_borders_rect(self.os_window_id, self.tab_id, *br, self.background) add_borders_rect(self.os_window_id, self.tab_id, *br, 1)
bw, pw = self.border_width, self.padding_width bw, pw = self.border_width, self.padding_width
fw = bw + pw fw = bw + pw
@@ -67,7 +64,7 @@ class Borders:
g = w.geometry g = w.geometry
if bw > 0 and draw_window_borders: if bw > 0 and draw_window_borders:
# Draw the border rectangles # Draw the border rectangles
color = self.active_border if w is active_window else self.inactive_border color = 2 if w is active_window else 4
border( border(
self.os_window_id, self.tab_id, self.os_window_id, self.tab_id,
color, bw, g.left - fw, g.top - fw, g.right + fw, color, bw, g.left - fw, g.top - fw, g.right + fw,
@@ -75,9 +72,9 @@ class Borders:
) )
if pw > 0: if pw > 0:
# Draw the background rectangles over the padding region # Draw the background rectangles over the padding region
color = self.background color = w.screen.color_profile.default_bg
border( border(
self.os_window_id, self.tab_id, self.os_window_id, self.tab_id,
color, pw, g.left - pw, g.top - pw, g.right + pw, (color << 8) | 8, pw, g.left - pw, g.top - pw, g.right + pw,
g.bottom + pw g.bottom + pw
) )

View File

@@ -2,26 +2,29 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import re
from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from weakref import WeakValueDictionary from weakref import WeakValueDictionary
from .cli import create_opts, parse_args from .cli import create_opts, parse_args
from .config import MINIMUM_FONT_SIZE, cached_values, initial_window_size from .config import MINIMUM_FONT_SIZE, initial_window_size
from .constants import set_boss, wakeup from .constants import appname, set_boss, wakeup
from .fast_data_types import ( from .fast_data_types import (
ChildMonitor, create_os_window, current_os_window, destroy_global_data, ChildMonitor, create_os_window, current_os_window, destroy_global_data,
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event, destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
layout_sprite_map, mark_os_window_for_close, show_window, layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
toggle_fullscreen, viewport_for_window set_dpi_from_os_window, show_window, toggle_fullscreen,
viewport_for_window
) )
from .fonts.render import prerender, resize_fonts, set_font_family from .fonts.render import prerender, resize_fonts, set_font_family
from .keys import get_shortcut from .keys import get_shortcut
from .remote_control import handle_cmd
from .session import create_session from .session import create_session
from .tabs import SpecialWindow, TabManager from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
from .utils import ( from .utils import (
encode_wm_class, end_startup_notification, get_primary_selection, end_startup_notification, get_primary_selection, init_startup_notification,
init_startup_notification, open_url, safe_print, set_primary_selection, open_url, safe_print, set_primary_selection, single_instance
single_instance
) )
@@ -59,8 +62,9 @@ class DumpCommands: # {{{
class Boss: class Boss:
def __init__(self, os_window_id, opts, args): def __init__(self, os_window_id, opts, args, cached_values):
self.window_id_map = WeakValueDictionary() self.window_id_map = WeakValueDictionary()
self.cached_values = cached_values
self.os_window_map = {} self.os_window_map = {}
self.cursor_blinking = True self.cursor_blinking = True
self.shutting_down = False self.shutting_down = False
@@ -79,18 +83,84 @@ class Boss:
startup_session = create_session(opts, args) startup_session = create_session(opts, args)
self.add_os_window(startup_session, os_window_id=os_window_id) self.add_os_window(startup_session, os_window_id=os_window_id)
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, visible=True): def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, startup_id=None):
dpi_changed = False
if os_window_id is None: if os_window_id is None:
w, h = initial_window_size(self.opts) if size is None else size w, h = initial_window_size(self.opts, self.cached_values) if size is None else size
os_window_id = create_os_window(w, h, encode_wm_class(wname or self.args.name, wclass or self.args.cls), visible) cls = wclass or self.args.cls or appname
os_window_id = create_os_window(w, h, appname, wname or self.args.name or cls, cls)
if startup_id:
ctx = init_startup_notification(os_window_id, startup_id)
dpi_changed = show_window(os_window_id)
if startup_id:
end_startup_notification(ctx)
tm = TabManager(os_window_id, self.opts, self.args, startup_session) tm = TabManager(os_window_id, self.opts, self.args, startup_session)
self.os_window_map[os_window_id] = tm self.os_window_map[os_window_id] = tm
return os_window_id if dpi_changed:
self.on_dpi_change(os_window_id)
def list_os_windows(self):
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'tabs': list(tm.list_tabs()),
}
def match_windows(self, match):
field, exp = match.split(':', 1)
pat = re.compile(exp)
for tm in self.os_window_map.values():
for tab in tm:
for window in tab:
if window.matches(field, pat):
yield window
def tab_for_window(self, window):
for tm in self.os_window_map.values():
for tab in tm:
for w in tab:
if w.id == window.id:
return tab
def match_tabs(self, match):
field, exp = match.split(':', 1)
pat = re.compile(exp)
tms = tuple(self.os_window_map.values())
found = False
if field in ('title', 'id'):
for tm in tms:
for tab in tm:
if tab.matches(field, pat):
yield tab
found = True
if not found:
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
for tab in tabs:
if tab:
yield tab
def set_active_window(self, window):
for tm in self.os_window_map.values():
for tab in tm:
for w in tab:
if w.id == window.id:
if tab is not self.active_tab:
tm.set_active_tab(tab)
tab.set_active_window(w)
return
def _new_os_window(self, args, cwd_from=None):
sw = self.args_to_special_window(args, cwd_from) if args else None
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
self.add_os_window(startup_session)
def new_os_window(self, *args): def new_os_window(self, *args):
sw = self.args_to_special_window(args) if args else None self._new_os_window(args)
startup_session = create_session(self.opts, special_window=sw)
return self.add_os_window(startup_session) def new_os_window_with_cwd(self, *args):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
self._new_os_window(args, cwd_from)
def add_child(self, window): def add_child(self, window):
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
@@ -106,19 +176,34 @@ class Boss:
args.args = rest args.args = rest
opts = create_opts(args) opts = create_opts(args)
session = create_session(opts, args) session = create_session(opts, args)
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts), visible=False) self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts, self.cached_values), startup_id=startup_id)
if startup_id:
ctx = init_startup_notification(os_window_id, startup_id)
show_window(os_window_id)
if startup_id:
end_startup_notification(ctx)
else: else:
safe_print('Unknown message received from peer, ignoring') safe_print('Unknown message received from peer, ignoring')
def handle_remote_cmd(self, cmd, window=None):
response = None
if self.opts.allow_remote_control:
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
import traceback
response = {'ok': False, 'error': str(err), 'tb': traceback.format_exc()}
else:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
if response is not None:
if window is not None:
window.send_cmd_response(response)
def on_child_death(self, window_id): def on_child_death(self, window_id):
window = self.window_id_map.pop(window_id, None) window = self.window_id_map.pop(window_id, None)
if window is None: if window is None:
return return
if window.action_on_close:
try:
window.action_on_close(window)
except Exception:
import traceback
traceback.print_exc()
os_window_id = window.os_window_id os_window_id = window.os_window_id
window.destroy() window.destroy()
tm = self.os_window_map.get(os_window_id) tm = self.os_window_map.get(os_window_id)
@@ -162,10 +247,16 @@ class Boss:
if tm is not None: if tm is not None:
tm.activate_tab_at(x) tm.activate_tab_at(x)
def on_window_resize(self, os_window_id, w, h): def on_window_resize(self, os_window_id, w, h, dpi_changed):
tm = self.os_window_map.get(os_window_id) tm = self.os_window_map.get(os_window_id)
if tm is not None: if tm is not None:
tm.resize() if dpi_changed:
if set_dpi_from_os_window(os_window_id):
self.on_dpi_change(os_window_id)
else:
tm.resize()
else:
tm.resize()
def increase_font_size(self): def increase_font_size(self):
self.change_font_size( self.change_font_size(
@@ -182,13 +273,12 @@ class Boss:
def restore_font_size(self): def restore_font_size(self):
self.change_font_size(self.opts.font_size) self.change_font_size(self.opts.font_size)
def change_font_size(self, new_size): def _change_font_size(self, new_size=None, on_dpi_change=False):
if new_size == self.current_font_size: if new_size is not None:
return self.current_font_size = new_size
self.current_font_size = new_size
old_cell_width, old_cell_height = viewport_for_window()[-2:] old_cell_width, old_cell_height = viewport_for_window()[-2:]
windows = tuple(filter(None, self.window_id_map.values())) windows = tuple(filter(None, self.window_id_map.values()))
resize_fonts(self.current_font_size) resize_fonts(self.current_font_size, on_dpi_change=on_dpi_change)
layout_sprite_map() layout_sprite_map()
prerender() prerender()
for window in windows: for window in windows:
@@ -199,6 +289,14 @@ class Boss:
tm.refresh_sprite_positions() tm.refresh_sprite_positions()
glfw_post_empty_event() glfw_post_empty_event()
def change_font_size(self, new_size):
if new_size == self.current_font_size:
return
self._change_font_size(new_size)
def on_dpi_change(self, os_window_id):
self._change_font_size()
@property @property
def active_tab_manager(self): def active_tab_manager(self):
os_window_id = current_os_window() os_window_id = current_os_window()
@@ -222,6 +320,15 @@ class Boss:
self.current_key_press_info = key, scancode, action, mods self.current_key_press_info = key, scancode, action, mods
return self.dispatch_action(key_action) return self.dispatch_action(key_action)
def default_bg_changed_for(self, window_id):
w = self.window_id_map.get(window_id)
if w is not None:
tm = self.os_window_map.get(w.os_window_id)
if tm is not None:
t = tm.tab_for_id(w.tab_id)
if t is not None:
t.relayout_borders()
def dispatch_action(self, key_action): def dispatch_action(self, key_action):
if key_action is not None: if key_action is not None:
f = getattr(self, key_action.func, None) f = getattr(self, key_action.func, None)
@@ -262,22 +369,63 @@ class Boss:
w.paste('\n'.join(paths)) w.paste('\n'.join(paths))
def on_os_window_closed(self, os_window_id, viewport_width, viewport_height): def on_os_window_closed(self, os_window_id, viewport_width, viewport_height):
cached_values['window-size'] = viewport_width, viewport_height self.cached_values['window-size'] = viewport_width, viewport_height
tm = self.os_window_map.pop(os_window_id, None) tm = self.os_window_map.pop(os_window_id, None)
if tm is not None: if tm is not None:
tm.destroy() tm.destroy()
for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id): for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id):
self.window_id_map.pop(window_id, None) self.window_id_map.pop(window_id, None)
def display_scrollback(self, data): def display_scrollback(self, window, data):
if self.opts.scrollback_in_new_tab: tab = self.active_tab
self.display_scrollback_in_new_tab(data) if tab is not None and window.overlay_for is None:
else: tab.new_special_window(
tab = self.active_tab SpecialWindow(
if tab is not None: self.opts.scrollback_pager, data, _('History'), overlay_for=window.id))
tab.new_special_window(
SpecialWindow( def input_unicode_character(self):
self.opts.scrollback_pager, data, _('History'))) w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
overlay_window = tab.new_special_window(
SpecialWindow(
['kitty', '+runpy', 'from kittens.unicode_input.main import main; main()'],
overlay_for=w.id))
overlay_window.action_on_close = partial(self.send_unicode_character, w.id)
def send_unicode_character(self, target_window_id, source_window):
w = self.window_id_map.get(target_window_id)
if w is not None:
output = str(source_window.screen.linebuf.line(0))
if output.startswith('OK: '):
try:
text = chr(int(output.partition(' ')[2], 16))
except Exception:
import traceback
traceback.print_exc()
else:
w.paste(text)
def run_simple_kitten(self, type_of_input, kitten, *args):
import shlex
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
cmdline = args[0] if args else ''
args = shlex.split(cmdline) if cmdline else []
if '--program' not in cmdline:
args.extend(('--program', self.opts.open_url_with))
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
elif type_of_input == 'none':
data = None
else:
raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
tab.new_special_window(
SpecialWindow(
['kitty', '+runpy', 'from kittens.{}.main import main; main()'.format(kitten)] + args,
stdin=data,
overlay_for=w.id))
def switch_focus_to(self, window_idx): def switch_focus_to(self, window_idx):
tab = self.active_tab tab = self.active_tab
@@ -326,6 +474,8 @@ class Boss:
text = w.text_for_selection() text = w.text_for_selection()
if text: if text:
set_primary_selection(text) set_primary_selection(text)
if self.opts.copy_on_select:
set_clipboard_string(text)
def goto_tab(self, tab_num): def goto_tab(self, tab_num):
tm = self.active_tab_manager tm = self.active_tab_manager
@@ -342,7 +492,7 @@ class Boss:
if tm is not None: if tm is not None:
tm.next_tab(-1) tm.next_tab(-1)
def args_to_special_window(self, args): def args_to_special_window(self, args, cwd_from=None):
args = list(args) args = list(args)
stdin = None stdin = None
w = self.active_window w = self.active_window
@@ -351,9 +501,13 @@ class Boss:
if arg == '@selection': if arg == '@selection':
return w.text_for_selection() return w.text_for_selection()
if arg == '@ansi': if arg == '@ansi':
return w.buffer_as_ansi() return w.as_text(as_ansi=True, add_history=True)
if arg == '@text': if arg == '@text':
return w.buffer_as_text() return w.as_text(add_history=True)
if arg == '@screen':
return w.as_text()
if arg == '@ansi_screen':
return w.as_text(as_ansi=True)
if args[0].startswith('@'): if args[0].startswith('@'):
stdin = data_for_at(args[0]) or None stdin = data_for_at(args[0]) or None
@@ -368,23 +522,44 @@ class Boss:
if not arg: if not arg:
continue continue
cmd.append(arg) cmd.append(arg)
return SpecialWindow(cmd, stdin) return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
def new_tab(self, *args): def _new_tab(self, args, cwd_from=None):
special_window = None special_window = None
if args: if args:
special_window = self.args_to_special_window(args) if isinstance(args, SpecialWindowInstance):
special_window = args
else:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.new_tab(special_window=special_window) tm.new_tab(special_window=special_window, cwd_from=cwd_from)
def new_window(self, *args): def new_tab(self, *args):
self._new_tab(args)
def new_tab_with_cwd(self, *args):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
self._new_tab(args, cwd_from=cwd_from)
def _new_window(self, args, cwd_from=None):
tab = self.active_tab tab = self.active_tab
if tab is not None: if tab is not None:
if args: if args:
tab.new_special_window(self.args_to_special_window(args)) tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
else: else:
tab.new_window() tab.new_window(cwd_from=cwd_from)
def new_window(self, *args):
self._new_window(args)
def new_window_with_cwd(self, *args):
w = self.active_window
if w is None:
return self.new_window(*args)
cwd_from = w.child.pid if w is not None else None
self._new_window(args, cwd_from=cwd_from)
def move_tab_forward(self): def move_tab_forward(self):
tm = self.active_tab_manager tm = self.active_tab_manager
@@ -395,9 +570,3 @@ class Boss:
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.move_tab(-1) tm.move_tab(-1)
def display_scrollback_in_new_tab(self, data):
tm = self.active_tab_manager
if tm is not None:
tm.new_tab(special_window=SpecialWindow(
self.opts.scrollback_pager, data, _('History')))

View File

@@ -19,6 +19,7 @@ in float bg_alpha;
#ifdef NEEDS_FOREGROUND #ifdef NEEDS_FOREGROUND
uniform sampler2DArray sprites; uniform sampler2DArray sprites;
uniform float inactive_text_alpha;
in vec3 sprite_pos; in vec3 sprite_pos;
in vec3 underline_pos; in vec3 underline_pos;
in vec3 strike_pos; in vec3 strike_pos;
@@ -34,7 +35,7 @@ vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) {
// Alpha blend two colors returning the resulting color pre-multiplied by its alpha // Alpha blend two colors returning the resulting color pre-multiplied by its alpha
// and its alpha. // and its alpha.
// See https://en.wikipedia.org/wiki/Alpha_compositing // See https://en.wikipedia.org/wiki/Alpha_compositing
float alpha = mix(under_alpha, 1.0f, over_alpha); float alpha = mix(under_alpha, 1.0f, over_alpha);
vec3 combined_color = mix(under * under_alpha, over, over_alpha); vec3 combined_color = mix(under * under_alpha, over, over_alpha);
return vec4(combined_color, alpha); return vec4(combined_color, alpha);
} }
@@ -45,7 +46,7 @@ vec3 premul_blend(vec3 over, float over_alpha, vec3 under) {
vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alpha) { vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alpha) {
// Same as alpha_blend() except that it assumes over and under are both premultiplied. // Same as alpha_blend() except that it assumes over and under are both premultiplied.
float alpha = mix(under_alpha, 1.0f, over_alpha); float alpha = mix(under_alpha, 1.0f, over_alpha);
return vec4(premul_blend(over, over_alpha, under), alpha); return vec4(premul_blend(over, over_alpha, under), alpha);
} }
// }}} // }}}
@@ -60,7 +61,7 @@ vec4 calculate_foreground() {
// Since strike and text are the same color, we simply add the alpha values // Since strike and text are the same color, we simply add the alpha values
float combined_alpha = min(text_alpha + strike_alpha, 1.0f); float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
// Underline color might be different, so alpha blend // Underline color might be different, so alpha blend
return alpha_blend(decoration_fg, underline_alpha, fg, combined_alpha); return alpha_blend(decoration_fg, underline_alpha * inactive_text_alpha, fg, combined_alpha * inactive_text_alpha);
} }
#endif #endif
@@ -70,7 +71,7 @@ void main() {
final_color = vec4(background.rgb * bg_alpha, bg_alpha); final_color = vec4(background.rgb * bg_alpha, bg_alpha);
#else #else
final_color = vec4(background.rgb, 1.0f); final_color = vec4(background.rgb, 1.0f);
#endif #endif
#endif #endif
#ifdef SPECIAL #ifdef SPECIAL
@@ -78,10 +79,10 @@ void main() {
final_color = vec4(background.rgb * bg_alpha, bg_alpha); final_color = vec4(background.rgb * bg_alpha, bg_alpha);
#else #else
final_color = vec4(background.rgb, bg_alpha); final_color = vec4(background.rgb, bg_alpha);
#endif #endif
#endif #endif
#if defined(FOREGROUND) || defined(SIMPLE) #if defined(FOREGROUND) || defined(SIMPLE)
// FOREGROUND or SIMPLE // FOREGROUND or SIMPLE
vec4 fg = calculate_foreground(); // pre-multiplied foreground vec4 fg = calculate_foreground(); // pre-multiplied foreground
@@ -99,7 +100,7 @@ void main() {
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha); 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, final_color.a);
#else #else
// since background alpha is 1.0, it is effectively premultipled // 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(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, final_color.a);
#endif #endif

View File

@@ -12,7 +12,7 @@ layout(std140) uniform CellRenderData {
uint xnum, ynum, cursor_x, cursor_y, cursor_w; uint xnum, ynum, cursor_x, cursor_y, cursor_w;
uint color_table[256]; uint color_table[256];
}; };
// Have to use fixed locations here as all variants of the cell program share the same VAO // Have to use fixed locations here as all variants of the cell program share the same VAO
@@ -21,7 +21,7 @@ layout(location=1) in uvec4 sprite_coords;
layout(location=2) in uint is_selected; layout(location=2) in uint is_selected;
const uvec2 cell_pos_map[] = uvec2[4]( const uvec2 cell_pos_map[] = uvec2[4](
uvec2(1, 0), // right, top uvec2(1, 0), // right, top
uvec2(1, 1), // right, bottom uvec2(1, 1), // right, bottom
@@ -118,27 +118,27 @@ float is_cursor(uint x, uint y) {
void main() { void main() {
// set cell vertex position {{{ // set cell vertex position {{{
uint instance_id = uint(gl_InstanceID); uint instance_id = uint(gl_InstanceID);
/* The current cell being rendered */ /* The current cell being rendered */
uint r = instance_id / xnum; uint r = instance_id / xnum;
uint c = instance_id - r * xnum; uint c = instance_id - r * xnum;
/* The position of this vertex, at a corner of the cell */ /* The position of this vertex, at a corner of the cell */
float left = xstart + c * dx; float left = xstart + c * dx;
float top = ystart - r * dy; float top = ystart - r * dy;
vec2 xpos = vec2(left, left + dx); vec2 xpos = vec2(left, left + dx);
vec2 ypos = vec2(top, top - dy); vec2 ypos = vec2(top, top - dy);
uvec2 pos = cell_pos_map[gl_VertexID]; uvec2 pos = cell_pos_map[gl_VertexID];
gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1); gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1);
// }}} // }}}
// set cell color indices {{{ // set cell color indices {{{
uvec2 default_colors = uvec2(default_fg, default_bg); uvec2 default_colors = uvec2(default_fg, default_bg);
ivec2 color_indices = ivec2(color1, color2); ivec2 color_indices = ivec2(color1, color2);
uint text_attrs = sprite_coords[3]; uint text_attrs = sprite_coords[3];
int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK]; int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK];
int bg_index = color_indices[1 - fg_index]; int bg_index = color_indices[1 - fg_index];
float cursor = is_cursor(c, r); float cursor = is_cursor(c, r);
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]); vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
// }}} // }}}
@@ -150,7 +150,7 @@ void main() {
sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & Z_MASK); sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & Z_MASK);
colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14); colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14);
// Foreground // Foreground
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]); uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
foreground = color_to_vec(resolved_fg); foreground = color_to_vec(resolved_fg);
// Selection // Selection
@@ -165,7 +165,7 @@ void main() {
foreground = choose_color(cursor, bg, foreground); foreground = choose_color(cursor, bg, foreground);
decoration_fg = choose_color(cursor, bg, decoration_fg); decoration_fg = choose_color(cursor, bg, decoration_fg);
#endif #endif
// }}} // }}}
// Background {{{ // Background {{{
#ifdef NEEDS_BACKROUND #ifdef NEEDS_BACKROUND

18
kitty/charsets.c generated
View File

@@ -43,7 +43,7 @@ static uint32_t charset_translations[5][256] = {
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
}, },
/* VT100 graphics mapped to Unicode */ /* VT100 graphics mapped to Unicode */
{ {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
@@ -81,7 +81,7 @@ static uint32_t charset_translations[5][256] = {
}, },
/* IBM Codepage 437 mapped to Unicode */ /* IBM Codepage 437 mapped to Unicode */
{ {
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
@@ -113,7 +113,7 @@ static uint32_t charset_translations[5][256] = {
0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
}, },
// VAX 42 map // VAX 42 map
{ {
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
@@ -183,13 +183,13 @@ static uint32_t charset_translations[5][256] = {
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
}, },
}; };
uint32_t* uint32_t*
translation_table(uint32_t which) { translation_table(uint32_t which) {
switch(which){ switch(which){
case 'B': case 'B':
return charset_translations[0]; return charset_translations[0];
case '0': case '0':
@@ -208,7 +208,7 @@ translation_table(uint32_t which) {
uint32_t *latin1_charset = charset_translations[0]; uint32_t *latin1_charset = charset_translations[0];
// UTF-8 decode taken from: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ // UTF-8 decode taken from: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
static const uint8_t utf8_data[] = { static const uint8_t utf8_data[] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
@@ -226,7 +226,7 @@ static const uint8_t utf8_data[] = {
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
}; };
uint32_t uint32_t
decode_utf8(uint32_t* state, uint32_t* codep, uint8_t byte) { decode_utf8(uint32_t* state, uint32_t* codep, uint8_t byte) {
uint32_t type = utf8_data[byte]; uint32_t type = utf8_data[byte];
@@ -278,7 +278,7 @@ base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, size_t dest_cap
if (!src_sz) { *dest_sz = 0; return NULL; } if (!src_sz) { *dest_sz = 0; return NULL; }
if (src_sz % 4 != 0) return "base64 encoded data must have a length that is a multiple of four"; if (src_sz % 4 != 0) return "base64 encoded data must have a length that is a multiple of four";
*dest_sz = (src_sz / 4) * 3; *dest_sz = (src_sz / 4) * 3;
if (src[src_sz - 1] == '=') (*dest_sz)--; if (src[src_sz - 1] == '=') (*dest_sz)--;
if (src[src_sz - 2] == '=') (*dest_sz)--; if (src[src_sz - 2] == '=') (*dest_sz)--;
if (*dest_sz > dest_capacity) return "output buffer too small"; if (*dest_sz > dest_capacity) return "output buffer too small";
for (size_t i = 0, j = 0; i < src_sz;) { for (size_t i = 0, j = 0; i < src_sz;) {

View File

@@ -95,7 +95,7 @@ set_thread_name(const char *name) {
#define FREE_CHILD(x) \ #define FREE_CHILD(x) \
Py_CLEAR((x).screen); x = EMPTY_CHILD; Py_CLEAR((x).screen); x = EMPTY_CHILD;
#define XREF_CHILD(x, OP) OP(x.screen); #define XREF_CHILD(x, OP) OP(x.screen);
#define INCREF_CHILD(x) XREF_CHILD(x, Py_INCREF) #define INCREF_CHILD(x) XREF_CHILD(x, Py_INCREF)
#define DECREF_CHILD(x) XREF_CHILD(x, Py_DECREF) #define DECREF_CHILD(x) XREF_CHILD(x, Py_DECREF)
@@ -138,12 +138,12 @@ self_pipe(int fds[2]) {
static PyObject * static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
ChildMonitor *self; ChildMonitor *self;
PyObject *dump_callback, *death_notify; PyObject *dump_callback, *death_notify;
int talk_fd = -1; int talk_fd = -1;
int ret; int ret;
if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; } if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; }
if (!PyArg_ParseTuple(args, "OO|i", &death_notify, &dump_callback, &talk_fd)) return NULL; if (!PyArg_ParseTuple(args, "OO|i", &death_notify, &dump_callback, &talk_fd)) return NULL;
if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) { if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) {
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret)); PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
return NULL; return NULL;
@@ -163,7 +163,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->dump_callback = dump_callback; Py_INCREF(dump_callback); self->dump_callback = dump_callback; Py_INCREF(dump_callback);
parse_func = parse_worker_dump; parse_func = parse_worker_dump;
} else parse_func = parse_worker; } else parse_func = parse_worker;
self->count = 0; self->count = 0;
fds[0].fd = wakeup_fds[0]; fds[1].fd = signal_fds[0]; fds[0].fd = wakeup_fds[0]; fds[1].fd = signal_fds[0];
fds[0].events = POLLIN; fds[1].events = POLLIN; fds[0].events = POLLIN; fds[1].events = POLLIN;
the_monitor = self; the_monitor = self;
@@ -187,7 +187,7 @@ dealloc(ChildMonitor* self) {
} }
close(wakeup_fds[0]); close(wakeup_fds[0]);
close(wakeup_fds[1]); close(wakeup_fds[1]);
close(signal_fds[0]); close(signal_fds[0]);
close(signal_fds[1]); close(signal_fds[1]);
} }
@@ -243,7 +243,7 @@ add_child(ChildMonitor *self, PyObject *args) {
#define A(attr) &add_queue[add_queue_count].attr #define A(attr) &add_queue[add_queue_count].attr
if (!PyArg_ParseTuple(args, "kiiO", A(id), A(pid), A(fd), A(screen))) { if (!PyArg_ParseTuple(args, "kiiO", A(id), A(pid), A(fd), A(screen))) {
children_mutex(unlock); children_mutex(unlock);
return NULL; return NULL;
} }
#undef A #undef A
INCREF_CHILD(add_queue[add_queue_count]); INCREF_CHILD(add_queue[add_queue_count]);
@@ -259,12 +259,12 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
bool found = false; bool found = false;
children_mutex(lock); children_mutex(lock);
for (size_t i = 0; i < self->count; i++) { for (size_t i = 0; i < self->count; i++) {
if (children[i].id == id) { if (children[i].id == id) {
found = true; found = true;
Screen *screen = children[i].screen; Screen *screen = children[i].screen;
screen_mutex(lock, write); screen_mutex(lock, write);
size_t space_left = screen->write_buf_sz - screen->write_buf_used; size_t space_left = screen->write_buf_sz - screen->write_buf_used;
if (space_left < sz) { if (space_left < sz) {
if (screen->write_buf_used + sz > 100 * 1024 * 1024) { if (screen->write_buf_used + sz > 100 * 1024 * 1024) {
fprintf(stderr, "Too much data being sent to child with id: %lu, ignoring it\n", id); fprintf(stderr, "Too much data being sent to child with id: %lu, ignoring it\n", id);
screen_mutex(unlock, write); screen_mutex(unlock, write);
@@ -295,7 +295,7 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
#define needs_write_doc "needs_write(id, data) -> Queue data to be written to child." #define needs_write_doc "needs_write(id, data) -> Queue data to be written to child."
unsigned long id, sz; unsigned long id, sz;
const char *data; const char *data;
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL; if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; } if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; }
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
@@ -332,7 +332,7 @@ parse_input(ChildMonitor *self) {
PyObject *msg = NULL; PyObject *msg = NULL;
children_mutex(lock); children_mutex(lock);
while (remove_queue_count) { while (remove_queue_count) {
remove_queue_count--; remove_queue_count--;
remove_notify[remove_count] = remove_queue[remove_queue_count].id; remove_notify[remove_count] = remove_queue[remove_queue_count].id;
remove_count++; remove_count++;
FREE_CHILD(remove_queue[remove_queue_count]); FREE_CHILD(remove_queue[remove_queue_count]);
@@ -517,13 +517,23 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
ColorProfile *cp = rd->screen->color_profile; ColorProfile *cp = rd->screen->color_profile;
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape); ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color); ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
if (ans->shape == CURSOR_BLOCK) return; ans->is_focused = os_window->is_focused;
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
double left = rd->xstart + cursor->x * rd->dx; double left = rd->xstart + cursor->x * rd->dx;
double top = rd->ystart - cursor->y * rd->dy; double top = rd->ystart - cursor->y * rd->dy;
unsigned long mult = MAX(1, screen_current_char_width(rd->screen)); unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult); double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
double bottom = top - rd->dy; double bottom = top - rd->dy;
if (ans->shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false, os_window); switch (ans->shape) {
case CURSOR_UNDERLINE:
top = bottom + cursor_width(2.0, false, os_window);
break;
case CURSOR_BLOCK:
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
break;
default:
break;
}
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom; ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
} }
@@ -544,29 +554,38 @@ update_window_title(Window *w, OSWindow *os_window) {
static PyObject* static PyObject*
simple_render_screen(PyObject UNUSED *self, PyObject *args) { simple_render_screen(PyObject UNUSED *self, PyObject *args) {
#define simple_render_screen_doc "Render a Screen object, with no cursor" #define simple_render_screen_doc "Render a Screen object, with no cursor"
Screen *screen; Screen *screen;
float xstart, ystart, dx, dy; float xstart, ystart, dx, dy;
static ssize_t vao_idx = -1, gvao_idx = -1; static ssize_t vao_idx = -1, gvao_idx = -1;
if (vao_idx == -1) vao_idx = create_cell_vao(); if (vao_idx == -1) vao_idx = create_cell_vao();
if (gvao_idx == -1) gvao_idx = create_graphics_vao(); if (gvao_idx == -1) gvao_idx = create_graphics_vao();
if (!PyArg_ParseTuple(args, "O!ffff", &Screen_Type, &screen, &xstart, &ystart, &dx, &dy)) return NULL; if (!PyArg_ParseTuple(args, "O!ffff", &Screen_Type, &screen, &xstart, &ystart, &dx, &dy)) return NULL;
draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window()); send_cell_data_to_gpu(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window());
draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window(), true);
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline void static inline bool
render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id) { prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows) {
#define TD os_window->tab_bar_render_data
bool needs_render = false;
if (TD.screen && os_window->num_tabs > 1) {
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 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
Tab *tab = os_window->tabs + os_window->active_tab; Tab *tab = os_window->tabs + os_window->active_tab;
*active_window_bg = OPT(background);
for (unsigned int i = 0; i < tab->num_windows; i++) { for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i; Window *w = tab->windows + i;
#define WD w->render_data #define WD w->render_data
if (w->visible && WD.screen) { if (w->visible && WD.screen) {
*num_visible_windows += 1;
if (w->last_drag_scroll_at > 0) { if (w->last_drag_scroll_at > 0) {
if (now - w->last_drag_scroll_at >= 0.02) { if (now - w->last_drag_scroll_at >= 0.02) {
if (drag_scroll(w, os_window)) { if (drag_scroll(w, os_window)) {
w->last_drag_scroll_at = now; w->last_drag_scroll_at = now;
set_maximum_wait(0.02); set_maximum_wait(0.02);
needs_render = true;
} else w->last_drag_scroll_at = 0; } else w->last_drag_scroll_at = 0;
} else set_maximum_wait(now - w->last_drag_scroll_at); } else set_maximum_wait(now - w->last_drag_scroll_at);
} }
@@ -574,48 +593,71 @@ render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id
if (is_active_window) { if (is_active_window) {
*active_window_id = w->id; *active_window_id = w->id;
collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window); collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window);
if (w->cursor_visible_at_last_render != WD.screen->cursor_render_info.is_visible || w->last_cursor_x != WD.screen->cursor_render_info.x || w->last_cursor_y != WD.screen->cursor_render_info.y || w->last_cursor_shape != WD.screen->cursor_render_info.shape) needs_render = true;
update_window_title(w, os_window); update_window_title(w, os_window);
*active_window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg);
} else WD.screen->cursor_render_info.is_visible = false; } else WD.screen->cursor_render_info.is_visible = false;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window); if (send_cell_data_to_gpu(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
if (is_active_window && WD.screen->cursor_render_info.is_visible && WD.screen->cursor_render_info.shape != CURSOR_BLOCK) { if (WD.screen->start_visual_bell_at != 0) needs_render = true;
}
}
return needs_render;
}
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) {
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);
if (TD.screen && os_window->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window, true);
for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i;
if (w->visible && WD.screen) {
bool is_active_window = i == tab->active_window;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window);
if (is_active_window && WD.screen->cursor_render_info.is_visible && (!WD.screen->cursor_render_info.is_focused || WD.screen->cursor_render_info.shape != CURSOR_BLOCK)) {
draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused); draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused);
} }
if (WD.screen->start_visual_bell_at != 0) { if (WD.screen->start_visual_bell_at != 0) {
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at); double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
set_maximum_wait(bell_left); set_maximum_wait(bell_left);
} }
w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_x = WD.screen->cursor_render_info.x; w->last_cursor_y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape;
} }
} }
swap_window_buffers(os_window);
br->is_dirty = false;
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;
#undef WD #undef WD
#undef TD
} }
static inline void static inline void
render(double now) { render(double now) {
double time_since_last_render = now - last_render_at; double time_since_last_render = now - last_render_at;
if (time_since_last_render < OPT(repaint_delay)) { if (time_since_last_render < OPT(repaint_delay)) {
set_maximum_wait(OPT(repaint_delay) - time_since_last_render); set_maximum_wait(OPT(repaint_delay) - time_since_last_render);
return; return;
} }
#define TD w->tab_bar_render_data
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i; OSWindow *w = global_state.os_windows + i;
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue; if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
bool needs_render = w->is_damaged;
make_os_window_context_current(w); make_os_window_context_current(w);
if (w->viewport_size_dirty) { if (w->viewport_size_dirty) {
w->clear_count = 0; w->clear_count = 0;
update_surface_size(w->viewport_width, w->viewport_height, w->offscreen_texture_id); update_surface_size(w->viewport_width, w->viewport_height, w->offscreen_texture_id);
w->viewport_size_dirty = false; w->viewport_size_dirty = false;
needs_render = true;
} }
unsigned int active_window_id = 0; unsigned int active_window_id = 0, num_visible_windows = 0;
render_os_window(w, now, &active_window_id); color_type active_window_bg = 0;
Tab *active_tab = w->tabs + w->active_tab; if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows)) needs_render = true;
BorderRects *br = &active_tab->border_rects; if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true;
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, w->viewport_width, w->viewport_height); if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows);
if (TD.screen && w->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, w);
swap_window_buffers(w);
w->last_active_tab = w->active_tab; w->last_num_tabs = w->num_tabs; w->last_active_window_id = active_window_id;
br->is_dirty = false;
} }
last_render_at = now; last_render_at = now;
#undef TD #undef TD
@@ -663,7 +705,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "is#", &fd, &buf, &sz)) return NULL; if (!PyArg_ParseTuple(args, "is#", &fd, &buf, &sz)) return NULL;
ThreadWriteData *data = alloc_twd(sz); ThreadWriteData *data = alloc_twd(sz);
if (data == NULL) return PyErr_NoMemory(); if (data == NULL) return PyErr_NoMemory();
data->fd = fd; data->fd = fd;
memcpy(data->buf, buf, data->sz); memcpy(data->buf, buf, data->sz);
int ret = pthread_create(&thread, NULL, thread_write, data); int ret = pthread_create(&thread, NULL, thread_write, data);
if (ret != 0) { free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); } if (ret != 0) { free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
@@ -682,10 +724,10 @@ process_pending_resizes(double now) {
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i; OSWindow *w = global_state.os_windows + i;
if (w->has_pending_resizes) { if (w->has_pending_resizes) {
if (now - w->last_resize_at >= RESIZE_DEBOUNCE_TIME) update_os_window_viewport(w, true); if (now - w->last_resize_event_at >= RESIZE_DEBOUNCE_TIME) update_os_window_viewport(w, true);
else { else {
global_state.has_pending_resizes = true; global_state.has_pending_resizes = true;
set_maximum_wait(RESIZE_DEBOUNCE_TIME - now + w->last_resize_at); set_maximum_wait(RESIZE_DEBOUNCE_TIME - now + w->last_resize_event_at);
} }
} }
} }
@@ -711,12 +753,12 @@ main_loop(ChildMonitor *self) {
OSWindow *os_window = global_state.os_windows + w - 1; OSWindow *os_window = global_state.os_windows + w - 1;
if (should_os_window_close(os_window)) { if (should_os_window_close(os_window)) {
destroy_os_window(os_window); destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->viewport_width, os_window->viewport_width); call_boss(on_os_window_closed, "Kii", os_window->id, os_window->viewport_width, os_window->viewport_height);
for (size_t t=0; t < os_window->num_tabs; t++) { for (size_t t=0; t < os_window->num_tabs; t++) {
Tab *tab = os_window->tabs + t; Tab *tab = os_window->tabs + t;
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id); for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
} }
remove_os_window(os_window->id); remove_os_window(os_window->id);
} else has_open_windows = true; } else has_open_windows = true;
} }
} }
@@ -748,7 +790,7 @@ hangup(pid_t pid) {
if (errno == ESRCH) return; if (errno == ESRCH) return;
if (errno != 0) { perror("Failed to get process group id for child"); return; } if (errno != 0) { perror("Failed to get process group id for child"); return; }
if (killpg(pgid, SIGHUP) != 0) { if (killpg(pgid, SIGHUP) != 0) {
if (errno != ESRCH) perror("Failed to kill child"); if (errno != ESRCH) perror("Failed to kill child");
} }
} }
@@ -763,7 +805,7 @@ cleanup_child(ssize_t i) {
static inline void static inline void
remove_children(ChildMonitor *self) { remove_children(ChildMonitor *self) {
if (self->count > 0) { if (self->count > 0) {
size_t count = 0; size_t count = 0;
for (ssize_t i = self->count - 1; i >= 0; i--) { for (ssize_t i = self->count - 1; i >= 0; i--) {
if (children[i].needs_removal) { if (children[i].needs_removal) {
count++; count++;
@@ -839,7 +881,7 @@ write_to_child(int fd, Screen *screen) {
while (written < screen->write_buf_used) { while (written < screen->write_buf_used) {
ret = write(fd, screen->write_buf + written, screen->write_buf_used - written); ret = write(fd, screen->write_buf + written, screen->write_buf_used - written);
if (ret > 0) { written += ret; } if (ret > 0) { written += ret; }
else if (ret == 0) { else if (ret == 0) {
// could mean anything, ignore // could mean anything, ignore
break; break;
} else { } else {
@@ -858,7 +900,7 @@ io_loop(void *data) {
// The I/O thread loop // The I/O thread loop
size_t i; size_t i;
int ret; int ret;
bool has_more, data_received; bool has_more, data_received;
Screen *screen; Screen *screen;
ChildMonitor *self = (ChildMonitor*)data; ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyChildMon"); set_thread_name("KittyChildMon");
@@ -880,7 +922,7 @@ io_loop(void *data) {
ret = poll(fds, self->count + EXTRA_FDS, -1); ret = poll(fds, self->count + EXTRA_FDS, -1);
if (ret > 0) { if (ret > 0) {
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
if (fds[1].revents && POLLIN) { if (fds[1].revents && POLLIN) {
data_received = true; data_received = true;
drain_fd(fds[1].fd); drain_fd(fds[1].fd);
children_mutex(lock); children_mutex(lock);
@@ -891,7 +933,7 @@ io_loop(void *data) {
if (fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) { if (fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) {
data_received = true; data_received = true;
has_more = read_bytes(fds[EXTRA_FDS + i].fd, children[i].screen); has_more = read_bytes(fds[EXTRA_FDS + i].fd, children[i].screen);
if (!has_more) { if (!has_more) {
// child is dead // child is dead
children_mutex(lock); children_mutex(lock);
children[i].needs_removal = true; children[i].needs_removal = true;
@@ -968,7 +1010,7 @@ handle_peer(ChildMonitor *self, int s) {
static void* static void*
talk_loop(void *data) { talk_loop(void *data) {
// The I/O thread loop // The I/O thread loop
ChildMonitor *self = (ChildMonitor*)data; ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyTalkMon"); set_thread_name("KittyTalkMon");
@@ -986,7 +1028,7 @@ talk_loop(void *data) {
return 0; return 0;
} }
// }}} // }}}
// Boilerplate {{{ // Boilerplate {{{
static PyMethodDef methods[] = { static PyMethodDef methods[] = {
METHOD(add_child, METH_VARARGS) METHOD(add_child, METH_VARARGS)
@@ -1007,11 +1049,11 @@ PyTypeObject ChildMonitor_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.ChildMonitor", .tp_name = "fast_data_types.ChildMonitor",
.tp_basicsize = sizeof(ChildMonitor), .tp_basicsize = sizeof(ChildMonitor),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "ChildMonitor", .tp_doc = "ChildMonitor",
.tp_methods = methods, .tp_methods = methods,
.tp_new = new, .tp_new = new,
}; };
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
@@ -1023,10 +1065,9 @@ bool
init_child_monitor(PyObject *module) { init_child_monitor(PyObject *module) {
if (PyType_Ready(&ChildMonitor_Type) < 0) return false; if (PyType_Ready(&ChildMonitor_Type) < 0) return false;
if (PyModule_AddObject(module, "ChildMonitor", (PyObject *)&ChildMonitor_Type) != 0) return false; if (PyModule_AddObject(module, "ChildMonitor", (PyObject *)&ChildMonitor_Type) != 0) return false;
Py_INCREF(&ChildMonitor_Type); Py_INCREF(&ChildMonitor_Type);
if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true; return true;
} }
// }}} // }}}

113
kitty/child.c Normal file
View File

@@ -0,0 +1,113 @@
/*
* child.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
static inline char**
serialize_string_tuple(PyObject *src) {
Py_ssize_t sz = PyTuple_GET_SIZE(src);
char **ans = calloc(sz + 1, sizeof(char*));
if (!ans) fatal("Out of memory");
for (Py_ssize_t i = 0; i < sz; i++) ans[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i));
return ans;
}
extern char **environ;
static inline void
write_to_stderr(const char *text) {
size_t sz = strlen(text);
size_t written = 0;
while(written < sz) {
ssize_t amt = write(2, text + written, sz - written);
if (amt == 0) break;
if (amt < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
break;
}
written += amt;
}
}
static PyObject*
spawn(PyObject *self UNUSED, PyObject *args) {
PyObject *argv_p, *env_p;
int master, slave, stdin_read_fd, stdin_write_fd;
char* cwd;
if (!PyArg_ParseTuple(args, "sO!O!iiii", &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
char name[2048] = {0};
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
char **argv = serialize_string_tuple(argv_p);
char **env = serialize_string_tuple(env_p);
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
pid_t pid = fork();
switch(pid) {
case 0:
// child
// Use only signal-safe functions (man 7 signal-safety)
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
if (setsid() == -1) exit_on_err("setsid() in child process failed");
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
if (stdin_read_fd > -1) {
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
close(stdin_read_fd);
close(stdin_write_fd);
} else {
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
}
close(slave);
close(master);
for (int c = 3; c < 201; c++) close(c);
// Establish the controlling terminal (see man 7 credentials)
int tfd = open(name, O_RDWR);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
close(tfd);
environ = env;
execvp(argv[0], argv);
// Report the failure and exec a shell instead, so that we are not left
// with a forked but not exec'ed process
write_to_stderr("Failed to launch child: ");
write_to_stderr(argv[0]);
write_to_stderr("\nWith error: ");
write_to_stderr(strerror(errno));
write_to_stderr("\nPress Enter to exit.\n");
execlp("sh", "sh", "-c", "read w", NULL);
exit(EXIT_FAILURE);
break;
case -1:
PyErr_SetFromErrno(PyExc_OSError);
break;
default:
break;
}
#undef exit_on_err
free(argv);
free(env);
if (PyErr_Occurred()) return NULL;
return PyLong_FromLong(pid);
}
static PyMethodDef module_methods[] = {
METHODB(spawn, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_child(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@@ -4,11 +4,26 @@
import fcntl import fcntl
import os import os
import sys
import kitty.fast_data_types as fast_data_types import kitty.fast_data_types as fast_data_types
from .constants import terminfo_dir from .constants import terminfo_dir, is_macos
def cwd_of_process(pid):
if is_macos:
from kitty.fast_data_types import cwd_of_process
ans = cwd_of_process(pid)
else:
ans = '/proc/{}/cwd'.format(pid)
return os.path.realpath(ans)
def cmdline_of_process(pid):
if is_macos:
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
raise NotImplementedError()
return open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')
def remove_cloexec(fd): def remove_cloexec(fd):
@@ -20,9 +35,17 @@ class Child:
child_fd = pid = None child_fd = pid = None
forked = False forked = False
def __init__(self, argv, cwd, opts, stdin=None, env=None): def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
self.argv = argv self.argv = argv
self.cwd = os.path.abspath(os.path.expandvars(os.path.expanduser(cwd or os.getcwd()))) if cwd_from is not None:
try:
cwd = cwd_of_process(cwd_from)
except Exception:
import traceback
traceback.print_exc()
else:
cwd = os.path.expandvars(os.path.expanduser(cwd or os.getcwd()))
self.cwd = os.path.abspath(cwd)
self.opts = opts self.opts = opts
self.stdin = stdin self.stdin = stdin
self.env = env or {} self.env = env or {}
@@ -38,44 +61,34 @@ class Child:
if stdin is not None: if stdin is not None:
stdin_read_fd, stdin_write_fd = os.pipe() stdin_read_fd, stdin_write_fd = os.pipe()
remove_cloexec(stdin_read_fd) remove_cloexec(stdin_read_fd)
env = self.env else:
pid = os.fork() stdin_read_fd = stdin_write_fd = -1
if pid == 0: # child env = os.environ.copy()
try: env.update(self.env)
os.chdir(self.cwd) env['TERM'] = self.opts.term
except EnvironmentError: env['COLORTERM'] = 'truecolor'
os.chdir('/') if os.path.isdir(terminfo_dir):
os.setsid() env['TERMINFO'] = terminfo_dir
for i in range(3): env = tuple('{}={}'.format(k, v) for k, v in env.items())
if stdin is not None and i == 0: pid = fast_data_types.spawn(self.cwd, tuple(self.argv), env, master, slave, stdin_read_fd, stdin_write_fd)
os.dup2(stdin_read_fd, i) os.close(slave)
os.close(stdin_read_fd), os.close(stdin_write_fd) self.pid = pid
else: self.child_fd = master
os.dup2(slave, i) if stdin is not None:
os.close(slave), os.close(master) os.close(stdin_read_fd)
os.closerange(3, 200) fast_data_types.thread_write(stdin_write_fd, stdin)
# Establish the controlling terminal (see man 7 credentials) return pid
os.close(os.open(os.ttyname(1), os.O_RDWR))
os.environ.update(env) @property
os.environ['TERM'] = self.opts.term def cmdline(self):
os.environ['COLORTERM'] = 'truecolor' try:
if os.path.isdir(terminfo_dir): return cmdline_of_process(self.pid)
os.environ['TERMINFO'] = terminfo_dir except Exception:
try: return list(self.argv)
os.execvp(self.argv[0], self.argv)
except Exception as err: @property
# Report he failure and exec a shell instead so that def current_cwd(self):
# we are not left with a forked but not execed process try:
print('Could not launch:', self.argv[0]) return cwd_of_process(self.pid)
print('\t', err) except Exception:
print('\nPress Enter to exit:', end=' ') pass
sys.stdout.flush()
os.execvp('/bin/sh', ['/bin/sh', '-c', 'read w'])
else: # master
os.close(slave)
self.pid = pid
self.child_fd = master
if stdin is not None:
os.close(stdin_read_fd)
fast_data_types.thread_write(stdin_write_fd, stdin)
return pid

View File

@@ -206,7 +206,7 @@ def prettify(text):
def sub(m): def sub(m):
t = m.group(2) t = m.group(2)
for ch in m.group(1): for ch in m.group(1):
t = {'C': cyan, '_': italic, '*': bold}[ch](t) t = {'C': cyan, '_': italic, '*': bold, 'G': green, 'T': title}[ch](t)
return t return t
text = re.sub(r'[|]([a-zA-Z_*]+?) (.+?)[|]', sub, text) text = re.sub(r'[|]([a-zA-Z_*]+?) (.+?)[|]', sub, text)
@@ -263,18 +263,25 @@ def print_help_for_seq(seq, usage, message, appname):
if leading_indent is None: if leading_indent is None:
leading_indent = indent leading_indent = indent
j = '\n' + (' ' * indent) j = '\n' + (' ' * indent)
a((' ' * leading_indent) + j.join(wrap(text, limit=linesz - indent))) lines = []
for l in text.splitlines():
if l:
lines.extend(wrap(l, limit=linesz - indent))
else:
lines.append('')
a((' ' * leading_indent) + j.join(lines))
usage = usage or '[program-to-run ...]' usage = usage or '[program-to-run ...]'
a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage)) a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage))
a('') a('')
message = message or ( message = message or (
'Run the |_ {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal' 'Run the |G {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
' arguments following the |_ options|. For example: {appname} /bin/sh' ' arguments following the |_ options|. For example: {appname} /bin/sh'
).format(appname=appname) ).format(appname=appname)
wa(prettify(message)) wa(prettify(message))
a('') a('')
a('{}:'.format(title('Options'))) if seq:
a('{}:'.format(title('Options')))
for opt in seq: for opt in seq:
if isinstance(opt, str): if isinstance(opt, str):
a('{}:'.format(title(opt))) a('{}:'.format(title(opt)))

View File

@@ -6,12 +6,14 @@
*/ */
#include "data-types.h" #include "state.h"
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
#include <AvailabilityMacros.h> #include <AvailabilityMacros.h>
// Needed for _NSGetProgname // Needed for _NSGetProgname
#include <crt_externs.h> #include <crt_externs.h>
typedef void* rusage_info_t; // needed for libproc.h
#include <libproc.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200) #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask #define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -21,8 +23,11 @@
#endif #endif
static NSMenuItem* title_menu = NULL; static NSMenuItem* title_menu = NULL;
static bool change_titlebar_color = false;
static color_type titlebar_color = 0;
static NSString*
static NSString*
find_app_name(void) { find_app_name(void) {
size_t i; size_t i;
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary]; NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
@@ -141,7 +146,7 @@ cocoa_update_title(PyObject *pytitle) {
bool bool
cocoa_make_window_resizable(void *w) { cocoa_make_window_resizable(void *w) {
NSWindow *window = (NSWindow*)w; NSWindow *window = (NSWindow*)w;
@try { @try {
[window setStyleMask: [window setStyleMask:
[window styleMask] | NSWindowStyleMaskResizable]; [window styleMask] | NSWindowStyleMaskResizable];
@@ -152,7 +157,7 @@ cocoa_make_window_resizable(void *w) {
return true; return true;
} }
PyObject* static PyObject*
cocoa_get_lang(PyObject UNUSED *self) { cocoa_get_lang(PyObject UNUSED *self) {
NSString* locale = nil; NSString* locale = nil;
NSString* lang_code = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode]; NSString* lang_code = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode];
@@ -166,8 +171,68 @@ cocoa_get_lang(PyObject UNUSED *self) {
return Py_BuildValue("s", [locale UTF8String]); return Py_BuildValue("s", [locale UTF8String]);
} }
static PyObject*
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
long pid = PyLong_AsLong(pid_);
struct proc_vnodepathinfo vpi;
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
}
static inline color_type
color_as_int(PyObject *color) {
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s)
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff;
#undef I
}
static PyObject*
macos_change_titlebar_color(PyObject *self UNUSED, PyObject *val) {
if (val == Py_None) change_titlebar_color = false;
else if (val == Py_True) {
change_titlebar_color = true;
titlebar_color = OPT(background);
} else {
if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return NULL; }
change_titlebar_color = true;
titlebar_color = color_as_int(val);
}
Py_RETURN_NONE;
}
void
cocoa_set_titlebar_color(void *w)
{
if (!change_titlebar_color) return;
NSWindow *window = (NSWindow *)w;
double red = ((titlebar_color >> 16) & 0xFF) / 255.0;
double green = ((titlebar_color >> 8) & 0xFF) / 255.0;
double blue = (titlebar_color & 0xFF) / 255.0;
NSColor *background =
[NSColor colorWithSRGBRed:red
green:green
blue:blue
alpha:1.0];
[window setTitlebarAppearsTransparent:YES];
[window setBackgroundColor:background];
double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
if (luma < 0.5) {
[window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
} else {
[window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
}
}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"macos_change_titlebar_color", (PyCFunction)macos_change_titlebar_color, METH_O, ""},
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@@ -42,7 +42,7 @@ PyObject* create_256_color_table() {
uint8_t v = 8 + i * 10; uint8_t v = 8 + i * 10;
FG_BG_256[j] = (v << 16) | (v << 8) | v; FG_BG_256[j] = (v << 16) | (v << 8) | v;
} }
PyObject *ans = PyTuple_New(255); PyObject *ans = PyTuple_New(255);
if (ans == NULL) return PyErr_NoMemory(); if (ans == NULL) return PyErr_NoMemory();
for (i=0; i < 255; i++) { for (i=0; i < 255; i++) {
@@ -62,7 +62,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
if (FG_BG_256[255] == 0) create_256_color_table(); if (FG_BG_256[255] == 0) create_256_color_table();
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256));
self->dirty = true; self->dirty = true;
} }
return (PyObject*) self; return (PyObject*) self;
} }
@@ -93,7 +93,7 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
color_type color_type
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) { colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
color_type t = entry & 0xFF, r; color_type t = entry & 0xFF, r;
switch(t) { switch(t) {
@@ -128,7 +128,7 @@ as_color(ColorProfile *self, PyObject *val) {
default: default:
ans = Py_None; Py_INCREF(Py_None); ans = Py_None; Py_INCREF(Py_None);
} }
if (ans == NULL) ans = Py_BuildValue("BBB", (unsigned char)(col >> 16), (unsigned char)((col >> 8) & 0xFF), (unsigned char)(col & 0xFF)); if (ans == NULL) ans = Py_BuildValue("BBB", (unsigned char)(col >> 16), (unsigned char)((col >> 8) & 0xFF), (unsigned char)(col & 0xFF));
return ans; return ans;
} }
@@ -177,13 +177,13 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
} }
self->dirty = false; self->dirty = false;
} }
static PyObject* static PyObject*
color_table_address(ColorProfile *self) { color_table_address(ColorProfile *self) {
#define color_table_address_doc "Pointer address to start of color table" #define color_table_address_doc "Pointer address to start of color table"
return PyLong_FromVoidPtr((void*)self->color_table); return PyLong_FromVoidPtr((void*)self->color_table);
} }
// Boilerplate {{{ // Boilerplate {{{
@@ -227,13 +227,13 @@ PyTypeObject ColorProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.ColorProfile", .tp_name = "fast_data_types.ColorProfile",
.tp_basicsize = sizeof(ColorProfile), .tp_basicsize = sizeof(ColorProfile),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "ColorProfile", .tp_doc = "ColorProfile",
.tp_members = members, .tp_members = members,
.tp_methods = methods, .tp_methods = methods,
.tp_getset = getsetters, .tp_getset = getsetters,
.tp_new = new, .tp_new = new,
}; };
INIT_TYPE(ColorProfile) INIT_TYPE(ColorProfile)

View File

@@ -10,15 +10,18 @@ import shlex
import sys import sys
import tempfile import tempfile
from collections import namedtuple from collections import namedtuple
from contextlib import contextmanager
from . import fast_data_types as defines from . import fast_data_types as defines
from .constants import config_dir from .config_utils import (
init_config, parse_config_base, positive_float, positive_int, to_bool,
to_color, unit_float
)
from .constants import cache_dir
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts from .layout import all_layouts
from .rgb import to_color
from .utils import safe_print from .utils import safe_print
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
MINIMUM_FONT_SIZE = 4 MINIMUM_FONT_SIZE = 4
@@ -44,14 +47,10 @@ def to_cursor_shape(x):
) )
def to_bool(x):
return x.lower() in 'y yes true'.split()
def parse_mods(parts): def parse_mods(parts):
def map_mod(m): def map_mod(m):
return {'CTRL': 'CONTROL', 'CMD': 'CONTROL'}.get(m, m) return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.get(m, m)
mods = 0 mods = 0
for m in parts: for m in parts:
@@ -94,7 +93,10 @@ def parse_shortcut(sc):
KeyAction = namedtuple('KeyAction', 'func args') KeyAction = namedtuple('KeyAction', 'func args')
shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab'} shlex_actions = {
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'
}
def parse_key_action(action): def parse_key_action(action):
@@ -109,8 +111,10 @@ def parse_key_action(action):
args = tuple(map(parse_key_action, filter(None, parts))) args = tuple(map(parse_key_action, filter(None, parts)))
elif func == 'send_text': elif func == 'send_text':
args = rest.split(' ', 1) args = rest.split(' ', 1)
elif func == 'run_simple_kitten':
args = rest.split(' ', 2)
elif func == 'goto_tab': elif func == 'goto_tab':
args = (max(0, int(rest)),) args = (max(0, int(rest)), )
elif func in shlex_actions: elif func in shlex_actions:
args = shlex.split(rest) args = shlex.split(rest)
return KeyAction(func, args) return KeyAction(func, args)
@@ -174,7 +178,8 @@ def parse_symbol_map(val):
def parse_send_text_bytes(text): def parse_send_text_bytes(text):
return ast.literal_eval("'''" + text + "'''").encode('utf-8') return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''"
).encode('utf-8')
def parse_send_text(val, keymap): def parse_send_text(val, keymap):
@@ -195,7 +200,7 @@ def parse_send_text(val, keymap):
return parse_key(key_str, keymap) return parse_key(key_str, keymap)
def to_open_url_modifiers(val): def to_modifiers(val):
return parse_mods(val.split('+')) return parse_mods(val.split('+'))
@@ -209,24 +214,21 @@ def to_layout_names(raw):
return parts return parts
def positive_int(x):
return max(0, int(x))
def positive_float(x):
return max(0, float(x))
def unit_float(x):
return max(0, min(float(x), 1))
def adjust_line_height(x): def adjust_line_height(x):
if x.endswith('%'): if x.endswith('%'):
return float(x[:-1].strip()) / 100.0 return float(x[:-1].strip()) / 100.0
return int(x) return int(x)
def macos_titlebar_color(x):
x = x.strip('"')
if x == 'system':
return
if x == 'background':
return True
return to_color(x)
def box_drawing_scale(x): def box_drawing_scale(x):
ans = tuple(float(x.strip()) for x in x.split(',')) ans = tuple(float(x.strip()) for x in x.split(','))
if len(ans) != 4: if len(ans) != 4:
@@ -240,33 +242,45 @@ def tab_separator(x):
x = x[1:-1] x = x[1:-1]
break break
if not x.strip(): if not x.strip():
x = defaults.tab_separator x = ('\xa0' * len(x)) if x else defaults.tab_separator
return x return x
def tab_font_style(x): def tab_font_style(x):
return {'bold-italic': (True, True), 'bold': (True, False), 'italic': (False, True)}.get(x.lower().replace('_', '-'), (False, False)) return {
'bold-italic': (True, True),
'bold': (True, False),
'italic': (False, True)
}.get(x.lower().replace('_', '-'), (False, False))
def tab_bar_edge(x):
return {'top': 1, 'bottom': 3}.get(x.lower(), 3)
def url_style(x): def url_style(x):
return url_style.map.get(x, url_style.map['curly']) return url_style.map.get(x, url_style.map['curly'])
url_style.map = dict(((v, i) for i, v in enumerate('none single double curly'.split()))) url_style.map = dict(
((v, i) for i, v in enumerate('none single double curly'.split()))
)
type_map = { type_map = {
'allow_remote_control': to_bool,
'adjust_line_height': adjust_line_height, 'adjust_line_height': adjust_line_height,
'adjust_column_width': adjust_line_height,
'scrollback_lines': positive_int, 'scrollback_lines': positive_int,
'scrollback_pager': shlex.split, 'scrollback_pager': shlex.split,
'scrollback_in_new_tab': to_bool,
'font_size': to_font_size, 'font_size': to_font_size,
'font_size_delta': positive_float, 'font_size_delta': positive_float,
'focus_follows_mouse': to_bool, 'focus_follows_mouse': to_bool,
'cursor_shape': to_cursor_shape, 'cursor_shape': to_cursor_shape,
'open_url_modifiers': to_open_url_modifiers, 'open_url_modifiers': to_modifiers,
'rectangle_select_modifiers': to_modifiers,
'repaint_delay': positive_int, 'repaint_delay': positive_int,
'input_delay': positive_int, 'input_delay': positive_int,
'sync_to_monitor': to_bool,
'window_border_width': positive_float, 'window_border_width': positive_float,
'window_margin_width': positive_float, 'window_margin_width': positive_float,
'window_padding_width': positive_float, 'window_padding_width': positive_float,
@@ -281,29 +295,50 @@ type_map = {
'remember_window_size': to_bool, 'remember_window_size': to_bool,
'initial_window_width': positive_int, 'initial_window_width': positive_int,
'initial_window_height': positive_int, 'initial_window_height': positive_int,
'use_system_wcwidth': to_bool,
'macos_hide_titlebar': to_bool, 'macos_hide_titlebar': to_bool,
'macos_option_as_alt': to_bool, 'macos_option_as_alt': to_bool,
'macos_titlebar_color': macos_titlebar_color,
'box_drawing_scale': box_drawing_scale, 'box_drawing_scale': box_drawing_scale,
'x11_bell_volume': int, 'x11_bell_volume': int,
'background_opacity': unit_float, 'background_opacity': unit_float,
'tab_separator': tab_separator, 'tab_separator': tab_separator,
'active_tab_font_style': tab_font_style, 'active_tab_font_style': tab_font_style,
'inactive_tab_font_style': tab_font_style, 'inactive_tab_font_style': tab_font_style,
'inactive_text_alpha': unit_float,
'url_style': url_style, 'url_style': url_style,
'prefer_color_emoji': to_bool, 'copy_on_select': to_bool,
'tab_bar_edge': tab_bar_edge,
} }
for name in ( for name in (
'foreground background cursor active_border_color inactive_border_color' 'foreground background cursor active_border_color inactive_border_color'
' selection_foreground selection_background url_color' ' selection_foreground selection_background url_color'
).split(): ).split():
type_map[name] = lambda x: to_color(x, validate=True) type_map[name] = to_color
for i in range(16): for i in range(16):
type_map['color%d' % i] = lambda x: to_color(x, validate=True) type_map['color%d' % i] = to_color
for a in ('active', 'inactive'): for a in ('active', 'inactive'):
for b in ('foreground', 'background'): for b in ('foreground', 'background'):
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True) type_map['%s_tab_%s' % (a, b)] = to_color
def special_handling(key, val, ans):
if key == 'map':
parse_key(val, ans['keymap'])
return True
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
return True
if key == 'send_text':
# For legacy compatibility
parse_send_text(val, ans['keymap'])
return True
defaults = None
default_config_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), 'kitty.conf'
)
def parse_config(lines, check_keys=True): def parse_config(lines, check_keys=True):
@@ -311,46 +346,22 @@ def parse_config(lines, check_keys=True):
'keymap': {}, 'keymap': {},
'symbol_map': {}, 'symbol_map': {},
} }
if check_keys: parse_config_base(
all_keys = defaults._asdict() lines,
for line in lines: defaults,
line = line.strip() type_map,
if not line or line.startswith('#'): special_handling,
continue ans,
m = key_pat.match(line) check_keys=check_keys
if m is not None: )
key, val = m.groups()
if key == 'map':
parse_key(val, ans['keymap'])
continue
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
continue
if key == 'send_text':
# For legacy compatibility
parse_send_text(val, ans['keymap'])
continue
if check_keys:
if key not in all_keys:
safe_print(
'Ignoring unknown config key: {}'.format(key),
file=sys.stderr
)
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
return ans return ans
with open( Options, defaults = init_config(default_config_path, parse_config)
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf'), 'rb' actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset(
) as f: 'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
defaults = parse_config(f.read().decode('utf-8').splitlines(), check_keys=False) split()
Options = namedtuple('Defaults', ','.join(defaults.keys())) )
defaults = Options(**defaults)
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset({'combine', 'send_text', 'goto_tab'})
no_op_actions = frozenset({'noop', 'no-op', 'no_op'}) no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
@@ -415,37 +426,12 @@ def build_ansi_color_table(opts: Options = defaults):
return list(map(col, range(16))) return list(map(col, range(16)))
cached_values = {} def atomic_save(data, path):
cached_path = os.path.join(config_dir, 'cached.json') fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
def load_cached_values():
cached_values.clear()
try:
with open(cached_path, 'rb') as f:
cached_values.update(json.loads(f.read().decode('utf-8')))
except FileNotFoundError:
pass
except Exception as err:
safe_print(
'Failed to load cached values with error: {}'.format(err),
file=sys.stderr
)
def save_cached_values():
fd, p = tempfile.mkstemp(
dir=os.path.dirname(cached_path), suffix='cached.json.tmp'
)
try: try:
with os.fdopen(fd, 'wb') as f: with os.fdopen(fd, 'wb') as f:
f.write(json.dumps(cached_values).encode('utf-8')) f.write(data)
os.rename(p, cached_path) os.rename(p, path)
except Exception as err:
safe_print(
'Failed to save cached values with error: {}'.format(err),
file=sys.stderr
)
finally: finally:
try: try:
os.remove(p) os.remove(p)
@@ -453,13 +439,40 @@ def save_cached_values():
pass pass
except Exception as err: except Exception as err:
safe_print( safe_print(
'Failed to delete temp file for saved cached values with error: {}'. 'Failed to delete temp file {} for atomic save with error: {}'.
format(err), format(p, err),
file=sys.stderr file=sys.stderr
) )
def initial_window_size(opts): @contextmanager
def cached_values_for(name):
cached_path = os.path.join(cache_dir(), name + '.json')
cached_values = {}
try:
with open(cached_path, 'rb') as f:
cached_values.update(json.loads(f.read().decode('utf-8')))
except FileNotFoundError:
pass
except Exception as err:
safe_print(
'Failed to load cached in {} values with error: {}'.format(name, err),
file=sys.stderr
)
yield cached_values
try:
data = json.dumps(cached_values).encode('utf-8')
atomic_save(data, cached_path)
except Exception as err:
safe_print(
'Failed to save cached values with error: {}'.format(err),
file=sys.stderr
)
def initial_window_size(opts, cached_values):
w, h = opts.initial_window_width, opts.initial_window_height w, h = opts.initial_window_width, opts.initial_window_height
if 'window-size' in cached_values and opts.remember_window_size: if 'window-size' in cached_values and opts.remember_window_size:
ws = cached_values['window-size'] ws = cached_values['window-size']

67
kitty/config_utils.py Normal file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import re
import sys
from collections import namedtuple
from .utils import safe_print
from .rgb import to_color as as_color
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
def to_color(x):
return as_color(x, validate=True)
def positive_int(x):
return max(0, int(x))
def positive_float(x):
return max(0, float(x))
def unit_float(x):
return max(0, min(float(x), 1))
def to_bool(x):
return x.lower() in 'y yes true'.split()
def parse_config_base(
lines, defaults, type_map, special_handling, ans, check_keys=True
):
if check_keys:
all_keys = defaults._asdict()
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if special_handling(key, val, ans):
continue
if check_keys:
if key not in all_keys:
safe_print(
'Ignoring unknown config key: {}'.format(key),
file=sys.stderr
)
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
def init_config(defaults_path, parse_config):
with open(defaults_path, encoding='utf-8') as f:
defaults = parse_config(f.read().splitlines(), check_keys=False)
Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults)
return Options, defaults

View File

@@ -10,7 +10,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss from .fast_data_types import set_boss as set_c_boss
appname = 'kitty' appname = 'kitty'
version = (0, 6, 0) version = (0, 8, 0)
str_version = '.'.join(map(str, version)) str_version = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos = 'darwin' in _plat is_macos = 'darwin' in _plat
@@ -22,9 +22,8 @@ WindowGeometry = namedtuple('WindowGeometry', 'left top right bottom xnum ynum')
def _get_config_dir(): def _get_config_dir():
# This must be called before calling setApplicationName
if 'KITTY_CONFIG_DIRECTORY' in os.environ: if 'KITTY_CONFIG_DIRECTORY' in os.environ:
return os.path.abspath(os.path.expanduser(os.environ['VISE_CONFIG_DIRECTORY'])) return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config'))) candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config')))
ans = os.path.join(candidate, appname) ans = os.path.join(candidate, appname)
@@ -37,6 +36,28 @@ del _get_config_dir
defconf = os.path.join(config_dir, 'kitty.conf') defconf = os.path.join(config_dir, 'kitty.conf')
def _get_cache_dir():
if 'KITTY_CACHE_DIRECTORY' in os.environ:
candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY'])
elif is_macos:
candidate = os.path.join(os.path.expanduser('~/Library/Caches'), appname)
else:
candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache')
candidate = os.path.join(os.path.expanduser(candidate), appname)
try:
os.makedirs(candidate)
except FileExistsError:
pass
return candidate
def cache_dir():
ans = getattr(cache_dir, 'ans', None)
if ans is None:
ans = cache_dir.ans = _get_cache_dir()
return ans
def get_boss(): def get_boss():
return get_boss.boss return get_boss.boss

View File

@@ -69,7 +69,7 @@
// Sharp control codes // Sharp control codes
// ------------------- // -------------------
// Align display // Align display
#define DECALN '8' #define DECALN '8'
// Esc control codes // Esc control codes
@@ -180,7 +180,7 @@
// Scroll down by the specified number of lines // Scroll down by the specified number of lines
#define SD 'T' #define SD 'T'
// *Erase character*: Erase the indicated # of characters on the // *Erase character*: Erase the indicated # of characters on the
// current line. // current line.
#define ECH 'X' #define ECH 'X'
@@ -212,7 +212,7 @@
// *Select graphics rendition*: The terminal can display the following // *Select graphics rendition*: The terminal can display the following
// character attributes that change the character display without // character attributes that change the character display without
// changing the character // changing the character
#define SGR 'm' #define SGR 'm'
// *Device status report*. // *Device status report*.
@@ -230,12 +230,6 @@
// Forward tab // Forward tab
#define CHT 'I' #define CHT 'I'
// Save cursor
#define SC 's'
// Restore cursor
#define RC 'u'
// Misc sequences // Misc sequences
// ---------------- // ----------------

View File

@@ -86,7 +86,7 @@ dealloc(CTFace* self) {
static PyObject* static PyObject*
font_descriptor_to_python(CTFontDescriptorRef descriptor) { font_descriptor_to_python(CTFontDescriptorRef descriptor) {
NSURL *url = (NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); NSURL *url = (NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute);
NSString *psName = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute); NSString *psName = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute);
NSString *family = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute); NSString *family = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute);
NSString *style = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute); NSString *style = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute);
NSDictionary *traits = (NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute); NSDictionary *traits = (NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute);
@@ -94,8 +94,8 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) {
NSNumber *weightVal = traits[(id)kCTFontWeightTrait]; NSNumber *weightVal = traits[(id)kCTFontWeightTrait];
NSNumber *widthVal = traits[(id)kCTFontWidthTrait]; NSNumber *widthVal = traits[(id)kCTFontWidthTrait];
PyObject *ans = Py_BuildValue("{ssssssss sOsOsO sfsfsI}", PyObject *ans = Py_BuildValue("{ssssssss sOsOsO sfsfsI}",
"path", [[url path] UTF8String], "path", [[url path] UTF8String],
"postscript_name", [psName UTF8String], "postscript_name", [psName UTF8String],
"family", [family UTF8String], "family", [family UTF8String],
"style", [style UTF8String], "style", [style UTF8String],
@@ -148,7 +148,7 @@ static PyObject*
coretext_all_fonts(PyObject UNUSED *_self) { coretext_all_fonts(PyObject UNUSED *_self) {
static CTFontCollectionRef collection = NULL; static CTFontCollectionRef collection = NULL;
if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL); if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL);
NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection); NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection);
PyObject *ans = PyTuple_New([matches count]), *temp; PyObject *ans = PyTuple_New([matches count]), *temp;
if (ans == NULL) return PyErr_NoMemory(); if (ans == NULL) return PyErr_NoMemory();
for (unsigned int i = 0; i < [matches count]; i++) { for (unsigned int i = 0; i < [matches count]; i++) {
@@ -173,24 +173,28 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
if (new_font == old_font) { CFRelease(new_font); continue; } if (new_font == old_font) { CFRelease(new_font); continue; }
return new_font; return new_font;
} }
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font"); PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
return NULL; return NULL;
} }
PyObject* PyObject*
create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic) { create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic, bool emoji_presentation) {
CTFace *self = (CTFace*)base_face; CTFace *self = (CTFace*)base_face;
char text[128] = {0}; CTFontRef new_font;
cell_as_utf8(cell, true, text, ' '); if (emoji_presentation) new_font = CTFontCreateWithName((CFStringRef)@"AppleColorEmoji", self->scaled_point_sz, NULL);
CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); else {
if (str == NULL) return PyErr_NoMemory(); char text[256] = {0};
CTFontRef new_font = find_substitute_face(str, self->ct_font); cell_as_utf8(cell, true, text, ' ');
CFRelease(str); CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
if (str == NULL) return PyErr_NoMemory();
new_font = find_substitute_face(str, self->ct_font);
CFRelease(str);
}
if (new_font == NULL) return NULL; if (new_font == NULL) return NULL;
return (PyObject*)ct_face(new_font); return (PyObject*)ct_face(new_font);
} }
unsigned int unsigned int
glyph_id_for_codepoint(PyObject *s, char_type ch) { glyph_id_for_codepoint(PyObject *s, char_type ch) {
CTFace *self = (CTFace*)s; CTFace *self = (CTFace*)s;
unichar chars[2] = {0}; unichar chars[2] = {0};
@@ -200,6 +204,15 @@ glyph_id_for_codepoint(PyObject *s, char_type ch) {
return glyphs[0]; return glyphs[0];
} }
bool
is_glyph_empty(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 <= 0;
}
static inline float static inline float
scaled_point_sz() { scaled_point_sz() {
return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts; return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts;
@@ -216,7 +229,7 @@ set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force) {
return true; return true;
} }
hb_font_t* hb_font_t*
harfbuzz_font_for_face(PyObject* s) { harfbuzz_font_for_face(PyObject* s) {
CTFace *self = (CTFace*)s; CTFace *self = (CTFace*)s;
if (!self->hb_font) { if (!self->hb_font) {
@@ -227,7 +240,7 @@ harfbuzz_font_for_face(PyObject* s) {
return self->hb_font; return self->hb_font;
} }
void void
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) { cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
// See https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/TypoFeatures/TextSystemFeatures.html // See https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/TypoFeatures/TextSystemFeatures.html
CTFace *self = (CTFace*)s; CTFace *self = (CTFace*)s;
@@ -241,10 +254,10 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
if (glyphs[i]) { if (glyphs[i]) {
w = (unsigned int)(ceilf( w = (unsigned int)(ceilf(
CTFontGetAdvancesForGlyphs(self->ct_font, kCTFontOrientationHorizontal, glyphs+i, NULL, 1))); CTFontGetAdvancesForGlyphs(self->ct_font, kCTFontOrientationHorizontal, glyphs+i, NULL, 1)));
if (w > width) width = w; if (w > width) width = w;
} }
} }
*cell_width = MAX(1, width); *cell_width = MAX(1, width);
*underline_position = floor(self->ascent - self->underline_position + 0.5); *underline_position = floor(self->ascent - self->underline_position + 0.5);
*underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness)); *underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness));
*baseline = (unsigned int)self->ascent; *baseline = (unsigned int)self->ascent;
@@ -252,7 +265,7 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
// Let CoreText's layout engine calculate the line height. Slower, but hopefully more accurate. // Let CoreText's layout engine calculate the line height. Slower, but hopefully more accurate.
CFStringRef ts = CFSTR("test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test"); CFStringRef ts = CFSTR("test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test");
CFMutableAttributedStringRef test_string = CFAttributedStringCreateMutable(kCFAllocatorDefault, CFStringGetLength(ts)); CFMutableAttributedStringRef test_string = CFAttributedStringCreateMutable(kCFAllocatorDefault, CFStringGetLength(ts));
CFAttributedStringReplaceString(test_string, CFRangeMake(0, 0), ts); CFAttributedStringReplaceString(test_string, CFRangeMake(0, 0), ts);
CFAttributedStringSetAttribute(test_string, CFRangeMake(0, CFStringGetLength(ts)), kCTFontAttributeName, self->ct_font); CFAttributedStringSetAttribute(test_string, CFRangeMake(0, CFStringGetLength(ts)), kCTFontAttributeName, self->ct_font);
CGMutablePathRef path = CGPathCreateMutable(); CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(10, 10, 200, 200)); CGPathAddRect(path, NULL, CGRectMake(10, 10, 200, 200));
@@ -263,7 +276,7 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
CTFrameGetLineOrigins(test_frame, CFRangeMake(0, 1), &origin1); CTFrameGetLineOrigins(test_frame, CFRangeMake(0, 1), &origin1);
CTFrameGetLineOrigins(test_frame, CFRangeMake(1, 1), &origin2); CTFrameGetLineOrigins(test_frame, CFRangeMake(1, 1), &origin2);
CGFloat line_height = origin1.y - origin2.y; CGFloat line_height = origin1.y - origin2.y;
CFRelease(test_frame); CFRelease(path); CFRelease(framesetter); CFRelease(test_frame); CFRelease(path); CFRelease(framesetter);
*cell_height = MAX(4, (unsigned int)ceilf(line_height)); *cell_height = MAX(4, (unsigned int)ceilf(line_height));
#undef count #undef count
} }
@@ -278,7 +291,7 @@ face_from_descriptor(PyObject *descriptor) {
return (PyObject*) ct_face(font); return (PyObject*) ct_face(font);
} }
PyObject* PyObject*
face_from_path(const char *path, int UNUSED index) { face_from_path(const char *path, int UNUSED index) {
CFStringRef s = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8); CFStringRef s = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, s, kCFURLPOSIXPathStyle, false); CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, s, kCFURLPOSIXPathStyle, false);
@@ -315,7 +328,7 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt
if (ctx == NULL) fatal("Out of memory"); if (ctx == NULL) fatal("Out of memory");
CGContextSetShouldAntialias(ctx, true); CGContextSetShouldAntialias(ctx, true);
CGContextSetShouldSmoothFonts(ctx, true); // sub-pixel antialias CGContextSetShouldSmoothFonts(ctx, true); // sub-pixel antialias
CGContextSetRGBFillColor(ctx, 1, 1, 1, 1); CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
CGAffineTransform transform = CGAffineTransformIdentity; CGAffineTransform transform = CGAffineTransformIdentity;
CGContextSetTextDrawingMode(ctx, kCGTextFill); CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGGlyph glyph = glyph_id; CGGlyph glyph = glyph_id;
@@ -353,7 +366,7 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
CGContextSetTextDrawingMode(render_ctx, kCGTextFill); CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity); CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
CGContextSetTextPosition(render_ctx, 0, height - baseline); CGContextSetTextPosition(render_ctx, 0, height - baseline);
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx); CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
} }
@@ -420,7 +433,7 @@ static PyMethodDef methods[] = {
static PyObject * static PyObject *
repr(CTFace *self) { repr(CTFace *self) {
char buf[1024] = {0}; char buf[1024] = {0};
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f", snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
(self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness)); (self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
return PyUnicode_FromFormat( return PyUnicode_FromFormat(
"Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)", "Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)",
@@ -455,8 +468,8 @@ PyTypeObject CTFace_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.CTFace", .tp_name = "fast_data_types.CTFace",
.tp_basicsize = sizeof(CTFace), .tp_basicsize = sizeof(CTFace),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "CoreText Font face", .tp_doc = "CoreText Font face",
.tp_methods = methods, .tp_methods = methods,
.tp_members = members, .tp_members = members,
@@ -465,10 +478,10 @@ PyTypeObject CTFace_Type = {
int int
init_CoreText(PyObject *module) { init_CoreText(PyObject *module) {
if (PyType_Ready(&CTFace_Type) < 0) return 0; if (PyType_Ready(&CTFace_Type) < 0) return 0;
if (PyModule_AddObject(module, "CTFace", (PyObject *)&CTFace_Type) != 0) return 0; if (PyModule_AddObject(module, "CTFace", (PyObject *)&CTFace_Type) != 0) return 0;
if (PyModule_AddFunctions(module, module_methods) != 0) return 0; if (PyModule_AddFunctions(module, module_methods) != 0) return 0;
if (Py_AtExit(finalize) != 0) { if (Py_AtExit(finalize) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the CoreText at exit handler"); PyErr_SetString(PyExc_RuntimeError, "Failed to register the CoreText at exit handler");

View File

@@ -39,7 +39,7 @@ repr(Cursor *self) {
); );
} }
void void
cursor_reset_display_attrs(Cursor *self) { cursor_reset_display_attrs(Cursor *self) {
self->bg = 0; self->fg = 0; self->decoration_fg = 0; self->bg = 0; self->fg = 0; self->decoration_fg = 0;
self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false; self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false;
@@ -50,25 +50,25 @@ static inline void
parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) { parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) {
unsigned int attr; unsigned int attr;
uint8_t r, g, b; uint8_t r, g, b;
if (*i < count) { if (*i < count) {
attr = params[(*i)++]; attr = params[(*i)++];
switch(attr) { switch(attr) {
case 5: case 5:
if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1; if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1;
break; break;
case 2: \ case 2: \
if (*i < count - 2) { if (*i < count - 2) {
/* Ignore the first parameter in a four parameter RGB */ /* Ignore the first parameter in a four parameter RGB */
/* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */ /* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */
if (*i < count - 3) (*i)++; if (*i < count - 3) (*i)++;
r = params[(*i)++] & 0xFF; r = params[(*i)++] & 0xFF;
g = params[(*i)++] & 0xFF; g = params[(*i)++] & 0xFF;
b = params[(*i)++] & 0xFF; b = params[(*i)++] & 0xFF;
*result = r << 24 | g << 16 | b << 8 | 2; *result = r << 24 | g << 16 | b << 8 | 2;
} }
break; break;
} }
} }
} }
@@ -109,13 +109,13 @@ START_ALLOW_CASE_RANGE
self->strikethrough = false; break; self->strikethrough = false; break;
case 30 ... 37: case 30 ... 37:
self->fg = ((attr - 30) << 8) | 1; break; self->fg = ((attr - 30) << 8) | 1; break;
case 38: case 38:
SET_COLOR(fg); SET_COLOR(fg);
case 39: case 39:
self->fg = 0; break; self->fg = 0; break;
case 40 ... 47: case 40 ... 47:
self->bg = ((attr - 40) << 8) | 1; break; self->bg = ((attr - 40) << 8) | 1; break;
case 48: case 48:
SET_COLOR(bg); SET_COLOR(bg);
case 49: case 49:
self->bg = 0; break; self->bg = 0; break;
@@ -124,7 +124,7 @@ START_ALLOW_CASE_RANGE
case 100 ... 107: case 100 ... 107:
self->bg = ((attr - 100 + 8) << 8) | 1; break; self->bg = ((attr - 100 + 8) << 8) | 1; break;
case DECORATION_FG_CODE: case DECORATION_FG_CODE:
SET_COLOR(decoration_fg); SET_COLOR(decoration_fg);
case DECORATION_FG_CODE + 1: case DECORATION_FG_CODE + 1:
self->decoration_fg = 0; break; self->decoration_fg = 0; break;
} }
@@ -149,7 +149,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
attr = params[i++]; attr = params[i++];
switch(attr) { switch(attr) {
case 0: case 0:
RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; } RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; }
break; break;
case 1: case 1:
SET(BOLD_SHIFT); SET(BOLD_SHIFT);
@@ -177,13 +177,13 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
START_ALLOW_CASE_RANGE START_ALLOW_CASE_RANGE
case 30 ... 37: case 30 ... 37:
SIMPLE(fg, ((attr - 30) << 8) | 1); SIMPLE(fg, ((attr - 30) << 8) | 1);
case 38: case 38:
SET_COLOR(fg); SET_COLOR(fg);
case 39: case 39:
SIMPLE(fg, 0); SIMPLE(fg, 0);
case 40 ... 47: case 40 ... 47:
SIMPLE(bg, ((attr - 40) << 8) | 1); SIMPLE(bg, ((attr - 40) << 8) | 1);
case 48: case 48:
SET_COLOR(bg); SET_COLOR(bg);
case 49: case 49:
SIMPLE(bg, 0); SIMPLE(bg, 0);
@@ -224,14 +224,14 @@ color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsi
static inline const char* static inline const char*
decoration_as_sgr(uint8_t decoration) { decoration_as_sgr(uint8_t decoration) {
switch(decoration) { switch(decoration) {
case 1: return "4"; case 1: return "4";
case 2: return "4:2"; case 2: return "4:2";
case 3: return "4:3"; case 3: return "4:3";
default: return "24"; default: return "24";
} }
} }
const char* const char*
cursor_as_sgr(Cursor *self, Cursor *prev) { cursor_as_sgr(Cursor *self, Cursor *prev) {
static char buf[128]; static char buf[128];
#define SZ sizeof(buf) - (p - buf) - 2 #define SZ sizeof(buf) - (p - buf) - 2
@@ -267,8 +267,8 @@ void cursor_reset(Cursor *self) {
void cursor_copy_to(Cursor *src, Cursor *dest) { void cursor_copy_to(Cursor *src, Cursor *dest) {
#define CCY(x) dest->x = src->x; #define CCY(x) dest->x = src->x;
CCY(x); CCY(y); CCY(shape); CCY(blink); CCY(x); CCY(y); CCY(shape); CCY(blink);
CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg); CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
} }
static PyObject* static PyObject*
@@ -317,21 +317,21 @@ PyTypeObject Cursor_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.Cursor", .tp_name = "fast_data_types.Cursor",
.tp_basicsize = sizeof(Cursor), .tp_basicsize = sizeof(Cursor),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_repr = (reprfunc)repr, .tp_repr = (reprfunc)repr,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "Cursors", .tp_doc = "Cursors",
.tp_richcompare = richcmp, .tp_richcompare = richcmp,
.tp_methods = methods, .tp_methods = methods,
.tp_members = members, .tp_members = members,
.tp_getset = getseters, .tp_getset = getseters,
.tp_new = new, .tp_new = new,
}; };
RICHCMP(Cursor) RICHCMP(Cursor)
// }}} // }}}
Cursor* Cursor*
cursor_copy(Cursor *self) { cursor_copy(Cursor *self) {
Cursor* ans; Cursor* ans;

View File

@@ -70,17 +70,6 @@ static inline double monotonic_() {
double monotonic() { return monotonic_(); } double monotonic() { return monotonic_(); }
static PyObject*
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromUnsignedLong(safe_wcwidth(PyLong_AsLong(chr)));
}
static PyObject*
change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
change_wcwidth(PyObject_IsTrue(use9));
Py_RETURN_NONE;
}
static PyObject* static PyObject*
redirect_std_streams(PyObject UNUSED *self, PyObject *args) { redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
char *devnull = NULL; char *devnull = NULL;
@@ -146,8 +135,6 @@ static PyMethodDef module_methods[] = {
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""}, {"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""}, {"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
#ifdef __APPLE__ #ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS), METHODB(user_cache_dir, METH_NOARGS),
@@ -163,8 +150,8 @@ static PyMethodDef module_methods[] = {
static struct PyModuleDef module = { static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT, .m_base = PyModuleDef_HEAD_INIT,
.m_name = "fast_data_types", /* name of module */ .m_name = "fast_data_types", /* name of module */
.m_doc = NULL, .m_doc = NULL,
.m_size = -1, .m_size = -1,
.m_methods = module_methods .m_methods = module_methods
}; };
@@ -180,10 +167,13 @@ extern bool init_fontconfig_library(PyObject*);
extern bool init_desktop(PyObject*); extern bool init_desktop(PyObject*);
extern bool init_fonts(PyObject*); extern bool init_fonts(PyObject*);
extern bool init_glfw(PyObject *m); extern bool init_glfw(PyObject *m);
extern bool init_child(PyObject *m);
extern bool init_state(PyObject *module); extern bool init_state(PyObject *module);
extern bool init_keys(PyObject *module); extern bool init_keys(PyObject *module);
extern bool init_graphics(PyObject *module); extern bool init_graphics(PyObject *module);
extern bool init_shaders(PyObject *module); extern bool init_shaders(PyObject *module);
extern bool init_mouse(PyObject *module);
extern bool init_kittens(PyObject *module);
#ifdef __APPLE__ #ifdef __APPLE__
extern int init_CoreText(PyObject *); extern int init_CoreText(PyObject *);
extern bool init_cocoa(PyObject *module); extern bool init_cocoa(PyObject *module);
@@ -211,10 +201,13 @@ PyInit_fast_data_types(void) {
if (!init_ColorProfile(m)) return NULL; if (!init_ColorProfile(m)) return NULL;
if (!init_Screen(m)) return NULL; if (!init_Screen(m)) return NULL;
if (!init_glfw(m)) return NULL; if (!init_glfw(m)) return NULL;
if (!init_child(m)) return NULL;
if (!init_state(m)) return NULL; if (!init_state(m)) return NULL;
if (!init_keys(m)) return NULL; if (!init_keys(m)) return NULL;
if (!init_graphics(m)) return NULL; if (!init_graphics(m)) return NULL;
if (!init_shaders(m)) return NULL; if (!init_shaders(m)) return NULL;
if (!init_mouse(m)) return NULL;
if (!init_kittens(m)) return NULL;
#ifdef __APPLE__ #ifdef __APPLE__
if (!init_CoreText(m)) return NULL; if (!init_CoreText(m)) return NULL;
if (!init_cocoa(m)) return NULL; if (!init_cocoa(m)) return NULL;
@@ -227,7 +220,7 @@ PyInit_fast_data_types(void) {
#define OOF(n) #n, offsetof(Cell, n) #define OOF(n) #n, offsetof(Cell, n)
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}", if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",
OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL; OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc_idx), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
#undef OOF #undef OOF
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);

View File

@@ -25,12 +25,13 @@
#define MIN(x, y) (((x) > (y)) ? (y) : (x)) #define MIN(x, y) (((x) > (y)) ? (y) : (x))
#define xstr(s) str(s) #define xstr(s) str(s)
#define str(s) #s #define str(s) #s
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } #define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); }
typedef unsigned long long id_type; typedef unsigned long long id_type;
typedef uint32_t char_type; typedef uint32_t char_type;
typedef uint32_t color_type; typedef uint32_t color_type;
typedef uint32_t combining_type; typedef uint16_t combining_type;
typedef uint32_t pixel; typedef uint32_t pixel;
typedef unsigned int index_type; typedef unsigned int index_type;
typedef uint16_t sprite_index; typedef uint16_t sprite_index;
@@ -55,8 +56,6 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
#define REVERSE_SHIFT 6 #define REVERSE_SHIFT 6
#define STRIKE_SHIFT 7 #define STRIKE_SHIFT 7
#define COL_MASK 0xFFFFFFFF #define COL_MASK 0xFFFFFFFF
#define CC_MASK 0xFFFF
#define CC_SHIFT 16
#define UTF8_ACCEPT 0 #define UTF8_ACCEPT 0
#define UTF8_REJECT 1 #define UTF8_REJECT 1
#define DECORATION_FG_CODE 58 #define DECORATION_FG_CODE 58
@@ -69,7 +68,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
#define CURSOR_TO_ATTRS(c, w) \ #define CURSOR_TO_ATTRS(c, w) \
((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \ ((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \
((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | ((c->strikethrough & 1) << STRIKE_SHIFT))) ((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | ((c->strikethrough & 1) << STRIKE_SHIFT)))
#define ATTRS_TO_CURSOR(a, c) \ #define ATTRS_TO_CURSOR(a, c) \
(c)->decoration = (a >> DECORATION_SHIFT) & 3; (c)->bold = (a >> BOLD_SHIFT) & 1; (c)->italic = (a >> ITALIC_SHIFT) & 1; \ (c)->decoration = (a >> DECORATION_SHIFT) & 3; (c)->bold = (a >> BOLD_SHIFT) & 1; (c)->italic = (a >> ITALIC_SHIFT) & 1; \
@@ -133,11 +132,12 @@ typedef struct {
} Region; } Region;
typedef struct { typedef struct {
char_type ch;
color_type fg, bg, decoration_fg; color_type fg, bg, decoration_fg;
combining_type cc;
sprite_index sprite_x, sprite_y, sprite_z; sprite_index sprite_x, sprite_y, sprite_z;
attrs_type attrs; attrs_type attrs;
// The following are only needed on the CPU, not the GPU
char_type ch;
combining_type cc_idx[2];
} Cell; } Cell;
typedef struct { typedef struct {
@@ -181,7 +181,7 @@ typedef struct {
} Cursor; } Cursor;
typedef struct { typedef struct {
bool is_visible; bool is_visible, is_focused;
CursorShape shape; CursorShape shape;
unsigned int x, y; unsigned int x, y;
double left, right, top, bottom; double left, right, top, bottom;
@@ -201,27 +201,11 @@ typedef struct {
DynamicColor configured, overridden; DynamicColor configured, overridden;
} ColorProfile; } ColorProfile;
#define SAVEPOINTS_SZ 256
typedef struct {
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
bool use_latin1;
Cursor cursor;
bool mDECOM, mDECAWM, mDECSCNM;
} Savepoint;
typedef struct {
Savepoint buf[SAVEPOINTS_SZ];
index_type start_of_data, count;
} SavepointBuffer;
#define PARSER_BUF_SZ (8 * 1024) #define PARSER_BUF_SZ (8 * 1024)
#define READ_BUF_SZ (1024*1024) #define READ_BUF_SZ (1024*1024)
#define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0; #define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0;
#define left_shift_line(line, at, num) \ #define left_shift_line(line, at, num) \
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \ for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
@@ -243,7 +227,7 @@ typedef struct {
} }
// Global functions // Global functions
const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, size_t dest_capacity, size_t *dest_sz); const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, size_t dest_capacity, size_t *dest_sz);
Line* alloc_line(); Line* alloc_line();
Cursor* alloc_cursor(); Cursor* alloc_cursor();
@@ -271,14 +255,12 @@ bool set_iutf8(int, bool);
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval); color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride); void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
unsigned int safe_wcwidth(uint32_t ch);
void change_wcwidth(bool use9);
void set_mouse_cursor(MouseShape); void set_mouse_cursor(MouseShape);
void mouse_event(int, int); void mouse_event(int, int);
void focus_in_event(); void focus_in_event();
void wakeup_io_loop(bool); void wakeup_io_loop(bool);
void scroll_event(double, double); void scroll_event(double, double);
void fake_scroll(bool); void fake_scroll(int, bool);
void set_special_key_combo(int glfw_key, int mods); void set_special_key_combo(int glfw_key, int mods);
void on_text_input(unsigned int codepoint, int mods); void on_text_input(unsigned int codepoint, int mods);
void on_key_input(int key, int scancode, int action, int mods); void on_key_input(int key, int scancode, int action, int mods);

232
kitty/emoji.h generated
View File

@@ -1,13 +1,45 @@
// unicode data, built from the unicode standard on: 2018-02-09
// see gen-wcwidth.py
#pragma once #pragma once
#include "data-types.h" #include "data-types.h"
START_ALLOW_CASE_RANGE START_ALLOW_CASE_RANGE
static inline bool is_emoji(uint32_t code) {
static inline bool
is_emoji(char_type code) {
switch(code) { switch(code) {
case 0x23:
return true;
case 0x2a:
return true;
case 0x30 ... 0x39:
return true;
case 0xa9:
return true;
case 0xae:
return true;
case 0x200d:
return true;
case 0x203c:
return true;
case 0x2049:
return true;
case 0x20e3:
return true;
case 0x2122:
return true;
case 0x2139:
return true;
case 0x2194 ... 0x2199:
return true;
case 0x21a9 ... 0x21aa:
return true;
case 0x231a ... 0x231b: case 0x231a ... 0x231b:
return true; return true;
case 0x2328: case 0x2328:
return true; return true;
case 0x2388:
return true;
case 0x23cf: case 0x23cf:
return true; return true;
case 0x23e9 ... 0x23f3: case 0x23e9 ... 0x23f3:
@@ -24,89 +56,15 @@ static inline bool is_emoji(uint32_t code) {
return true; return true;
case 0x25fb ... 0x25fe: case 0x25fb ... 0x25fe:
return true; return true;
case 0x2600 ... 0x2604: case 0x2600 ... 0x2605:
return true; return true;
case 0x260e: case 0x2607 ... 0x2612:
return true; return true;
case 0x2611: case 0x2614 ... 0x2685:
return true; return true;
case 0x2614 ... 0x2615: case 0x2690 ... 0x2705:
return true; return true;
case 0x2618: case 0x2708 ... 0x2712:
return true;
case 0x261d:
return true;
case 0x2620:
return true;
case 0x2622 ... 0x2623:
return true;
case 0x2626:
return true;
case 0x262a:
return true;
case 0x262e ... 0x262f:
return true;
case 0x2638 ... 0x263a:
return true;
case 0x2640:
return true;
case 0x2642:
return true;
case 0x2648 ... 0x2653:
return true;
case 0x2660:
return true;
case 0x2663:
return true;
case 0x2665 ... 0x2666:
return true;
case 0x2668:
return true;
case 0x267b:
return true;
case 0x267f:
return true;
case 0x2692 ... 0x2697:
return true;
case 0x2699:
return true;
case 0x269b ... 0x269c:
return true;
case 0x26a0 ... 0x26a1:
return true;
case 0x26aa ... 0x26ab:
return true;
case 0x26b0 ... 0x26b1:
return true;
case 0x26bd ... 0x26be:
return true;
case 0x26c4 ... 0x26c5:
return true;
case 0x26c8:
return true;
case 0x26ce ... 0x26cf:
return true;
case 0x26d1:
return true;
case 0x26d3 ... 0x26d4:
return true;
case 0x26e9 ... 0x26ea:
return true;
case 0x26f0 ... 0x26f5:
return true;
case 0x26f7 ... 0x26fa:
return true;
case 0x26fd:
return true;
case 0x2702:
return true;
case 0x2705:
return true;
case 0x2708 ... 0x270d:
return true;
case 0x270f:
return true;
case 0x2712:
return true; return true;
case 0x2714: case 0x2714:
return true; return true;
@@ -132,7 +90,7 @@ static inline bool is_emoji(uint32_t code) {
return true; return true;
case 0x2757: case 0x2757:
return true; return true;
case 0x2763 ... 0x2764: case 0x2763 ... 0x2767:
return true; return true;
case 0x2795 ... 0x2797: case 0x2795 ... 0x2797:
return true; return true;
@@ -160,11 +118,15 @@ static inline bool is_emoji(uint32_t code) {
return true; return true;
case 0x3299: case 0x3299:
return true; return true;
case 0x1f004: case 0xfe0f:
return true; return true;
case 0x1f0cf: case 0x1f000 ... 0x1f0ff:
return true; return true;
case 0x1f170 ... 0x1f171: case 0x1f10d ... 0x1f10f:
return true;
case 0x1f12f:
return true;
case 0x1f16c ... 0x1f171:
return true; return true;
case 0x1f17e ... 0x1f17f: case 0x1f17e ... 0x1f17f:
return true; return true;
@@ -172,9 +134,9 @@ static inline bool is_emoji(uint32_t code) {
return true; return true;
case 0x1f191 ... 0x1f19a: case 0x1f191 ... 0x1f19a:
return true; return true;
case 0x1f1e6 ... 0x1f1ff: case 0x1f1ad ... 0x1f1ff:
return true; return true;
case 0x1f201 ... 0x1f202: case 0x1f201 ... 0x1f20f:
return true; return true;
case 0x1f21a: case 0x1f21a:
return true; return true;
@@ -182,106 +144,48 @@ static inline bool is_emoji(uint32_t code) {
return true; return true;
case 0x1f232 ... 0x1f23a: case 0x1f232 ... 0x1f23a:
return true; return true;
case 0x1f250 ... 0x1f251: case 0x1f23c ... 0x1f23f:
return true; return true;
case 0x1f300 ... 0x1f321: case 0x1f249 ... 0x1f53d:
return true; return true;
case 0x1f324 ... 0x1f393: case 0x1f546 ... 0x1f64f:
return true; return true;
case 0x1f396 ... 0x1f397: case 0x1f680 ... 0x1f6ff:
return true; return true;
case 0x1f399 ... 0x1f39b: case 0x1f774 ... 0x1f77f:
return true; return true;
case 0x1f39e ... 0x1f3f0: case 0x1f7d5 ... 0x1f7ff:
return true; return true;
case 0x1f3f3 ... 0x1f3f5: case 0x1f80c ... 0x1f80f:
return true; return true;
case 0x1f3f7 ... 0x1f4fd: case 0x1f848 ... 0x1f84f:
return true; return true;
case 0x1f4ff ... 0x1f53d: case 0x1f85a ... 0x1f85f:
return true; return true;
case 0x1f549 ... 0x1f54e: case 0x1f888 ... 0x1f88f:
return true; return true;
case 0x1f550 ... 0x1f567: case 0x1f8ae ... 0x1f8ff:
return true; return true;
case 0x1f56f ... 0x1f570: case 0x1f90c ... 0x1f93a:
return true; return true;
case 0x1f573 ... 0x1f57a: case 0x1f93c ... 0x1f945:
return true; return true;
case 0x1f587: case 0x1f947 ... 0x1fffd:
return true; return true;
case 0x1f58a ... 0x1f58d: case 0xe0020 ... 0xe007f:
return true;
case 0x1f590:
return true;
case 0x1f595 ... 0x1f596:
return true;
case 0x1f5a4 ... 0x1f5a5:
return true;
case 0x1f5a8:
return true;
case 0x1f5b1 ... 0x1f5b2:
return true;
case 0x1f5bc:
return true;
case 0x1f5c2 ... 0x1f5c4:
return true;
case 0x1f5d1 ... 0x1f5d3:
return true;
case 0x1f5dc ... 0x1f5de:
return true;
case 0x1f5e1:
return true;
case 0x1f5e3:
return true;
case 0x1f5e8:
return true;
case 0x1f5ef:
return true;
case 0x1f5f3:
return true;
case 0x1f5fa ... 0x1f64f:
return true;
case 0x1f680 ... 0x1f6c5:
return true;
case 0x1f6cb ... 0x1f6d2:
return true;
case 0x1f6e0 ... 0x1f6e5:
return true;
case 0x1f6e9:
return true;
case 0x1f6eb ... 0x1f6ec:
return true;
case 0x1f6f0:
return true;
case 0x1f6f3 ... 0x1f6f8:
return true;
case 0x1f910 ... 0x1f93a:
return true;
case 0x1f93c ... 0x1f93e:
return true;
case 0x1f940 ... 0x1f945:
return true;
case 0x1f947 ... 0x1f94c:
return true;
case 0x1f950 ... 0x1f96b:
return true;
case 0x1f980 ... 0x1f997:
return true;
case 0x1f9c0:
return true;
case 0x1f9d0 ... 0x1f9e6:
return true; return true;
default: return false; default: return false;
} }
return false; return false;
} }
static inline bool is_emoji_modifier(uint32_t code) { static inline bool
is_emoji_modifier(char_type code) {
switch(code) { switch(code) {
case 0x1f3fb ... 0x1f3ff: case 0x1f3fb ... 0x1f3ff:
return true; return true;
default: return false; default: return false;
} }
return false; return false;
} }
END_ALLOW_CASE_RANGE END_ALLOW_CASE_RANGE

View File

@@ -131,9 +131,9 @@ add_charset(FcPattern *pat, size_t num) {
charset = FcCharSetCreate(); charset = FcCharSetCreate();
if (charset == NULL) { PyErr_NoMemory(); goto end; } if (charset == NULL) { PyErr_NoMemory(); goto end; }
for (size_t i = 0; i < num; i++) { for (size_t i = 0; i < num; i++) {
if (!FcCharSetAddChar(charset, char_buf[i])) { if (!FcCharSetAddChar(charset, char_buf[i])) {
PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset"); PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset");
goto end; goto end;
} }
} }
AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset"); AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset");
@@ -189,15 +189,14 @@ end:
} }
PyObject* PyObject*
create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic) { create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation) {
PyObject *ans = NULL; PyObject *ans = NULL;
FcPattern *pat = FcPatternCreate(); FcPattern *pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory(); if (pat == NULL) return PyErr_NoMemory();
bool emoji = is_emoji(cell->ch); AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji_presentation ? "emoji" : "monospace"), "family");
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji ? "emoji" : "monospace"), "family"); if (!emoji_presentation && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
if (!emoji && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); } if (!emoji_presentation && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
if (!emoji && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); } if (emoji_presentation) { AP(FcPatternAddBool, FC_COLOR, true, "color"); }
if (emoji) { AP(FcPatternAddBool, FC_COLOR, OPT(prefer_color_emoji), "color"); }
size_t num = cell_as_unicode(cell, true, char_buf, ' '); size_t num = cell_as_unicode(cell, true, char_buf, ' ');
add_charset(pat, num); add_charset(pat, num);
PyObject *d = _fc_match(pat); PyObject *d = _fc_match(pat);
@@ -214,7 +213,7 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };
bool bool
init_fontconfig_library(PyObject *module) { init_fontconfig_library(PyObject *module) {
if (!FcInit()) { if (!FcInit()) {
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize the fontconfig library"); PyErr_SetString(PyExc_RuntimeError, "Failed to initialize the fontconfig library");

View File

@@ -1,4 +1,5 @@
/* /*
* vim:fileencoding=utf-8
* fonts.c * fonts.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net> * Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
* *
@@ -8,11 +9,11 @@
#include "fonts.h" #include "fonts.h"
#include "state.h" #include "state.h"
#include "emoji.h" #include "emoji.h"
#include "unicode-data.h"
#define MISSING_GLYPH 4 #define MISSING_GLYPH 4
#define MAX_NUM_EXTRA_GLYPHS 8 #define MAX_NUM_EXTRA_GLYPHS 8
typedef uint16_t glyph_index;
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*); typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*);
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL; send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
static PyObject *python_send_to_gpu_impl = NULL; static PyObject *python_send_to_gpu_impl = NULL;
@@ -36,11 +37,16 @@ struct SpritePosition {
ExtraGlyphs extra_glyphs; ExtraGlyphs extra_glyphs;
}; };
#define SPECIAL_FILLED_MASK 1
#define SPECIAL_VALUE_MASK 2
#define EMPTY_FILLED_MASK 4
#define EMPTY_VALUE_MASK 8
#define SPECIAL_GLYPH_CACHE_SIZE 1024
struct SpecialGlyphCache { struct SpecialGlyphCache {
SpecialGlyphCache *next; SpecialGlyphCache *next;
glyph_index glyph; glyph_index glyph;
bool is_special, filled; uint8_t data;
}; };
typedef struct { typedef struct {
@@ -51,15 +57,15 @@ typedef struct {
static GPUSpriteTracker sprite_tracker = {0}; static GPUSpriteTracker sprite_tracker = {0};
static hb_buffer_t *harfbuzz_buffer = NULL; static hb_buffer_t *harfbuzz_buffer = NULL;
static char_type shape_buffer[2048] = {0}; static char_type shape_buffer[4096] = {0};
typedef struct { typedef struct {
PyObject *face; PyObject *face;
// Map glyphs to sprite map co-ords // Map glyphs to sprite map co-ords
SpritePosition sprite_map[1024]; SpritePosition sprite_map[1024];
SpecialGlyphCache special_glyph_cache[1024]; SpecialGlyphCache special_glyph_cache[SPECIAL_GLYPH_CACHE_SIZE];
bool bold, italic; bool bold, italic, emoji_presentation;
} Font; } Font;
typedef struct { typedef struct {
@@ -79,7 +85,7 @@ static Fonts fonts = {0};
// Sprites {{{ // Sprites {{{
static inline void static inline void
sprite_map_set_error(int error) { sprite_map_set_error(int error) {
switch(error) { switch(error) {
case 1: case 1:
@@ -123,7 +129,7 @@ extra_glyphs_equal(ExtraGlyphs *a, ExtraGlyphs *b) {
static SpritePosition* static SpritePosition*
sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) { sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) {
glyph_index idx = glyph & 0x3ff; glyph_index idx = glyph & (SPECIAL_GLYPH_CACHE_SIZE - 1);
SpritePosition *s = font->sprite_map + idx; SpritePosition *s = font->sprite_map + idx;
// Optimize for the common case of glyph under 1024 already in the cache // Optimize for the common case of glyph under 1024 already in the cache
if (LIKELY(s->glyph == glyph && s->filled && extra_glyphs_equal(&s->extra_glyphs, extra_glyphs) && s->ligature_index == ligature_index)) return s; // Cache hit if (LIKELY(s->glyph == glyph && s->filled && extra_glyphs_equal(&s->extra_glyphs, extra_glyphs) && s->ligature_index == ligature_index)) return s; // Cache hit
@@ -150,20 +156,21 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui
return s; return s;
} }
static SpecialGlyphCache* static inline SpecialGlyphCache*
special_glyph_cache_for(Font *font, glyph_index glyph) { special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t filled_mask) {
SpecialGlyphCache *s = font->special_glyph_cache + (glyph & 0x3ff); SpecialGlyphCache *s = font->special_glyph_cache + (glyph & 0x3ff);
// Optimize for the common case of glyph under 1024 already in the cache // Optimize for the common case of glyph under SPECIAL_GLYPH_CACHE_SIZE already in the cache
if (LIKELY(s->glyph == glyph && s->filled)) return s; // Cache hit if (LIKELY(s->glyph == glyph && s->data & filled_mask)) return s; // Cache hit
while(true) { while(true) {
if (s->filled) { if (s->data & filled_mask) {
if (s->glyph == glyph) return s; // Cache hit if (s->glyph == glyph) return s; // Cache hit
} else { } else {
break; if (!s->glyph) break; // Empty cache slot
else if (s->glyph == glyph) return s; // Cache slot that contains other data than the data indicated by filled_mask
} }
if (!s->next) { if (!s->next) {
s->next = calloc(1, sizeof(SpecialGlyphCache)); s->next = calloc(1, sizeof(SpecialGlyphCache));
if (s->next == NULL) return NULL; if (s->next == NULL) return NULL;
} }
s = s->next; s = s->next;
} }
@@ -211,7 +218,7 @@ clear_sprite_map(Font *font) {
void void
clear_special_glyph_cache(Font *font) { clear_special_glyph_cache(Font *font) {
#define CLEAR(s) s->filled = false; s->glyph = 0; #define CLEAR(s) s->data = 0; s->glyph = 0;
SpecialGlyphCache *s; SpecialGlyphCache *s;
for (size_t i = 0; i < sizeof(font->special_glyph_cache)/sizeof(font->special_glyph_cache[0]); i++) { for (size_t i = 0; i < sizeof(font->special_glyph_cache)/sizeof(font->special_glyph_cache[0]); i++) {
s = font->special_glyph_cache + i; s = font->special_glyph_cache + i;
@@ -243,18 +250,18 @@ desc_to_face(PyObject *desc) {
static inline bool static inline bool
init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face) { init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face, bool emoji_presentation) {
PyObject *face; PyObject *face;
if (is_face) { face = descriptor; Py_INCREF(face); } if (is_face) { face = descriptor; Py_INCREF(face); }
else { face = desc_to_face(descriptor); if (face == NULL) return false; } else { face = desc_to_face(descriptor); if (face == NULL) return false; }
f->face = face; f->face = face;
f->bold = bold; f->italic = italic; f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
return true; return true;
} }
static inline void static inline void
del_font(Font *f) { del_font(Font *f) {
Py_CLEAR(f->face); Py_CLEAR(f->face);
free_maps(f); free_maps(f);
f->bold = false; f->italic = false; f->bold = false; f->italic = false;
} }
@@ -262,7 +269,7 @@ del_font(Font *f) {
static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0; static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0;
static pixel *canvas = NULL; static pixel *canvas = NULL;
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3) #define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
static inline void static inline void
clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); } clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); }
static void static void
@@ -276,7 +283,7 @@ python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel* buf) {
static inline PyObject* static inline PyObject*
update_cell_metrics() { update_cell_metrics(bool on_dpi_change UNUSED) {
#define CALL(idx, desired_height, force) { if (idx >= 0) { Font *f = fonts.fonts + idx; if ((f)->face) { if(!set_size_for_face((f)->face, desired_height, force)) return NULL; } clear_sprite_map((f)); }} #define CALL(idx, desired_height, force) { if (idx >= 0) { Font *f = fonts.fonts + idx; if ((f)->face) { if(!set_size_for_face((f)->face, desired_height, force)) return NULL; } clear_sprite_map((f)); }}
CALL(BOX_FONT, 0, false); CALL(fonts.medium_font_idx, 0, false); CALL(BOX_FONT, 0, false); CALL(fonts.medium_font_idx, 0, false);
CALL(fonts.bold_font_idx, 0, false); CALL(fonts.italic_font_idx, 0, false); CALL(fonts.bi_font_idx, 0, false); CALL(fonts.bold_font_idx, 0, false); CALL(fonts.italic_font_idx, 0, false); CALL(fonts.bi_font_idx, 0, false);
@@ -285,10 +292,14 @@ update_cell_metrics() {
unsigned int before_cell_height = cell_height; unsigned int before_cell_height = cell_height;
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px); 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_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);
int line_height_adjustment = cell_height - before_cell_height; int line_height_adjustment = cell_height - before_cell_height;
if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; } if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; }
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; } if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
underline_position = MIN(cell_height - 1, underline_position); underline_position = MIN(cell_height - 1, underline_position);
// ensure there is at least a couple of pixels available to render styled underlines
while (underline_position > baseline + 1 && cell_height - underline_position < 2) underline_position--;
if (line_height_adjustment > 1) { if (line_height_adjustment > 1) {
baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2); baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2); underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
@@ -309,8 +320,9 @@ update_cell_metrics() {
static PyObject* static PyObject*
set_font_size(PyObject UNUSED *m, PyObject *args) { set_font_size(PyObject UNUSED *m, PyObject *args) {
if (!PyArg_ParseTuple(args, "f", &global_state.font_sz_in_pts)) return NULL; int on_dpi_change = 0;
return update_cell_metrics(); if (!PyArg_ParseTuple(args, "f|p", &global_state.font_sz_in_pts, &on_dpi_change)) return NULL;
return update_cell_metrics(on_dpi_change != 0);
} }
static inline bool static inline bool
@@ -318,37 +330,26 @@ face_has_codepoint(PyObject* face, char_type cp) {
return glyph_id_for_codepoint(face, cp) > 0; return glyph_id_for_codepoint(face, cp) > 0;
} }
static inline bool static inline bool
has_cell_text(Font *self, Cell *cell) { has_cell_text(Font *self, Cell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false; if (!face_has_codepoint(self->face, cell->ch)) return false;
if (cell->cc) { for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
if (!face_has_codepoint(self->face, cell->cc & CC_MASK)) return false; if (!face_has_codepoint(self->face, codepoint_for_mark(cell->cc_idx[i]))) return false;
char_type cc = cell->cc >> 16;
if (cc && !face_has_codepoint(self->face, cc)) return false;
} }
return true; return true;
} }
static inline ssize_t static inline ssize_t
fallback_font(Cell *cell) { load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
ssize_t f;
for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) {
Font *ff = fonts.fonts +j;
if (ff->bold == bold && ff->italic == italic && has_cell_text(ff, cell)) {
return j;
}
}
if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; } if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; }
ssize_t f;
if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx; if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx;
else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx; else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx;
if (f < 0) f = fonts.medium_font_idx; if (f < 0) f = fonts.medium_font_idx;
PyObject *face = create_fallback_face(fonts.fonts[f].face, cell, bold, italic); PyObject *face = create_fallback_face(fonts.fonts[f].face, cell, bold, italic, emoji_presentation);
if (face == NULL) { PyErr_Print(); return MISSING_FONT; } if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; } if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
set_size_for_face(face, cell_height, true); set_size_for_face(face, cell_height, true);
@@ -356,13 +357,31 @@ fallback_font(Cell *cell) {
ensure_space_for(&fonts, fonts, Font, fonts.fonts_count + 1, fonts_capacity, 5, true); ensure_space_for(&fonts, fonts, Font, fonts.fonts_count + 1, fonts_capacity, 5, true);
ssize_t ans = fonts.first_fallback_font_idx + fonts.fallback_fonts_count; ssize_t ans = fonts.first_fallback_font_idx + fonts.fallback_fonts_count;
Font *af = &fonts.fonts[ans]; Font *af = &fonts.fonts[ans];
if (!init_font(af, face, bold, italic, true)) fatal("Out of memory"); if (!init_font(af, face, bold, italic, true, emoji_presentation)) fatal("Out of memory");
Py_DECREF(face); Py_DECREF(face);
fonts.fallback_fonts_count++; fonts.fallback_fonts_count++;
fonts.fonts_count++; fonts.fonts_count++;
return ans; return ans;
} }
static inline ssize_t
fallback_font(Cell *cell) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
bool emoji_presentation = (cell->attrs & WIDTH_MASK) == 2 && is_emoji(cell->ch) && cell->cc_idx[0] != VS15;
// Check if one of the existing fallback fonts has this text
for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) {
Font *ff = fonts.fonts +j;
if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cell)) {
return j;
}
}
return load_fallback_font(cell, bold, italic, emoji_presentation);
}
static inline ssize_t static inline ssize_t
in_symbol_maps(char_type ch) { in_symbol_maps(char_type ch) {
for (size_t i = 0; i < fonts.symbol_maps_count; i++) { for (size_t i = 0; i < fonts.symbol_maps_count; i++) {
@@ -441,7 +460,7 @@ render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *de
} }
} }
static void static void
render_box_cell(Cell *cell) { render_box_cell(Cell *cell) {
int error = 0; int error = 0;
glyph_index glyph = box_glyph_id(cell->ch); glyph_index glyph = box_glyph_id(cell->ch);
@@ -467,19 +486,17 @@ render_box_cell(Cell *cell) {
} }
static inline void static inline void
load_hb_buffer(Cell *first_cell, index_type num_cells) { load_hb_buffer(Cell *first_cell, index_type num_cells) {
index_type num; index_type num;
hb_buffer_clear_contents(harfbuzz_buffer); hb_buffer_clear_contents(harfbuzz_buffer);
while (num_cells) { while (num_cells) {
attrs_type prev_width = 0; attrs_type prev_width = 0;
for (num = 0; num_cells && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++, num_cells--) { for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cell->cc_idx); first_cell++, num_cells--) {
if (prev_width == 2) { prev_width = 0; continue; } if (prev_width == 2) { prev_width = 0; continue; }
shape_buffer[num++] = first_cell->ch; shape_buffer[num++] = first_cell->ch;
prev_width = first_cell->attrs & WIDTH_MASK; prev_width = first_cell->attrs & WIDTH_MASK;
if (first_cell->cc) { for (unsigned i = 0; i < arraysz(first_cell->cc_idx) && first_cell->cc_idx[i]; i++) {
shape_buffer[num++] = first_cell->cc & CC_MASK; shape_buffer[num++] = codepoint_for_mark(first_cell->cc_idx[i]);
combining_type cc2 = first_cell->cc >> 16;
if (cc2) shape_buffer[num++] = cc2 & CC_MASK;
} }
} }
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num); hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
@@ -517,14 +534,14 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl
} }
clear_canvas(); clear_canvas();
bool was_colored = is_emoji(cells->ch); bool was_colored = (cells->attrs & WIDTH_MASK) == 2 && is_emoji(cells->ch);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, &was_colored); render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, &was_colored);
if (PyErr_Occurred()) PyErr_Print(); if (PyErr_Occurred()) PyErr_Print();
for (unsigned int i = 0; i < num_cells; i++) { for (unsigned int i = 0; i < num_cells; i++) {
sprite_position[i]->rendered = true; sprite_position[i]->rendered = true;
sprite_position[i]->colored = was_colored; sprite_position[i]->colored = was_colored;
set_cell_sprite(cells + i, sprite_position[i]); set_cell_sprite(cells + i, sprite_position[i]);
pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells); pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells);
current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf); current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
} }
@@ -538,28 +555,84 @@ typedef struct {
char_type current_codepoint; char_type current_codepoint;
} CellData; } CellData;
typedef struct {
unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells;
} Group;
typedef struct {
uint32_t previous_cluster;
bool prev_was_special, prev_was_empty;
CellData current_cell_data;
Group *groups;
size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs;
Cell *first_cell, *last_cell;
hb_glyph_info_t *info;
hb_glyph_position_t *positions;
} GroupState;
static GroupState group_state = {0};
static inline unsigned int
num_codepoints_in_cell(Cell *cell) {
unsigned int ans = 1;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
return ans;
}
static inline void
shape(Cell *first_cell, index_type num_cells, hb_font_t *font) {
if (group_state.groups_capacity <= 2 * num_cells) {
group_state.groups_capacity = MAX(128, 2 * num_cells); // avoid unnecessary reallocs
group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity);
if (!group_state.groups) fatal("Out of memory");
}
group_state.previous_cluster = UINT32_MAX;
group_state.prev_was_special = false;
group_state.prev_was_empty = false;
group_state.current_cell_data.cell = first_cell; group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cell); group_state.current_cell_data.codepoints_consumed = 0; group_state.current_cell_data.current_codepoint = first_cell->ch;
memset(group_state.groups, 0, sizeof(Group) * group_state.groups_capacity);
group_state.group_idx = 0;
group_state.glyph_idx = 0;
group_state.cell_idx = 0;
group_state.num_cells = num_cells;
group_state.first_cell = first_cell;
group_state.last_cell = first_cell + (num_cells ? num_cells - 1 : 0);
load_hb_buffer(first_cell, num_cells);
hb_shape(font, harfbuzz_buffer, NULL, 0);
unsigned int info_length, positions_length;
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
group_state.positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
if (!group_state.info || !group_state.positions) group_state.num_glyphs = 0;
else group_state.num_glyphs = MIN(info_length, positions_length);
}
static inline bool static inline bool
is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) { is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
// A glyph is special if the codepoint it corresponds to matches a // A glyph is special if the codepoint it corresponds to matches a
// different glyph in the font // different glyph in the font
SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id); SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, SPECIAL_FILLED_MASK);
if (s == NULL) return false; if (s == NULL) return false;
if (!s->filled) { if (!(s->data & SPECIAL_FILLED_MASK)) {
s->is_special = cell_data->current_codepoint ? ( bool is_special = cell_data->current_codepoint ? (
glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false) glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false)
: :
false; false;
s->filled = true; uint8_t val = is_special ? SPECIAL_VALUE_MASK : 0;
} s->data |= val | SPECIAL_FILLED_MASK;
return s->is_special; }
return s->data & SPECIAL_VALUE_MASK;
} }
static inline unsigned int static inline bool
num_codepoints_in_cell(Cell *cell) { is_empty_glyph(glyph_index glyph_id, Font *font) {
unsigned int ans = 1; // A glyph is empty if its metrics have a width of zero
if (cell->cc) ans += ((cell->cc >> CC_SHIFT) & CC_MASK) ? 2 : 1; SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, EMPTY_FILLED_MASK);
return ans; if (s == NULL) return false;
if (!(s->data & EMPTY_FILLED_MASK)) {
uint8_t val = is_glyph_empty(font->face, glyph_id) ? EMPTY_VALUE_MASK : 0;
s->data |= val | EMPTY_FILLED_MASK;
}
return s->data & EMPTY_VALUE_MASK;
} }
static inline unsigned int static inline unsigned int
@@ -579,100 +652,140 @@ check_cell_consumed(CellData *cell_data, Cell *last_cell) {
case 0: case 0:
cell_data->current_codepoint = cell_data->cell->ch; cell_data->current_codepoint = cell_data->cell->ch;
break; break;
case 1:
cell_data->current_codepoint = cell_data->cell->cc & CC_MASK;
break;
case 2:
cell_data->current_codepoint = (cell_data->cell->cc >> CC_SHIFT) & CC_MASK;
break;
default: default:
cell_data->current_codepoint = 0; cell_data->current_codepoint = codepoint_for_mark(cell_data->cell->cc_idx[cell_data->codepoints_consumed - 1]);
break; break;
} }
} }
return 0; return 0;
} }
static inline glyph_index
next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_glyphs, Cell *cells, hb_glyph_info_t *info, unsigned int max_num_glyphs, unsigned int max_num_cells, ExtraGlyphs *extra_glyphs) {
// See https://github.com/behdad/harfbuzz/issues/615 for a discussion of
// how to break text into cells. In addition, we have to deal with
// monospace ligature fonts that use dummy glyphs of zero size to implement
// their ligatures.
CellData cell_data;
cell_data.cell = cells; cell_data.num_codepoints = num_codepoints_in_cell(cells); cell_data.codepoints_consumed = 0; cell_data.current_codepoint = cells->ch;
#define LIMIT (MAX_NUM_EXTRA_GLYPHS + 1)
glyph_index glyphs_in_group[LIMIT];
unsigned int ncells = 0, nglyphs = 0, n;
uint32_t previous_cluster = UINT32_MAX, cluster;
Cell *last_cell = cells + max_num_cells;
unsigned int cell_limit = MIN(max_num_cells, LIMIT + 1), glyph_limit = MIN(LIMIT, max_num_glyphs);
bool is_special, prev_was_special = false;
while(nglyphs < glyph_limit && ncells < cell_limit) {
glyph_index glyph_id = info[nglyphs].codepoint;
cluster = info[nglyphs].cluster;
is_special = is_special_glyph(glyph_id, font, &cell_data);
if (prev_was_special && !is_special) break;
glyphs_in_group[nglyphs++] = glyph_id;
// Soak up a number of codepoints indicated by the difference in cluster numbers.
if (cluster > previous_cluster || nglyphs == 1) {
n = nglyphs == 1 ? 1 : cluster - previous_cluster;
unsigned int before = ncells;
while(n-- && ncells < max_num_cells) ncells += check_cell_consumed(&cell_data, last_cell);
if (ncells > before && !is_special) break;
}
previous_cluster = cluster;
prev_was_special = is_special;
}
*num_group_cells = MAX(1, MIN(ncells, cell_limit));
*num_group_glyphs = MAX(1, MIN(nglyphs, glyph_limit));
memset(extra_glyphs, 0, sizeof(ExtraGlyphs));
switch(nglyphs) {
case 0:
case 1:
break;
default:
memcpy(&extra_glyphs->data, glyphs_in_group + 1, sizeof(glyph_index) * MIN(MAX_NUM_EXTRA_GLYPHS, nglyphs - 1));
break;
}
#undef LIMIT
return glyphs_in_group[0];
}
static inline unsigned int
shape(Cell *first_cell, index_type num_cells, hb_font_t *font, hb_glyph_info_t **info, hb_glyph_position_t **positions) {
load_hb_buffer(first_cell, num_cells);
hb_shape(font, harfbuzz_buffer, NULL, 0);
unsigned int info_length, positions_length;
*info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
*positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
if (!info || !positions) return 0;
return MIN(info_length, positions_length);
}
static inline void static inline void
shape_run(Cell *first_cell, index_type num_cells, Font *font) { shape_run(Cell *first_cell, index_type num_cells, Font *font) {
hb_glyph_info_t *info; shape(first_cell, num_cells, harfbuzz_font_for_face(font->face));
hb_glyph_position_t *positions;
unsigned int num_glyphs = shape(first_cell, num_cells, harfbuzz_font_for_face(font->face), &info, &positions);
#if 0 #if 0
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text // You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS); hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
printf("\n%s\n", (char*)canvas); printf("\n%s\n", (char*)canvas);
clear_canvas(); clear_canvas();
#endif #endif
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells; /* Now distribute the glyphs into groups of cells
ExtraGlyphs extra_glyphs; * Considerations to keep in mind:
glyph_index first_glyph; * Group sizes should be as small as possible for best performance
while(run_pos < num_glyphs && cell_pos < num_cells) { * Combining chars can result in multiple glyphs rendered into a single cell
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, first_cell + cell_pos, info + run_pos, num_glyphs - run_pos, num_cells - cell_pos, &extra_glyphs); * Emoji and East Asian wide chars can cause a single glyph to be rendered over multiple cells
/* printf("Group: num_group_cells: %u num_group_glyphs: %u\n", num_group_cells, num_group_glyphs); */ * Ligature fonts, take two common approaches:
render_group(num_group_cells, num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, font, first_glyph, &extra_glyphs); * 1. ABC becomes EMPTY, EMPTY, WIDE GLYPH this means we have to render N glyphs in N cells (example Fira Code)
run_pos += num_group_glyphs; cell_pos += num_group_cells; * 2. ABC becomes WIDE GLYPH this means we have to render one glyph in N cells (example Operator Mono Lig)
*
* We rely on the cluster numbers from harfbuzz to tell us how many unicode codepoints a glyph corresponds to.
* Then we check if the glyph is a ligature glyph (is_special_glyph) and if it is an empty glyph. These three
* datapoints give us enough information to satisfy the constraint above, for a wide variety of fonts.
*/
uint32_t cluster, next_cluster;
bool add_to_current_group;
#define G(x) (group_state.x)
#define MAX_GLYPHS_IN_GROUP (MAX_NUM_EXTRA_GLYPHS + 1)
while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) {
glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint;
cluster = G(info)[G(glyph_idx)].cluster;
bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data));
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
uint32_t num_codepoints_used_by_glyph = 0;
bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1;
Group *current_group = G(groups) + G(group_idx);
if (is_last_glyph) {
num_codepoints_used_by_glyph = UINT32_MAX;
} else {
next_cluster = G(info)[G(glyph_idx) + 1].cluster;
if (next_cluster > cluster) num_codepoints_used_by_glyph = next_cluster - cluster;
}
if (!current_group->num_glyphs) {
add_to_current_group = true;
} else {
if (is_special) {
add_to_current_group = G(prev_was_empty);
} else {
add_to_current_group = !G(prev_was_special);
}
}
if (current_group->num_glyphs >= MAX_GLYPHS_IN_GROUP || current_group->num_cells >= MAX_GLYPHS_IN_GROUP) add_to_current_group = false;
if (!add_to_current_group) { G(group_idx)++; current_group = G(groups) + G(group_idx); }
if (!current_group->num_glyphs++) {
current_group->first_glyph_idx = G(glyph_idx);
current_group->first_cell_idx = G(cell_idx);
}
#define MOVE_GLYPH_TO_NEXT_GROUP(start_cell_idx) { \
current_group->num_glyphs--; \
G(group_idx)++; current_group = G(groups) + G(group_idx); \
current_group->first_cell_idx = start_cell_idx; \
current_group->num_glyphs = 1; \
current_group->first_glyph_idx = G(glyph_idx); \
}
if (is_last_glyph) {
// soak up all remaining cells
if (G(cell_idx) < G(num_cells)) {
unsigned int num_left = G(num_cells) - G(cell_idx);
if (current_group->num_cells + num_left > MAX_GLYPHS_IN_GROUP) MOVE_GLYPH_TO_NEXT_GROUP(G(cell_idx));
current_group->num_cells += num_left;
if (current_group->num_cells > MAX_GLYPHS_IN_GROUP) current_group->num_cells = MAX_GLYPHS_IN_GROUP; // leave any trailing cells empty
G(cell_idx) += num_left;
}
} else {
unsigned int num_cells_consumed = 0, start_cell_idx = G(cell_idx);
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cell));
G(cell_idx) += w;
num_cells_consumed += w;
num_codepoints_used_by_glyph--;
}
if (num_cells_consumed) {
if (num_cells_consumed > MAX_GLYPHS_IN_GROUP) {
// Nasty, a single glyph used more than MAX_GLYPHS_IN_GROUP cells, we cannot render this case correctly
fprintf(stderr, "The glyph: %u needs more than %u cells, cannot render it\n", glyph_id, MAX_GLYPHS_IN_GROUP);
current_group->num_glyphs--;
while (num_cells_consumed) {
G(group_idx)++; current_group = G(groups) + G(group_idx);
current_group->num_glyphs = 1; current_group->first_glyph_idx = G(glyph_idx);
current_group->num_cells = MIN(num_cells_consumed, MAX_GLYPHS_IN_GROUP);
current_group->first_cell_idx = start_cell_idx;
start_cell_idx += current_group->num_cells;
num_cells_consumed -= current_group->num_cells;
}
} else {
if (num_cells_consumed + current_group->num_cells > MAX_GLYPHS_IN_GROUP) MOVE_GLYPH_TO_NEXT_GROUP(start_cell_idx);
current_group->num_cells += num_cells_consumed;
if (!is_special) { // not a ligature, end the group
G(group_idx)++; current_group = G(groups) + G(group_idx);
}
}
}
}
G(prev_was_special) = is_special;
G(prev_was_empty) = is_empty;
G(previous_cluster) = cluster;
G(glyph_idx)++;
}
#undef MOVE_GLYPH_TO_NEXT_GROUP
#undef MAX_GLYPHS_IN_GROUP
}
static inline void
render_groups(Font *font) {
unsigned idx = 0;
ExtraGlyphs ed;
while (idx <= G(group_idx)) {
Group *group = G(groups) + idx;
if (!group->num_cells) break;
/* printf("Group: idx: %u num_cells: %u num_glyphs: %u first_glyph_idx: %u first_cell_idx: %u total_num_glyphs: %zu\n", */
/* idx, group->num_cells, group->num_glyphs, group->first_glyph_idx, group->first_cell_idx, group_state.num_glyphs); */
glyph_index primary = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
for (unsigned i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) ed.data[i-1] = G(info)[group->first_glyph_idx + i].codepoint;
render_group(group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
idx++;
} }
} }
@@ -691,31 +804,33 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
if (face == NULL) return NULL; if (face == NULL) return NULL;
font = calloc(1, sizeof(Font)); font = calloc(1, sizeof(Font));
font->face = face; font->face = face;
} }
hb_glyph_info_t *info; shape_run(line->cells, num, font);
hb_glyph_position_t *positions;
unsigned int num_glyphs = shape(line->cells, num, harfbuzz_font_for_face(font->face), &info, &positions);
PyObject *ans = PyList_New(0); PyObject *ans = PyList_New(0);
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells; unsigned int idx = 0;
ExtraGlyphs extra_glyphs;
glyph_index first_glyph; glyph_index first_glyph;
while(run_pos < num_glyphs && cell_pos < num) { while (idx <= G(group_idx)) {
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, line->cells + cell_pos, info + run_pos, num_glyphs - run_pos, num - cell_pos, &extra_glyphs); Group *group = G(groups) + idx;
if (!group->num_cells) break;
first_glyph = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS); PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", extra_glyphs.data[g])); for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", g + 1 < group->num_glyphs ? G(info)[group->first_glyph_idx + g].codepoint : 0));
PyList_Append(ans, Py_BuildValue("IIIN", num_group_cells, num_group_glyphs, first_glyph, eg)); PyList_Append(ans, Py_BuildValue("IIIN", group->num_cells, group->num_glyphs, first_glyph, eg));
run_pos += num_group_glyphs; cell_pos += num_group_cells; idx++;
} }
if (face) { Py_CLEAR(face); free(font); } if (face) { Py_CLEAR(face); free(font); }
return ans; return ans;
} }
#undef G
static inline void static inline void
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) { render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) {
switch(font_idx) { switch(font_idx) {
default: default:
shape_run(first_cell, num_cells, &fonts.fonts[font_idx]); shape_run(first_cell, num_cells, &fonts.fonts[font_idx]);
render_groups(&fonts.fonts[font_idx]);
break; break;
case BLANK_FONT: case BLANK_FONT:
while(num_cells--) set_sprite(first_cell++, 0, 0, 0); while(num_cells--) set_sprite(first_cell++, 0, 0, 0);
@@ -761,7 +876,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i); for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
ensure_space_for(&fonts, fonts, Font, num_fonts, fonts_capacity, 5, true); ensure_space_for(&fonts, fonts, Font, num_fonts, fonts_capacity, 5, true);
fonts.fonts_count = 1; fonts.fonts_count = 1;
#define A(attr, bold, italic) { if(attr) { if (!init_font(&fonts.fonts[fonts.fonts_count], attr, bold, italic, false)) return NULL; fonts.attr##_font_idx = fonts.fonts_count++; } else fonts.attr##_font_idx = -1; } #define A(attr, bold, italic) { if(attr) { if (!init_font(&fonts.fonts[fonts.fonts_count], attr, bold, italic, false, false)) return NULL; fonts.attr##_font_idx = fonts.fonts_count++; } else fonts.attr##_font_idx = -1; }
A(medium, false, false); A(medium, false, false);
A(bold, true, false); A(italic, false, true); A(bi, true, true); A(bold, true, false); A(italic, false, true); A(bi, true, true);
#undef A #undef A
@@ -773,7 +888,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
PyObject *face; PyObject *face;
int bold, italic; int bold, italic;
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL; if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL;
if (!init_font(fonts.fonts + fonts.fonts_count++, face, bold != 0, italic != 0, false)) return NULL; if (!init_font(fonts.fonts + fonts.fonts_count++, face, bold != 0, italic != 0, false, false)) return NULL;
} }
for (size_t i = 0; i < fonts.symbol_maps_count; i++) { for (size_t i = 0; i < fonts.symbol_maps_count; i++) {
unsigned int left, right, font_idx; unsigned int left, right, font_idx;
@@ -782,7 +897,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
} }
fonts.first_fallback_font_idx = fonts.fonts_count; fonts.first_fallback_font_idx = fonts.fonts_count;
fonts.fallback_fonts_count = 0; fonts.fallback_fonts_count = 0;
return update_cell_metrics(); return update_cell_metrics(false);
} }
static void static void
@@ -793,6 +908,7 @@ finalize(void) {
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i); for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
free(fonts.symbol_maps); free(fonts.fonts); free(fonts.symbol_maps); free(fonts.fonts);
if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer); if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer);
free(group_state.groups);
} }
static PyObject* static PyObject*
@@ -923,15 +1039,14 @@ error:
static PyObject* static PyObject*
get_fallback_font(PyObject UNUSED *self, PyObject *args) { get_fallback_font(PyObject UNUSED *self, PyObject *args) {
PyObject *text; PyObject *text;
int bold, italic; int bold, italic;
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
static Py_UCS4 char_buf[16];
if (!PyUnicode_AsUCS4(text, char_buf, sizeof(char_buf)/sizeof(char_buf[0]), 1)) return NULL;
Cell cell = {0}; Cell cell = {0};
static Py_UCS4 char_buf[2 + arraysz(cell.cc_idx)];
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
cell.ch = char_buf[0]; cell.ch = char_buf[0];
if (PyUnicode_GetLength(text) > 1) cell.cc |= char_buf[1] & CC_MASK; for (unsigned i = 0; i + 1 < PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
if (PyUnicode_GetLength(text) > 2) cell.cc |= (char_buf[2] & CC_MASK) << 16;
if (bold) cell.attrs |= 1 << BOLD_SHIFT; if (bold) cell.attrs |= 1 << BOLD_SHIFT;
if (italic) cell.attrs |= 1 << ITALIC_SHIFT; if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
ssize_t ans = fallback_font(&cell); ssize_t ans = fallback_font(&cell);
@@ -956,7 +1071,7 @@ static PyMethodDef module_methods[] = {
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };
bool bool
init_fonts(PyObject *module) { init_fonts(PyObject *module) {
if (Py_AtExit(finalize) != 0) { if (Py_AtExit(finalize) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the fonts module at exit handler"); PyErr_SetString(PyExc_RuntimeError, "Failed to register the fonts module at exit handler");

View File

@@ -14,12 +14,14 @@
// API that font backends need to implement // API that font backends need to implement
typedef uint16_t glyph_index;
unsigned int glyph_id_for_codepoint(PyObject *, char_type); unsigned int glyph_id_for_codepoint(PyObject *, char_type);
bool is_glyph_empty(PyObject *, glyph_index);
hb_font_t* harfbuzz_font_for_face(PyObject*); hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool); bool set_size_for_face(PyObject*, unsigned int, bool);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); 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); 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);
PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic); PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation);
PyObject* specialize_font_descriptor(PyObject *base_descriptor); PyObject* specialize_font_descriptor(PyObject *base_descriptor);
PyObject* face_from_path(const char *path, int index); PyObject* face_from_path(const char *path, int index);
PyObject* face_from_descriptor(PyObject*); PyObject* face_from_descriptor(PyObject*);

View File

@@ -10,7 +10,7 @@ from math import ceil, floor, pi, sin, sqrt
from kitty.config import defaults from kitty.config import defaults
from kitty.constants import is_macos from kitty.constants import is_macos
from kitty.fast_data_types import ( from kitty.fast_data_types import (
Screen, change_wcwidth, get_fallback_font, send_prerendered_sprites, Screen, get_fallback_font, send_prerendered_sprites,
set_font, set_font_size, set_logical_dpi, set_options, set_font, set_font_size, set_logical_dpi, set_options,
set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line, set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line,
test_shape test_shape
@@ -61,9 +61,9 @@ def set_font_family(opts=None, override_font_size=None):
return cell_width, cell_height return cell_width, cell_height
def resize_fonts(new_sz): def resize_fonts(new_sz, on_dpi_change=False):
s = set_font_family.state s = set_font_family.state
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz) cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz, on_dpi_change)
set_font_family.state = FontState( set_font_family.state = FontState(
s.family, new_sz, cell_width, cell_height, baseline, s.family, new_sz, cell_width, cell_height, baseline,
underline_position, underline_thickness underline_position, underline_thickness
@@ -73,7 +73,7 @@ def resize_fonts(new_sz):
def add_line(buf, cell_width, position, thickness, cell_height): def add_line(buf, cell_width, position, thickness, cell_height):
y = position - thickness // 2 y = position - thickness // 2
while thickness > 0 and y > -1 and y < cell_height - 1: while thickness > 0 and y > -1 and y < cell_height:
thickness -= 1 thickness -= 1
ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width) ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width)
y += 1 y += 1
@@ -260,7 +260,6 @@ def test_fallback_font(qtext=None, bold=False, italic=False):
def showcase(): def showcase():
change_wcwidth(True)
f = 'monospace' if is_macos else 'Liberation Mono' f = 'monospace' if is_macos else 'Liberation Mono'
test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f) test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f)
test_render_string('你好,世界', family=f) test_render_string('你好,世界', family=f)

View File

@@ -38,7 +38,7 @@ PyTypeObject Face_Type;
static PyObject* FreeType_Exception = NULL; static PyObject* FreeType_Exception = NULL;
void void
set_freetype_error(const char* prefix, int err_code) { set_freetype_error(const char* prefix, int err_code) {
int i = 0; int i = 0;
#undef FTERRORS_H_ #undef FTERRORS_H_
@@ -54,7 +54,7 @@ set_freetype_error(const char* prefix, int err_code) {
#ifdef FT_ERRORS_H #ifdef FT_ERRORS_H
#include FT_ERRORS_H #include FT_ERRORS_H
#else #else
FT_ERROR_START_LIST FT_ERROR_END_LIST FT_ERROR_START_LIST FT_ERROR_END_LIST
#endif #endif
@@ -121,8 +121,8 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
return true; return true;
} }
} }
set_freetype_error("Failed to set char size, with error:", error); set_freetype_error("Failed to set char size, with error:", error);
return false; return false;
} }
return !error; return !error;
} }
@@ -170,7 +170,7 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle) {
PyObject* PyObject*
ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float apple_leading) { ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float apple_leading) {
Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0);
if (ans == NULL) return NULL; if (ans == NULL) return NULL;
int error = FT_New_Memory_Face(library, data, sz, 0, &ans->face); int error = FT_New_Memory_Face(library, data, sz, 0, &ans->face);
if(error) { set_freetype_error("Failed to load memory face, with error:", error); Py_CLEAR(ans); return NULL; } if(error) { set_freetype_error("Failed to load memory face, with error:", error); Py_CLEAR(ans); return NULL; }
if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; } if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; }
@@ -232,7 +232,7 @@ face_from_descriptor(PyObject *descriptor) {
} }
return (PyObject*)self; return (PyObject*)self;
} }
PyObject* PyObject*
face_from_path(const char *path, int index) { face_from_path(const char *path, int index) {
Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0); Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0);
@@ -265,7 +265,7 @@ repr(Face *self) {
} }
static inline bool static inline bool
load_glyph(Face *self, int glyph_index, int load_type) { load_glyph(Face *self, int glyph_index, int load_type) {
int flags = get_load_flags(self->hinting, self->hintstyle, load_type); int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
int error = FT_Load_Glyph(self->face, glyph_index, flags); int error = FT_Load_Glyph(self->face, glyph_index, flags);
@@ -285,7 +285,7 @@ calc_cell_width(Face *self) {
return ans; return ans;
} }
void void
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) { cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
Face *self = (Face*)s; Face *self = (Face*)s;
*cell_width = calc_cell_width(self); *cell_width = calc_cell_width(self);
@@ -295,11 +295,22 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
*underline_thickness = MAX(1, font_units_to_pixels(self, self->underline_thickness)); *underline_thickness = MAX(1, font_units_to_pixels(self, self->underline_thickness));
} }
unsigned int unsigned int
glyph_id_for_codepoint(PyObject *s, char_type cp) { glyph_id_for_codepoint(PyObject *s, char_type cp) {
return FT_Get_Char_Index(((Face*)s)->face, cp); return FT_Get_Char_Index(((Face*)s)->face, cp);
} }
bool
is_glyph_empty(PyObject *s, glyph_index g) {
Face *self = (Face*)s;
if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; }
#define M self->face->glyph->metrics
/* printf("glyph: %u horiBearingX: %ld horiBearingY: %ld width: %ld height: %ld\n", g, M.horiBearingX, M.horiBearingY, M.width, M.height); */
return M.width == 0;
#undef M
}
hb_font_t* hb_font_t*
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; } harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }
@@ -326,6 +337,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
if (!column_has_text) { ans->width--; extra--; } if (!column_has_text) { ans->width--; extra--; }
} }
// Remove any remaining extra columns from the left edge of the bitmap
ans->start_x = extra; ans->start_x = extra;
ans->width -= extra; ans->width -= extra;
} }
@@ -345,7 +357,7 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
size_t extra = bitmap->width - max_width; size_t extra = bitmap->width - max_width;
if (italic && extra < cell_width / 2) { if (italic && extra < cell_width / 2) {
trim_borders(ans, extra); trim_borders(ans, extra);
} else if (rescale && self->is_scalable && extra > MAX(2, cell_width / 4)) { } else if (rescale && self->is_scalable && extra > 1) {
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height; 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)bitmap->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)) { if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0)) {
@@ -376,12 +388,12 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
for (unsigned int y=sr; y < MIN(sr + factor, bm->rows); y++) { for (unsigned int y=sr; y < MIN(sr + factor, bm->rows); y++) {
uint8_t *p = bm->buf + (y * bm->stride) + sc * 4; uint8_t *p = bm->buf + (y * bm->stride) + sc * 4;
for (unsigned int x=sc; x < MIN(sc + factor, bm->width); x++, count++) { for (unsigned int x=sc; x < MIN(sc + factor, bm->width); x++, count++) {
b += *(p++); g += *(p++); r += *(p++); a += *(p++); b += *(p++); g += *(p++); r += *(p++); a += *(p++);
} }
} }
if (count) { if (count) {
d[0] = b / count; d[1] = g / count; d[2] = r / count; d[3] = a / count; d[0] = b / count; d[1] = g / count; d[2] = r / count; d[3] = a / count;
} }
} }
} }
@@ -450,7 +462,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
// We want the glyph to be positioned inside the cell based on the bearingX // 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. // and bearingY values, making sure that it does not overflow the cell.
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width }, dest = { .bottom = cell_height, .right = cell_width }; 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 // Calculate column bounds
float bearing_x = (float)metrics->horiBearingX / 64.f; float bearing_x = (float)metrics->horiBearingX / 64.f;
@@ -506,7 +518,7 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
y = (float)positions[i].y_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, &self->face->glyph->metrics, baseline);
x += (float)positions[i].x_advance / 64.0f; x += (float)positions[i].x_advance / 64.0f;
if (bm.needs_free) free(bm.buf); if (bm.needs_free) free(bm.buf);
} }
// center the glyphs in the canvas // center the glyphs in the canvas
@@ -560,8 +572,8 @@ PyTypeObject Face_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.Face", .tp_name = "fast_data_types.Face",
.tp_basicsize = sizeof(Face), .tp_basicsize = sizeof(Face),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "FreeType Font face", .tp_doc = "FreeType Font face",
.tp_methods = methods, .tp_methods = methods,
.tp_members = members, .tp_members = members,
@@ -573,11 +585,11 @@ free_freetype() {
FT_Done_FreeType(library); FT_Done_FreeType(library);
} }
bool bool
init_freetype_library(PyObject *m) { init_freetype_library(PyObject *m) {
if (PyType_Ready(&Face_Type) < 0) return 0; if (PyType_Ready(&Face_Type) < 0) return 0;
if (PyModule_AddObject(m, "Face", (PyObject *)&Face_Type) != 0) return 0; if (PyModule_AddObject(m, "Face", (PyObject *)&Face_Type) != 0) return 0;
Py_INCREF(&Face_Type); Py_INCREF(&Face_Type);
FreeType_Exception = PyErr_NewException("fast_data_types.FreeTypeError", NULL, NULL); FreeType_Exception = PyErr_NewException("fast_data_types.FreeTypeError", NULL, NULL);
if (FreeType_Exception == NULL) return false; if (FreeType_Exception == NULL) return false;
if (PyModule_AddObject(m, "FreeTypeError", FreeType_Exception) != 0) return false; if (PyModule_AddObject(m, "FreeTypeError", FreeType_Exception) != 0) return false;

1622
kitty/gl-wrapper.c generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,24 +22,24 @@ static void
check_for_gl_error(const char *name, void UNUSED *funcptr, int UNUSED len_args, ...) { check_for_gl_error(const char *name, void UNUSED *funcptr, int UNUSED len_args, ...) {
#define f(msg) fatal("OpenGL error: %s (calling function: %s)", msg, name); break; #define f(msg) fatal("OpenGL error: %s (calling function: %s)", msg, name); break;
GLenum code = glad_glGetError(); GLenum code = glad_glGetError();
switch(code) { switch(code) {
case GL_NO_ERROR: break; case GL_NO_ERROR: break;
case GL_INVALID_ENUM: case GL_INVALID_ENUM:
f("An enum value is invalid (GL_INVALID_ENUM)"); f("An enum value is invalid (GL_INVALID_ENUM)");
case GL_INVALID_VALUE: case GL_INVALID_VALUE:
f("An numeric value is invalid (GL_INVALID_VALUE)"); f("An numeric value is invalid (GL_INVALID_VALUE)");
case GL_INVALID_OPERATION: case GL_INVALID_OPERATION:
f("This operation is invalid (GL_INVALID_OPERATION)"); f("This operation is invalid (GL_INVALID_OPERATION)");
case GL_INVALID_FRAMEBUFFER_OPERATION: case GL_INVALID_FRAMEBUFFER_OPERATION:
f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)"); f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)");
case GL_OUT_OF_MEMORY: case GL_OUT_OF_MEMORY:
f("There is not enough memory left to execute the command. (GL_OUT_OF_MEMORY)"); f("There is not enough memory left to execute the command. (GL_OUT_OF_MEMORY)");
case GL_STACK_UNDERFLOW: case GL_STACK_UNDERFLOW:
f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_UNDERFLOW)"); f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_UNDERFLOW)");
case GL_STACK_OVERFLOW: case GL_STACK_OVERFLOW:
f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_OVERFLOW)"); f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_OVERFLOW)");
default: default:
fatal("An unknown OpenGL error occurred with code: %d (calling function: %s)", code, name); fatal("An unknown OpenGL error occurred with code: %d (calling function: %s)", code, name);
break; break;
} }
} }
@@ -59,6 +59,9 @@ gl_init() {
ARB_TEST(texture_storage); ARB_TEST(texture_storage);
#undef ARB_TEST #undef ARB_TEST
glad_loaded = true; glad_loaded = true;
if (GLVersion.major < OPENGL_REQUIRED_VERSION_MAJOR || (GLVersion.major == OPENGL_REQUIRED_VERSION_MAJOR && GLVersion.minor < OPENGL_REQUIRED_VERSION_MINOR)) {
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", GLVersion.major, GLVersion.minor);
}
} }
} }
@@ -73,7 +76,7 @@ update_surface_size(int w, int h, GLuint offscreen_texture_id) {
void void
free_texture(GLuint *tex_id) { free_texture(GLuint *tex_id) {
glDeleteTextures(1, tex_id); glDeleteTextures(1, tex_id);
*tex_id = 0; *tex_id = 0;
} }
@@ -288,7 +291,7 @@ add_buffer_to_vao(ssize_t vao_idx, GLenum usage) {
static void static void
add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) { add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
VAO *vao = vaos + vao_idx; VAO *vao = vaos + vao_idx;
if (!vao->num_buffers) fatal("You must create a buffer for this attribute first"); if (!vao->num_buffers) fatal("You must create a buffer for this attribute first");
ssize_t buf = vao->buffers[vao->num_buffers - 1]; ssize_t buf = vao->buffers[vao->num_buffers - 1];
bind_buffer(buf); bind_buffer(buf);
glEnableVertexAttribArray(aloc); glEnableVertexAttribArray(aloc);
@@ -315,7 +318,7 @@ add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum dat
static inline void static inline void
add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) { add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
GLint aloc = attrib_location(p, name); GLint aloc = attrib_location(p, name);
if (aloc == -1) fatal("No attribute named: %s found in this program", name); if (aloc == -1) fatal("No attribute named: %s found in this program", name);
add_located_attribute_to_vao(vao_idx, aloc, size, data_type, stride, offset, divisor); add_located_attribute_to_vao(vao_idx, aloc, size, data_type, stride, offset, divisor);
} }

21
kitty/glfw-wrapper.c generated
View File

@@ -23,9 +23,6 @@ load_glfw(const char* path) {
*(void **) (&glfwInitHint_impl) = dlsym(handle, "glfwInitHint"); *(void **) (&glfwInitHint_impl) = dlsym(handle, "glfwInitHint");
if (glfwInitHint_impl == NULL) fail("Failed to load glfw function glfwInitHint with error: %s", dlerror()); if (glfwInitHint_impl == NULL) fail("Failed to load glfw function glfwInitHint with error: %s", dlerror());
*(void **) (&glfwInitHintString_impl) = dlsym(handle, "glfwInitHintString");
if (glfwInitHintString_impl == NULL) fail("Failed to load glfw function glfwInitHintString with error: %s", dlerror());
*(void **) (&glfwGetVersion_impl) = dlsym(handle, "glfwGetVersion"); *(void **) (&glfwGetVersion_impl) = dlsym(handle, "glfwGetVersion");
if (glfwGetVersion_impl == NULL) fail("Failed to load glfw function glfwGetVersion with error: %s", dlerror()); if (glfwGetVersion_impl == NULL) fail("Failed to load glfw function glfwGetVersion with error: %s", dlerror());
@@ -56,6 +53,12 @@ load_glfw(const char* path) {
*(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName"); *(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName");
if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror()); if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror());
*(void **) (&glfwSetMonitorUserPointer_impl) = dlsym(handle, "glfwSetMonitorUserPointer");
if (glfwSetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetMonitorUserPointer with error: %s", dlerror());
*(void **) (&glfwGetMonitorUserPointer_impl) = dlsym(handle, "glfwGetMonitorUserPointer");
if (glfwGetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetMonitorUserPointer with error: %s", dlerror());
*(void **) (&glfwSetMonitorCallback_impl) = dlsym(handle, "glfwSetMonitorCallback"); *(void **) (&glfwSetMonitorCallback_impl) = dlsym(handle, "glfwSetMonitorCallback");
if (glfwSetMonitorCallback_impl == NULL) fail("Failed to load glfw function glfwSetMonitorCallback with error: %s", dlerror()); if (glfwSetMonitorCallback_impl == NULL) fail("Failed to load glfw function glfwSetMonitorCallback with error: %s", dlerror());
@@ -80,6 +83,9 @@ load_glfw(const char* path) {
*(void **) (&glfwWindowHint_impl) = dlsym(handle, "glfwWindowHint"); *(void **) (&glfwWindowHint_impl) = dlsym(handle, "glfwWindowHint");
if (glfwWindowHint_impl == NULL) fail("Failed to load glfw function glfwWindowHint with error: %s", dlerror()); if (glfwWindowHint_impl == NULL) fail("Failed to load glfw function glfwWindowHint with error: %s", dlerror());
*(void **) (&glfwWindowHintString_impl) = dlsym(handle, "glfwWindowHintString");
if (glfwWindowHintString_impl == NULL) fail("Failed to load glfw function glfwWindowHintString with error: %s", dlerror());
*(void **) (&glfwCreateWindow_impl) = dlsym(handle, "glfwCreateWindow"); *(void **) (&glfwCreateWindow_impl) = dlsym(handle, "glfwCreateWindow");
if (glfwCreateWindow_impl == NULL) fail("Failed to load glfw function glfwCreateWindow with error: %s", dlerror()); if (glfwCreateWindow_impl == NULL) fail("Failed to load glfw function glfwCreateWindow with error: %s", dlerror());
@@ -197,6 +203,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetFramebufferSizeCallback_impl) = dlsym(handle, "glfwSetFramebufferSizeCallback"); *(void **) (&glfwSetFramebufferSizeCallback_impl) = dlsym(handle, "glfwSetFramebufferSizeCallback");
if (glfwSetFramebufferSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetFramebufferSizeCallback with error: %s", dlerror()); if (glfwSetFramebufferSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetFramebufferSizeCallback with error: %s", dlerror());
*(void **) (&glfwSetWindowContentScaleCallback_impl) = dlsym(handle, "glfwSetWindowContentScaleCallback");
if (glfwSetWindowContentScaleCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowContentScaleCallback with error: %s", dlerror());
*(void **) (&glfwPollEvents_impl) = dlsym(handle, "glfwPollEvents"); *(void **) (&glfwPollEvents_impl) = dlsym(handle, "glfwPollEvents");
if (glfwPollEvents_impl == NULL) fail("Failed to load glfw function glfwPollEvents with error: %s", dlerror()); if (glfwPollEvents_impl == NULL) fail("Failed to load glfw function glfwPollEvents with error: %s", dlerror());
@@ -287,6 +296,12 @@ load_glfw(const char* path) {
*(void **) (&glfwGetJoystickGUID_impl) = dlsym(handle, "glfwGetJoystickGUID"); *(void **) (&glfwGetJoystickGUID_impl) = dlsym(handle, "glfwGetJoystickGUID");
if (glfwGetJoystickGUID_impl == NULL) fail("Failed to load glfw function glfwGetJoystickGUID with error: %s", dlerror()); if (glfwGetJoystickGUID_impl == NULL) fail("Failed to load glfw function glfwGetJoystickGUID with error: %s", dlerror());
*(void **) (&glfwSetJoystickUserPointer_impl) = dlsym(handle, "glfwSetJoystickUserPointer");
if (glfwSetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetJoystickUserPointer with error: %s", dlerror());
*(void **) (&glfwGetJoystickUserPointer_impl) = dlsym(handle, "glfwGetJoystickUserPointer");
if (glfwGetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetJoystickUserPointer with error: %s", dlerror());
*(void **) (&glfwJoystickIsGamepad_impl) = dlsym(handle, "glfwJoystickIsGamepad"); *(void **) (&glfwJoystickIsGamepad_impl) = dlsym(handle, "glfwJoystickIsGamepad");
if (glfwJoystickIsGamepad_impl == NULL) fail("Failed to load glfw function glfwJoystickIsGamepad with error: %s", dlerror()); if (glfwJoystickIsGamepad_impl == NULL) fail("Failed to load glfw function glfwJoystickIsGamepad with error: %s", dlerror());

88
kitty/glfw-wrapper.h generated
View File

@@ -251,17 +251,37 @@
* @{ */ * @{ */
/*! @brief If this bit is set one or more Shift keys were held down. /*! @brief If this bit is set one or more Shift keys were held down.
*
* If this bit is set one or more Shift keys were held down.
*/ */
#define GLFW_MOD_SHIFT 0x0001 #define GLFW_MOD_SHIFT 0x0001
/*! @brief If this bit is set one or more Control keys were held down. /*! @brief If this bit is set one or more Control keys were held down.
*
* If this bit is set one or more Control keys were held down.
*/ */
#define GLFW_MOD_CONTROL 0x0002 #define GLFW_MOD_CONTROL 0x0002
/*! @brief If this bit is set one or more Alt keys were held down. /*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/ */
#define GLFW_MOD_ALT 0x0004 #define GLFW_MOD_ALT 0x0004
/*! @brief If this bit is set one or more Super keys were held down. /*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.
*/ */
#define GLFW_MOD_SUPER 0x0008 #define GLFW_MOD_SUPER 0x0008
/*! @brief If this bit is set the Caps Lock key is enabled.
*
* If this bit is set the Caps Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_CAPS_LOCK 0x0010
/*! @brief If this bit is set the Num Lock key is enabled.
*
* If this bit is set the Num Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_NUM_LOCK 0x0020
/*! @} */ /*! @} */
@@ -552,6 +572,11 @@
* [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib). * [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
*/ */
#define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
/*! @brief Mouse cursor hover window attribute.
*
* Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib).
*/
#define GLFW_HOVERED 0x0002000B
/*! @brief Framebuffer bit depth hint. /*! @brief Framebuffer bit depth hint.
* *
@@ -702,8 +727,11 @@
#define GLFW_CONTEXT_CREATION_API 0x0002200B #define GLFW_CONTEXT_CREATION_API 0x0002200B
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
#define GLFW_COCOA_FRAME_AUTOSAVE 0x00023002 #define GLFW_COCOA_FRAME_NAME 0x00023002
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003 #define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
#define GLFW_X11_CLASS_NAME 0x00024001
#define GLFW_X11_INSTANCE_NAME 0x00024002
/*! @} */ /*! @} */
#define GLFW_NO_API 0 #define GLFW_NO_API 0
@@ -721,6 +749,7 @@
#define GLFW_CURSOR 0x00033001 #define GLFW_CURSOR 0x00033001
#define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_HIDDEN 0x00034002
@@ -783,9 +812,6 @@
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001 #define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
#define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_COCOA_MENUBAR 0x00051002
#define GLFW_X11_WM_CLASS_NAME 0x00052001
#define GLFW_X11_WM_CLASS_CLASS 0x00052002
/*! @} */ /*! @} */
#define GLFW_DONT_CARE -1 #define GLFW_DONT_CARE -1
@@ -804,7 +830,7 @@
* @sa @ref glfwGetProcAddress * @sa @ref glfwGetProcAddress
* *
* @since Added in version 3.0. * @since Added in version 3.0.
*
* @ingroup context * @ingroup context
*/ */
typedef void (*GLFWglproc)(void); typedef void (*GLFWglproc)(void);
@@ -1015,6 +1041,24 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
*/ */
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
/*! @brief The function signature for window content scale callbacks.
*
* This is the function signature for window content scale callback
* functions.
*
* @param[in] window The window whose content scale changed.
* @param[in] xscale The new x-axis content scale of the window.
* @param[in] yscale The new y-axis content scale of the window.
*
* @sa @ref window_scale
* @sa @ref glfwSetWindowContentScaleCallback
*
* @since Added in version 3.3.
*
* @ingroup window
*/
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
/*! @brief The function signature for mouse button callbacks. /*! @brief The function signature for mouse button callbacks.
* *
* This is the function signature for mouse button callback functions. * This is the function signature for mouse button callback functions.
@@ -1172,7 +1216,8 @@ typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**);
* This is the function signature for monitor configuration callback functions. * This is the function signature for monitor configuration callback functions.
* *
* @param[in] monitor The monitor that was connected or disconnected. * @param[in] monitor The monitor that was connected or disconnected.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
* values reserved for future use.
* *
* @sa @ref monitor_event * @sa @ref monitor_event
* @sa @ref glfwSetMonitorCallback * @sa @ref glfwSetMonitorCallback
@@ -1189,7 +1234,8 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int);
* functions. * functions.
* *
* @param[in] jid The joystick that was connected or disconnected. * @param[in] jid The joystick that was connected or disconnected.
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. * @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
* values reserved for future use.
* *
* @sa @ref joystick_event * @sa @ref joystick_event
* @sa @ref glfwSetJoystickCallback * @sa @ref glfwSetJoystickCallback
@@ -1359,10 +1405,6 @@ typedef void (*glfwInitHint_func)(int, int);
glfwInitHint_func glfwInitHint_impl; glfwInitHint_func glfwInitHint_impl;
#define glfwInitHint glfwInitHint_impl #define glfwInitHint glfwInitHint_impl
typedef void (*glfwInitHintString_func)(int, const char*);
glfwInitHintString_func glfwInitHintString_impl;
#define glfwInitHintString glfwInitHintString_impl
typedef void (*glfwGetVersion_func)(int*, int*, int*); typedef void (*glfwGetVersion_func)(int*, int*, int*);
glfwGetVersion_func glfwGetVersion_impl; glfwGetVersion_func glfwGetVersion_impl;
#define glfwGetVersion glfwGetVersion_impl #define glfwGetVersion glfwGetVersion_impl
@@ -1403,6 +1445,14 @@ typedef const char* (*glfwGetMonitorName_func)(GLFWmonitor*);
glfwGetMonitorName_func glfwGetMonitorName_impl; glfwGetMonitorName_func glfwGetMonitorName_impl;
#define glfwGetMonitorName glfwGetMonitorName_impl #define glfwGetMonitorName glfwGetMonitorName_impl
typedef void (*glfwSetMonitorUserPointer_func)(GLFWmonitor*, void*);
glfwSetMonitorUserPointer_func glfwSetMonitorUserPointer_impl;
#define glfwSetMonitorUserPointer glfwSetMonitorUserPointer_impl
typedef void* (*glfwGetMonitorUserPointer_func)(GLFWmonitor*);
glfwGetMonitorUserPointer_func glfwGetMonitorUserPointer_impl;
#define glfwGetMonitorUserPointer glfwGetMonitorUserPointer_impl
typedef GLFWmonitorfun (*glfwSetMonitorCallback_func)(GLFWmonitorfun); typedef GLFWmonitorfun (*glfwSetMonitorCallback_func)(GLFWmonitorfun);
glfwSetMonitorCallback_func glfwSetMonitorCallback_impl; glfwSetMonitorCallback_func glfwSetMonitorCallback_impl;
#define glfwSetMonitorCallback glfwSetMonitorCallback_impl #define glfwSetMonitorCallback glfwSetMonitorCallback_impl
@@ -1435,6 +1485,10 @@ typedef void (*glfwWindowHint_func)(int, int);
glfwWindowHint_func glfwWindowHint_impl; glfwWindowHint_func glfwWindowHint_impl;
#define glfwWindowHint glfwWindowHint_impl #define glfwWindowHint glfwWindowHint_impl
typedef void (*glfwWindowHintString_func)(int, const char*);
glfwWindowHintString_func glfwWindowHintString_impl;
#define glfwWindowHintString glfwWindowHintString_impl
typedef GLFWwindow* (*glfwCreateWindow_func)(int, int, const char*, GLFWmonitor*, GLFWwindow*); typedef GLFWwindow* (*glfwCreateWindow_func)(int, int, const char*, GLFWmonitor*, GLFWwindow*);
glfwCreateWindow_func glfwCreateWindow_impl; glfwCreateWindow_func glfwCreateWindow_impl;
#define glfwCreateWindow glfwCreateWindow_impl #define glfwCreateWindow glfwCreateWindow_impl
@@ -1591,6 +1645,10 @@ typedef GLFWframebuffersizefun (*glfwSetFramebufferSizeCallback_func)(GLFWwindow
glfwSetFramebufferSizeCallback_func glfwSetFramebufferSizeCallback_impl; glfwSetFramebufferSizeCallback_func glfwSetFramebufferSizeCallback_impl;
#define glfwSetFramebufferSizeCallback glfwSetFramebufferSizeCallback_impl #define glfwSetFramebufferSizeCallback glfwSetFramebufferSizeCallback_impl
typedef GLFWwindowcontentscalefun (*glfwSetWindowContentScaleCallback_func)(GLFWwindow*, GLFWwindowcontentscalefun);
glfwSetWindowContentScaleCallback_func glfwSetWindowContentScaleCallback_impl;
#define glfwSetWindowContentScaleCallback glfwSetWindowContentScaleCallback_impl
typedef void (*glfwPollEvents_func)(); typedef void (*glfwPollEvents_func)();
glfwPollEvents_func glfwPollEvents_impl; glfwPollEvents_func glfwPollEvents_impl;
#define glfwPollEvents glfwPollEvents_impl #define glfwPollEvents glfwPollEvents_impl
@@ -1711,6 +1769,14 @@ typedef const char* (*glfwGetJoystickGUID_func)(int);
glfwGetJoystickGUID_func glfwGetJoystickGUID_impl; glfwGetJoystickGUID_func glfwGetJoystickGUID_impl;
#define glfwGetJoystickGUID glfwGetJoystickGUID_impl #define glfwGetJoystickGUID glfwGetJoystickGUID_impl
typedef void (*glfwSetJoystickUserPointer_func)(int, void*);
glfwSetJoystickUserPointer_func glfwSetJoystickUserPointer_impl;
#define glfwSetJoystickUserPointer glfwSetJoystickUserPointer_impl
typedef void* (*glfwGetJoystickUserPointer_func)(int);
glfwGetJoystickUserPointer_func glfwGetJoystickUserPointer_impl;
#define glfwGetJoystickUserPointer glfwGetJoystickUserPointer_impl
typedef int (*glfwJoystickIsGamepad_func)(int); typedef int (*glfwJoystickIsGamepad_func)(int);
glfwJoystickIsGamepad_func glfwJoystickIsGamepad_impl; glfwJoystickIsGamepad_func glfwJoystickIsGamepad_impl;
#define glfwJoystickIsGamepad glfwJoystickIsGamepad_impl #define glfwJoystickIsGamepad glfwJoystickIsGamepad_impl

View File

@@ -9,6 +9,7 @@
#include "glfw-wrapper.h" #include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w); extern bool cocoa_make_window_resizable(void *w);
extern void cocoa_create_global_menu(void); extern void cocoa_create_global_menu(void);
extern void cocoa_set_titlebar_color(void *w);
#if GLFW_KEY_LAST >= MAX_KEY_COUNT #if GLFW_KEY_LAST >= MAX_KEY_COUNT
#error "glfw has too many keys, you should increase MAX_KEY_COUNT" #error "glfw has too many keys, you should increase MAX_KEY_COUNT"
@@ -21,20 +22,23 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
int w, h; int w, h;
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height); glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
glfwGetWindowSize(window->handle, &w, &h); glfwGetWindowSize(window->handle, &w, &h);
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
window->viewport_x_ratio = (double)window->viewport_width / (double)w; window->viewport_x_ratio = (double)window->viewport_width / (double)w;
window->viewport_y_ratio = (double)window->viewport_height / (double)h; window->viewport_y_ratio = (double)window->viewport_height / (double)h;
bool dpi_changed = (xr != 0.0 && xr != window->viewport_x_ratio) || (yr != 0.0 && yr != window->viewport_y_ratio);
window->viewport_size_dirty = true; window->viewport_size_dirty = true;
window->has_pending_resizes = false; window->has_pending_resizes = false;
window->viewport_width = MAX(window->viewport_width, 100);
window->viewport_height = MAX(window->viewport_height, 100);
if (notify_boss) { if (notify_boss) {
call_boss(on_window_resize, "Kii", window->id, window->viewport_width, window->viewport_height); call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
} }
window->last_resize_at = monotonic();
} }
// callbacks {{{ // callbacks {{{
void void
update_os_window_references() { update_os_window_references() {
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i; OSWindow *w = global_state.os_windows + i;
@@ -68,25 +72,28 @@ is_window_ready_for_callbacks() {
static inline void static inline void
show_mouse_cursor(GLFWwindow *w) { show_mouse_cursor(GLFWwindow *w) {
if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); }
} }
static void static void
framebuffer_size_callback(GLFWwindow *w, int width, int height) { framebuffer_size_callback(GLFWwindow *w, int width, int height) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
if (width > 100 && height > 100) { if (width > 100 && height > 100) {
double now = monotonic();
OSWindow *window = global_state.callback_os_window; OSWindow *window = global_state.callback_os_window;
if (now - window->last_resize_at < RESIZE_DEBOUNCE_TIME) { window->has_pending_resizes = true; global_state.has_pending_resizes = true; } window->has_pending_resizes = true; global_state.has_pending_resizes = true;
else { window->last_resize_event_at = monotonic();
update_os_window_viewport(global_state.callback_os_window, is_window_ready_for_callbacks());
glfwPostEmptyEvent();
}
} else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height); } else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height);
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static void
refresh_callback(GLFWwindow *w) {
if (!set_callback_window(w)) return;
global_state.callback_os_window->is_damaged = true;
global_state.callback_os_window = NULL;
}
static void
char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) { char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
global_state.callback_os_window->cursor_blink_zero_time = monotonic(); global_state.callback_os_window->cursor_blink_zero_time = monotonic();
@@ -94,7 +101,7 @@ char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static void
key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) { key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
global_state.callback_os_window->cursor_blink_zero_time = monotonic(); global_state.callback_os_window->cursor_blink_zero_time = monotonic();
@@ -105,7 +112,7 @@ key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static void
mouse_button_callback(GLFWwindow *w, int button, int action, int mods) { mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
show_mouse_cursor(w); show_mouse_cursor(w);
@@ -118,7 +125,7 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static void
cursor_pos_callback(GLFWwindow *w, double x, double y) { cursor_pos_callback(GLFWwindow *w, double x, double y) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
show_mouse_cursor(w); show_mouse_cursor(w);
@@ -131,7 +138,7 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static void
scroll_callback(GLFWwindow *w, double xoffset, double yoffset) { scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
show_mouse_cursor(w); show_mouse_cursor(w);
@@ -141,13 +148,40 @@ scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static struct {
id_type entries[16];
int next_entry;
} focus_history;
#ifdef __APPLE__
static inline id_type
pop_focus_history() {
int index = --focus_history.next_entry;
if (index < 0) {
focus_history.next_entry = index = 0;
}
id_type result = focus_history.entries[index];
focus_history.entries[index] = 0;
return result;
}
#endif
static inline void
push_focus_history(OSWindow *w) {
focus_history.entries[focus_history.next_entry++] = w->id;
focus_history.next_entry %= (sizeof(focus_history.entries) / sizeof(*(focus_history.entries)));
}
static void
window_focus_callback(GLFWwindow *w, int focused) { window_focus_callback(GLFWwindow *w, int focused) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
global_state.callback_os_window->is_focused = focused ? true : false; global_state.callback_os_window->is_focused = focused ? true : false;
if (focused) { if (focused) {
show_mouse_cursor(w); show_mouse_cursor(w);
focus_in_event(); focus_in_event();
push_focus_history(global_state.callback_os_window);
} }
double now = monotonic(); double now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->last_mouse_activity_at = now;
@@ -158,7 +192,7 @@ window_focus_callback(GLFWwindow *w, int focused) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static void static void
drop_callback(GLFWwindow *w, int count, const char **paths) { drop_callback(GLFWwindow *w, int count, const char **paths) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
PyObject *p = PyTuple_New(count); PyObject *p = PyTuple_New(count);
@@ -201,7 +235,7 @@ set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
static GLFWwindow *current_os_window_ctx = NULL; static GLFWwindow *current_os_window_ctx = NULL;
void void
make_os_window_context_current(OSWindow *w) { make_os_window_context_current(OSWindow *w) {
if (w->handle != current_os_window_ctx) { if (w->handle != current_os_window_ctx) {
current_os_window_ctx = w->handle; current_os_window_ctx = w->handle;
@@ -246,8 +280,8 @@ current_monitor(GLFWwindow *window) {
} }
static inline void void
set_dpi_from_window(OSWindow *w) { set_dpi_from_os_window(OSWindow *w) {
GLFWmonitor *monitor = NULL; GLFWmonitor *monitor = NULL;
if (w) { monitor = current_monitor(w->handle); } if (w) { monitor = current_monitor(w->handle); }
if (monitor == NULL) monitor = glfwGetPrimaryMonitor(); if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
@@ -265,13 +299,14 @@ set_dpi_from_window(OSWindow *w) {
static PyObject* static PyObject*
create_os_window(PyObject UNUSED *self, PyObject *args) { create_os_window(PyObject UNUSED *self, PyObject *args) {
int width, height, visible = 1, swap_interval = 0, x = -1, y = -1; int width, height, x = -1, y = -1;
char *title; char *title, *wm_class_class, *wm_class_name;
PyObject *load_programs = NULL; PyObject *load_programs = NULL;
if (!PyArg_ParseTuple(args, "iis|pOiii", &width, &height, &title, &visible, &load_programs, &swap_interval, &x, &y)) return NULL; if (!PyArg_ParseTuple(args, "iisss|Oiii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
bool is_first_window = standard_cursor == NULL; bool is_first_window = standard_cursor == NULL;
if (is_first_window) { if (is_first_window) {
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@@ -288,15 +323,19 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
arrow_cursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); arrow_cursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
if (standard_cursor == NULL || click_cursor == NULL || arrow_cursor == NULL) { if (standard_cursor == NULL || click_cursor == NULL || arrow_cursor == NULL) {
PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL; PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL;
} }
} }
#ifndef __APPLE__
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
#endif
if (global_state.num_os_windows >= MAX_CHILDREN) { if (global_state.num_os_windows >= MAX_CHILDREN) {
PyErr_SetString(PyExc_ValueError, "Too many windows"); PyErr_SetString(PyExc_ValueError, "Too many windows");
return NULL; return NULL;
} }
glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE);
bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false; bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false;
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent);
GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL); GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL);
@@ -308,9 +347,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwMakeContextCurrent(glfw_window); glfwMakeContextCurrent(glfw_window);
if (x != -1 && y != -1) glfwSetWindowPos(glfw_window, x, y); if (x != -1 && y != -1) glfwSetWindowPos(glfw_window, x, y);
current_os_window_ctx = glfw_window; current_os_window_ctx = glfw_window;
glfwSwapInterval(swap_interval); // a value of 1 makes mouse selection laggy glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0); // a value of 1 makes mouse selection laggy
if (is_first_window) { if (is_first_window) {
set_dpi_from_window(NULL); set_dpi_from_os_window(NULL);
gl_init(); gl_init();
PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER)); PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER));
if (ret == NULL) return NULL; if (ret == NULL) return NULL;
@@ -332,6 +371,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwSetCursor(glfw_window, standard_cursor); glfwSetCursor(glfw_window, standard_cursor);
update_os_window_viewport(w, false); update_os_window_viewport(w, false);
glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback); glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback);
glfwSetWindowRefreshCallback(glfw_window, refresh_callback);
glfwSetCharModsCallback(glfw_window, char_mods_callback); glfwSetCharModsCallback(glfw_window, char_mods_callback);
glfwSetMouseButtonCallback(glfw_window, mouse_button_callback); glfwSetMouseButtonCallback(glfw_window, mouse_button_callback);
glfwSetScrollCallback(glfw_window, scroll_callback); glfwSetScrollCallback(glfw_window, scroll_callback);
@@ -344,6 +384,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } } if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
else fprintf(stderr, "Failed to load glfwGetCocoaWindow\n"); else fprintf(stderr, "Failed to load glfwGetCocoaWindow\n");
} }
cocoa_set_titlebar_color(glfwGetCocoaWindow(glfw_window));
#endif #endif
double now = monotonic(); double now = monotonic();
w->is_focused = true; w->is_focused = true;
@@ -364,28 +405,59 @@ static PyObject*
show_window(PyObject UNUSED *self, PyObject *args) { show_window(PyObject UNUSED *self, PyObject *args) {
id_type os_window_id; id_type os_window_id;
int yes = 1; int yes = 1;
bool dpi_changed = false;
if (!PyArg_ParseTuple(args, "K|p", &os_window_id, &yes)) return NULL; if (!PyArg_ParseTuple(args, "K|p", &os_window_id, &yes)) return NULL;
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i; OSWindow *w = global_state.os_windows + i;
if (w->id == os_window_id) { if (w->id == os_window_id) {
if (yes) glfwShowWindow(w->handle); else glfwHideWindow(w->handle); if (yes) {
bool first_show = !w->shown_once;
glfwShowWindow(w->handle);
w->shown_once = true;
push_focus_history(w);
if (first_show) {
double before_x = global_state.logical_dpi_x, before_y = global_state.logical_dpi_y;
set_dpi_from_os_window(w);
dpi_changed = before_x != global_state.logical_dpi_x || before_y != global_state.logical_dpi_y;
w->has_pending_resizes = true;
global_state.has_pending_resizes = true;
}
} else glfwHideWindow(w->handle);
break; break;
} }
} }
Py_RETURN_NONE; if (dpi_changed) Py_RETURN_TRUE;
Py_RETURN_FALSE;
} }
void void
destroy_os_window(OSWindow *w) { destroy_os_window(OSWindow *w) {
if (w->handle) { if (w->handle) {
glfwDestroyWindow(w->handle); glfwDestroyWindow(w->handle);
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL; if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
} }
w->handle = NULL; w->handle = NULL;
#ifdef __APPLE__
// On macOS when closing a window, any other existing windows belonging to the same application do not
// automatically get focus, so we do it manually.
bool change_focus = true;
while (change_focus) {
id_type new_focus_id = pop_focus_history();
if (new_focus_id == 0) break;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *c = global_state.os_windows + i;
if (c->id != w->id && c->handle && c->shown_once && (c->id == new_focus_id)) {
glfwFocusWindow(c->handle);
change_focus = false;
break;
}
}
}
#endif
} }
// Global functions {{{ // Global functions {{{
static void static void
error_callback(int error, const char* description) { error_callback(int error, const char* description) {
fprintf(stderr, "[glfw error %d]: %s\n", error, description); fprintf(stderr, "[glfw error %d]: %s\n", error, description);
} }
@@ -522,11 +594,11 @@ toggle_fullscreen(PyObject UNUSED *self) {
} }
} }
void void
ring_audio_bell(OSWindow *w) { ring_audio_bell(OSWindow *w) {
static double last_bell_at = -1; static double last_bell_at = -1;
double now = monotonic(); double now = monotonic();
if (now - last_bell_at <= 0.1) return; if (now - last_bell_at <= 0.1) return;
last_bell_at = now; last_bell_at = now;
if (w->handle) { if (w->handle) {
glfwWindowBell(w->handle, OPT(x11_bell_volume)); glfwWindowBell(w->handle, OPT(x11_bell_volume));
@@ -555,7 +627,7 @@ hide_mouse(OSWindow *w) {
} }
} }
void void
swap_window_buffers(OSWindow *w) { swap_window_buffers(OSWindow *w) {
glfwSwapBuffers(w->handle); glfwSwapBuffers(w->handle);
} }
@@ -571,7 +643,7 @@ wakeup_main_loop() {
glfwPostEmptyEvent(); glfwPostEmptyEvent();
} }
void void
mark_os_window_for_close(OSWindow* w, bool yes) { mark_os_window_for_close(OSWindow* w, bool yes) {
glfwSetWindowShouldClose(w->handle, yes); glfwSetWindowShouldClose(w->handle, yes);
} }
@@ -694,14 +766,14 @@ static PyMethodDef module_methods[] = {
METHODB(x11_window_id, METH_O), METHODB(x11_window_id, METH_O),
METHODB(set_primary_selection, METH_VARARGS), METHODB(set_primary_selection, METH_VARARGS),
METHODB(glfw_poll_events, METH_NOARGS), METHODB(glfw_poll_events, METH_NOARGS),
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""}, {"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""}, {"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
{"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""}, {"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""},
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""},
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, {"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, {"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""},
{"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""}, {"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""},
{"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""}, {"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };
@@ -710,8 +782,6 @@ bool
init_glfw(PyObject *m) { init_glfw(PyObject *m) {
if (PyModule_AddFunctions(m, module_methods) != 0) return false; if (PyModule_AddFunctions(m, module_methods) != 0) return false;
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; #define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
ADDC(GLFW_X11_WM_CLASS_NAME)
ADDC(GLFW_X11_WM_CLASS_CLASS)
ADDC(GLFW_RELEASE); ADDC(GLFW_RELEASE);
ADDC(GLFW_PRESS); ADDC(GLFW_PRESS);
ADDC(GLFW_REPEAT); ADDC(GLFW_REPEAT);

View File

@@ -81,7 +81,7 @@ static inline void
remove_from_array(void *array, size_t item_size, size_t idx, size_t array_count) { remove_from_array(void *array, size_t item_size, size_t idx, size_t array_count) {
size_t num_to_right = array_count - 1 - idx; size_t num_to_right = array_count - 1 - idx;
uint8_t *p = (uint8_t*)array; uint8_t *p = (uint8_t*)array;
if (num_to_right > 0) memmove(p + (idx * item_size), p + ((idx + 1) * item_size), num_to_right * item_size); if (num_to_right > 0) memmove(p + (idx * item_size), p + ((idx + 1) * item_size), num_to_right * item_size);
memset(p + (item_size * (array_count - 1)), 0, item_size); memset(p + (item_size * (array_count - 1)), 0, item_size);
} }
@@ -130,7 +130,7 @@ trim_predicate(Image *img) {
static int static int
oldest_last(const void* a, const void *b) { oldest_last(const void* a, const void *b) {
double ans = ((Image*)(b))->atime - ((Image*)(a))->atime; double ans = ((Image*)(b))->atime - ((Image*)(a))->atime;
return ans < 0 ? -1 : (ans == 0 ? 0 : 1); return ans < 0 ? -1 : (ans == 0 ? 0 : 1);
} }
static inline void static inline void
@@ -171,7 +171,7 @@ mmap_img_file(GraphicsManager UNUSED *self, Image *img, int fd, size_t sz, off_t
sz = s.st_size; sz = s.st_size;
} }
void *addr = mmap(0, sz, PROT_READ, MAP_SHARED, fd, offset); void *addr = mmap(0, sz, PROT_READ, MAP_SHARED, fd, offset);
if (addr == MAP_FAILED) ABRT(EBADF, "Failed to map image file fd: %d at offset: %zd with size: %zu with error: [%d] %s", fd, offset, sz, errno, strerror(errno)); if (addr == MAP_FAILED) ABRT(EBADF, "Failed to map image file fd: %d at offset: %zd with size: %zu with error: [%d] %s", fd, offset, sz, errno, strerror(errno));
img->load_data.mapped_file = addr; img->load_data.mapped_file = addr;
img->load_data.mapped_file_sz = sz; img->load_data.mapped_file_sz = sz;
return true; return true;
@@ -267,7 +267,7 @@ inflate_png_inner(struct png_jmp_data *d, uint8_t *buf, size_t bufsz) {
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure"); if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
if (setjmp(jb)) goto err; if (setjmp(jb)) goto err;
png_set_read_fn(png, &f, read_png_from_buffer); png_set_read_fn(png, &f, read_png_from_buffer);
png_read_info(png, info); png_read_info(png, info);
png_byte color_type, bit_depth; png_byte color_type, bit_depth;
@@ -312,7 +312,7 @@ inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz
if (d.ok) { if (d.ok) {
free_load_data(&img->load_data); free_load_data(&img->load_data);
img->load_data.buf = d.decompressed; img->load_data.buf = d.decompressed;
img->load_data.buf_capacity = d.sz; img->load_data.buf_capacity = d.sz;
img->load_data.buf_used = d.sz; img->load_data.buf_used = d.sz;
img->load_data.data_sz = d.sz; img->load_data.data_sz = d.sz;
img->width = d.width; img->height = d.height; img->width = d.width; img->height = d.height;
@@ -376,7 +376,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
img->atime = monotonic(); img->used_storage = 0; img->atime = monotonic(); img->used_storage = 0;
img->width = g->data_width; img->height = g->data_height; img->width = g->data_width; img->height = g->data_height;
switch(fmt) { switch(fmt) {
case PNG: case PNG:
if (g->data_sz > MAX_DATA_SZ) ABRT(EINVAL, "PNG data size too large"); if (g->data_sz > MAX_DATA_SZ) ABRT(EINVAL, "PNG data size too large");
img->load_data.is_4byte_aligned = true; img->load_data.is_4byte_aligned = true;
img->load_data.is_opaque = false; img->load_data.is_opaque = false;
@@ -513,7 +513,7 @@ create_add_response(GraphicsManager UNUSED *self, bool data_loaded, uint32_t iid
if (iid) { if (iid) {
if (!has_add_respose) { if (!has_add_respose) {
if (!data_loaded) return NULL; if (!data_loaded) return NULL;
snprintf(add_response, 10, "OK"); snprintf(add_response, 10, "OK");
} }
snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u;%s", iid, add_response); snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u;%s", iid, add_response);
return rbuf; return rbuf;
@@ -541,18 +541,18 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows) {
t = ref->src_width + ref->cell_x_offset; t = ref->src_width + ref->cell_x_offset;
num_cols = t / global_state.cell_width; num_cols = t / global_state.cell_width;
if (t > num_cols * global_state.cell_width) num_cols += 1; if (t > num_cols * global_state.cell_width) num_cols += 1;
} }
if (num_rows == 0) { if (num_rows == 0) {
t = ref->src_height + ref->cell_y_offset; t = ref->src_height + ref->cell_y_offset;
num_rows = t / global_state.cell_height; num_rows = t / global_state.cell_height;
if (t > num_rows * global_state.cell_height) num_rows += 1; if (t > num_rows * global_state.cell_height) num_rows += 1;
} }
ref->effective_num_rows = num_rows; ref->effective_num_rows = num_rows;
ref->effective_num_cols = num_cols; ref->effective_num_cols = num_cols;
} }
static void static void
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img) { handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img) {
has_add_respose = false; has_add_respose = false;
if (img == NULL) img = img_by_client_id(self, g->id); if (img == NULL) img = img_by_client_id(self, g->id);
@@ -584,12 +584,12 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1; c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
} }
static int static int
cmp_by_zindex_and_image(const void *a_, const void *b_) { cmp_by_zindex_and_image(const void *a_, const void *b_) {
const ImageRenderData *a = (const ImageRenderData*)a_, *b = (const ImageRenderData*)b_; const ImageRenderData *a = (const ImageRenderData*)a_, *b = (const ImageRenderData*)b_;
int ans = a->z_index - b->z_index; int ans = a->z_index - b->z_index;
if (ans == 0) ans = a->image_id - b->image_id; if (ans == 0) ans = a->image_id - b->image_id;
return ans; return ans;
} }
bool bool
@@ -656,9 +656,9 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi
size_t i, j; size_t i, j;
if (self->image_count) { if (self->image_count) {
for (i = self->image_count; i-- > 0;) { for (i = self->image_count; i-- > 0;) {
img = self->images + i; img = self->images + i;
for (j = img->refcnt; j-- > 0;) { for (j = img->refcnt; j-- > 0;) {
ref = img->refs + j; ref = img->refs + j;
if (filter_func(ref, img, data)) { if (filter_func(ref, img, data)) {
remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--); remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--);
@@ -718,7 +718,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) {
return false; return false;
} }
void void
grman_scroll_images(GraphicsManager *self, const ScrollData *data) { grman_scroll_images(GraphicsManager *self, const ScrollData *data) {
filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func); filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func);
} }
@@ -728,9 +728,14 @@ clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) {
return ref->start_row + (int32_t)ref->effective_num_rows > 0; return ref->start_row + (int32_t)ref->effective_num_rows > 0;
} }
static inline bool
clear_all_filter_func(ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data) {
return true;
}
void void
grman_clear(GraphicsManager *self) { grman_clear(GraphicsManager *self, bool all) {
filter_refs(self, NULL, true, clear_filter_func); filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func);
} }
static inline bool static inline bool
@@ -799,18 +804,18 @@ handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c
// }}} // }}}
void void
grman_resize(GraphicsManager *self, index_type UNUSED old_lines, index_type UNUSED lines, index_type UNUSED old_columns, index_type UNUSED columns) { grman_resize(GraphicsManager *self, index_type UNUSED old_lines, index_type UNUSED lines, index_type UNUSED old_columns, index_type UNUSED columns) {
self->layers_dirty = true; self->layers_dirty = true;
} }
void void
grman_rescale(GraphicsManager *self, unsigned int UNUSED old_cell_width, unsigned int UNUSED old_cell_height) { grman_rescale(GraphicsManager *self, unsigned int UNUSED old_cell_width, unsigned int UNUSED old_cell_height) {
ImageRef *ref; Image *img; ImageRef *ref; Image *img;
self->layers_dirty = true; self->layers_dirty = true;
for (size_t i = self->image_count; i-- > 0;) { for (size_t i = self->image_count; i-- > 0;) {
img = self->images + i; img = self->images + i;
for (size_t j = img->refcnt; j-- > 0;) { for (size_t j = img->refcnt; j-- > 0;) {
ref = img->refs + j; ref = img->refs + j;
ref->cell_x_offset = MIN(ref->cell_x_offset, global_state.cell_width - 1); ref->cell_x_offset = MIN(ref->cell_x_offset, global_state.cell_width - 1);
ref->cell_y_offset = MIN(ref->cell_y_offset, global_state.cell_height - 1); ref->cell_y_offset = MIN(ref->cell_y_offset, global_state.cell_height - 1);
@@ -925,7 +930,7 @@ W(update_layers) {
for (size_t i = 0; i < self->count; i++) { for (size_t i = 0; i < self->count; i++) {
ImageRenderData *r = self->render_data + i; ImageRenderData *r = self->render_data + i;
#define R(offset) Py_BuildValue("{sf sf sf sf}", "left", r->vertices[offset + 8], "top", r->vertices[offset + 1], "right", r->vertices[offset], "bottom", r->vertices[offset + 5]) #define R(offset) Py_BuildValue("{sf sf sf sf}", "left", r->vertices[offset + 8], "top", r->vertices[offset + 1], "right", r->vertices[offset], "bottom", r->vertices[offset + 5])
PyTuple_SET_ITEM(ans, i, PyTuple_SET_ITEM(ans, i,
Py_BuildValue("{sN sN sI si sI}", "src_rect", R(0), "dest_rect", R(2), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id) Py_BuildValue("{sN sN sI si sI}", "src_rect", R(0), "dest_rect", R(2), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id)
); );
#undef R #undef R
@@ -950,10 +955,10 @@ PyTypeObject GraphicsManager_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.GraphicsManager", .tp_name = "fast_data_types.GraphicsManager",
.tp_basicsize = sizeof(GraphicsManager), .tp_basicsize = sizeof(GraphicsManager),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "GraphicsManager", .tp_doc = "GraphicsManager",
.tp_new = new, .tp_new = new,
.tp_methods = methods, .tp_methods = methods,
.tp_members = members, .tp_members = members,
}; };
@@ -969,7 +974,7 @@ static PyMethodDef module_methods[] = {
bool bool
init_graphics(PyObject *module) { init_graphics(PyObject *module) {
if (PyType_Ready(&GraphicsManager_Type) < 0) return false; if (PyType_Ready(&GraphicsManager_Type) < 0) return false;
if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false; if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false;
if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false;
Py_INCREF(&GraphicsManager_Type); Py_INCREF(&GraphicsManager_Type);
return true; return true;

View File

@@ -83,7 +83,7 @@ typedef struct {
} ScrollData; } ScrollData;
GraphicsManager* grman_alloc(); GraphicsManager* grman_alloc();
void grman_clear(GraphicsManager*); void grman_clear(GraphicsManager*, bool);
const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty); const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty);
bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows); bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows);
void grman_scroll_images(GraphicsManager *self, const ScrollData*); void grman_scroll_images(GraphicsManager *self, const ScrollData*);

View File

@@ -2,12 +2,14 @@
#define ALPHA_TYPE #define ALPHA_TYPE
uniform sampler2D image; uniform sampler2D image;
uniform float inactive_text_alpha;
in vec2 texcoord; in vec2 texcoord;
out vec4 color; out vec4 color;
void main() { void main() {
color = texture(image, texcoord); color = texture(image, texcoord);
color.a *= inactive_text_alpha;
#ifdef PREMULT #ifdef PREMULT
color = vec4(color.rgb * color.a, color.a); color = vec4(color.rgb * color.a, color.a);
#endif #endif

View File

@@ -58,7 +58,7 @@ dealloc(HistoryBuf* self) {
Py_TYPE(self)->tp_free((PyObject*)self); Py_TYPE(self)->tp_free((PyObject*)self);
} }
static inline index_type static inline index_type
index_of(HistoryBuf *self, index_type lnum) { index_of(HistoryBuf *self, index_type lnum) {
// The index (buffer position) of the line with line number lnum // The index (buffer position) of the line with line number lnum
// This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer. // This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer.
@@ -67,7 +67,7 @@ index_of(HistoryBuf *self, index_type lnum) {
return (self->start_of_data + idx) % self->ynum; return (self->start_of_data + idx) % self->ynum;
} }
static inline void static inline void
init_line(HistoryBuf *self, index_type num, Line *l) { init_line(HistoryBuf *self, index_type num, Line *l) {
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num // Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
l->cells = lineptr(self, num); l->cells = lineptr(self, num);
@@ -75,22 +75,28 @@ init_line(HistoryBuf *self, index_type num, Line *l) {
l->has_dirty_text = self->line_attrs[num] & TEXT_DIRTY_MASK ? true : false; l->has_dirty_text = self->line_attrs[num] & TEXT_DIRTY_MASK ? true : false;
} }
void void
historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) { historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) {
init_line(self, index_of(self, lnum), l); init_line(self, index_of(self, lnum), l);
} }
void void
historybuf_mark_line_clean(HistoryBuf *self, index_type y) { historybuf_mark_line_clean(HistoryBuf *self, index_type y) {
self->line_attrs[index_of(self, y)] &= ~TEXT_DIRTY_MASK; self->line_attrs[index_of(self, y)] &= ~TEXT_DIRTY_MASK;
} }
void void
historybuf_mark_line_dirty(HistoryBuf *self, index_type y) { historybuf_mark_line_dirty(HistoryBuf *self, index_type y) {
self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK; self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK;
} }
static inline index_type inline void
historybuf_clear(HistoryBuf *self) {
self->count = 0;
self->start_of_data = 0;
}
static inline index_type
historybuf_push(HistoryBuf *self) { historybuf_push(HistoryBuf *self) {
index_type idx = (self->start_of_data + self->count) % self->ynum; index_type idx = (self->start_of_data + self->count) % self->ynum;
init_line(self, idx, self->line); init_line(self, idx, self->line);
@@ -124,7 +130,7 @@ historybuf_resize(HistoryBuf *self, index_type lines) {
return true; return true;
} }
void void
historybuf_add_line(HistoryBuf *self, const Line *line) { historybuf_add_line(HistoryBuf *self, const Line *line) {
index_type idx = historybuf_push(self); index_type idx = historybuf_push(self);
copy_line(line, self->line); copy_line(line, self->line);
@@ -196,6 +202,18 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline Line*
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, y, l); return l; }
static PyObject*
as_text(HistoryBuf *self, PyObject *args) {
Line l = {.xnum=self->xnum};
#define gl(self, y) get_line(self, y, &l);
as_text_generic(args, self, gl, self->count, self->xnum, callback, as_ansi);
#undef gl
}
static PyObject* static PyObject*
dirty_lines(HistoryBuf *self) { dirty_lines(HistoryBuf *self) {
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text." #define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
@@ -217,6 +235,7 @@ static PyMethodDef methods[] = {
METHOD(change_num_of_lines, METH_O) METHOD(change_num_of_lines, METH_O)
METHOD(line, METH_O) METHOD(line, METH_O)
METHOD(as_ansi, METH_O) METHOD(as_ansi, METH_O)
METHODB(as_text, METH_VARARGS),
METHOD(dirty_lines, METH_NOARGS) METHOD(dirty_lines, METH_NOARGS)
METHOD(push, METH_VARARGS) METHOD(push, METH_VARARGS)
METHOD(rewrap, METH_VARARGS) METHOD(rewrap, METH_VARARGS)
@@ -234,11 +253,11 @@ PyTypeObject HistoryBuf_Type = {
PyVarObject_HEAD_INIT(NULL, 0) PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "fast_data_types.HistoryBuf", .tp_name = "fast_data_types.HistoryBuf",
.tp_basicsize = sizeof(HistoryBuf), .tp_basicsize = sizeof(HistoryBuf),
.tp_dealloc = (destructor)dealloc, .tp_dealloc = (destructor)dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT, .tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "History buffers", .tp_doc = "History buffers",
.tp_methods = methods, .tp_methods = methods,
.tp_members = members, .tp_members = members,
.tp_str = (reprfunc)__str__, .tp_str = (reprfunc)__str__,
.tp_new = new .tp_new = new
}; };
@@ -258,9 +277,9 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (src->line_attrs[map_src_index(src_y + 1)] & CONTINUED_MASK) : false) #define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (src->line_attrs[map_src_index(src_y + 1)] & CONTINUED_MASK) : false)
#define next_dest_line(cont) dest->line_attrs[historybuf_push(dest)] = cont & CONTINUED_MASK; dest->line->continued = cont; #define next_dest_line(cont) dest->line_attrs[historybuf_push(dest)] = cont & CONTINUED_MASK; dest->line->continued = cont;
#define first_dest_line next_dest_line(false); #define first_dest_line next_dest_line(false);
#include "rewrap.h" #include "rewrap.h"
@@ -273,8 +292,9 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
return; return;
} }
other->count = 0; other->start_of_data = 0; other->count = 0; other->start_of_data = 0;
index_type x = 0, y = 0;
if (self->count > 0) { if (self->count > 0) {
rewrap_inner(self, other, self->count, NULL); rewrap_inner(self, other, self->count, NULL, &x, &y);
for (index_type i = 0; i < other->count; i++) other->line_attrs[(other->start_of_data + i) % other->ynum] |= TEXT_DIRTY_MASK; for (index_type i = 0; i < other->count; i++) other->line_attrs[(other->start_of_data + i) % other->ynum] |= TEXT_DIRTY_MASK;
} }
} }

View File

@@ -2,27 +2,25 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import array
import codecs import codecs
import fcntl import fcntl
import mimetypes import mimetypes
import os import os
import re import re
import selectors
import signal import signal
import struct
import subprocess import subprocess
import sys import sys
import termios import termios
import tty
import zlib import zlib
from base64 import standard_b64encode from base64 import standard_b64encode
from collections import namedtuple from collections import namedtuple
from math import ceil, floor from math import ceil, floor
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from time import monotonic
from kitty.constants import appname
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.constants import appname
from kitty.utils import read_with_timeout
try: try:
fsenc = sys.getfilesystemencoding() or 'utf-8' fsenc = sys.getfilesystemencoding() or 'utf-8'
@@ -56,6 +54,13 @@ are in cells (i.e. cursor positions) with the origin |_ (0, 0)| at
the top-left corner of the screen. the top-left corner of the screen.
--scale-up
type=bool-set
When used in combination with |_ --place| it will cause images that
are smaller than the specified area to be scaled up to use as much
of the specified area as possible.
--clear --clear
type=bool-set type=bool-set
Remove all images currently displayed on the screen. Remove all images currently displayed on the screen.
@@ -98,14 +103,18 @@ def options_spec():
Size = namedtuple('Size', 'rows cols width height') Size = namedtuple('Size', 'rows cols width height')
def screen_size(refresh=False): def screen_size():
if refresh or getattr(screen_size, 'ans', None) is None: if screen_size.changed:
s = struct.pack('HHHH', 0, 0, 0, 0) buf = array.array('H', [0, 0, 0, 0])
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
screen_size.ans = Size(*struct.unpack('HHHH', x)) screen_size.ans = Size(*buf)
screen_size.changed = False
return screen_size.ans return screen_size.ans
screen_size.changed = True
def write_gr_cmd(cmd, payload=None): def write_gr_cmd(cmd, payload=None):
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items()) cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
w = sys.stdout.buffer.write w = sys.stdout.buffer.write
@@ -131,6 +140,17 @@ def fit_image(width, height, pwidth, pheight):
return int(width), int(height) return int(width), int(height)
def calculate_in_cell_x_offset(width, cell_width, align):
if align == 'left':
return 0
extra_pixels = width % cell_width
if not extra_pixels:
return 0
if align == 'right':
return cell_width - extra_pixels
return (cell_width - extra_pixels) // 2
def set_cursor(cmd, width, height, align): def set_cursor(cmd, width, height, align):
ss = screen_size() ss = screen_size()
cw = int(ss.width / ss.cols) cw = int(ss.width / ss.cols)
@@ -139,12 +159,9 @@ def set_cursor(cmd, width, height, align):
w, h = fit_image(width, height, ss.width, height) w, h = fit_image(width, height, ss.width, height)
ch = int(ss.height / ss.rows) ch = int(ss.height / ss.rows)
num_of_rows_needed = int(ceil(height / ch)) num_of_rows_needed = int(ceil(height / ch))
y_off = height % ch
cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed
cmd['Y'] = y_off
else: else:
x_off = width % cw cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
cmd['X'] = x_off
extra_cells = 0 extra_cells = 0
if align == 'center': if align == 'center':
extra_cells = (ss.cols - num_of_cells_needed) // 2 extra_cells = (ss.cols - num_of_cells_needed) // 2
@@ -159,8 +176,7 @@ def set_cursor_for_place(place, cmd, width, height, align):
ss = screen_size() ss = screen_size()
cw = int(ss.width / ss.cols) cw = int(ss.width / ss.cols)
num_of_cells_needed = int(ceil(width / cw)) num_of_cells_needed = int(ceil(width / cw))
x_off = width % cw cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
cmd['X'] = x_off
extra_cells = 0 extra_cells = 0
if align == 'center': if align == 'center':
extra_cells = (place.width - num_of_cells_needed) // 2 extra_cells = (place.width - num_of_cells_needed) // 2
@@ -221,14 +237,29 @@ def identify(path):
return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode) return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode)
def convert(path, m, available_width, available_height): def convert(path, m, available_width, available_height, scale_up):
width, height = m.width, m.height width, height = m.width, m.height
cmd = ['convert', '-background', 'none', path] cmd = ['convert', '-background', 'none', path]
if scale_up:
if width < available_width:
r = available_width / width
width, height = available_width, int(height * r)
if width > available_width or height > available_height: if width > available_width or height > available_height:
width, height = fit_image(width, height, available_width, available_height) width, height = fit_image(width, height, available_width, available_height)
cmd += ['-resize', '{}x{}'.format(width, height)] cmd += ['-resize', '{}x{}'.format(width, height)]
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False) as outfile: with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False) as outfile:
run_imagemagick(path, cmd + [outfile.name]) run_imagemagick(path, cmd + [outfile.name])
# ImageMagick sometimes generated rgba images smaller than the specified
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
sz = os.path.getsize(outfile.name)
bytes_per_pixel = 3 if m.mode == 'rgb' else 4
expected_size = bytes_per_pixel * width * height
if sz < expected_size:
missing = expected_size - sz
if missing % (bytes_per_pixel * width) != 0:
raise SystemExit('ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
height -= missing // (bytes_per_pixel * width)
return outfile.name, width, height return outfile.name, width, height
@@ -238,6 +269,7 @@ def process(path, args):
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height
needs_scaling = m.width > available_width or m.height > available_height needs_scaling = m.width > available_width or m.height > available_height
needs_scaling = needs_scaling or args.scale_up
if m.fmt == 'png' and not needs_scaling: if m.fmt == 'png' and not needs_scaling:
outfile = path outfile = path
transmit_mode = 'f' transmit_mode = 'f'
@@ -246,7 +278,7 @@ def process(path, args):
else: else:
fmt = 24 if m.mode == 'rgb' else 32 fmt = 24 if m.mode == 'rgb' else 32
transmit_mode = 't' transmit_mode = 't'
outfile, width, height = convert(path, m, available_width, available_height) outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place) show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place)
if not args.place: if not args.place:
print() # ensure cursor is on a new line print() # ensure cursor is on a new line
@@ -261,17 +293,11 @@ def scan(d):
def detect_support(wait_for=10, silent=False): def detect_support(wait_for=10, silent=False):
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
if not silent: if not silent:
print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r') print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r')
sys.stdout.flush() sys.stdout.flush()
tty.setraw(fd)
try: try:
received = b'' received = b''
start_time = monotonic()
responses = {} responses = {}
def parse_responses(): def parse_responses():
@@ -282,29 +308,20 @@ def detect_support(wait_for=10, silent=False):
if iid not in responses: if iid not in responses:
responses[iid] = m.group(2) == b'OK' responses[iid] = m.group(2) == b'OK'
def read(): def more_needed(data):
nonlocal received nonlocal received
d = sys.stdin.buffer.read() received += data
if not d: # EOF
responses[1] = responses[2] = False
return
received += d
parse_responses() parse_responses()
return 1 not in responses or 2 not in responses
with NamedTemporaryFile() as f: with NamedTemporaryFile() as f:
f.write(b'abcd'), f.flush() f.write(b'abcd'), f.flush()
write_gr_cmd(dict(a='q', s=1, v=1, i=1), standard_b64encode(b'abcd')) write_gr_cmd(dict(a='q', s=1, v=1, i=1), standard_b64encode(b'abcd'))
write_gr_cmd(dict(a='q', s=1, v=1, i=2, t='f'), standard_b64encode(f.name.encode(fsenc))) write_gr_cmd(dict(a='q', s=1, v=1, i=2, t='f'), standard_b64encode(f.name.encode(fsenc)))
sel = selectors.DefaultSelector() read_with_timeout(more_needed, timeout=wait_for)
sel.register(sys.stdin, selectors.EVENT_READ, read)
while monotonic() - start_time < wait_for and 1 not in responses and 2 not in responses:
for key, mask in sel.select(0.1):
read()
finally: finally:
if not silent: if not silent:
sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush() sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush()
termios.tcsetattr(fd, termios.TCSADRAIN, old)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl)
detect_support.has_files = bool(responses.get(2)) detect_support.has_files = bool(responses.get(2))
return responses.get(1, False) return responses.get(1, False)
@@ -324,7 +341,7 @@ def main(args=sys.argv):
' Directories are scanned recursively for image files.') ' Directories are scanned recursively for image files.')
args, items = parse_args(args[1:], options_spec, 'image-file ...', msg, '{} icat'.format(appname)) args, items = parse_args(args[1:], options_spec, 'image-file ...', msg, '{} icat'.format(appname))
signal.signal(signal.SIGWINCH, lambda: screen_size(refresh=True)) signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
if not sys.stdout.isatty() or not sys.stdin.isatty(): if not sys.stdout.isatty() or not sys.stdin.isatty():
raise SystemExit( raise SystemExit(
'Must be run in a terminal, stdout and/or stdin is currently not a terminal' 'Must be run in a terminal, stdout and/or stdin is currently not a terminal'

24
kitty/key_encoding.py generated
View File

@@ -3,6 +3,7 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import string import string
from collections import namedtuple
from . import fast_data_types as defines from . import fast_data_types as defines
@@ -302,3 +303,26 @@ def update_encoding():
f.seek(0), f.truncate() f.seek(0), f.truncate()
f.write(nraw) f.write(nraw)
subprocess.check_call(['yapf', '-i', __file__]) subprocess.check_call(['yapf', '-i', __file__])
PRESS, REPEAT, RELEASE = 1, 2, 4
SHIFT, ALT, CTRL, SUPER = 1, 2, 4, 8
KeyEvent = namedtuple('KeyEvent', 'type mods key')
type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE}
mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
key_rmap = {}
g = globals()
for key_name, enc in ENCODING.items():
key_name = key_name.replace(' ', '_')
g[key_name] = key_name
key_rmap[enc] = key_name
del key_name, enc, g
enter_key = KeyEvent(PRESS, 0, ENCODING['ENTER'])
backspace_key = KeyEvent(PRESS, 0, ENCODING['BACKSPACE'])
def decode_key_event(text):
typ = type_map[text[1]]
mods = mod_map[text[2]]
key = key_rmap[text[3:5]]
return KeyEvent(typ, mods, key)

View File

@@ -11,6 +11,8 @@
#include "glfw-wrapper.h" #include "glfw-wrapper.h"
#include "control-codes.h" #include "control-codes.h"
static bool needs_special_handling[128 * 16] = {0};
const char* const char*
key_to_bytes(int glfw_key, bool smkx, bool extended, int mods, int action) { key_to_bytes(int glfw_key, bool smkx, bool extended, int mods, int action) {
if ((action & 3) == 3) return NULL; if ((action & 3) == 3) return NULL;
@@ -61,7 +63,7 @@ static inline bool
is_modifier_key(int key) { is_modifier_key(int key) {
switch(key) { switch(key) {
case GLFW_KEY_LEFT_SHIFT: case GLFW_KEY_LEFT_SHIFT:
case GLFW_KEY_RIGHT_SHIFT: case GLFW_KEY_RIGHT_SHIFT:
case GLFW_KEY_LEFT_ALT: case GLFW_KEY_LEFT_ALT:
case GLFW_KEY_RIGHT_ALT: case GLFW_KEY_RIGHT_ALT:
case GLFW_KEY_LEFT_CONTROL: case GLFW_KEY_LEFT_CONTROL:
@@ -83,7 +85,7 @@ get_localized_key(int key, int scancode) {
// key names {{{ // key names {{{
K('A', A); K('a', A); K('A', A); K('a', A);
K('B', B); K('b', B); K('B', B); K('b', B);
K('C', C); K('c', C); K('C', C); K('c', C);
K('D', D); K('d', D); K('D', D); K('d', D);
K('E', E); K('e', E); K('E', E); K('e', E);
K('F', F); K('f', F); K('F', F); K('f', F);
@@ -139,8 +141,10 @@ send_key_to_child(Window *w, int key, int mods, int action) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
const char *data = key_to_bytes(key, screen->modes.mDECCKM, screen->modes.mEXTENDED_KEYBOARD, mods, action); const char *data = key_to_bytes(key, screen->modes.mDECCKM, screen->modes.mEXTENDED_KEYBOARD, mods, action);
if (data) { if (data) {
if (screen->modes.mEXTENDED_KEYBOARD) write_escape_code_to_child(screen, APC, data + 1); if (screen->modes.mEXTENDED_KEYBOARD) {
else { if (*data == 1) schedule_write_to_child(w->id, (data + 1), 1);
else write_escape_code_to_child(screen, APC, data + 1);
} else {
if (*data > 2 && data[1] == 0x1b && data[2] == '[') { // CSI code if (*data > 2 && data[1] == 0x1b && data[2] == '[') { // CSI code
write_escape_code_to_child(screen, CSI, data + 3); write_escape_code_to_child(screen, CSI, data + 3);
} else schedule_write_to_child(w->id, (data + 1), *data); } else schedule_write_to_child(w->id, (data + 1), *data);
@@ -176,7 +180,9 @@ on_key_input(int key, int scancode, int action, int mods) {
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
} }
#ifdef __APPLE__ #ifdef __APPLE__
if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods)) return; if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods) && !(
key == GLFW_KEY_UP || key == GLFW_KEY_DOWN || key == GLFW_KEY_LEFT || key == GLFW_KEY_RIGHT) // alt+arrow keys do not generate text on macOS
) return;
#endif #endif
if ( if (
action == GLFW_PRESS || action == GLFW_PRESS ||
@@ -188,12 +194,14 @@ on_key_input(int key, int scancode, int action, int mods) {
} }
void void
fake_scroll(bool upwards) { fake_scroll(int amount, bool upwards) {
Window *w = active_window(); Window *w = active_window();
if (!w) return; if (!w) return;
Screen *screen = w->render_data.screen; int key = upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN;
send_key_to_child(w, upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN, 0, GLFW_PRESS); while (amount-- > 0) {
if (screen->modes.mEXTENDED_KEYBOARD) send_key_to_child(w, upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN, 0, GLFW_RELEASE); send_key_to_child(w, key, 0, GLFW_PRESS);
send_key_to_child(w, key, 0, GLFW_RELEASE);
}
} }
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
@@ -209,7 +217,7 @@ PYWRAP1(key_to_bytes) {
} }
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
M(key_to_bytes, METH_VARARGS), M(key_to_bytes, METH_VARARGS),
{0} {0}
}; };

537
kitty/keys.h generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,10 +26,16 @@ def modify_complex_key(name, amt):
control_codes = {} control_codes = {}
smkx_key_map = {} smkx_key_map = {}
alt_codes = {defines.GLFW_KEY_TAB: b'\033\t', defines.GLFW_KEY_ENTER: b'\033\r', defines.GLFW_KEY_ESCAPE: b'\033\033', defines.GLFW_KEY_BACKSPACE: b'\033\177'} alt_codes = {
defines.GLFW_KEY_TAB: b'\033\t',
defines.GLFW_KEY_ENTER: b'\033\r',
defines.GLFW_KEY_ESCAPE: b'\033\033',
defines.GLFW_KEY_BACKSPACE: b'\033\177'
}
shift_alt_codes = alt_codes.copy() shift_alt_codes = alt_codes.copy()
shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt') 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) 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
for kf, kn in { for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1', defines.GLFW_KEY_UP: 'kcuu1',
@@ -121,13 +127,17 @@ action_map = {
def extended_key_event(key, mods, action): def extended_key_event(key, mods, action):
if key >= defines.GLFW_KEY_LAST or key == defines.GLFW_KEY_UNKNOWN or ( if key >= defines.GLFW_KEY_LAST or key == defines.GLFW_KEY_UNKNOWN or (
# Shifted printable key should be handled by on_text_input() # Shifted printable key should be handled by on_text_input()
mods == defines.GLFW_MOD_SHIFT and 32 <= key <= 126 mods <= defines.GLFW_MOD_SHIFT and 32 <= key <= 126
): ):
return b'' return b''
if mods == 0 and key in ( if mods == 0 and key in (
defines.GLFW_KEY_BACKSPACE, defines.GLFW_KEY_ENTER defines.GLFW_KEY_BACKSPACE, defines.GLFW_KEY_ENTER
): ):
if action == defines.GLFW_RELEASE:
return b''
return smkx_key_map[key] return smkx_key_map[key]
if key in (defines.GLFW_KEY_LEFT_SHIFT, defines.GLFW_KEY_RIGHT_SHIFT):
return b''
name = KEY_MAP.get(key) name = KEY_MAP.get(key)
if name is None: if name is None:
return b'' return b''
@@ -183,6 +193,18 @@ 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()}
def key_to_bytes(key, smkx, extended, mods, action): def key_to_bytes(key, smkx, extended, mods, action):
if extended: if extended:
@@ -191,6 +213,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
if mods == defines.GLFW_MOD_CONTROL and key in control_codes: if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
# Map Ctrl-key to ascii control code # Map Ctrl-key to ascii control code
data.extend(control_codes[key]) data.extend(control_codes[key])
elif mods == ctrl_shift_mod and key in CTRL_SHIFT_KEYS:
data.extend(CTRL_SHIFT_KEYS[key])
elif mods in alt_mods: elif mods in alt_mods:
if key in alt_codes: if key in alt_codes:
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key]) data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
@@ -235,7 +259,6 @@ def generate_key_table():
w('#include <stdint.h>') w('#include <stdint.h>')
w('#include <stdbool.h>') w('#include <stdbool.h>')
w('#include <limits.h>') w('#include <limits.h>')
w('static bool needs_special_handling[%d] = {0};' % (128 * 16))
number_of_keys = defines.GLFW_KEY_LAST + 1 number_of_keys = defines.GLFW_KEY_LAST + 1
w('// map glfw key numbers to 7-bit numbers for compact data storage') w('// map glfw key numbers to 7-bit numbers for compact data storage')
w('static const uint8_t key_map[%d] = {' % number_of_keys) w('static const uint8_t key_map[%d] = {' % number_of_keys)
@@ -275,13 +298,13 @@ def generate_key_table():
i += 1 i += 1
ind('case {}:'.format(mode)) ind('case {}:'.format(mode))
i += 1 i += 1
ind('switch(action & 3) { case 3: return NULL; ') ind('switch(action & 3) { case 3: return NULL;')
for action in (defines.GLFW_RELEASE, defines.GLFW_PRESS, defines.GLFW_REPEAT): for action in (defines.GLFW_RELEASE, defines.GLFW_PRESS, defines.GLFW_REPEAT):
i += 1 i += 1
ind('case {}: // {}'.format(action, 'RELEASE PRESS REPEAT'.split()[action])) ind('case {}: // {}'.format(action, 'RELEASE PRESS REPEAT'.split()[action]))
i += 1 i += 1
if action != defines.GLFW_RELEASE or mode == 'EXTENDED': if action != defines.GLFW_RELEASE or mode == 'EXTENDED':
ind('switch (mods & 0xf) { ') ind('switch (mods & 0xf) {')
i += 1 i += 1
for mods in range(16): for mods in range(16):
key_bytes = {} key_bytes = {}

115
kitty/kittens.c Normal file
View File

@@ -0,0 +1,115 @@
/*
* kittens.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
static PyObject*
parse_input_from_terminal(PyObject *self UNUSED, PyObject *args) {
enum State { NORMAL, ESC, CSI, ST, ESC_ST };
enum State state = NORMAL;
PyObject *uo, *text_callback, *dcs_callback, *csi_callback, *osc_callback, *pm_callback, *apc_callback, *callback;
int inbp = 0;
if (!PyArg_ParseTuple(args, "OOOOOOUp", &text_callback, &dcs_callback, &csi_callback, &osc_callback, &pm_callback, &apc_callback, &uo, &inbp)) return NULL;
Py_ssize_t sz = PyUnicode_GET_LENGTH(uo), pos = 0, start = 0, count = 0, consumed = 0;
callback = text_callback;
int kind = PyUnicode_KIND(uo);
void *data = PyUnicode_DATA(uo);
bool in_bracketed_paste_mode = inbp != 0;
#define CALL(cb, s_, num_) {\
PyObject *fcb = cb; \
Py_ssize_t s = s_, num = num_; \
if (in_bracketed_paste_mode && fcb != text_callback) { \
fcb = text_callback; num += 2; s -= 2; \
} \
if (num > 0) { \
PyObject *ret = PyObject_CallFunction(fcb, "N", PyUnicode_Substring(uo, s, s + num)); \
if (ret == NULL) return NULL; \
Py_DECREF(ret); \
} \
consumed = s_ + num_; \
count = 0; \
}
START_ALLOW_CASE_RANGE;
while (pos < sz) {
Py_UCS4 ch = PyUnicode_READ(kind, data, pos);
switch(state) {
case NORMAL:
if (ch == 0x1b) {
state = ESC;
CALL(text_callback, start, count);
start = pos;
} else count++;
break;
case ESC:
start = pos;
count = 0;
switch(ch) {
case 'P':
state = ST; callback = dcs_callback; break;
case '[':
state = CSI; callback = csi_callback; break;
case ']':
state = ST; callback = osc_callback; break;
case '^':
state = ST; callback = pm_callback; break;
case '_':
state = ST; callback = apc_callback; break;
default:
state = NORMAL; break;
}
break;
case CSI:
count++;
switch (ch) {
case 'a' ... 'z':
case 'A' ... 'Z':
case '@':
case '`':
case '{':
case '|':
case '}':
case '~':
#define IBP(w) ch == '~' && PyUnicode_READ(kind, data, start + 1) == '2' && PyUnicode_READ(kind, data, start + 2) == '0' && PyUnicode_READ(kind, data, start + 3) == w
if (IBP('1')) in_bracketed_paste_mode = false;
CALL(callback, start + 1, count);
if (IBP('0')) in_bracketed_paste_mode = true;
#undef IBP
state = NORMAL;
start = pos + 1;
break;
}
break;
case ESC_ST:
if (ch == '\\') {
CALL(callback, start + 1, count);
state = NORMAL; start = pos + 1;
consumed += 2;
} else count += 2;
break;
case ST:
if (ch == 0x1b) { state = ESC_ST; }
else count++;
break;
}
pos++;
}
if (state == NORMAL && count > 0) CALL(text_callback, start, count);
return PyUnicode_Substring(uo, consumed, sz);
END_ALLOW_CASE_RANGE;
#undef CALL
}
static PyMethodDef module_methods[] = {
METHODB(parse_input_from_terminal, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_kittens(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@@ -8,7 +8,7 @@
# bold_font Operator Mono Medium # bold_font Operator Mono Medium
# italic_font Operator Mono Book Italic # italic_font Operator Mono Book Italic
# bold_italic_font Operator Mono Medium Italic # bold_italic_font Operator Mono Medium Italic
# #
# You can get a list of full family names available on your computer by running # You can get a list of full family names available on your computer by running
# kitty list-fonts # kitty list-fonts
font_family monospace font_family monospace
@@ -24,15 +24,16 @@ font_size 11.0
font_size_delta 2 font_size_delta 2
# Adjust the line height. # Adjust the cell dimensions.
# You can use either numbers, which are interpreted as pixels or percentages # You can use either numbers, which are interpreted as pixels or percentages
# (number followed by %), which are interpreted as percentages of the # (number followed by %), which are interpreted as percentages of the
# unmodified line height. You can use negative pixels or percentages less than # unmodified values. You can use negative pixels or percentages less than
# 100% to reduce line height (but this might cause rendering artifacts). # 100% to reduce sizes (but this might cause rendering artifacts).
adjust_line_height 0 adjust_line_height 0
adjust_column_width 0
# Change the sizes of the lines used for the box drawing unicode characters # Change the sizes of the lines used for the box drawing unicode characters
# These values are in pts. They will be scaled by the monitor DPI to arrive at # These values are in pts. They will be scaled by the monitor DPI to arrive at
# a pixel value. There must be four values corresponding to thin, normal, thick, # a pixel value. There must be four values corresponding to thin, normal, thick,
# and very thick lines; # and very thick lines;
box_drawing_scale 0.001, 1, 1.5, 2 box_drawing_scale 0.001, 1, 1.5, 2
@@ -73,7 +74,7 @@ cursor #cccccc
cursor_shape block cursor_shape block
# The interval (in seconds) at which to blink the cursor. Set to zero to # The interval (in seconds) at which to blink the cursor. Set to zero to
# disable blinking. Note that numbers smaller than repaint_delay will be # disable blinking. Note that numbers smaller than repaint_delay will be
# limited to repaint_delay. # limited to repaint_delay.
cursor_blink_interval 0.5 cursor_blink_interval 0.5
@@ -89,9 +90,6 @@ scrollback_lines 2000
# handle ANSI escape sequences for colors and text formatting. # handle ANSI escape sequences for colors and text formatting.
scrollback_pager less +G -R scrollback_pager less +G -R
# When viewing scrollback in a new window, put it in a new tab as well
scrollback_in_new_tab no
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative # Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
# numbers to change scroll direction. # numbers to change scroll direction.
wheel_scroll_multiplier 5.0 wheel_scroll_multiplier 5.0
@@ -104,6 +102,10 @@ click_interval 0.5
# database will be matched. # database will be matched.
select_by_word_characters :@-./_~?&=%+# select_by_word_characters :@-./_~?&=%+#
# The shell program to execute. The default value of . means
# to use whatever shell is set as the default shell for the current user.
shell .
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to # Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
# zero to disable mouse cursor hiding. # zero to disable mouse cursor hiding.
mouse_hide_wait 3.0 mouse_hide_wait 3.0
@@ -125,7 +127,9 @@ initial_window_height 400
# Delay (in milliseconds) between screen updates. Decreasing it, increases # Delay (in milliseconds) between screen updates. Decreasing it, increases
# frames-per-second (FPS) at the cost of more CPU usage. The default value # frames-per-second (FPS) at the cost of more CPU usage. The default value
# yields ~100 FPS which is more than sufficient for most uses. # yields ~100 FPS which is more than sufficient for most uses. Note that to
# actually achieve 100FPS you have to either set sync_to_monitor to no or use a
# monitor with a high refresh rate.
repaint_delay 10 repaint_delay 10
# Delay (in milliseconds) before input from the program running in the terminal # Delay (in milliseconds) before input from the program running in the terminal
@@ -135,6 +139,13 @@ repaint_delay 10
# screen updates will be drawn. # screen updates will be drawn.
input_delay 3 input_delay 3
# Sync screen updates to the refresh rate of the monitor. This prevents
# tearing (https://en.wikipedia.org/wiki/Screen_tearing) when scrolling. However,
# it limits the rendering speed to the refresh rate of your monitor. With a
# very high speed mouse/high keyboard repeat rate, you may notice some slight input latency.
# If so, set this to no.
sync_to_monitor yes
# Visual bell duration. Flash the screen when a bell occurs for the specified number of # Visual bell duration. Flash the screen when a bell occurs for the specified number of
# seconds. Set to zero to disable. # seconds. Set to zero to disable.
visual_bell_duration 0.0 visual_bell_duration 0.0
@@ -149,14 +160,16 @@ open_url_modifiers ctrl+shift
# use the operating system's default URL handler. # use the operating system's default URL handler.
open_url_with default open_url_with default
# Choose whether to use the system implementation of wcwidth() (used to # The modifiers to use rectangular selection (i.e. to select text in a
# control how many cells a character is rendered in). If you use the system # rectangular block with the mouse)
# implementation, then kitty and any programs running in it will agree. The rectangle_select_modifiers ctrl+alt
# problem is that system implementations often are based on outdated unicode
# standards and get the width of many characters, such as emoji, wrong. So if
# you are using kitty with programs that have their own up-to-date wcwidth() # Allow other programs to control kitty. If you turn this on other programs can
# implementation, set this option to no, otherwise set it to yes. # control all aspects of kitty, including sending text to kitty windows,
use_system_wcwidth no # opening new windows, closing windows, reading the content of windows, etc.
# Note that this even works over ssh connections.
allow_remote_control no
# The value of the TERM environment variable to set # The value of the TERM environment variable to set
term xterm-kitty term xterm-kitty
@@ -177,14 +190,23 @@ active_border_color #00ff00
# The color for the border of inactive windows # The color for the border of inactive windows
inactive_border_color #cccccc inactive_border_color #cccccc
# Tab-bar customization # Fade the text in inactive windows by the specified amount (a number between
# zero and one, with 0 being fully faded).
inactive_text_alpha 1.0
# Which edge to show the tab bar on, top or bottom
tab_bar_edge bottom
# The separator between tabs in the tab bar
tab_separator " ┇"
# Tab bar colors and styles
active_tab_foreground #000 active_tab_foreground #000
active_tab_background #eee active_tab_background #eee
active_tab_font_style bold-italic active_tab_font_style bold-italic
inactive_tab_foreground #444 inactive_tab_foreground #444
inactive_tab_background #999 inactive_tab_background #999
inactive_tab_font_style normal inactive_tab_font_style normal
tab_separator " ┇"
# The 16 terminal colors. There are 8 basic colors, each color has a dull and # The 16 terminal colors. There are 8 basic colors, each color has a dull and
@@ -192,7 +214,7 @@ tab_separator " ┇"
# black # black
color0 #000000 color0 #000000
color8 #4d4d4d color8 #767676
# red # red
color1 #cc0403 color1 #cc0403
@@ -226,10 +248,10 @@ color15 #ffffff
# Key mapping # Key mapping
# For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html # For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html
# For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html # For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html
# #
# You can use the special action no_op to unmap a keyboard shortcut that is # You can use the special action no_op to unmap a keyboard shortcut that is
# assigned in the default configuration. # assigned in the default configuration.
# #
# You can combine multiple actions to be triggered by a single shortcut, using the # You can combine multiple actions to be triggered by a single shortcut, using the
# syntax below: # syntax below:
# map key combine <separator> action1 <separator> action2 <separator> action3 ... # map key combine <separator> action1 <separator> action2 <separator> action3 ...
@@ -246,7 +268,7 @@ map shift+insert paste_from_selection
# pass_selection_to_program. By default, the system's open program is used, but # pass_selection_to_program. By default, the system's open program is used, but
# you can specify your own, for example: # you can specify your own, for example:
# map ctrl+shift+o pass_selection_to_program firefox # map ctrl+shift+o pass_selection_to_program firefox
map ctrl+shift+o pass_selection_to_program map ctrl+shift+o pass_selection_to_program
# Scrolling # Scrolling
map ctrl+shift+up scroll_line_up map ctrl+shift+up scroll_line_up
@@ -260,8 +282,8 @@ map ctrl+shift+end scroll_end
map ctrl+shift+h show_scrollback map ctrl+shift+h show_scrollback
# Window management # Window management
map ctrl+shift+enter new_window map ctrl+shift+enter new_window
map ctrl+shift+n new_os_window map ctrl+shift+n new_os_window
map ctrl+shift+w close_window map ctrl+shift+w close_window
map ctrl+shift+] next_window map ctrl+shift+] next_window
map ctrl+shift+[ previous_window map ctrl+shift+[ previous_window
@@ -278,14 +300,21 @@ map ctrl+shift+7 seventh_window
map ctrl+shift+8 eighth_window map ctrl+shift+8 eighth_window
map ctrl+shift+9 ninth_window map ctrl+shift+9 ninth_window
map ctrl+shift+0 tenth_window map ctrl+shift+0 tenth_window
# You can also open a new window running an arbitrary program, for example: # You can open a new window running an arbitrary program, for example:
# map ctrl+shift+y new_window mutt # map ctrl+shift+y new_window mutt
# You can also pass the current selection to the new program by using the @selection placeholder #
# You can pass the current selection to the new program by using the @selection placeholder
# map ctrl+shift+y new_window less @selection # map ctrl+shift+y new_window less @selection
# Finally, you can even send the contents of the current screen + history buffer as stdin using #
# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes) # You can even send the contents of the current screen + history buffer as stdin using
# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes).
# For only the current screen, use @screen or @ansi_screen.
# For example, the following command opens the scrollback buffer in less in a new window. # For example, the following command opens the scrollback buffer in less in a new window.
# map ctrl+shift+y new_window @ansi less +G -R # map ctrl+shift+y new_window @ansi less +G -R
#
# You can open a new window with the current working directory set to the
# working directory of the current window using
# map ctrl+alt+enter new_window_with_cwd
# Tab management # Tab management
@@ -301,7 +330,7 @@ map ctrl+shift+, move_tab_backward
# map ctrl+alt+2 goto_tab 2 # map ctrl+alt+2 goto_tab 2
# Just as with new_window above, you can also pass the name of arbitrary # Just as with new_window above, you can also pass the name of arbitrary
# commands to run when using new_tab. # commands to run when using new_tab and use new_tab_with_cwd.
# Miscellaneous # Miscellaneous
@@ -309,6 +338,12 @@ map ctrl+shift+equal increase_font_size
map ctrl+shift+minus decrease_font_size map ctrl+shift+minus decrease_font_size
map ctrl+shift+backspace restore_font_size map ctrl+shift+backspace restore_font_size
map ctrl+shift+f11 toggle_fullscreen map ctrl+shift+f11 toggle_fullscreen
map ctrl+shift+u input_unicode_character
# Open a currently visible URL using the keyboard. The program used ot open the URL is specified in open_url_with.
# You can customize how the URLs are detected and opened by specifying command line options to
# url_hints. For example:
# map ctrl+shift+e run_simple_kitten text url_hints --program firefox --regex "http://[^ ]+"
map ctrl+shift+e run_simple_kitten text url_hints
# Sending arbitrary text on shortcut key presses # Sending arbitrary text on shortcut key presses
# You can tell kitty to send arbitrary (UTF-8) encoded text to # You can tell kitty to send arbitrary (UTF-8) encoded text to
@@ -334,20 +369,33 @@ map ctrl+shift+f11 toggle_fullscreen
# in hexadecimal>. You can specify multiple code points, separated by commas # in hexadecimal>. You can specify multiple code points, separated by commas
# and ranges separated by hyphens. symbol_map itself can be specified multiple times. # and ranges separated by hyphens. symbol_map itself can be specified multiple times.
# Syntax is: # Syntax is:
# #
# symbol_map codepoints Font Family Name # symbol_map codepoints Font Family Name
# #
# For example: # For example:
# #
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols # symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
# OS specific tweaks # OS specific tweaks
# 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
# that this is a security risk, as all programs, including websites open in your
# browser can read the contents of the clipboard.
copy_on_select no
# Change the color of the kitty window's titlebar on macOS. A value of "system"
# means to use the default system color, a value of "background" means to use
# the default background color and finally you can use an arbitrary color, such
# as #12af59 or "red".
macos_titlebar_color system
# Hide the kitty window's title bar on macOS. # Hide the kitty window's title bar on macOS.
macos_hide_titlebar no macos_hide_titlebar no
# Use the option key as an alt key. With this set to no, kitty will use # Use the option key as an alt key. With this set to no, kitty will use
# the macOS native Option+Key = unicode character behavior. This will # the macOS native Option+Key = unicode character behavior. This will
# break any Alt+key keyboard shortcuts in your terminal programs, but you # break any Alt+key keyboard shortcuts in your terminal programs, but you
# can use the macOS unicode input technique. # can use the macOS unicode input technique.
@@ -356,9 +404,3 @@ macos_option_as_alt yes
# The number is a percentage of maximum volume. # The number is a percentage of maximum volume.
# See man XBell for details. # See man XBell for details.
x11_bell_volume 80 x11_bell_volume 80
# Prefer color emoji fonts when available. Note that this only works
# on systems such as Linux that use fontconfig. On other OSes, the emoji
# font used is system dependent. It can be overriden using symbol_map in the kitty
# configuration.
prefer_color_emoji yes

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