Compare commits

...

437 Commits

Author SHA1 Message Date
Kovid Goyal
361b23e3f1 Fix long text docs for copy_to_clipboard 2019-01-04 09:37:59 +05:30
Kovid Goyal
8feccdf63f version 0.13.2 2019-01-04 09:26:51 +05:30
Kovid Goyal
ed905fcf6d Add a new `copy_or_interrupt action that can be mapped to kbd:ctrl+c`. It will copy if there is a selection and interrupt otherwise
Fixes #1286
2019-01-04 09:21:29 +05:30
Kovid Goyal
50a6abaac1 Also fix move_tab to preserve existing tab order 2019-01-04 06:59:38 +05:30
Kovid Goyal
718f7e77a1 Use the current working directory of the foreground process for the *_with_cwd actions that open a new window with the current working directory. 2019-01-03 13:29:52 +05:30
Kovid Goyal
c4aeb1adba Report the current foreground processes as well as the original child process, when using kitty @ ls 2019-01-03 13:14:39 +05:30
Kovid Goyal
d9c69f0a54 Merge branch 'sentexttab' of https://github.com/grimpy/kitty 2019-01-01 18:29:10 +05:30
Jo De Boeck
5afdbbe6f1 Add match-tab for send-text
Signed-off-by: Jo De Boeck <deboeck.jo@gmail.com>
2019-01-01 14:39:28 +02:00
Kovid Goyal
4273cb35bf Linux: Dont fail on systems with fonts that have non-UTF-8 names. Fixes #1281 2018-12-31 07:31:17 +05:30
Kovid Goyal
89be7a031d Fix #1278 2018-12-30 15:22:05 +05:30
Kovid Goyal
06c5127856 Fix #1275 2018-12-29 08:08:24 +05:30
Kovid Goyal
fd298256d7 Merge branch 'more_switch_cases' of https://github.com/Luflosi/kitty 2018-12-28 14:57:18 +05:30
Luflosi
e0246044b7 Add remaining momentum scroll phases to switch statement
According to https://developer.apple.com/documentation/appkit/nsevent/1533550-phase the momentum phase may end with "cancelled" instead of "ended"
2018-12-28 09:03:16 +01:00
Kovid Goyal
2f677990b2 Fix glfwSetGamma generating ramps of invalid sizes
3531c320af
2018-12-28 08:19:17 +05:30
Kovid Goyal
da2e4c8503 ... 2018-12-28 08:14:08 +05:30
Kovid Goyal
a1eb236616 Fix glfwGetGammaRamp error handling
751c6f9a27
2018-12-28 08:13:28 +05:30
Kovid Goyal
11186735c2 Couple of trivial commits from glfw upstream
064dfaa549
751c6f9a27
2018-12-28 08:06:26 +05:30
Kovid Goyal
a038f62a5b Fix #1272 2018-12-28 07:52:29 +05:30
Kovid Goyal
f45acc055e Merge branch 'more_event_phases' of https://github.com/Luflosi/kitty 2018-12-28 07:45:43 +05:30
Luflosi
43a25c49c4 Micro optimization 2018-12-27 15:19:22 +01:00
Luflosi
e47e2b2560 Move check for zero scroll distance to scroll_event() 2018-12-27 15:17:18 +01:00
Luflosi
7652065134 Add more momentum scroll phases 2018-12-27 15:12:57 +01:00
Kovid Goyal
d0da418e49 ... 2018-12-27 08:19:06 +05:30
Kovid Goyal
77e72cfd9a Fix #1270 2018-12-27 08:05:31 +05:30
Kovid Goyal
7b2ecb0498 Forgot a break 2018-12-27 08:02:24 +05:30
Kovid Goyal
318c5bca67 Merge branch 'fix_bitshift' of https://github.com/Luflosi/kitty 2018-12-27 07:57:46 +05:30
Luflosi
d52da934ca Fix typo in bitshift 2018-12-26 22:47:23 +01:00
Kovid Goyal
21951e3668 Add explicit default case 2018-12-26 20:39:39 +05:30
Kovid Goyal
64bef38813 macOS: Fix momentum scrolling continuing when changing the active window/tab
Fixes #1267
2018-12-26 20:39:04 +05:30
Kovid Goyal
fe67e3dde7 Cocoa: Remove subclassing of NSApplication
From upstream: 88c5edb409
2018-12-26 12:29:38 +05:30
Kovid Goyal
fc5e74e457 Report momentum scrolling status of scroll events on cocoa 2018-12-26 11:03:36 +05:30
Kovid Goyal
fdbd8df7b2 Add patch from upstream to report caps lock key presses on cocoa
c4903d9267
2018-12-26 10:01:02 +05:30
Kovid Goyal
246972b59f Add a patch from upstream, altered to not change behavior 2018-12-26 09:58:12 +05:30
Kovid Goyal
6057296499 Cocoa: Move to modern Objective-C literals 2018-12-26 09:45:24 +05:30
Kovid Goyal
b4b2207b93 Merge branch 'issue_1176' of https://github.com/daniel-pfeiffer/kitty
Add a FAQ entry about terminfo and sudo/su
2018-12-25 08:48:57 +05:30
Kovid Goyal
b881364c50 Merge branch 'mouse_scroll_acceleration_alternate_screen_mode' of https://github.com/Luflosi/kitty 2018-12-23 20:41:48 +05:30
Luflosi
76059882c4 Allow for scrolling acceleration in alternate screen mode when using low-resolution input devices like mice 2018-12-23 14:37:50 +01:00
Kovid Goyal
be71f5f1f4 Merge branch 'fix_downward_scrolling' of https://github.com/Luflosi/kitty 2018-12-23 17:25:46 +05:30
Luflosi
021dfcad39 Fix broken downward scrolling 2018-12-23 12:49:00 +01:00
Kovid Goyal
4e7498b854 Fix using the `new_tab !neighbor` action changing the order of the non-neighboring tabs
Fixes #1256
2018-12-23 08:45:34 +05:30
Kovid Goyal
494035c94b remove redundant code 2018-12-23 08:15:57 +05:30
Kovid Goyal
9c4230ad9c Add a comment for why we reset SIGPIPE 2018-12-20 09:29:09 +05:30
Kovid Goyal
056c2c12bb Explicitly reset the SIGPIPE handler in child processes
Fixes #1253
2018-12-20 09:06:21 +05:30
Kovid Goyal
a97c440872 Merge branch 'fix-resize-window-doc' of https://github.com/eepp/kitty 2018-12-20 05:50:37 +05:30
Kovid Goyal
653d823175 Merge branch 'doc-change-font-size-current' of https://github.com/eepp/kitty 2018-12-20 05:49:16 +05:30
Daniel Pfeiffer
c2a2dbc88c Help others avoid issue 1176. 2018-12-20 00:31:57 +01:00
Philippe Proulx
77492d31cf change_font_size current ... changes the current OS window's font size
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
2018-12-19 11:51:05 -05:00
Philippe Proulx
aaa609109e Fix resize_window documentation example
Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
2018-12-19 11:34:15 -05:00
Kovid Goyal
2367fad90b Merge branch 'resize-window-cfg' of https://github.com/eepp/kitty 2018-12-19 11:05:31 +05:30
Kovid Goyal
aaa521bd24 Merge branch 'sigint' of https://github.com/jacwah/kitty 2018-12-19 10:07:11 +05:30
Philippe Proulx
d22686da10 Add resize_window configuration action
This patch adds the `resize_window` configuration action, which has the
following arguments:

1. Quality of resize amongst `wider`, `narrower`, `taller`, and
   `shorter` (mandatory).
2. Increment in number of cells (optional, default: 1).

This makes it possible to configure keys as such:

    map ctrl+shift+left resize_window narrower
    map ctrl+shift+right resize_window wider
    map ctrl+shift+up resize_window taller
    map ctrl+shift+down resize_window shorter

and have a behaviour which is somewhat close to Terminator's.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
2018-12-18 20:33:21 -05:00
Jacob Wahlgren
48b93ab28f macOS: Exit on SIGINT
With the default macos_quit_when_last_window_closed no setting, Kitty
would not exit when receiving a SIGINT signal (e.g. from Ctrl-C).
2018-12-19 01:52:47 +01:00
Kovid Goyal
703a1b83a8 macOS: Fix scrolling very slowly with wheel mice not working
Fixes #1238
2018-12-18 11:15:22 +05:30
Kovid Goyal
c9441bd3e2 Add a changelog entry for the dock menu item 2018-12-18 11:00:07 +05:30
Kovid Goyal
9de6d619ee Merge branch 'dockmenu' of https://github.com/jacwah/kitty
Fixes #1242
2018-12-18 10:59:13 +05:30
Jacob Wahlgren
9aae31ef2f macOS: Add "New OS Window" to dock menu
Discussed in #1242.
2018-12-17 23:53:43 +01:00
Kovid Goyal
dca04c549b Forgot to regenerate keys table with plus key. Fixes #1246 2018-12-18 03:03:35 +05:30
Kovid Goyal
69e76d6eeb Frogot to ad dplus key to _glfwGetKeyName 2018-12-17 21:16:57 +05:30
Kovid Goyal
b4a93c67c9 + key change also works on macOS apparently 2018-12-17 13:57:20 +05:30
Kovid Goyal
f098e6c973 Use -std=c11 when building
This is needed for harfbuzz 2.0 on macOS. -std=c11 was supported as of
gcc 4.7 released in 2014. So hopefully this wont break compilation on any
systems otherwise capable of running kitty. Fixes #1196
2018-12-17 08:41:06 +05:30
Kovid Goyal
f8ebba3954 Fix changing cursor_text_color via remote control not working
Fixes #1229
2018-12-16 17:12:49 +05:30
Kovid Goyal
37ee693de2 Fix Private use Unicode area characters followed by spaces at the end of text not being rendered correctly
Fixes #1210
2018-12-16 13:10:47 +05:30
Kovid Goyal
f77ad2b41d Add a couple of extra lib names for libstartup-notification.so
Fixes #1235
2018-12-14 23:01:49 +05:30
Kovid Goyal
e99eb2e72b Handle malformed tab title templates gracefully 2018-12-13 13:43:22 +05:30
Kovid Goyal
86b89072f3 ... 2018-12-13 13:35:17 +05:30
Kovid Goyal
022bee0560 Add a new option tab_title_template to control how tab titles are formatted.
In particular the template can be used to display the tab number next to the title
Merge branch 'tab-title-index' of https://github.com/nerdrew/kitty
2018-12-13 13:33:06 +05:30
Andrew Lazarus
204ea44946 add option to show tab index in tab_bar title 2018-12-12 23:21:08 -08:00
Kovid Goyal
3f2489b306 Linux: Handle keyboards with a "+" key
Adds a plus key to glfw so that it can be mapped as a shortcut. Fixes #1224
2018-12-13 09:56:30 +05:30
Kovid Goyal
291a2d6295 Merge branch 'kitten-stdin-docs' of https://github.com/nerdrew/kitty 2018-12-12 23:07:48 +05:30
Andrew Lazarus
b244d13e8a update docs for kitten STDIN 2018-12-12 09:20:41 -08:00
Kovid Goyal
b33324d07c Merge branch 'patch-1' of https://github.com/voxadam/kitty 2018-12-12 17:41:18 +05:30
Adam Hunt
ae2a641db5 Typo 2018-12-12 03:43:08 -08:00
Kovid Goyal
1cac757365 Merge branch 'yk-terminfo' of https://github.com/yurikhan/kitty 2018-12-12 06:26:45 +05:30
Yuri Khan
992de77040 Declare more function and editing keys with modifiers
Resolves #1220.
2018-12-11 23:42:59 +07:00
Kovid Goyal
f6b5e54890 Fix failing test 2018-12-11 10:08:52 +05:30
Kovid Goyal
d20c65ef80 Graphics protocol: Only delete temporary image-data files if they are in a known temporary file location 2018-12-11 10:00:43 +05:30
Kovid Goyal
4a8562a85f Fix setting background_opacity causing window margins/padding to be slightly different shade from background
Fixes #1221
2018-12-11 08:36:03 +05:30
Kovid Goyal
1ba7b5f5a3 version 0.13.1 2018-12-06 13:23:33 +05:30
Kovid Goyal
f895b7f8cb ... 2018-12-06 13:19:58 +05:30
Kovid Goyal
16542e96bf Fix resizing window smaller and then restoring causing some wrapped lines to not be properly unwrapped
Fix #1206
2018-12-06 12:56:24 +05:30
Kovid Goyal
3005b8b9d6 ... 2018-12-06 11:05:11 +05:30
Kovid Goyal
35653ef4b4 Linux: Fix a regression in the previous release that caused automatic selection of fonts when using aliases such as "monospace" to not work
Fix #1209
2018-12-06 08:42:13 +05:30
Kovid Goyal
36b3582825 Fix passing input via the pipe action to a program without a window not working. 2018-12-06 08:20:25 +05:30
Kovid Goyal
c17c801a31 Detect URL when clicking to activate URL
Makes the case where the screen contents have changed
between the last mouse move and the click work. See #1203
2018-12-05 14:43:01 +05:30
Kovid Goyal
460c353898 version 0.13.0 2018-12-05 14:21:26 +05:30
Kovid Goyal
3fa7007e91 Use the Wu line anti-alias algorithm for drawing undercurls
Fixes #853
2018-12-05 14:02:21 +05:30
Kovid Goyal
4cf6373203 Generate OpenGL bindings with updated glad
See #1179
2018-12-04 16:31:26 +05:30
Kovid Goyal
e0b3bb1830 Move URL parser enum into parser function 2018-12-04 06:59:03 +05:30
Kovid Goyal
47ec372c30 Fix hover detection of URLs not working when hovering over the first colon and slash characters in short URLs
Fixes #1201
2018-12-04 06:52:26 +05:30
Kovid Goyal
d5366a421f ... 2018-12-03 21:35:03 +05:30
Kovid Goyal
3d5977669e Micro optimization 2018-12-03 20:04:58 +05:30
Kovid Goyal
36da668e6f Forgot to fill bitmap offset data when rendering colored glyphs 2018-12-03 20:00:24 +05:30
Kovid Goyal
052913c656 Merge branch 'typo' of https://github.com/Luflosi/kitty 2018-12-03 15:38:18 +05:30
Luflosi
0ef055a2b2 Fix typo in docs 2018-12-03 10:58:23 +01:00
Kovid Goyal
a7bd6922e5 Follow xterm's behavior for the menu key
Fixes #597
Also generate the modified variants of the function keys.
2018-12-03 14:04:26 +05:30
Kovid Goyal
8d9ba4a772 Handle ctrl-c while paging help gracefully 2018-12-03 13:53:13 +05:30
Kovid Goyal
222beedc2a Fix freetype based rendering of non-hinted fonts at small sizes
Now use the post hinting bitmap bearing values instead of the hinting
independent glyph metrics bearing values. Fixes #1173
2018-12-02 14:12:46 +05:30
Kovid Goyal
8ba8320189 Disable brew based build since harfbuzz is currently broken in brew 2018-12-02 14:08:35 +05:30
Kovid Goyal
3501a2a1da ... 2018-12-02 13:52:06 +05:30
Kovid Goyal
819ee94a70 Merge branch 'reset_osc' of https://github.com/martinetd/kitty 2018-11-30 13:52:42 +05:30
Kovid Goyal
7e7dbc5ecf Merge branch 'history_crash' of https://github.com/martinetd/kitty 2018-11-30 13:45:27 +05:30
Dominique Martinet
3a6bc94d25 pager history: fix bufsize calculation
bufsize was bigger than the actual allocated memory

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

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

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

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

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

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

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

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

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

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

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

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

Fixes:

    Linking kitty/fast_data_types ...
    kitty/freetype.c: In function ‘render_glyphs_in_cells’:
    kitty/freetype.c:514:82: warning: ‘bm.right_edge’ may be used uninitialized in this function [-Wmaybe-uninitialized]
             if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
                                                                                      ^
    kitty/freetype.c:490:21: note: ‘bm.right_edge’ was declared here
         ProcessedBitmap bm;
                         ^
2018-09-29 16:20:10 +02:00
Daniel Hahler
1d50a188c4 render_glyphs_in_cells: skip centering glyph for PUA 2018-09-29 16:19:53 +02:00
Kovid Goyal
b0d34b136f version 0.12.3 2018-09-29 09:15:40 +05:30
Kovid Goyal
7eb234c250 Move kitty.app build instructions from the FAQ to the build page 2018-09-29 08:47:30 +05:30
Daniel Hahler
d52434c0aa fixup! Handle multiple spaces after PUA glyphs
[ci skip]
2018-09-28 23:01:35 +02:00
Daniel Hahler
efc0b830cf Handle multiple spaces after PUA glyphs 2018-09-28 15:34:14 +02:00
Kovid Goyal
4a8c4c4601 Unicode input: Fix an error when searching for the string 'fir'
Fixes #1035
2018-09-28 19:01:20 +05:30
Kovid Goyal
e498cedf56 remove unused import 2018-09-27 08:52:40 +05:30
Kovid Goyal
6c8a52875e Fix #1029 2018-09-27 08:40:16 +05:30
Kovid Goyal
c1397fd366 Merge branch 'unicode_input-reverse' of https://github.com/blueyed/kitty 2018-09-27 08:35:25 +05:30
Daniel Hahler
3323ddcdef unicode_input: use reverse mode for menu selection 2018-09-26 22:50:22 +02:00
Kovid Goyal
1be394cda7 Update CHANGELOG 2018-09-26 19:54:42 +05:30
Kovid Goyal
f292092ffc ... 2018-09-26 19:47:53 +05:30
Kovid Goyal
b82e74f99a Fix for kitty window not being rendered until moved/resized on macOS Mojave
Fixes #887
2018-09-26 19:43:04 +05:30
Kovid Goyal
9b293ad66a Add some more explanation of why TERM is important 2018-09-24 09:28:21 +05:30
Kovid Goyal
3a4420f2d3 version 0.12.2 2018-09-24 05:26:11 +05:30
Kovid Goyal
1eb8d6e845 Merge branch 'doc-fix' of https://github.com/nicoulaj/kitty 2018-09-23 08:49:15 +05:30
Julien Nicoulaud
e2f97dc44a Fix broken links 2018-09-22 20:22:15 +02:00
Kovid Goyal
ea9af96d2b Fix using focus_follows_mouse causing text selection with the mouse to malfunction when using multiple kitty windows
Fixes #1002
2018-09-22 18:15:03 +05:30
Kovid Goyal
e4033d997c Merge branch 'prev_' of https://github.com/Luflosi/kitty 2018-09-22 17:28:53 +05:30
Luflosi
bc38357193 Add prev_tab and prev_window as aliases for previous_tab and previous_window 2018-09-22 13:24:51 +02:00
Dominique Martinet
618b486fd5 pager history buffer: remove dead code looking for next new line 2018-09-22 19:10:37 +09:00
Dominique Martinet
1986403e42 Add scrollback benchmark 2018-09-22 19:10:37 +09:00
Dominique Martinet
cf51f80b79 cell_as_sgr: avoid unnecessary formatting functions 2018-09-22 19:10:37 +09:00
Dominique Martinet
2fe17aa889 cursor_as_sgr now works on GPUCells becomes cell_as_sgr 2018-09-22 19:10:09 +09:00
Dominique Martinet
f76c8ad30b pager history buffer: alloc buffer progressively
Realloc buffer 1MB at a time until the configured size is reached
2018-09-22 19:10:09 +09:00
Dominique Martinet
c421d3bb59 line_as_ansi: don't reset SGR at start of line
less does not carry the mode over from the previous line anyway, let's
save a few bytes for every line
2018-09-22 19:10:09 +09:00
Dominique Martinet
3d862da2be pager history buffer: implement rewrap
rewarp the buffer the first time it is displayed after the window
changed
2018-09-22 19:10:09 +09:00
Dominique Martinet
10b15caad9 tests: add options param to create_screen()
Always apply defaults when creating a screen (the C-side global state
default will not have the intended values without this), and provide a
way to override default values with a new optional parameter
2018-09-22 18:05:13 +09:00
Dominique Martinet
e08238d5a0 pager history buffer: add config option, keep buffer on resize 2018-09-22 18:02:11 +09:00
Dominique Martinet
d0104660c8 Implement pager scrollback history buffer
For now, use big fixed-size buffer that is lost on resize
2018-09-22 18:02:11 +09:00
Dominique Martinet
a4c2a9ef0d line_as_ansi: only compute sgr if needed 2018-09-22 18:02:11 +09:00
Dominique Martinet
ee39d7ba14 line_as_ansi: remove dead undefs
CHECK_BOOL, CHECK_COLOR and WRITE_COLOR have been removed in
commit 27cd303a05 ("Centralize code to convert to SGR")
2018-09-22 18:02:11 +09:00
Kovid Goyal
62cb035afe Remote control: Fix the `focus_window` command not focusing the top-level OS window of the specified kitty window
Fixes #1003
2018-09-22 12:17:35 +05:30
Kovid Goyal
7a8bc6b5d2 Fix #1001 2018-09-21 12:26:44 +05:30
Kovid Goyal
0d86ac23bb Merge branch 'ime' of https://github.com/martinetd/kitty 2018-09-21 09:28:56 +05:30
Dominique Martinet
897fd9486e glfw/ibus input: use text from ime event instead of global
When a key is pressed, text is generated from xkb and attached to the
global key_event variable.
If another key is pressed before ibus replies, this text in global
variable would be overwritten before the processed reply comes in and
the wrong character would be input, as shown in logs with
--debug-keyboard:

Press scancode: 0x19 clean_sym: w composed_sym: w text: w mods: numlock glfw_key: 87 (W) xkb_key: 119 (w)
↳ to IBUS: keycode: 0x11 keysym: 0x77 (w) mods: numlock
Press scancode: 0x20 clean_sym: o composed_sym: o text: o mods: numlock glfw_key: 79 (O) xkb_key: 111 (o)
↳ to IBUS: keycode: 0x18 keysym: 0x6f (o) mods: numlockIBUS processed scancode: 0x19 release: 0 handled: 0
From IBUS: scancode: 0x19 name: w is_release: 0
↳ to application: glfw_keycode: 0x57 (W) keysym: 0x77 (w) action: PRESS mods: numlock  text: o
on_key_input: glfw key: 87 native_code: 0x77 action: PRESS mods: 0x0 text: 'o' state: 0 sent text to child
IBUS processed scancode: 0x20 release: 0 handled: 0
From IBUS: scancode: 0x20 name: o is_release: 0
↳ to application: glfw_keycode: 0x4f (O) keysym: 0x6f (o) action: PRESS mods: numlock  text: o
on_key_input: glfw key: 79 native_code: 0x6f action: PRESS mods: 0x0 text: 'o' state: 0 sent text to child

Since the whole event is memcpy'd we can just use the text from the
event attached to the reply instead.
2018-09-21 07:34:01 +09:00
Kovid Goyal
39d3d093c4 ... 2018-09-20 09:10:57 +05:30
Kovid Goyal
3f0b52bef4 Workaround for broken editors like nano that cannot handle newlines in pasted text
Fixes #994
2018-09-20 09:08:29 +05:30
Kovid Goyal
4a04f27fde Fix #991 2018-09-19 19:58:23 +05:30
Kovid Goyal
5fb02c0439 When mapping the new_tab action allow specifying that the tab should open next to the current tab instead of at the end of the tabs list
Fixes #979
2018-09-19 19:43:41 +05:30
Kovid Goyal
edfb4ef3f1 Merge branch 'master' of https://github.com/esell/kitty 2018-09-19 18:13:20 +05:30
Kovid Goyal
f037923b00 Document the basic process for creating custom kittens. Fixes #975 2018-09-19 12:55:50 +05:30
Kovid Goyal
424aafb514 Merge branch 'fix_sgr_bold' of https://github.com/martinetd/kitty 2018-09-19 08:07:00 +05:30
Dominique Martinet
1b64167a3e cursor_as_sgr: fix bold/dim
The old code would print "\e[22m" if either bold or dim was unset, which
is the "normal, neither bold nor dim" control sequence.

Print that only if neither are set, and instead print "1" and "2" (bold,
dim) values individually.
2018-09-19 07:04:01 +09:00
esell
a9a86e4604 Add option to enable custom cursor on macos 2018-09-18 10:56:26 -06:00
Kovid Goyal
1dc7c23b2f Merge branch 'bash_completion' of https://github.com/martinetd/kitty 2018-09-18 06:19:24 +05:30
Dominique Martinet
5bdcfbc2ce bash completion: make complete helper start with _
It is standard to start complete helpers with underscore to avoid
clobbering the function namespace ; like other shells do.
It is ironic for a completion helper to make completion less agreable...
2018-09-18 09:10:47 +09:00
Kovid Goyal
74e9bf4f31 Another sentence worth of documentation 2018-09-17 06:42:20 +05:30
Kovid Goyal
0652529839 Remove dead code 2018-09-12 21:44:59 +05:30
Kovid Goyal
772d6597a9 macOS: Add an option :opt:macos_traditional_fullscreen to make full-screening of kitty windows much faster, but less pretty.
See #911
2018-09-12 21:27:49 +05:30
Kovid Goyal
4e99194022 Document platform fullscreen shortcut on macOS 2018-09-12 20:43:22 +05:30
Kovid Goyal
f0ff2f4ea7 Fix a bug causing incorrect line ordering when viewing the scrollback buffer if the scrollback buffer is full
Fixes #960
2018-09-12 20:31:09 +05:30
Kovid Goyal
d583c533c2 Linux: Fix window icon not set on X11 for the first OS window
Fix #961
2018-09-12 18:29:06 +05:30
Kovid Goyal
ec1f219850 macOS: Make full screening of kitty much faster by using the "traditional full screen" mode of cocoa, similar to iTerm and MacVim
Hopefully workaround a bug in glfw that causes crashes when unplugging
monitors with full screen windows.

Fixes #911
Fixes #898
2018-09-12 15:45:08 +05:30
Kovid Goyal
21d586cc86 macOS: Fix resizing semi-transparent windows causing the windows to be invisible during the resize
Fixes #941
Workaround for  https://github.com/glfw/glfw/issues/1251
2018-09-12 10:44:24 +05:30
Kovid Goyal
8f7139d48f Also respond to a terminfo query for "name" in addition to "TN" 2018-09-11 21:41:50 +05:30
Kovid Goyal
cfb6dae04d ... 2018-09-11 17:51:06 +05:30
Kovid Goyal
bceac3ee0b ... 2018-09-11 09:58:26 +05:30
Kovid Goyal
38d6edb0ca Merge branch 'macos-thicken-font' of https://github.com/tbodt/kitty 2018-09-11 09:57:28 +05:30
Kovid Goyal
1b41a3bcee ... 2018-09-11 08:26:31 +05:30
Kovid Goyal
c54f934f6e Merge branch 'patch-1' of https://github.com/tanrax/kitty 2018-09-11 08:25:22 +05:30
Theodore Dubois
94052bb712 Add option to increase font thickness on macOS 2018-09-10 19:30:29 -07:00
Andros Fenollosa
7eb1dcc7ee Fixbug Gnome cache 2018-09-10 18:44:34 +02:00
Andros Fenollosa
626fbe827e Add local icon 2018-09-10 18:41:07 +02:00
Kovid Goyal
28c154ecf0 typo 2018-09-10 21:58:15 +05:30
Kovid Goyal
8176f661ea macOS: Fix small leak in glyph rendering code 2018-09-10 21:41:26 +05:30
Kovid Goyal
0195e7f841 Wayland: Add support for animated cursors 2018-09-10 11:54:22 +05:30
Kovid Goyal
141b8a1481 Update glfw from upstream 2018-09-10 09:51:06 +05:30
Kovid Goyal
be3ab90cff Add a reference to piping to the index page 2018-09-10 08:19:14 +05:30
Kovid Goyal
9782f16635 Support os_window as a destination for the pipe function 2018-09-10 08:10:50 +05:30
Kovid Goyal
6930edfd8a ... 2018-09-10 08:08:16 +05:30
Kovid Goyal
6b1bec6550 Add a FAQ entry about global hotkey access to kitty 2018-09-10 07:57:33 +05:30
Andros Fenollosa
dac0304815 Update binary.rst 2018-09-09 20:08:12 +02:00
Andros Fenollosa
cfcf81b5fb Update binary.rst 2018-09-09 20:06:56 +02:00
Kovid Goyal
f072a02215 ... 2018-09-09 21:08:11 +05:30
Kovid Goyal
5f6e21da9b Fix kitty @set-colors not working for tab backgrounds when using the fade tabbar style
Fixes #937
2018-09-09 19:41:32 +05:30
Kovid Goyal
b9b38a4ec1 A new pipe function that can be used to pipe the contents of the screen and scrollback buffer to any desired program running in a new window, tab or overlay window. 2018-09-09 18:29:59 +05:30
Kovid Goyal
e5a7ba4f22 Allow viewing the current contents of the alternate screen in a new window.
Fixes #933
2018-09-09 15:32:05 +05:30
Kovid Goyal
1dcf3f2afb ... 2018-09-09 14:32:07 +05:30
Kovid Goyal
2ec84969dd Fix drag-scrolling not working when the mouse leaves the window confines
Fixes #917
2018-09-09 14:30:32 +05:30
Kovid Goyal
a08b945124 Forgot to revert test 2018-09-09 14:02:46 +05:30
Kovid Goyal
16f712fbaa Revert the wide-character backspace patch
Fixes #875
2018-09-09 13:51:15 +05:30
Kovid Goyal
1f63ff1d41 Have the kitty --title flag apply to all windows created using kitty --session
Fixes #921
2018-09-09 10:49:20 +05:30
Kovid Goyal
49d8f4689f ... 2018-09-09 10:24:01 +05:30
Kovid Goyal
e1f993938a Add a new kitty --start-as command line flag to start kitty fullscreen/maximized/minimized.
This obsoletes the --start-in-fullscreen flag introduced in the previous release
which has been removed since it has only existed for a few days.

Fixes #935
2018-09-09 10:23:48 +05:30
Kovid Goyal
ab65b9b2fb Do not convert bitmaps when loading a glyph to access its metrics 2018-09-09 09:46:19 +05:30
Kovid Goyal
f7a621f89f Use bitmap pitch to iterate over pixels 2018-09-09 09:42:51 +05:30
Kovid Goyal
b808a05e5e Clean up monochrome bitmap support code 2018-09-09 08:33:32 +05:30
Kovid Goyal
72c37c519d ... 2018-09-09 08:23:59 +05:30
Kovid Goyal
af4b072b29 Merge branch 'master' of https://github.com/isaz/kitty 2018-09-09 08:20:32 +05:30
ismed
739ed906f0 Use the old FT_Bitmap_New for backward compatibility 2018-09-08 21:07:38 +01:00
ismed
9fd462077a Add support for embedded bitmap glyphs 2018-09-08 20:22:48 +01:00
Kovid Goyal
cb3671343e Implement directional movement of windows 2018-09-08 23:51:09 +05:30
Kovid Goyal
b18b705632 Fix overlay window handling when witching to neighboring windows 2018-09-08 23:31:16 +05:30
Kovid Goyal
9963e78636 Linux: Ensure that the python embedded in the kitty binary build always uses UTF-8 mode
Fixes #924
2018-09-08 23:03:36 +05:30
Kovid Goyal
e39635ea16 Add a new `neighboring_window` function to switch to neighboring windows in the current layout, similar to window movement in vim
Fixes #916
2018-09-08 08:59:11 +05:30
Kovid Goyal
9329b2c202 Allow using "all" as "*" for enabled_layouts 2018-09-08 08:22:37 +05:30
Kovid Goyal
791f70e5a2 API to get neighbors from the current layout 2018-09-08 08:09:35 +05:30
Kovid Goyal
8272c4a429 Fix #929 2018-09-08 06:28:30 +05:30
Kovid Goyal
f0357b061e Add a new `last_used_layout` function that can be mapped to a shortcut to switch to the previously used window layout
Fixes #870
2018-09-08 05:56:02 +05:30
Kovid Goyal
7438e0e997 version 0.12.1 2018-09-08 05:22:02 +05:30
Kovid Goyal
cca672e536 typo 2018-09-07 16:24:29 +05:30
Kovid Goyal
0fa12a687d Linux: Turn off IME unless the GLFW_IM_MODULE=ibus env var is set 2018-09-07 16:07:01 +05:30
Kovid Goyal
d2b900c77f Changelog cleanups 2018-09-07 15:38:04 +05:30
Kovid Goyal
21242d9144 ... 2018-09-07 15:30:36 +05:30
Kovid Goyal
03fd138b63 Linux: Fix crash with some nvidia drivers when creating tabs in the first top level-window after creating a second top-level window.
Fixes #873
The wrong window's OpenGL context was current when creating the tabs.
2018-09-07 15:23:10 +05:30
Kovid Goyal
1eeaef6dbb Get rid of extra global variable 2018-09-07 14:53:29 +05:30
Kovid Goyal
b9a93b68c4 ... 2018-09-07 11:37:33 +05:30
Kovid Goyal
e3728942dc Fix a small memory leak when creating a new child process 2018-09-07 11:35:00 +05:30
Kovid Goyal
7f0674ac27 Fix backspacing of wide characters in wide-character unaware programs not working
Fixes #875
2018-09-07 10:08:25 +05:30
Kovid Goyal
60fcf7ab3d Merge branch 'prev_tab_default_macos' of https://github.com/Luflosi/kitty 2018-09-07 06:53:24 +05:30
Luflosi
ee82a6ff18 Make ctrl+shift+tab previous_tab by default on macOS 2018-09-06 22:43:36 +02:00
Kovid Goyal
bac5e402b7 Fix #890 2018-09-06 21:25:29 +05:30
Kovid Goyal
732108109e Nicer error message when sphinx-build is not in PATH 2018-09-06 21:23:03 +05:30
Kovid Goyal
ab3b86d65b Merge branch 'void' of https://github.com/SolitudeSF/kitty 2018-09-06 21:11:03 +05:30
Kovid Goyal
d4d63506e1 Fix pre-allocation of the full history buffer
Also map negative scrollback numbers to (effectively) infinite
scrollback.
2018-09-06 21:06:31 +05:30
SolitudeSF
3ad69f8ba3 Add link for the Void Linux package 2018-09-06 18:36:29 +03:00
Kovid Goyal
9fd50a6ac0 ... 2018-09-06 20:26:51 +05:30
Kovid Goyal
9b5c8a18d4 Merge branch 'master' of https://github.com/eigengrau/kitty 2018-09-06 18:29:06 +05:30
Kovid Goyal
9f3112ca56 Merge branch 'spelling' of https://github.com/EdwardBetts/kitty 2018-09-06 18:27:02 +05:30
Kovid Goyal
aa9de47da3 Support compose sequences with no symbol
Compose sequences can be defined without an associated symbol. To test for
completeness, we now also rely on XKB’s reported compose state.

Fixes #880.
2018-09-06 14:53:16 +02:00
Edward Betts
5eaeda4402 Correct spelling mistakes. 2018-09-06 12:07:09 +01:00
Kovid Goyal
ecb03d7dba ... 2018-09-06 11:58:43 +05:30
Kovid Goyal
88612d3691 Better error message when font size is too large 2018-09-06 11:52:48 +05:30
Kovid Goyal
2c0e70a423 Fix #868 2018-09-06 11:49:30 +05:30
Kovid Goyal
d6b70f10d3 Cross-linking options in the docs 2018-09-06 11:02:53 +05:30
Kovid Goyal
40b01fb702 Add a note about using an alias for icat 2018-09-06 10:27:13 +05:30
Kovid Goyal
89222d93f4 Change legacy kitty icat reference 2018-09-06 10:20:23 +05:30
Kovid Goyal
b3db3e5732 Fix completion of paths with spaces 2018-09-06 09:47:36 +05:30
Kovid Goyal
b45161ecc3 zsh completion: Eliminate common prefix when completing file names 2018-09-06 09:46:04 +05:30
Kovid Goyal
d4fc0af7de Cleanup up path completions 2018-09-06 09:34:25 +05:30
Kovid Goyal
5ef8cce1d7 Fix filenames not being completed for the diff kitten 2018-09-06 07:50:07 +05:30
Kovid Goyal
c98a147f94 Add changelog entry 2018-09-06 07:05:41 +05:30
Kovid Goyal
e9975cb7eb Merge branch 'fish-completion' of https://github.com/scorphus/kitty 2018-09-06 07:04:54 +05:30
Kovid Goyal
33f0ac83c1 Wayland: Allow using the --class to set the app id
Fixes #862
2018-09-06 06:54:38 +05:30
Kovid Goyal
3bd45966c6 diff kitten: Fix spurious traceback on exit when pygments is missing
Fixes #863
2018-09-06 06:05:08 +05:30
Pablo S. Blum de Aguiar
1b7244f3c8 Completion for fish 2018-09-05 22:33:39 +02:00
Kovid Goyal
1949f3c3d5 Wayland fix self copy detection across multiple processes 2018-09-05 21:45:32 +05:30
Kovid Goyal
6a51ce5dc4 Wayland: Work on supporting drop of file paths 2018-09-05 21:41:47 +05:30
Kovid Goyal
c0c8e1ce5e Fill up the rest of the v1 data_source listener struct 2018-09-05 20:04:38 +05:30
Kovid Goyal
6a3d6191bd Implement the rest of the the data_offer listener callbacks 2018-09-05 19:58:55 +05:30
Kovid Goyal
b2aa07d29f Wayland: Fix missing data source canceled listener 2018-09-05 19:37:55 +05:30
Kovid Goyal
c84fb68bf8 Wayland: fix copy/pasting into self 2018-09-05 19:23:42 +05:30
Kovid Goyal
128b9e1cd0 Wayland: Implement support for clipboard copy/paste
Fix #855
2018-09-05 17:54:26 +05:30
Kovid Goyal
bbe8bcb28d Fix a regression that broke layout resizing
Fixes #860
2018-09-05 10:51:08 +05:30
Kovid Goyal
6dc9c22543 Remove unnecessary use of globals 2018-09-05 10:48:23 +05:30
Kovid Goyal
59f5781f19 Merge branch 'patch-1' of https://github.com/skorokithakis/kitty 2018-09-05 10:41:42 +05:30
Stavros Korokithakis
d61fb9cc42 Add sentence about layouts using windows 2018-09-05 08:08:31 +03:00
Kovid Goyal
ace052ee70 Move data device initialization into its own function 2018-09-05 07:51:15 +05:30
Kovid Goyal
1451a8ccc0 Add a new --start-in-fullscreen command line flag
Fixes #856
2018-09-05 07:36:13 +05:30
Kovid Goyal
547959c1ef Linux: Fix numpad arrow keys not working when num lock is off
Fixes #857
2018-09-05 07:31:55 +05:30
Kovid Goyal
b1a5e98630 Update link to Fedora package to a newer one 2018-09-04 22:28:52 +05:30
Kovid Goyal
f3a1ee7b9f Wayland backend: Add support for copy to clipboard 2018-09-04 22:22:43 +05:30
Kovid Goyal
b0a39b127d Remove unused import 2018-09-04 17:54:33 +05:30
Kovid Goyal
e36f1157cc Improve anti-aliasing at small sizes for wavy underlines 2018-09-04 15:48:01 +05:30
Kovid Goyal
053b9a786c Use a cosine instead of sine wave for wavy underlines
Matches other terminals
2018-09-04 15:35:48 +05:30
Kovid Goyal
2561d937da Forgot to remove mac os filter for native shortcut dispatching in one place 2018-09-04 15:00:09 +05:30
Kovid Goyal
55f457caf4 macOS: Fix a character that cannot be rendered in any font causing font fallback for all subsequent characters that cannot be rendered in the main font to fail
Fixes #799
2018-09-04 14:21:36 +05:30
Kovid Goyal
f5cc2fd8ad Update GLFW from upstream 2018-09-04 09:49:18 +05:30
Kovid Goyal
e630b76fac Don't patch glfw when importing since the patch has moved upstream 2018-09-04 09:46:09 +05:30
Kovid Goyal
0b6948cb4f macOS: Fix using open --args not working
Fixes #854
2018-09-03 20:58:31 +05:30
Kovid Goyal
b0a2dea747 Improve rendering of wavy underline at small font sizes
See #853
2018-09-03 10:15:53 +05:30
Kovid Goyal
f8395e36a1 Draw underlines under the text instead of over it 2018-09-03 09:48:53 +05:30
Kovid Goyal
6ec58f58ce Allow mapping shortcuts using the raw key code from the OS
Fixes #848
2018-09-02 18:46:10 +05:30
Kovid Goyal
96cbfe946c Fix legacy form of invoking icat broken in 0.12.0
Fixes #850
2018-09-02 15:59:18 +05:30
Kovid Goyal
ac686a47c1 Fail early when opening tty fails 2018-09-02 14:29:10 +05:30
Kovid Goyal
e05aaf77ce spelling typo 2018-09-01 10:41:35 +05:30
Kovid Goyal
6b371eb8a8 Merge branch 'master' of github.com:kovidgoyal/kitty 2018-09-01 10:32:44 +05:30
Kovid Goyal
f6660a0d55 Require PENV when publishing 2018-09-01 10:32:08 +05:30
Kovid Goyal
3b9f213ea2 ... 2018-09-01 10:00:35 +05:30
128 changed files with 29163 additions and 25365 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@@ -10,9 +10,9 @@ config to reproduce the issue with).
### Contributing code
Install [the dependencies](https://github.com/kovidgoyal/kitty#dependencies)
Install [the dependencies](https://sw.kovidgoyal.net/kitty/build.html#dependencies)
using your favorite package manager. Build and run kitty [from
source](https://github.com/kovidgoyal/kitty#install-and-run-from-source).
source](https://sw.kovidgoyal.net/kitty/build.html#install-and-run-from-source).
Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue
beforehand to discuss it, so that you don't waste your time making a pull

View File

@@ -7,8 +7,9 @@ import os
def icat(args):
from kittens.icat.main import main
main(args)
from kittens.runner import run_kitten
sys.argv = args
run_kitten('icat')
def list_fonts(args):
@@ -41,7 +42,13 @@ def complete(args):
def launch(args):
import runpy
sys.argv = args[1:]
runpy.run_path(args[1], run_name='__main__')
exe = args[1]
if exe.startswith(':'):
import shutil
exe = shutil.which(exe[1:])
if not exe:
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
runpy.run_path(exe, run_name='__main__')
def run_kitten(args):

View File

@@ -3,7 +3,7 @@
# You can set these variables from the command line.
SPHINXOPTS = -T $(FAIL_WARN)
SPHINXBUILD = python3 .. +launch $(shell which sphinx-build)
SPHINXBUILD = python3 .. +launch :sphinx-build
SPHINXPROJ = kitty
SOURCEDIR = .
BUILDDIR = _build

View File

@@ -16,7 +16,8 @@ the following simple command:
The binaries will be installed in the standard location for your OS,
:file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on
Linux. The installer only touches files in that directory.
Linux. The installer only touches files in that directory. To update kitty,
simply re-run the command.
Manually installing
@@ -29,6 +30,25 @@ the :file:`.dmg` and install as normal. If you are on Linux, download the tarbal
and extract it into a directory. The |kitty| executable will be in the
:file:`bin` sub-directory.
Desktop integration on Linux
--------------------------------
If you want the kitty icon to appear in the taskbar and an entry for it to be
present in the menus, you will need to install the :file:`kitty.desktop` file.
The details of the following procedure may need to be adjusted for your
particular desktop, but it should work for most major desktop environments.
.. code-block:: sh
# Create a symbolic link to add kitty to PATH (assuming ~/.local/bin is in
# your PATH)
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
# Place the kitty.desktop file somewhere it can be found by the OS
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
# Update the path to the kitty icon in the kitty.desktop file
sed -i "s/Icon\=kitty/Icon\=\/home\/$USER\/.local\/kitty.app\/share\/icons\/hicolor\/256x256\/apps\/kitty.png/g" ~/.local/share/applications/kitty.desktop
Customizing the installation
--------------------------------
@@ -58,6 +78,12 @@ Customizing the installation
|ins| \\
installer=/path/to/dmg or tarball
Uninstalling
----------------
All the installer does is copy the kitty files into the install directory. To
uninstall, simply delete that directory.
Building from source
------------------------

View File

@@ -28,7 +28,7 @@ Build-time dependencies:
* gcc or clang
* pkg-config
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev`` and ``libxkbcommon-x11-dev``,
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev`` and ``libpython-dev``.
if they are not already installed by your distro.
Install and run from source
@@ -57,6 +57,25 @@ If that works, you can create a script to launch |kitty|:
And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using
just ``kitty``.
Building kitty.app on macOS from source
-------------------------------------------
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
for the logo generation step). And run::
make app
This :file:`kitty.app` unlike the released one does not include its own copy of
python and the other dependencies. So if you ever un-install/upgrade those dependencies
you might have to rebuild the app.
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
is designed to run on Linux and is not for the faint of heart.
Note for Linux/macOS packagers
----------------------------------

View File

@@ -3,6 +3,277 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
0.13.2 [2019-01-04]
------------------------------
- Add a new option :opt:`tab_title_template` to control how tab titles
are formatted. In particular the template can be used to display
the tab number next to the title (:iss:`1223`)
- Report the current foreground processes as well as the original child process,
when using `kitty @ ls`
- Use the current working directory of the foreground process for the
`*_with_cwd` actions that open a new window with the current working
directory.
- Add a new ``copy_or_interrupt`` action that can be mapped to kbd:`ctrl+c`. It
will copy if there is a selection and interrupt otherwise (:iss:`1286`)
- Fix setting :opt:`background_opacity` causing window margins/padding to be slightly
different shade from background (:iss:`1221`)
- Handle keyboards with a "+" key (:iss:`1224`)
- Fix Private use Unicode area characters followed by spaces at the end of text
not being rendered correctly (:iss:`1210`)
- macOS: Add an entry to the dock menu to open a new OS window (:iss:`1242`)
- macOS: Fix scrolling very slowly with wheel mice not working (:iss:`1238`)
- Fix changing :opt:`cursor_text_color` via remote control not working
(:iss:`1229`)
- Add an action to resize windows that can be mapped to shortcuts in :file:`kitty.conf`
(:pull:`1245`)
- Fix using the ``new_tab !neighbor`` action changing the order of the
non-neighboring tabs (:iss:`1256`)
- macOS: Fix momentum scrolling continuing when changing the active window/tab
(:iss:`1267`)
0.13.1 [2018-12-06]
------------------------------
- Fix passing input via the pipe action to a program without a window not
working.
- Linux: Fix a regression in the previous release that caused automatic
selection of bold/italic fonts when using aliases such as "monospace" to not
work (:iss:`1209`)
- Fix resizing window smaller and then restoring causing some wrapped lines to not
be properly unwrapped (:iss:`1206`)
0.13.0 [2018-12-05]
------------------------------
- Add an option :opt:`scrollback_pager_history_size` to tell kitty to store
extended scrollback to use when viewing the scrollback buffer in a pager
(:iss:`970`)
- Modify the kittens sub-system to allow creating custom kittens without any
user interface. This is useful for creating more complex actions that can
be bound to key presses in :file:`kitty.conf`. See
doc:`kittens/custom`. (:iss:`870`)
- Add a new ``nth_window`` action that can be used to go to the nth window and
also previously active windows, using negative numbers. Similarly,
``goto_tab`` now accepts negative numbers to go to previously active tabs
(:iss:`1040`)
- Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to
``hidden``. (:iss:`1014`)
- Allow private use unicode characters to stretch over more than a single
neighboring space (:pull:`1036`)
- Add a new :opt:`touch_scroll_multiplier` option to modify the amount
scrolled by high precision scrolling devices such as touchpads (:pull:`1129`)
- icat kitten: Implement reading image data from STDIN, if STDIN is not
connected to a terminal (:iss:`1130`)
- hints kitten: Insert trailing spaces after matches when using the
``--multiple`` option. Also add a separate ``--add-trailing-space``
option to control this behavior (:pull:`1132`)
- Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather
than the underlying window's cwd (:iss:`1045`)
- Fix incorrect key repeat rate on wayland (:pull:`1055`)
- macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`)
- macOS: Fix IME input for East Asian languages (:iss:`910`)
- macOS: Fix rendering frames-per-second very low when processing
large amounts of input in small chunks (:pull:`1082`)
- macOS: Fix incorrect text sizes calculated when using an external display
that is set to mirror the main display (:iss:`1056`)
- macOS: Use the system default double click interval (:pull:`1090`)
- macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`)
- Linux: Fix incorrect rendering of some fonts when hinting is disabled at
small sizes (:iss:`1173`)
- Linux: Fix match rules used as aliases in Fontconfig configuration not being
respected (:iss:`1085`)
- Linux: Fix a crash when using the GNU Unifont as a fallback font
(:iss:`1087`)
- Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`)
- Wayland: Add support for the primary selection protocol
implemented by some compositors (:pull:`1095`)
- Fix expansion of env vars not working in the :opt:`env` directive
(:iss:`1075`)
- Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor
blink or key press (:iss:`1073`)
- Fix the ``set_background_opacity`` action not working correctly
(:pull:`1147`)
- Fix second cell of emoji created using variation selectors not having
the same attributes as the first cell (:iss:`1109`)
- Fix focusing neighboring windows in the grid layout with less than 4 windows
not working (:iss:`1115`)
- Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard
modes (:iss:`1114`)
- Add a terminfo entry for full keyboard mode.
- Fix incorrect text-antialiasing when using very low background opacity
(:iss:`1005`)
- When double or triple clicking ignore clicks if they are "far" from each
other (:iss:`1093`)
- Follow xterm's behavior for the menu key (:iss:`597`)
- Fix hover detection of URLs not working when hovering over the first colon
and slash characters in short URLs (:iss:`1201`)
0.12.3 [2018-09-29]
------------------------------
- macOS: Fix kitty window not being rendered on macOS Mojave until the window is
moved or resized at least once (:iss:`887`)
- Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`)
0.12.2 [2018-09-24]
------------------------------
- A new ``last_used_layout`` function that can be mapped to a shortcut to
switch to the previously used window layout (:iss:`870`)
- New ``neighboring_window`` and ``move_window`` functions to switch to
neighboring windows in the current layout, and move them around, similar to
window movement in vim (:iss:`916`)
- A new ``pipe`` function that can be used to pipe the contents of the screen
and scrollback buffer to any desired program running in a new window, tab or
overlay window. (:iss:`933`)
- Add a new :option:`kitty --start-as` command line flag to start kitty
full-screen/maximized/minimized. This replaces the ``--start-in-fullscreen``
flag introduced in the previous release (:iss:`935`)
- When mapping the ``new_tab`` action allow specifying that the tab should open
next to the current tab instead of at the end of the tabs list (:iss:`979`)
- macOS: Add a new :opt:`macos_thicken_font` to make text rendering
on macs thicker, which makes it similar to the result of
sub-pixel antialiasing (:pull:`950`)
- macOS: Add an option :opt:`macos_traditional_fullscreen` to make
full-screening of kitty windows much faster, but less pretty. (:iss:`911`)
- Fix a bug causing incorrect line ordering when viewing the scrollback buffer
if the scrollback buffer is full (:iss:`960`)
- Fix drag-scrolling not working when the mouse leaves the window confines
(:iss:`917`)
- Workaround for broken editors like nano that cannot handle newlines in pasted text
(:iss:`994`)
- Linux: Ensure that the python embedded in the kitty binary build uses
UTF-8 mode to process command-line arguments (:iss:`924`)
- Linux: Handle fonts that contain monochrome bitmaps (such as the Terminus TTF
font) (:pull:`934`)
- Have the :option:`kitty --title` flag apply to all windows created
using :option:`kitty --session` (:iss:`921`)
- Revert change for backspacing of wide characters in the previous release,
as it breaks backspacing in some wide character aware programs (:iss:`875`)
- Fix kitty @set-colors not working for tab backgrounds when using the `fade` tabbar style
(:iss:`937`)
- macOS: Fix resizing semi-transparent windows causing the windows to be
invisible during the resize (:iss:`941`)
- Linux: Fix window icon not set on X11 for the first OS window (:iss:`961`)
- macOS: Add an :opt:`macos_custom_beam_cursor` option to use a special
mouse cursor image that can be seen on both light and dark backgrounds
(:iss:`359`)
- Remote control: Fix the ``focus_window`` command not focusing the
top-level OS window of the specified kitty window (:iss:`1003`)
- Fix using :opt:`focus_follows_mouse` causing text selection with the
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
0.12.1 [2018-09-08]
------------------------------
- Add a new ``--start-in-fullscreen`` command line flag to start
kitty in full screen mode (:iss:`856`)
- macOS: Fix a character that cannot be rendered in any font causing
font fallback for all subsequent characters that cannot be rendered in the
main font to fail (:iss:`799`)
- Linux: Do not enable IME input via ibus unless the ``GLFW_IM_MODULE=ibus``
environment variable is set. IME causes key processing latency and even
missed keystrokes for many people, so it is now off by default.
- Fix backspacing of wide characters in wide-character unaware programs not working (:iss:`875`)
- Linux: Fix number pad arrow keys not working when Numlock is off (:iss:`857`)
- Wayland: Implement support for clipboard copy/paste (:iss:`855`)
- Allow mapping shortcuts using the raw key code from the OS (:iss:`848`)
- Allow mapping of individual key-presses without modifiers as shortcuts
- Fix legacy invocation of icat as `kitty icat` not working (:iss:`850`)
- Improve rendering of wavy underline at small font sizes (:iss:`853`)
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
- Wayland: Allow using the :option:`kitty --class` command line flag
to set the app id (:iss:`862`)
- Add completion of the kitty command for the fish shell (:pull:`829`)
- Linux: Fix XCompose rules with no defined symbol not working (:iss:`880`)
- Linux: Fix crash with some Nvidia drivers when creating tabs in the first
top level-window after creating a second top-level window. (:iss:`873`)
- macOS: Diff kitten: Fix syntax highlighting not working because of
a bug in the 0.12.0 macOS package
0.12.0 [2018-09-01]
------------------------------
@@ -23,6 +294,9 @@ Changelog
- Add an option :opt:`env` to set environment variables in child processes
from kitty.conf
- Add an action to the ``clear_terminal`` function to scroll the screen
contents into the scrollback buffer (:iss:`1113`)
- Implement high precision scrolling with the trackpad on platforms such as
macOS and Wayland that implement it. (:pull:`819`)
@@ -232,7 +506,7 @@ Changelog
- Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`)
- Add a config option (:opt:`x11_hide_window_decorations`) to hide window
- Add a config option (``x11_hide_window_decorations``) to hide window
decorations under X11/Wayland (:iss:`607`)
- Add an option to @set-window-title to make the title change non-permanent

View File

@@ -6,7 +6,7 @@ Frequently Asked Questions
.. contents::
Some special symbols are rendered small/truncated in kitty?
-----------------------------------------------------------------
-----------------------------------------------------------
The number of cells a unicode character takes up are controlled by the unicode
standard. All characters are rendered in a single cell unless the unicode
@@ -25,24 +25,6 @@ these characters are followed by a space or empty cell in which case kitty
makes use of the extra cell to render them in two cells.
How do I build kitty.app on macOS?
----------------------------------------
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
for the logo generation step). And run::
make app
This :file:`kitty.app` unlike the released one does not include its own copy of
python and the other dependencies. So if you ever un-install/upgrade those dependencies
you might have to rebuild the app.
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
is designed to run on Linux and is not for the faint of heart.
Using a color theme with a background color does not work well in vim?
-----------------------------------------------------------------------
@@ -66,15 +48,43 @@ terminfo files to the server::
kitty +kitten ssh myserver
If for some reason that does not work (typically because the server is using a
a non POSIX compliant shell), you can use the following one-liner instead (it
non POSIX compliant shell), you can use the following one-liner instead (it
is slower as it needs to ssh into the server twice, but will work with most
servers)::
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
If you are behind a proxy (like Balabit) that prevents this, you must redirect the
1st command to a file, copy that to the server and run ``tic`` manually. If you
connect to a server, embedded or Android system that doesn't have ``tic``, copy over
your local file terminfo to the other system as :file:`~/.terminfo/x/xterm-kitty`.
Really, the correct solution for this is to convince the OpenSSH maintainers to
have ssh do this automatically when connecting to a server, so that all
terminals work transparently.
have ssh do this automatically, if possible, when connecting to a server, so that
all terminals work transparently.
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
-------------------------------------------------------------------------------------------------
Make sure the TERM environment variable, is ``xterm-kitty``. And either the
TERMINFO environment variable points to a directory containing :file:`x/xterm-kitty`
or that file is under :file:`~/.terminfo/x/`.
Note that ``sudo`` might remove TERMINFO. Then setting it at the shell prompt can
be too late, because command line editing may not be reinitialized. In that case
you can either ask ``sudo`` to set it or if that is not supported, insert an ``env``
command before starting the shell, or, if not possible, after sudo start another
Shell providing the right terminfo path::
sudo … TERMINFO=$HOME/.terminfo bash -i
sudo … env TERMINFO=$HOME/.terminfo bash -i
TERMINFO=/home/ORIGINALUSER/.terminfo exec bash -i
If you have double width characters in your prompt, you may also need to
explicitly set a UTF-8 locale, like::
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
How do I change the colors in a running kitty instance?
@@ -113,3 +123,12 @@ your font is not listed in ``kitty list-fonts`` it means that it is not
monospace. On Linux you can list all monospace fonts with::
fc-list : family spacing | grep spacing=100
How can I assign a single global shortcut to bring up the kitty terminal?
-----------------------------------------------------------------------------
Bringing up applications on a single key press is the job of the window
manager/desktop environment. For ways to do it with kitty (or indeed any
terminal) in different environments,
see `here <https://github.com/kovidgoyal/kitty/issues/45>`_.

View File

@@ -19,7 +19,7 @@ For some discussion regarding the design choices, see `#33
To see a quick demo, inside a |kitty| terminal run::
kitty icat path/to/some/image.png
kitty +kitten icat path/to/some/image.png
You can also see a screenshot with more sophisticated features such as
alpha-blending and text over graphics.
@@ -75,6 +75,47 @@ You can also use the *CSI t* escape code to get the screen size. Send
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
pixels. This escape code is supported in many terminals, not just kitty.
A minimal example
------------------
Some minimal python code to display PNG images in kitty, using the most basic
features of the graphics protocol:
.. code-block:: python
import sys
from base64 import standard_b64encode
def serialize_gr_command(cmd, payload=None):
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
ans = []
w = ans.append
w(b'\033_G'), w(cmd.encode('ascii'))
if payload:
w(b';')
w(payload)
w(b'\033\\')
return b''.join(ans)
def write_chunked(cmd, data):
data = standard_b64encode(data)
while data:
chunk, data = data[:4096], data[4096:]
m = 1 if data else 0
cmd['m'] = m
sys.stdout.buffer.write(serialize_gr_command(cmd, chunk))
sys.stdout.flush()
cmd.clear()
write_chunked({'a': 'T', 'f': 100}, open(sys.argv[-1], 'rb').read())
Save this script as :file:`png.py`, then you can use it to display any PNG
file in kitty as::
python png.py file.png
The graphics escape code
---------------------------
@@ -160,7 +201,11 @@ Value of `t` Meaning
================== ============
``d`` Direct (the data is transmitted within the escape code itself)
``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. For security reasons
the terminal emulator should only delete the file if it
is in a known temporary directory, such as :file:`/tmp`,
:file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform
specific temporary directories.
``s`` A `POSIX shared memory object <http://man7.org/linux/man-pages/man7/shm_overview.7.html>`_.
The terminal emulator will delete it after reading the pixel data
================== ============

View File

@@ -51,7 +51,8 @@ Quickstart
--------------
Pre-built binaries of |kitty| are available for both macOS and Linux.
See the :doc:`binary install instructions </binary>`.
See the :doc:`binary install instructions </binary>`. You can also
:doc:`build from source </build>`.
If you are on Linux, you can also use your distribution's |kitty| package.
|kitty| packages are available for:
@@ -60,7 +61,8 @@ If you are on Linux, you can also use your distribution's |kitty| package.
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
`Fedora <https://copr.fedorainfracloud.org/coprs/oleastre/kitty-terminal/>`_.
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_.
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
@@ -123,7 +125,7 @@ Action Shortcut
New tab :sc:`new_tab`
Close tab :sc:`close_tab`
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
Previous tab :sc:`previous_tab`
Previous tab :sc:`previous_tab` (also :kbd:`control+shift+tab` on macOS)
Next layout :sc:`next_layout`
Move tab forward :sc:`move_tab_forward`
Move tab backward :sc:`move_tab_backward`
@@ -149,6 +151,22 @@ Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`ten
(clockwise from the top-left)
======================== =======================
Additionally, you can define shortcuts in :file:`kitty.conf` to focus neighboring
windows and move windows around (similar to window movement in vim)::
map ctrl+left neighboring_window left
map shift+left move_window right
map ctrl+down neighboring_window down
map shift+down move_window up
...
You can also define a shortcut to switch to the previously active window::
map ctrl+p nth_window -1
``nth_window`` will focus the nth window for positive numbers and the
previously active windows for negative numbers.
Other keyboard shortcuts
----------------------------------
@@ -162,7 +180,8 @@ Paste from selection :sc:`paste_from_selection`
Increase font size :sc:`increase_font_size`
Decrease font size :sc:`decrease_font_size`
Restore font size :sc:`reset_font_size`
Toggle fullscreen :sc:`toggle_fullscreen`
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`^⌘+f` on macOS)
on
Input unicode character :sc:`input_unicode_character`
Click URL using the keyboard :sc:`open_url`
Reset the terminal :sc:`reset_terminal`
@@ -181,6 +200,9 @@ Reset background opacity :sc:`reset_background_opacity`
Layouts
----------
A layout is an arrangement of multiple *windows*. You can create a new window
using the :sc:`new_window` key combination.
Currently, there are five layouts available,
* **Stack** -- Only a single maximized window is shown at a time
@@ -201,6 +223,19 @@ example, in the Tall layout you can make the first window wider/narrower, but
not taller/shorter. Note that what you are resizing is actually not a window,
but a row/column in the layout, all windows in that row/column will be resized.
You can also define shortcuts in :file:`kitty.conf` to make the active window
wider, narrower, taller, or shorter by mapping to the ``resize_window``
action, for example::
map ctrl+left resize_window narrower
map ctrl+right resize_window wider
map ctrl+up resize_window taller
map ctrl+down resize_window shorter 3
The ``resize_window`` action has a second, optional argument to control
the resizing increment (a positive integer that defaults to 1).
Some layouts take options to control their behavior. For example, the ``fat``
and ``tall`` layouts accept the ``bias`` option to control how the available
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
@@ -250,6 +285,8 @@ Some prominent kittens:
:doc:`Clipboard <kittens/clipboard>`
Copy/paste to the clipboard from shell scripts, even over SSH.
You can also :doc:`Learn to create your own kittens <kittens/custom>`.
Configuring kitty
-------------------
@@ -258,7 +295,6 @@ Configuring kitty
painting frames-per-second. For details and a sample :file:`kitty.conf`,
see the :doc:`configuration docs <conf>`.
.. _sessions:
Remote control
------------------
@@ -269,6 +305,8 @@ fonts, open new windows, tabs, set their titles, change window layout, get text
from one window and send text to another, etc, etc. The possibilities are
endless. See the :doc:`tutorial <remote-control>` to get started.
.. _sessions:
Startup Sessions
------------------
@@ -344,6 +382,17 @@ scrollback buffer in your favorite pager program (which is ``less`` by default).
Colors and text formatting are preserved. You can explore the scrollback buffer
comfortably within the pager.
Additionally, you can pipe the contents of the scrollback buffer to an
arbitrary, command running in a new window, tab or overlay, for example::
map f1 pipe @ansi window less +G -R
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
key. See :sc:`show_scrollback` for details.
If you wish to store very large amounts of scrollback to view using the piping or
:sc:`show_scrollback` features, you can use the :opt:`scrollback_pager_history_size`
option.
Frequently Asked Questions
---------------------------------
@@ -384,6 +433,16 @@ The important thing above is to make sure the call to |kitty| to load the zsh
completions happens after the call to :file:`compinit`.
fish
~~~~~~~~
Add the following to your :file:`~/.config/fish/config.fish`
.. code-block:: sh
kitty + complete setup fish | source
Changelog
------------------

129
docs/kittens/custom.rst Normal file
View File

@@ -0,0 +1,129 @@
Custom kittens
=================
You can easily create your own kittens to extend kitty. They are just
terminal programs written in Python. When launching a kitten, kitty will
open an overlay window over the current window and optionally pass the
contents of the current window/scrollback to the kitten over its :file:`STDIN`.
The kitten can then perform whatever actions it likes, just as a normal
terminal program. After execution of the kitten is complete, it has access
to the running kitty instance so it can perform arbitrary actions
such as closing windows, pasting text, etc.
Let's see a simple example of creating a kitten. It will ask the user for some
input and paste it into the terminal window.
Create a file in the kitty config folder, :file:`~/.config/kitty/mykitten.py`
(you might need to adjust the path to wherever the kitty config folder is on
your machine).
.. code-block:: python
def main(args):
# this is the main entry point of the kitten, it will be executed in
# the overlay window when the kitten is launched
answer = input('Enter some text: ')
# whatever this function returns will be available in the
# handle_result() function
return answer
def handle_result(args, answer, target_window_id, boss):
# get the kitty window into which to paste answer
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(answer)
Now in :file:`kitty.conf` add the lines::
map ctrl+k kitten mykitten.py
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
The best way to develop your own kittens is to modify one of the built in
kittens. Look in the kittens sub-directory of the kitty source code for those.
Passing arguments to kittens
------------------------------
You can pass arguments to kittens by defining them in the map directive in
:file:`kitty.conf`. For example::
map ctrl+k kitten mykitten.py arg1 arg2
These will be available as the ``args`` parameter in the ``main()`` and
``handle_result()`` functions. Note also that the current working directory
of the kitten is set to the working directory of whatever program is
running in the active kitty window.
Passing the contents of the screen to the kitten
---------------------------------------------------
If you would like your kitten to have access to the contents of the screen
and/or the scrollback buffer, you just need to add an annotation to the ``handle_result()``
function, telling kitty what kind of input your kitten would like. For example:
.. code-block:: py
# in main, STDIN is for the kitten process and will contain
# the contents of the screen
def main(args):
return sys.stdin.read()
# in handle_result, STDIN is for the kitty process itself, rather
# than the kitten process and should not be read from.
def handle_result(args, stdin_data, target_window_id, boss):
pass
handle_result.type_of_input = 'text'
This will send the plain text of the active window to the kitten's
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
instead. If you want line wrap markers as well, use ``screen-ansi``
or just ``screen``. For the scrollback buffer as well, use
``history``, ``ansi-history`` or ``screen-history``.
Using kittens to script kitty, without any terminal UI
-----------------------------------------------------------
If you would like your kitten to script kitty, without bothering to write a
terminal program, you can tell the kittens system to run the
``handle_result()`` function without first running the ``main()`` function.
For example, here is a kitten that "zooms/unzooms" the current terminal window
by switching to the stack layout or back to the previous layout.
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
.. code-block:: py
def main(args):
pass
def handle_result(args, answer, target_window_id, boss):
tab = boss.active_tab
if tab is not None:
if tab.current_layout.name == 'stack':
tab.last_used_layout()
else:
tab.goto_layout('stack')
handle_result.no_ui = True
Now in kitty.conf add::
map f11 kitten zoom_toggle.py
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
more fancy, switching the kitty OS window to fullscreen as well as changing the
layout, by simply adding the line::
boss.toggle_fullscreen()
to the ``handle_result()`` function, above.

View File

@@ -19,7 +19,9 @@ terminal, very useful for picking files from the output of a ``git`` or ``ls`` c
adding them to the command line for the next command.
The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, see below.
options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text
snippets. See :sc:`insert_selected_path` for examples.
Command Line Interface
-------------------------

View File

@@ -10,6 +10,12 @@ It supports all image types supported by `ImageMagick
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
the :doc:`kitty graphics protocol </graphics-protocol>`.
You might want to create an alias in your shell's configuration files::
alias icat="kitty +kitten icat"
Then you can simply use ``icat image.png`` to view images.
.. note::
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to

View File

@@ -8,10 +8,10 @@ glyph in video RAM so that font rendering is not a bottleneck. Interaction
with child programs takes place in a separate thread from rendering, to improve
smoothness.
There are two parameters you can tune to adjust the performance. ``repaint_delay``
and ``input_delay``. These control the artificial delays introduced into the
There are two parameters you can tune to adjust the performance. :opt:`repaint_delay`
and :opt:`input_delay`. These control the artificial delays introduced into the
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
See also the ``sync_to_monitor`` option to further decrease latency at the cost
See also the :opt:`sync_to_monitor` option to further decrease latency at the cost
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
You can generate detailed per-function performance data using

88
docs/pipe.rst Normal file
View File

@@ -0,0 +1,88 @@
Working with the screen and history buffer contents
======================================================
You can pipe the contents of the current screen and history buffer as
:file:`STDIN` to an arbitrary program using the ``pipe`` function. The program
can be displayed in a kitty window or overlay.
For example, the following in :file:`kitty.conf` will open the scrollback
buffer in less in an overlay window, when you press :kbd:`F1`::
map f1 pipe @ansi overlay less +G -R
The syntax of the ``pipe`` function is::
pipe <input placeholder> <destination window type> <command line to run>
The piping environment
--------------------------
The program to which the data is piped has a special environment variable
declared, ``KITTY_PIPE_DATA`` whose contents are::
KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}
where ``scrolled_by`` is the number of lines kitty is currently scrolled by,
``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)``
being the top left corner and ``{lines},{columns}`` being the number of rows
and columns of the screen.
You can choose where to run the pipe program:
``overlay``
An overlay window over the current kitty window
``window``
A new kitty window
``os_window``
A new top-level window
``tab``
A new window in a new tab
``none``
Run it in the background
Input placeholders
--------------------
There are various different kinds of placeholders
``@text``
Plain text, current screen + scrollback buffer
``@ansi``
Text with formatting, current screen + scrollback buffer
``@screen``
Plain text, only current screen
``@ansi_screen``
Text with formatting, only current screen
``@alternate``
Plain text, secondary screen. The secondary screen is the screen not currently displayed. For
example if you run a fullscreen terminal application, the secondary screen will
be the screen you return to when quitting the application.
``@ansi_alternate``
Text with formatting, secondary screen.
``@alternate_scrollback``
Plain text, secondary screen + scrollback, if any.
``@ansi_alternate_scrollback``
Text with formatting, secondary screen + scrollback, if any.
``none``
No input
You can also add the suffix ``_wrap`` to the placeholder, in which case kitty
will insert the carriage return at every line wrap location (where long lines
are wrapped at screen edges). This is useful if you want to pipe to program
that wants to duplicate the screen layout of the screen.

View File

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

View File

@@ -14,7 +14,7 @@ Let's walk through a few examples of controlling |kitty|.
Start by running |kitty| as::
kitty -o allow_remote_control=yes --window-layout tall
kitty -o allow_remote_control=yes -o enabled_layouts=tall
In order for control to work, :opt:`allow_remote_control` must be enabled in
:file:`kitty.conf`. Here we turn it on explicitly at the command line.

View File

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

51
glfw/backend_utils.c vendored
View File

@@ -261,3 +261,54 @@ closeFds(int *fds, size_t count) {
fds++;
}
}
// Splits and translates a text/uri-list into separate file paths
// NOTE: This function destroys the provided string
//
char** parseUriList(char* text, int* count)
{
const char* prefix = "file://";
char** paths = NULL;
char* line;
*count = 0;
while ((line = strtok(text, "\r\n")))
{
text = NULL;
if (line[0] == '#')
continue;
if (strncmp(line, prefix, strlen(prefix)) == 0)
{
line += strlen(prefix);
// TODO: Validate hostname
while (*line != '/')
line++;
}
(*count)++;
char* path = calloc(strlen(line) + 1, 1);
paths = realloc(paths, *count * sizeof(char*));
paths[*count - 1] = path;
while (*line)
{
if (line[0] == '%' && line[1] && line[2])
{
const char digits[3] = { line[1], line[2], '\0' };
*path = strtol(digits, NULL, 16);
line += 2;
}
else
*path = *line;
path++;
line++;
}
}
return paths;
}

View File

@@ -71,3 +71,4 @@ int pollForEvents(EventLoopData *eld, double timeout);
unsigned dispatchTimers(EventLoopData *eld);
void closeFds(int *fds, size_t count);
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
char** parseUriList(char* text, int* count);

View File

@@ -26,7 +26,11 @@
#include "internal.h"
#include <sys/param.h> // For MAXPATHLEN
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
#define NSEventMaskKeyUp NSKeyUpMask
#define NSEventMaskKeyDown NSKeyDownMask
#define NSEventModifierFlagCommand NSCommandKeyMask
#endif
// Change to our application bundle's resources directory, if present
//
@@ -274,17 +278,21 @@ static GLFWbool initializeTIS(void)
return updateUnicodeDataNS();
}
@interface GLFWLayoutListener : NSObject
@interface GLFWHelper : NSObject
@end
@implementation GLFWLayoutListener
@implementation GLFWHelper
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object
{
updateUnicodeDataNS();
}
@end
- (void)doNothing:(id)object
{
}
@end // GLFWHelper
//////////////////////////////////////////////////////////////////////////
@@ -294,13 +302,53 @@ static GLFWbool initializeTIS(void)
int _glfwPlatformInit(void)
{
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
_glfw.ns.helper = [[GLFWHelper alloc] init];
[NSThread detachNewThreadSelector:@selector(doNothing:)
toTarget:_glfw.ns.helper
withObject:nil];
[NSApplication sharedApplication];
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
NSEventModifierFlags modifierFlags = [event modifierFlags];
if (event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
// Cocoa swallows Ctrl+Tab to cycle between views
[[NSApp keyWindow].contentView keyDown:event];
}
return event;
};
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
{
NSEventModifierFlags modifierFlags = [event modifierFlags];
if ([event modifierFlags] & NSEventModifierFlagCommand) {
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
// This works around an AppKit bug, where key up events while holding
// down the command key don't get sent to the key window.
[[NSApp keyWindow] sendEvent:event];
}
if (event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
// Cocoa swallows Ctrl+Tab to cycle between views
[[NSApp keyWindow].contentView keyUp:event];
}
return event;
};
_glfw.ns.keyUpMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
handler:keyup_block];
_glfw.ns.keyDownMonitor =
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
handler:keydown_block];
if (_glfw.hints.init.ns.chdir)
changeToResourcesDirectory();
_glfw.ns.listener = [[GLFWLayoutListener alloc] init];
[[NSNotificationCenter defaultCenter]
addObserver:_glfw.ns.listener
addObserver:_glfw.ns.helper
selector:@selector(selectedKeyboardInputSourceChanged:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
@@ -345,17 +393,21 @@ void _glfwPlatformTerminate(void)
_glfw.ns.delegate = nil;
}
if (_glfw.ns.listener)
if (_glfw.ns.helper)
{
[[NSNotificationCenter defaultCenter]
removeObserver:_glfw.ns.listener
removeObserver:_glfw.ns.helper
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object:nil];
[[NSNotificationCenter defaultCenter]
removeObserver:_glfw.ns.listener];
[_glfw.ns.listener release];
_glfw.ns.listener = nil;
removeObserver:_glfw.ns.helper];
[_glfw.ns.helper release];
_glfw.ns.helper = nil;
}
if (_glfw.ns.keyUpMonitor)
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
if (_glfw.ns.keyDownMonitor)
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
free(_glfw.ns.clipboardString);

View File

@@ -467,7 +467,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
CVDisplayLinkRelease(link);
}
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
uint32_t i, size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
@@ -489,6 +489,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
}
free(values);
return GLFW_TRUE;
}
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
@@ -523,4 +524,3 @@ GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
return monitor->ns.displayID;
}

11
glfw/cocoa_platform.h vendored
View File

@@ -27,18 +27,17 @@
#include <stdint.h>
#include <dlfcn.h>
#include <Carbon/Carbon.h>
#if defined(__OBJC__)
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#else
#include <Carbon/Carbon.h>
#include <ApplicationServices/ApplicationServices.h>
typedef void* id;
#endif
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef struct VkMacOSSurfaceCreateInfoMVK
{
@@ -102,6 +101,8 @@ typedef struct _GLFWwindowNS
// The text input filter callback
GLFWcocoatextinputfilterfun textInputFilterCallback;
// The toggle fullscreen intercept callback
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
// Dead key state
UInt32 deadKeyState;
} _GLFWwindowNS;
@@ -117,7 +118,9 @@ typedef struct _GLFWlibraryNS
TISInputSourceRef inputSource;
IOHIDManagerRef hidManager;
id unicodeData;
id listener;
id helper;
id keyUpMonitor;
id keyDownMonitor;
char keyName[64];
char text[256];

View File

@@ -47,9 +47,13 @@
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
#define NSEventMaskAny NSAnyEventMask
#define NSEventTypeApplicationDefined NSApplicationDefined
#define NSEventTypeKeyUp NSKeyUp
#endif
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
#define NSPasteboardTypeFileURL NSFilenamesPboardType
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
#define NSPasteboardTypeString NSStringPboardType
#endif
// Returns the style mask corresponding to the window settings
//
@@ -307,6 +311,7 @@ static int translateKey(unsigned int key, GLFWbool apply_keymap)
K(';', SEMICOLON);
K('[', LEFT_BRACKET);
K(']', RIGHT_BRACKET);
K('+', PLUS);
K('`', GRAVE_ACCENT);
K('\\', BACKSLASH);
#undef K
@@ -339,6 +344,8 @@ static NSUInteger translateKeyToModifierFlag(int key)
case GLFW_KEY_LEFT_SUPER:
case GLFW_KEY_RIGHT_SUPER:
return NSEventModifierFlagCommand;
case GLFW_KEY_CAPS_LOCK:
return NSEventModifierFlagCapsLock;
}
return 0;
@@ -450,6 +457,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
_glfwInputWindowFocus(window, GLFW_TRUE);
updateCursorMode(window);
if (_glfw.ns.disabledCursorWindow != window && cursorInClientArea(window))
{
double x = 0, y = 0;
_glfwPlatformGetCursorPos(window, &x, &y);
_glfwInputCursorPos(window, x, y);
}
}
- (void)windowDidResignKey:(NSNotification *)notification
@@ -549,8 +562,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
markedText = [[NSMutableAttributedString alloc] init];
[self updateTrackingAreas];
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSFilenamesPboardType, nil]];
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL]];
}
return self;
@@ -563,6 +575,10 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
[super dealloc];
}
- (_GLFWwindow*)glfwWindow {
return window;
}
- (BOOL)isOpaque
{
return [window->ns.object isOpaque];
@@ -583,6 +599,21 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
return YES;
}
- (void)updateLayer
{
if (window->context.client != GLFW_NO_API) {
@try {
[window->context.nsgl.object update];
} @catch (NSException *e) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Failed to update NSGL Context object with error: %s (%s)",
[[e name] UTF8String], [[e reason] UTF8String]);
}
}
_glfwInputWindowDamage(window);
}
- (id)makeBackingLayer
{
if (window->ns.layer)
@@ -596,6 +627,11 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
updateCursorImage(window);
}
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
return NO; // changed by Kovid, to follow cocoa platform conventions
}
- (void)mouseDown:(NSEvent *)event
{
_glfwInputMouseClick(window,
@@ -824,28 +860,22 @@ is_ascii_control_char(char x) {
window->ns.deadKeyState = 0;
return;
}
debug_key(@"scancode: 0x%x (%s) %schar_count: %lu deadKeyState: %u ", scancode, safe_name_for_scancode(scancode), format_mods(mods), char_count, window->ns.deadKeyState);
if (process_text) {
// We check if cocoa wants to insert text, as UCKeyTranslate
// inserts text even when the cmd key is pressed. For instance,
// cmd+a will result in the text a.
// this will call insertText which will fill up _glfw.ns.text
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
_glfw.ns.text[0] = 0;
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
} else {
window->ns.deadKeyState = 0;
}
if (window->ns.deadKeyState && char_count == 0) {
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) {
// 0x75 is the delete key which needs to be ignored during a compose sequence
debug_key(@"Ignoring dead key.\n");
return;
}
}
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
scancode, safe_name_for_scancode(scancode), format_mods(mods),
format_text(_glfw.ns.text), _glfwGetKeyName(key));
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
debug_key(@"text: %s glfw_key: %s\n",
format_text(_glfw.ns.text), _glfwGetKeyName(key));
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
}
@@ -885,52 +915,64 @@ is_ascii_control_char(char x) {
deltaX = [event scrollingDeltaX];
deltaY = [event scrollingDeltaY];
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
if (flags) {
float xscale = 1, yscale = 1;
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
if (xscale > 0) deltaX *= xscale;
if (yscale > 0) deltaY *= yscale;
}
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
_glfwInputScroll(window, deltaX, deltaY, flags);
switch([event momentumPhase]) {
case NSEventPhaseBegan:
flags |= (1 << 1); break;
case NSEventPhaseStationary:
flags |= (2 << 1); break;
case NSEventPhaseChanged:
flags |= (3 << 1); break;
case NSEventPhaseEnded:
flags |= (4 << 1); break;
case NSEventPhaseCancelled:
flags |= (5 << 1); break;
case NSEventPhaseMayBegin:
flags |= (6 << 1); break;
case NSEventPhaseNone:
default:
break;
}
_glfwInputScroll(window, deltaX, deltaY, flags);
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
== NSDragOperationGeneric)
{
[self setNeedsDisplay:YES];
return NSDragOperationGeneric;
}
return NSDragOperationNone;
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
[self setNeedsDisplay:YES];
return YES;
// HACK: We don't know what to say here because we don't know what the
// application wants to do with the paths
return NSDragOperationGeneric;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard* pasteboard = [sender draggingPasteboard];
NSArray* files = [pasteboard propertyListForType:NSFilenamesPboardType];
const NSRect contentRect = [window->ns.view frame];
_glfwInputCursorPos(window,
[sender draggingLocation].x,
contentRect.size.height - [sender draggingLocation].y);
NSPasteboard* pasteboard = [sender draggingPasteboard];
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
options:options];
if (!urls) return NO;
const NSUInteger count = [urls count];
const NSUInteger count = [files count];
if (count)
{
NSEnumerator* e = [files objectEnumerator];
char** paths = calloc(count, sizeof(char*));
NSUInteger i;
for (i = 0; i < count; i++)
paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
for (NSUInteger i = 0; i < count; i++)
paths[i] = _glfw_strdup([[urls objectAtIndex:i] fileSystemRepresentation]);
_glfwInputDrop(window, (int) count, (const char**) paths);
for (i = 0; i < count; i++)
for (NSUInteger i = 0; i < count; i++)
free(paths[i]);
free(paths);
}
@@ -938,11 +980,6 @@ is_ascii_control_char(char x) {
return YES;
}
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
[self setNeedsDisplay:YES];
}
- (BOOL)hasMarkedText
{
return [markedText length] > 0;
@@ -1040,70 +1077,21 @@ is_ascii_control_char(char x) {
return YES;
}
@end
//------------------------------------------------------------------------
// GLFW application class
//------------------------------------------------------------------------
@interface GLFWApplication : NSApplication
- (void)toggleFullScreen:(nullable id)sender
{
NSArray* nibObjects;
}
@end
@implementation GLFWApplication
- (void)sendEvent:(NSEvent *)event
{
NSEventType etype = [event type];
NSEventModifierFlags flags;
switch(etype) {
case NSEventTypeKeyUp:
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
if (flags & NSEventModifierFlagCommand) {
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
// This works around an AppKit bug, where key up events while holding
// down the command key don't get sent to the key window.
[[self keyWindow] sendEvent:event];
return;
}
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
// Cocoa swallows Ctrl+Tab to cycle between views
[[self keyWindow].contentView keyUp:event];
return;
}
break;
case NSEventTypeKeyDown:
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
// Cocoa swallows Ctrl+Tab to cycle between views
[[self keyWindow].contentView keyDown:event];
return;
}
break;
default:
break;
GLFWContentView *view = [self contentView];
if (view)
{
_GLFWwindow *window = [view glfwWindow];
if (window && window->ns.toggleFullscreenCallback && window->ns.toggleFullscreenCallback((GLFWwindow*)window) == 1)
return;
}
[super sendEvent:event];
}
// No-op thread entry point
//
- (void)doNothing:(id)object
{
}
- (void)loadMainMenu
{ // removed by Kovid as it generated compiler warnings
[super toggleFullScreen:sender];
}
@end
// Set up the menu bar (manually)
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
// could go away at any moment, lots of stuff that really should be
@@ -1213,32 +1201,9 @@ static void createMenuBar(void)
//
static GLFWbool initializeAppKit(void)
{
if (NSApp)
if (_glfw.ns.delegate)
return GLFW_TRUE;
// Implicitly create shared NSApplication instance
[GLFWApplication sharedApplication];
// Make Cocoa enter multi-threaded mode
[NSThread detachNewThreadSelector:@selector(doNothing:)
toTarget:NSApp
withObject:nil];
if (_glfw.hints.init.ns.menubar)
{
// In case we are unbundled, make us a proper UI application
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// Menu bar setup must go between sharedApplication above and
// finishLaunching below, in order to properly emulate the behavior
// of NSApplicationMain
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
[NSApp loadMainMenu];
else
createMenuBar();
}
// There can only be one application delegate, but we allocate it the
// first time a window is created to keep all window code in this file
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
@@ -1248,15 +1213,29 @@ static GLFWbool initializeAppKit(void)
"Cocoa: Failed to create application delegate");
return GLFW_FALSE;
}
[NSApp setDelegate:_glfw.ns.delegate];
if (_glfw.hints.init.ns.menubar)
{
// In case we are unbundled, make us a proper UI application
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
// Menu bar setup must go between sharedApplication above and
// finishLaunching below, in order to properly emulate the behavior
// of NSApplicationMain
// disabled by Kovid
/* if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) */
/* [NSApp loadMainMenu]; */
/* else */
createMenuBar();
}
[NSApp run];
// Press and Hold prevents some keys from emitting repeated characters
NSDictionary* defaults =
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],
@"ApplePressAndHoldEnabled",
nil];
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
return GLFW_TRUE;
@@ -1553,6 +1532,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = (float) (pixels.size.height / points.size.height);
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return [NSEvent doubleClickInterval];
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
[window->ns.object miniaturize:nil];
@@ -1763,9 +1747,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
void _glfwPlatformPollEvents(void)
{
if (!initializeAppKit())
return;
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
@@ -1775,7 +1756,7 @@ void _glfwPlatformPollEvents(void)
if (event == nil)
break;
[NSApp sendEvent:event];
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
}
[_glfw.ns.autoreleasePool drain];
@@ -1791,7 +1772,7 @@ void _glfwPlatformWaitEvents(void)
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
_glfwPlatformPollEvents();
}
@@ -1803,8 +1784,7 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
untilDate:date
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event)
[NSApp sendEvent:event];
if (event && [event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
_glfwPlatformPollEvents();
}
@@ -1907,8 +1887,6 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
NSImage* native;
NSBitmapImageRep* rep;
if (!initializeAppKit())
return GLFW_FALSE;
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
if (native == nil)
return GLFW_FALSE;
@@ -1924,7 +1902,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:src->width * 4
bitsPerPixel:32];
if (rep == nil)
@@ -1945,8 +1923,6 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
if (!initializeAppKit())
return GLFW_FALSE;
if (shape == GLFW_ARROW_CURSOR)
cursor->ns.object = [NSCursor arrowCursor];
@@ -1986,26 +1962,24 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
void _glfwPlatformSetClipboardString(const char* string)
{
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:types owner:nil];
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
[pasteboard setString:[NSString stringWithUTF8String:string]
forType:NSStringPboardType];
forType:NSPasteboardTypeString];
}
const char* _glfwPlatformGetClipboardString(void)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (![[pasteboard types] containsObject:NSStringPboardType])
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"Cocoa: Failed to retrieve string from pasteboard");
return NULL;
}
NSString* object = [pasteboard stringForType:NSStringPboardType];
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
if (!object)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -2114,6 +2088,14 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
return previous;
}
GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
GLFWcocoatogglefullscreenfun previous = window->ns.toggleFullscreenCallback;
window->ns.toggleFullscreenCallback = callback;
return previous;
}
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
handle_reopen_callback = callback;
@@ -2181,6 +2163,7 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
K(';', SEMICOLON);
K('[', LEFT_BRACKET);
K(']', RIGHT_BRACKET);
K('+', PLUS);
K('`', GRAVE_ACCENT);
K('\\', BACKSLASH);

5
glfw/egl_context.h vendored
View File

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

View File

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

51
glfw/glfw3.h vendored
View File

@@ -407,6 +407,7 @@ extern "C" {
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
#define GLFW_KEY_PLUS 163
/* Function keys */
#define GLFW_KEY_ESCAPE 256
@@ -973,6 +974,10 @@ extern "C" {
* [attribute](@ref GLFW_CLIENT_API_attrib).
*/
#define GLFW_CONTEXT_CREATION_API 0x0002200B
/*! @brief Window content area scaling window
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
*/
#define GLFW_SCALE_TO_MONITOR 0x0002200C
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
#define GLFW_COCOA_FRAME_NAME 0x00023002
@@ -980,6 +985,8 @@ extern "C" {
#define GLFW_X11_CLASS_NAME 0x00024001
#define GLFW_X11_INSTANCE_NAME 0x00024002
#define GLFW_WAYLAND_APP_ID 0x00025001
/*! @} */
#define GLFW_NO_API 0
@@ -1373,7 +1380,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
* @param[in] window The window that received the event.
* @param[in] xoffset The scroll offset along the x-axis.
* @param[in] yoffset The scroll offset along the y-axis.
* @param[in] flags A bit-mask providing extra data about the event. flags & 1 will be true if and only if the offset values are "high-precision". Typically pixel values. Otherwise the offset values are number of lines.
* @param[in] flags A bit-mask providing extra data about the event.
* flags & 1 will be true if and only if the offset values are "high-precision".
* Typically pixel values. Otherwise the offset values are number of lines.
* (flags >> 1) & 7 will have value 1 for the start of momentum scrolling,
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
* scrolling cancelled and value 6 if scrolling may begin soon.
*
* @sa @ref scrolling
* @sa @ref glfwSetScrollCallback
@@ -2115,9 +2128,9 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor);
/*! @brief Generates a gamma ramp and sets it for the specified monitor.
*
* This function generates a 256-element gamma ramp from the specified exponent
* and then calls @ref glfwSetGammaRamp with it. The value must be a finite
* number greater than zero.
* This function generates an appropriately sized gamma ramp from the
* specified exponent and then calls @ref glfwSetGammaRamp with it. The value
* must be a finite number greater than zero.
*
* The software controlled gamma ramp is applied _in addition_ to the hardware
* gamma correction, which today is usually an approximation of sRGB gamma.
@@ -2196,8 +2209,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* monitor);
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @remark Gamma ramp sizes other than 256 are not supported by all platforms
* or graphics hardware.
* @remark The size of the specified gamma ramp should match the size of the
* current ramp for that monitor.
*
* @remark @win32 The gamma ramp size must be 256.
*
@@ -2924,6 +2937,32 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
*/
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);
/*! @brief Returns the double click time interval.
*
* This function returns the maximum time between clicks to count as a
* double click.
*
* The double click interval is a positive finite number greater than zero,
* where zero means that no click is ever recognized as a double click. If the
* system does not support a double click interval, this function always returns one half.
*
* @return The double click interval.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref double_click
* @sa @ref click_interval
* @sa @ref double_click_interval
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* window);
/*! @brief Returns the opacity of the whole window.
*
* This function returns the opacity of the window, including any decorations.

3
glfw/ibus_glfw.c vendored
View File

@@ -280,8 +280,7 @@ setup_connection(_GLFWIBUSData *ibus) {
void
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
if (ibus->inited) return;
if (!test_env_var("XMODIFIERS", "@im=ibus") && !test_env_var("GTK_IM_MODULE", "ibus") && !test_env_var("QT_IM_MODULE", "ibus") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
if (getenv("GLFW_IM_MODULE") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
ibus->inited = GLFW_TRUE;
setup_connection(ibus);
}

17
glfw/input.c vendored
View File

@@ -487,6 +487,7 @@ const char* _glfwGetKeyName(int key)
case GLFW_KEY_SLASH: return "SLASH";
case GLFW_KEY_WORLD_1: return "WORLD 1";
case GLFW_KEY_WORLD_2: return "WORLD 2";
case GLFW_KEY_PLUS: return "PLUS";
// Function keys
case GLFW_KEY_ESCAPE: return "ESCAPE";
@@ -1375,6 +1376,22 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle)
return _glfwPlatformGetClipboardString();
}
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle, const char* string)
{
assert(string != NULL);
_GLFW_REQUIRE_INIT();
_glfwPlatformSetPrimarySelectionString(string);
}
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetPrimarySelectionString();
}
#endif
GLFWAPI double glfwGetTime(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);

18
glfw/internal.h vendored
View File

@@ -31,6 +31,8 @@
#include "glfw_config.h"
#endif
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#if defined(GLFW_INCLUDE_GLCOREARB) || \
defined(GLFW_INCLUDE_ES1) || \
defined(GLFW_INCLUDE_ES2) || \
@@ -127,7 +129,6 @@ typedef enum VkStructureType
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
@@ -189,8 +190,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
#include "x11_platform.h"
#elif defined(_GLFW_WAYLAND)
#include "wl_platform.h"
#elif defined(_GLFW_MIR)
#include "mir_platform.h"
#elif defined(_GLFW_OSMESA)
#include "null_platform.h"
#else
@@ -271,6 +270,7 @@ struct _GLFWwndconfig
GLFWbool maximized;
GLFWbool centerCursor;
GLFWbool focusOnShow;
GLFWbool scaleToMonitor;
struct {
GLFWbool retina;
char frameName[256];
@@ -279,6 +279,9 @@ struct _GLFWwndconfig
char className[256];
char instanceName[256];
} x11;
struct {
char appId[256];
} wl;
};
// Context configuration
@@ -565,8 +568,6 @@ struct _GLFWlibrary
GLFWbool KHR_xcb_surface;
#elif defined(_GLFW_WAYLAND)
GLFWbool KHR_wayland_surface;
#elif defined(_GLFW_MIR)
GLFWbool KHR_mir_surface;
#endif
} vk;
@@ -618,11 +619,15 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
float* xscale, float* yscale);
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
void _glfwPlatformSetClipboardString(const char* string);
const char* _glfwPlatformGetClipboardString(void);
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
void _glfwPlatformSetPrimarySelectionString(const char* string);
const char* _glfwPlatformGetPrimarySelectionString(void);
#endif
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
void _glfwPlatformUpdateGamepadGUID(char* guid);
@@ -652,6 +657,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* right, int* bottom);
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
float* xscale, float* yscale);
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);

View File

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

54
glfw/memfd.h vendored Normal file
View File

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

27
glfw/monitor.c vendored
View File

@@ -427,11 +427,11 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* handle)
GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
{
int i;
unsigned short values[256];
unsigned int i;
unsigned short* values;
GLFWgammaramp ramp;
const GLFWgammaramp* original;
assert(handle != NULL);
assert(gamma == gamma);
assert(gamma >= 0.f);
assert(gamma <= FLT_MAX);
@@ -442,13 +442,18 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
_glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma);
return;
}
original = glfwGetGammaRamp(handle);
if (!original)
return;
for (i = 0; i < 256; i++)
values = calloc(original->size, sizeof(unsigned short));
for (i = 0; i < original->size; i++)
{
float value;
// Calculate intensity
value = i / 255.f;
value = i / (float) (original->size - 1);
// Apply gamma curve
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
@@ -462,9 +467,10 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
ramp.red = values;
ramp.green = values;
ramp.blue = values;
ramp.size = 256;
ramp.size = original->size;
glfwSetGammaRamp(handle, &ramp);
free(values);
}
GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
@@ -475,7 +481,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_glfwFreeGammaArrays(&monitor->currentRamp);
_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp);
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp))
return NULL;
return &monitor->currentRamp;
}
@@ -501,8 +508,10 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* handle, const GLFWgammaramp* ramp)
_GLFW_REQUIRE_INIT();
if (!monitor->originalRamp.size)
_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp);
{
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp))
return;
}
_glfwPlatformSetGammaRamp(monitor, ramp);
}

View File

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

4
glfw/null_monitor.c vendored
View File

@@ -58,11 +58,11 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
{
}
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
return GLFW_FALSE;
}
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
{
}

5
glfw/null_window.c vendored
View File

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

View File

@@ -240,7 +240,7 @@ GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window,
if (ctxconfig->forward)
{
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
"OSMesa: Foward-compatible contexts not supported");
"OSMesa: Forward-compatible contexts not supported");
return GLFW_FALSE;
}
@@ -367,4 +367,3 @@ GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle)
return window->context.osmesa.handle;
}

View File

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

4
glfw/vulkan.c vendored
View File

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

34
glfw/wgl_context.c vendored
View File

@@ -256,12 +256,20 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
static void swapBuffersWGL(_GLFWwindow* window)
{
// HACK: Use DwmFlush when desktop composition is enabled
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
if (!window->monitor)
{
int count = abs(window->context.wgl.interval);
while (count--)
DwmFlush();
if (IsWindowsVistaOrGreater())
{
BOOL enabled;
// HACK: Use DwmFlush when desktop composition is enabled
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
{
int count = abs(window->context.wgl.interval);
while (count--)
DwmFlush();
}
}
}
SwapBuffers(window->context.wgl.dc);
@@ -273,10 +281,18 @@ static void swapIntervalWGL(int interval)
window->context.wgl.interval = interval;
// HACK: Disable WGL swap interval when desktop composition is enabled to
// avoid interfering with DWM vsync
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
interval = 0;
if (!window->monitor)
{
if (IsWindowsVistaOrGreater())
{
BOOL enabled;
// HACK: Disable WGL swap interval when desktop composition is enabled to
// avoid interfering with DWM vsync
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
interval = 0;
}
}
if (_glfw.wgl.EXT_swap_control)
_glfw.wgl.SwapIntervalEXT(interval);

68
glfw/win32_init.c vendored
View File

@@ -62,17 +62,6 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
#endif // _GLFW_BUILD_DLL
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp)
{
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
return VerifyVersionInfoW(&osvi, mask, cond);
}
// Load necessary libraries (DLLs)
//
static GLFWbool loadLibraries(void)
@@ -100,6 +89,14 @@ static GLFWbool loadLibraries(void)
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
_glfw.win32.user32.EnableNonClientDpiScaling_ = (PFN_EnableNonClientDpiScaling)
GetProcAddress(_glfw.win32.user32.instance, "EnableNonClientDpiScaling");
_glfw.win32.user32.SetProcessDpiAwarenessContext_ = (PFN_SetProcessDpiAwarenessContext)
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDpiAwarenessContext");
_glfw.win32.user32.GetDpiForWindow_ = (PFN_GetDpiForWindow)
GetProcAddress(_glfw.win32.user32.instance, "GetDpiForWindow");
_glfw.win32.user32.AdjustWindowRectExForDpi_ = (PFN_AdjustWindowRectExForDpi)
GetProcAddress(_glfw.win32.user32.instance, "AdjustWindowRectExForDpi");
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
if (_glfw.win32.dinput8.instance)
@@ -155,6 +152,13 @@ static GLFWbool loadLibraries(void)
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
}
_glfw.win32.ntdll.instance = LoadLibraryA("ntdll.dll");
if (_glfw.win32.ntdll.instance)
{
_glfw.win32.ntdll.RtlVerifyVersionInfo_ = (PFN_RtlVerifyVersionInfo)
GetProcAddress(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo");
}
return GLFW_TRUE;
}
@@ -179,6 +183,9 @@ static void freeLibraries(void)
if (_glfw.win32.shcore.instance)
FreeLibrary(_glfw.win32.shcore.instance);
if (_glfw.win32.ntdll.instance)
FreeLibrary(_glfw.win32.ntdll.instance);
}
// Create key code translation tables
@@ -309,6 +316,7 @@ static void createKeyTables(void)
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
_glfw.win32.keycodes[0x059] = GLFW_KEY_KP_EQUAL;
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
@@ -339,8 +347,8 @@ static HWND createHelperWindow(void)
return NULL;
}
// HACK: The first call to ShowWindow is ignored if the parent process
// passed along a STARTUPINFO, so clear that flag with a no-op call
// HACK: The command to the first ShowWindow call is ignored if the parent
// process passed along a STARTUPINFO, so clear that with a no-op call
ShowWindow(window, SW_HIDE);
// Register for HID device notifications
@@ -502,6 +510,36 @@ void _glfwUpdateKeyNamesWin32(void)
}
}
// Replacement for IsWindowsVersionOrGreater as MinGW lacks versionhelpers.h
//
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp)
{
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
// latter lies unless the user knew to embedd a non-default manifest
// announcing support for Windows 10 via supportedOS GUID
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
}
// Checks whether we are on at least the specified build of Windows 10
//
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build)
{
OSVERSIONINFOEXW osvi = { sizeof(osvi), 10, 0, build };
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER;
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
cond = VerSetConditionMask(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
// latter lies unless the user knew to embedd a non-default manifest
// announcing support for Windows 10 via supportedOS GUID
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
@@ -523,7 +561,9 @@ int _glfwPlatformInit(void)
createKeyTables();
_glfwUpdateKeyNamesWin32();
if (IsWindows8Point1OrGreater())
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
else if (IsWindows8Point1OrGreater())
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
else if (IsWindowsVistaOrGreater())
SetProcessDPIAware();

View File

@@ -324,9 +324,9 @@ void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* ysc
}
if (xscale)
*xscale = xdpi / 96.f;
*xscale = xdpi / (float) USER_DEFAULT_SCREEN_DPI;
if (yscale)
*yscale = ydpi / 96.f;
*yscale = ydpi / (float) USER_DEFAULT_SCREEN_DPI;
}
@@ -455,7 +455,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
&mode->blueBits);
}
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
HDC dc;
WORD values[768];
@@ -469,6 +469,8 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
memcpy(ramp->red, values + 0, 256 * sizeof(unsigned short));
memcpy(ramp->green, values + 256, 256 * sizeof(unsigned short));
memcpy(ramp->blue, values + 512, 256 * sizeof(unsigned short));
return GLFW_TRUE;
}
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
@@ -510,4 +512,3 @@ GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->win32.publicDisplayName;
}

71
glfw/win32_platform.h vendored
View File

@@ -98,6 +98,12 @@
#ifndef _WIN32_WINNT_WINBLUE
#define _WIN32_WINNT_WINBLUE 0x0602
#endif
#ifndef WM_GETDPISCALEDSIZE
#define WM_GETDPISCALEDSIZE 0x02e4
#endif
#ifndef USER_DEFAULT_SCREEN_DPI
#define USER_DEFAULT_SCREEN_DPI 96
#endif
#if WINVER < 0x0601
typedef struct
@@ -140,23 +146,32 @@ typedef enum
} MONITOR_DPI_TYPE;
#endif /*DPI_ENUMS_DECLARED*/
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) -4)
#endif /*DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2*/
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp);
#define IsWindowsXPOrGreater() \
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), \
LOBYTE(_WIN32_WINNT_WINXP), 0)
#define IsWindowsVistaOrGreater() \
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), \
LOBYTE(_WIN32_WINNT_VISTA), 0)
#define IsWindows7OrGreater() \
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), \
LOBYTE(_WIN32_WINNT_WIN7), 0)
#define IsWindows8OrGreater() \
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), \
LOBYTE(_WIN32_WINNT_WIN8), 0)
#define IsWindows8Point1OrGreater() \
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), \
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINXP), \
LOBYTE(_WIN32_WINNT_WINXP), 0)
#define IsWindowsVistaOrGreater() \
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_VISTA), \
LOBYTE(_WIN32_WINNT_VISTA), 0)
#define IsWindows7OrGreater() \
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN7), \
LOBYTE(_WIN32_WINNT_WIN7), 0)
#define IsWindows8OrGreater() \
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN8), \
LOBYTE(_WIN32_WINNT_WIN8), 0)
#define IsWindows8Point1OrGreater() \
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINBLUE), \
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
#define _glfwIsWindows10AnniversaryUpdateOrGreaterWin32() \
_glfwIsWindows10BuildOrGreaterWin32(14393)
#define _glfwIsWindows10CreatorsUpdateOrGreaterWin32() \
_glfwIsWindows10BuildOrGreaterWin32(15063)
// HACK: Define macros that some xinput.h variants don't
#ifndef XINPUT_CAPS_WIRELESS
@@ -209,8 +224,16 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
// user32.dll function pointer typedefs
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
typedef BOOL (WINAPI * PFN_EnableNonClientDpiScaling)(HWND);
typedef BOOL (WINAPI * PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
typedef UINT (WINAPI * PFN_GetDpiForWindow)(HWND);
typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
#define EnableNonClientDpiScaling _glfw.win32.user32.EnableNonClientDpiScaling_
#define SetProcessDpiAwarenessContext _glfw.win32.user32.SetProcessDpiAwarenessContext_
#define GetDpiForWindow _glfw.win32.user32.GetDpiForWindow_
#define AdjustWindowRectExForDpi _glfw.win32.user32.AdjustWindowRectExForDpi_
// dwmapi.dll function pointer typedefs
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
@@ -226,6 +249,10 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
// ntdll.dll function pointer typedefs
typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG);
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
typedef struct VkWin32SurfaceCreateInfoKHR
@@ -279,6 +306,7 @@ typedef struct _GLFWwindowWin32
GLFWbool maximized;
// Whether to enable framebuffer transparency on DWM
GLFWbool transparent;
GLFWbool scaleToMonitor;
// The last received cursor position, regardless of source
int lastCursorPosX, lastCursorPosY;
@@ -326,6 +354,10 @@ typedef struct _GLFWlibraryWin32
HINSTANCE instance;
PFN_SetProcessDPIAware SetProcessDPIAware_;
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
PFN_EnableNonClientDpiScaling EnableNonClientDpiScaling_;
PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContext_;
PFN_GetDpiForWindow GetDpiForWindow_;
PFN_AdjustWindowRectExForDpi AdjustWindowRectExForDpi_;
} user32;
struct {
@@ -341,6 +373,11 @@ typedef struct _GLFWlibraryWin32
PFN_GetDpiForMonitor GetDpiForMonitor_;
} shcore;
struct {
HINSTANCE instance;
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
} ntdll;
} _GLFWlibraryWin32;
// Win32-specific per-monitor data
@@ -396,10 +433,11 @@ typedef struct _GLFWmutexWin32
GLFWbool _glfwRegisterWindowClassWin32(void);
void _glfwUnregisterWindowClassWin32(void);
GLFWbool _glfwIsCompositionEnabledWin32(void);
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp);
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build);
void _glfwInputErrorWin32(int error, const char* description);
void _glfwUpdateKeyNamesWin32(void);
@@ -409,4 +447,3 @@ void _glfwPollMonitorsWin32(void);
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);

270
glfw/win32_window.c vendored
View File

@@ -186,14 +186,20 @@ static HICON createIcon(const GLFWimage* image,
return handle;
}
// Translate client window size to full window size according to styles
// Translate client window size to full window size according to styles and DPI
//
static void getFullWindowSize(DWORD style, DWORD exStyle,
int clientWidth, int clientHeight,
int* fullWidth, int* fullHeight)
int* fullWidth, int* fullHeight,
UINT dpi)
{
RECT rect = { 0, 0, clientWidth, clientHeight };
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, dpi);
else
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
*fullWidth = rect.right - rect.left;
*fullHeight = rect.bottom - rect.top;
}
@@ -206,7 +212,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area)
const float ratio = (float) window->numer / (float) window->denom;
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
0, 0, &xoff, &yoff);
0, 0, &xoff, &yoff,
GetDpiForWindow(window->win32.handle));
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
@@ -337,7 +344,16 @@ static void updateWindowStyles(const _GLFWwindow* window)
style |= getWindowStyle(window);
GetClientRect(window->win32.handle, &rect);
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, style, FALSE,
getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
}
else
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
@@ -351,10 +367,12 @@ static void updateWindowStyles(const _GLFWwindow* window)
//
static void updateFramebufferTransparency(const _GLFWwindow* window)
{
BOOL enabled;
if (!IsWindowsVistaOrGreater())
return;
if (_glfwIsCompositionEnabledWin32())
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
{
HRGN region = CreateRectRgn(0, 0, -1, -1);
DWM_BLURBEHIND bb = {0};
@@ -396,29 +414,6 @@ static void updateFramebufferTransparency(const _GLFWwindow* window)
}
}
// Translates a GLFW standard cursor to a resource ID
//
static LPWSTR translateCursorShape(int shape)
{
switch (shape)
{
case GLFW_ARROW_CURSOR:
return IDC_ARROW;
case GLFW_IBEAM_CURSOR:
return IDC_IBEAM;
case GLFW_CROSSHAIR_CURSOR:
return IDC_CROSS;
case GLFW_HAND_CURSOR:
return IDC_HAND;
case GLFW_HRESIZE_CURSOR:
return IDC_SIZEWE;
case GLFW_VRESIZE_CURSOR:
return IDC_SIZENS;
}
return NULL;
}
// Retrieves and translates modifier keys
//
static int getKeyMods(void)
@@ -578,9 +573,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
if (!window)
{
// This is the message handling for the hidden helper window
// and for a regular window during its initial creation
switch (uMsg)
{
case WM_NCCREATE:
{
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
EnableNonClientDpiScaling(hWnd);
break;
}
case WM_DISPLAYCHANGE:
_glfwPollMonitorsWin32();
break;
@@ -1003,7 +1007,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
break;
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
0, 0, &xoff, &yoff);
0, 0, &xoff, &yoff,
GetDpiForWindow(window->win32.handle));
if (window->minwidth != GLFW_DONT_CARE &&
window->minheight != GLFW_DONT_CARE)
@@ -1056,10 +1061,52 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
return 0;
}
case WM_GETDPISCALEDSIZE:
{
if (window->win32.scaleToMonitor)
break;
// Adjust the window size to keep the client area size constant
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
{
RECT source = {0}, target = {0};
SIZE* size = (SIZE*) lParam;
AdjustWindowRectExForDpi(&source, getWindowStyle(window),
FALSE, getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
AdjustWindowRectExForDpi(&target, getWindowStyle(window),
FALSE, getWindowExStyle(window),
LOWORD(wParam));
size->cx += (target.right - target.left) -
(source.right - source.left);
size->cy += (target.bottom - target.top) -
(source.bottom - source.top);
return TRUE;
}
break;
}
case WM_DPICHANGED:
{
const float xscale = HIWORD(wParam) / 96.f;
const float yscale = LOWORD(wParam) / 96.f;
const float xscale = HIWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
const float yscale = LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
// Only apply the suggested size if the OS is new enough to have
// sent a WM_GETDPISCALEDSIZE before this
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
{
RECT* suggested = (RECT*) lParam;
SetWindowPos(window->win32.handle, HWND_TOP,
suggested->left,
suggested->top,
suggested->right - suggested->left,
suggested->bottom - suggested->top,
SWP_NOACTIVATE | SWP_NOZORDER);
}
_glfwInputWindowContentScale(window, xscale, yscale);
break;
}
@@ -1146,7 +1193,8 @@ static int createNativeWindow(_GLFWwindow* window,
getFullWindowSize(style, exStyle,
wndconfig->width, wndconfig->height,
&fullWidth, &fullHeight);
&fullWidth, &fullHeight,
USER_DEFAULT_SCREEN_DPI);
}
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
@@ -1185,6 +1233,40 @@ static int createNativeWindow(_GLFWwindow* window,
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
}
window->win32.scaleToMonitor = wndconfig->scaleToMonitor;
// Adjust window size to account for DPI scaling of the window frame and
// optionally DPI scaling of the client area
// This cannot be done until we know what monitor it was placed on
if (!window->monitor)
{
RECT rect = { 0, 0, wndconfig->width, wndconfig->height };
if (wndconfig->scaleToMonitor)
{
float xscale, yscale;
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
rect.right = (int) (rect.right * xscale);
rect.bottom = (int) (rect.bottom * yscale);
}
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle,
GetDpiForWindow(window->win32.handle));
}
else
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
SetWindowPos(window->win32.handle, NULL,
rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOZORDER);
}
DragAcceptFiles(window->win32.handle, TRUE);
if (fbconfig->transparent)
@@ -1244,20 +1326,6 @@ void _glfwUnregisterWindowClassWin32(void)
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
}
// Returns whether desktop compositing is enabled
//
GLFWbool _glfwIsCompositionEnabledWin32(void)
{
if (IsWindowsVistaOrGreater())
{
BOOL enabled;
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)))
return enabled;
}
return FALSE;
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
@@ -1395,8 +1463,19 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
{
RECT rect = { xpos, ypos, xpos, ypos };
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
}
else
{
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
}
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
}
@@ -1425,8 +1504,19 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
else
{
RECT rect = { 0, 0, width, height };
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
}
else
{
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
}
SetWindowPos(window->win32.handle, HWND_TOP,
0, 0, rect.right - rect.left, rect.bottom - rect.top,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
@@ -1481,8 +1571,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
_glfwPlatformGetWindowSize(window, &width, &height);
SetRect(&rect, 0, 0, width, height);
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
}
else
{
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
}
if (left)
*left = -rect.left;
@@ -1502,6 +1602,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
_glfwGetMonitorContentScaleWin32(handle, xscale, yscale);
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
ShowWindow(window->win32.handle, SW_MINIMIZE);
@@ -1563,8 +1668,19 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
else
{
RECT rect = { xpos, ypos, xpos + width, ypos + height };
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
}
else
{
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
}
SetWindowPos(window->win32.handle, HWND_TOP,
rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
@@ -1624,8 +1740,18 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
else
after = HWND_NOTOPMOST;
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
{
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window),
GetDpiForWindow(window->win32.handle));
}
else
{
AdjustWindowRectEx(&rect, getWindowStyle(window),
FALSE, getWindowExStyle(window));
}
SetWindowPos(window->win32.handle, after,
rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
@@ -1660,7 +1786,15 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{
return window->win32.transparent && _glfwIsCompositionEnabledWin32();
BOOL enabled;
if (!window->win32.transparent)
return GLFW_FALSE;
if (!IsWindowsVistaOrGreater())
return GLFW_FALSE;
return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled;
}
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
@@ -1867,8 +2001,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
cursor->win32.handle =
CopyCursor(LoadCursorW(NULL, translateCursorShape(shape)));
LPCWSTR name = NULL;
if (shape == GLFW_ARROW_CURSOR)
name = IDC_ARROW;
else if (shape == GLFW_IBEAM_CURSOR)
name = IDC_IBEAM;
else if (shape == GLFW_CROSSHAIR_CURSOR)
name = IDC_CROSS;
else if (shape == GLFW_HAND_CURSOR)
name = IDC_HAND;
else if (shape == GLFW_HRESIZE_CURSOR)
name = IDC_SIZEWE;
else if (shape == GLFW_VRESIZE_CURSOR)
name = IDC_SIZENS;
else
return GLFW_FALSE;
cursor->win32.handle = CopyCursor(LoadCursorW(NULL, name));
if (!cursor->win32.handle)
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,

16
glfw/window.c vendored
View File

@@ -393,6 +393,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_COCOA_GRAPHICS_SWITCHING:
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_SCALE_TO_MONITOR:
_glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_CENTER_CURSOR:
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
return;
@@ -457,6 +460,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value)
strncpy(_glfw.hints.window.x11.instanceName, value,
sizeof(_glfw.hints.window.x11.instanceName) - 1);
return;
case GLFW_WAYLAND_APP_ID:
strncpy(_glfw.hints.window.wl.appId, value,
sizeof(_glfw.hints.window.wl.appId) - 1);
return;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
@@ -714,6 +721,15 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle,
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
}
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(0.5f);
return _glfwPlatformGetDoubleClickInterval(window);
}
GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;

75
glfw/wl_init.c vendored
View File

@@ -29,6 +29,8 @@
#include "backend_utils.h"
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
@@ -139,6 +141,7 @@ static void setCursor(const char* name)
"Wayland: Standard cursor not found");
return;
}
// TODO: handle animated cursors too.
image = cursor->images[0];
if (!image)
@@ -406,7 +409,7 @@ static void
dispatchPendingKeyRepeats(id_type timer_id, void *data) {
if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, ((double)_glfw.wl.keyboardRepeatRate) / 1000.0);
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
}
@@ -560,6 +563,12 @@ static void registryHandleGlobal(void* data,
_glfw.wl.seatVersion);
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
}
if (_glfw.wl.seat) {
if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice();
if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
_glfwSetupWaylandPrimarySelectionDevice();
}
}
}
else if (strcmp(interface, "xdg_wm_base") == 0)
{
@@ -567,6 +576,12 @@ static void registryHandleGlobal(void* data,
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
}
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
{
_glfw.wl.decorationManager =
wl_registry_bind(registry, name,
&zxdg_decoration_manager_v1_interface, 1);
}
else if (strcmp(interface, "wp_viewporter") == 0)
{
_glfw.wl.viewporter =
@@ -593,6 +608,28 @@ static void registryHandleGlobal(void* data,
&zwp_idle_inhibit_manager_v1_interface,
1);
}
else if (strcmp(interface, "wl_data_device_manager") == 0)
{
_glfw.wl.dataDeviceManager =
wl_registry_bind(registry, name,
&wl_data_device_manager_interface,
1);
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
_glfwSetupWaylandDataDevice();
}
}
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 ||
strcmp(interface, "gtk_primary_selection_device_manager") == 0)
{
_glfw.wl.primarySelectionDeviceManager =
wl_registry_bind(registry, name,
&zwp_primary_selection_device_manager_v1_interface,
1);
if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
_glfwSetupWaylandPrimarySelectionDevice();
}
}
}
static void registryHandleGlobalRemove(void *data,
@@ -674,7 +711,8 @@ int _glfwPlatformInit(void)
}
initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-keyrepeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", 0.5, 0, animateCursorImage, NULL);
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
wl_registry_add_listener(_glfw.wl.registry, &registryListener, NULL);
@@ -698,7 +736,17 @@ int _glfwPlatformInit(void)
if (_glfw.wl.pointer && _glfw.wl.shm)
{
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
char *cursorSizeEnd;
int cursorSize = 32;
if (cursorSizeStr)
{
errno = 0;
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
cursorSize = (int)cursorSizeLong;
}
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
if (!_glfw.wl.cursorTheme)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -747,6 +795,8 @@ void _glfwPlatformTerminate(void)
wl_shell_destroy(_glfw.wl.shell);
if (_glfw.wl.viewporter)
wp_viewporter_destroy(_glfw.wl.viewporter);
if (_glfw.wl.decorationManager)
zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager);
if (_glfw.wl.wmBase)
xdg_wm_base_destroy(_glfw.wl.wmBase);
if (_glfw.wl.pointer)
@@ -761,6 +811,21 @@ void _glfwPlatformTerminate(void)
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
if (_glfw.wl.idleInhibitManager)
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
if (_glfw.wl.dataSourceForClipboard)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
if (_glfw.wl.dataOffers[doi].id) {
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
}
}
if (_glfw.wl.dataDevice)
wl_data_device_destroy(_glfw.wl.dataDevice);
if (_glfw.wl.dataDeviceManager)
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
if (_glfw.wl.primarySelectionDevice)
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
if (_glfw.wl.primarySelectionDeviceManager)
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
if (_glfw.wl.registry)
wl_registry_destroy(_glfw.wl.registry);
if (_glfw.wl.display)
@@ -769,6 +834,10 @@ void _glfwPlatformTerminate(void)
wl_display_disconnect(_glfw.wl.display);
}
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
}
const char* _glfwPlatformGetVersionString(void)

58
glfw/wl_monitor.c vendored
View File

@@ -32,16 +32,16 @@
#include <errno.h>
static void geometry(void* data,
struct wl_output* output,
int32_t x,
int32_t y,
int32_t physicalWidth,
int32_t physicalHeight,
int32_t subpixel,
const char* make,
const char* model,
int32_t transform)
static void outputHandleGeometry(void* data,
struct wl_output* output,
int32_t x,
int32_t y,
int32_t physicalWidth,
int32_t physicalHeight,
int32_t subpixel,
const char* make,
const char* model,
int32_t transform)
{
struct _GLFWmonitor *monitor = data;
char name[1024];
@@ -55,12 +55,12 @@ static void geometry(void* data,
monitor->name = _glfw_strdup(name);
}
static void mode(void* data,
struct wl_output* output,
uint32_t flags,
int32_t width,
int32_t height,
int32_t refresh)
static void outputHandleMode(void* data,
struct wl_output* output,
uint32_t flags,
int32_t width,
int32_t height,
int32_t refresh)
{
struct _GLFWmonitor *monitor = data;
GLFWvidmode mode;
@@ -81,16 +81,16 @@ static void mode(void* data,
monitor->wl.currentMode = monitor->modeCount - 1;
}
static void done(void* data, struct wl_output* output)
static void outputHandleDone(void* data, struct wl_output* output)
{
struct _GLFWmonitor *monitor = data;
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
}
static void scale(void* data,
struct wl_output* output,
int32_t factor)
static void outputHandleScale(void* data,
struct wl_output* output,
int32_t factor)
{
struct _GLFWmonitor *monitor = data;
@@ -98,10 +98,10 @@ static void scale(void* data,
}
static const struct wl_output_listener outputListener = {
geometry,
mode,
done,
scale,
outputHandleGeometry,
outputHandleMode,
outputHandleDone,
outputHandleScale,
};
@@ -180,18 +180,17 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
*mode = monitor->modes[monitor->wl.currentMode];
}
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Gamma ramp getting not supported yet");
"Wayland: Gamma ramp access is not available");
return GLFW_FALSE;
}
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Gamma ramp setting not supported yet");
"Wayland: Gamma ramp access is not available");
}
@@ -205,4 +204,3 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->wl.output;
}

66
glfw/wl_platform.h vendored
View File

@@ -56,9 +56,11 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wayland-xdg-shell-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@@ -143,12 +145,14 @@ typedef struct _GLFWwindowWayland
struct {
struct xdg_surface* surface;
struct xdg_toplevel* toplevel;
struct zxdg_toplevel_decoration_v1* decoration;
} xdg;
_GLFWcursor* currentCursor;
double cursorPosX, cursorPosY;
char* title;
char appId[256];
// We need to track the monitors the window spans on to calculate the
// optimal scaling factor.
@@ -168,13 +172,53 @@ typedef struct _GLFWwindowWayland
GLFWbool justCreated;
struct {
GLFWbool serverSide;
struct wl_buffer* buffer;
_GLFWdecorationWayland top, left, right, bottom;
int focus;
} decorations;
struct {
unsigned long long id;
void(*callback)(unsigned long long id);
struct wl_callback *current_wl_callback;
} frameCallbackData;
} _GLFWwindowWayland;
typedef enum _GLFWWaylandOfferType
{
EXPIRED,
CLIPBOARD,
DRAG_AND_DROP,
PRIMARY_SELECTION
}_GLFWWaylandOfferType ;
typedef struct _GLFWWaylandDataOffer
{
struct wl_data_offer *id;
const char *mime;
_GLFWWaylandOfferType offer_type;
size_t idx;
int is_self_offer;
int has_uri_list;
uint32_t source_actions;
uint32_t dnd_action;
struct wl_surface *surface;
} _GLFWWaylandDataOffer;
typedef struct _GLFWWaylandPrimaryOffer
{
struct zwp_primary_selection_offer_v1 *id;
const char *mime;
_GLFWWaylandOfferType offer_type;
size_t idx;
int is_self_offer;
int has_uri_list;
struct wl_surface *surface;
} _GLFWWaylandPrimaryOffer;
// Wayland-specific global data
//
typedef struct _GLFWlibraryWayland
@@ -189,10 +233,17 @@ typedef struct _GLFWlibraryWayland
struct wl_pointer* pointer;
struct wl_keyboard* keyboard;
struct xdg_wm_base* wmBase;
struct zxdg_decoration_manager_v1* decorationManager;
struct wp_viewporter* viewporter;
struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints;
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
struct wl_data_device_manager* dataDeviceManager;
struct wl_data_device* dataDevice;
struct wl_data_source* dataSourceForClipboard;
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
struct zwp_primary_selection_device_v1* primarySelectionDevice;
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
int compositorVersion;
int seatVersion;
@@ -208,6 +259,7 @@ typedef struct _GLFWlibraryWayland
id_type keyRepeatTimer;
_GLFWwindow* keyboardFocus;
} keyRepeatInfo;
id_type cursorAnimationTimer;
_GLFWXKBData xkb;
_GLFWDBUSData dbus;
@@ -232,7 +284,13 @@ typedef struct _GLFWlibraryWayland
} egl;
EventLoopData eventLoopData;
char* pasteString;
char* clipboardString;
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
size_t primarySelectionOffersCounter;
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@@ -253,11 +311,15 @@ typedef struct _GLFWmonitorWayland
//
typedef struct _GLFWcursorWayland
{
struct wl_cursor_image* image;
struct wl_cursor* cursor;
struct wl_buffer* buffer;
int width, height;
int xhot, yhot;
int currentImage;
} _GLFWcursorWayland;
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwSetupWaylandDataDevice();
void _glfwSetupWaylandPrimarySelectionDevice();
void animateCursorImage(id_type timer_id, void *data);

715
glfw/wl_window.c vendored
View File

@@ -28,6 +28,7 @@
#include "internal.h"
#include "backend_utils.h"
#include "memfd.h"
#include <stdio.h>
#include <stdlib.h>
@@ -37,6 +38,18 @@
#include <sys/mman.h>
#define URI_LIST_MIME "text/uri-list"
static const char*
clipboard_mime() {
static char buf[128] = {0};
if (buf[0] == 0) {
snprintf(buf, sizeof(buf), "application/glfw+clipboard-%d", getpid());
}
return buf;
}
static void handlePing(void* data,
struct wl_shell_surface* shellSurface,
uint32_t serial)
@@ -103,18 +116,6 @@ static const struct wl_shell_surface_listener shellSurfaceListener = {
handlePopupDone
};
static int
createTmpfileCloexec(char* tmpname)
{
int fd;
fd = mkostemp(tmpname, O_CLOEXEC);
if (fd >= 0)
unlink(tmpname);
return fd;
}
/*
* Create a new, unique, anonymous file of the given size, and
* return the file descriptor for it. The file descriptor is set
@@ -138,11 +139,21 @@ createTmpfileCloexec(char* tmpname)
static int
createAnonymousFile(off_t size)
{
int ret;
#ifdef HAS_MEMFD_CREATE
int fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd < 0) return -1;
// We can add this seal before calling posix_fallocate(), as the file
// is currently zero-sized anyway.
//
// There is also no need to check for the return value, we couldnt do
// anything with it anyway.
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
#else
static const char template[] = "/glfw-shared-XXXXXX";
const char* path;
char* name;
int fd;
int ret;
path = getenv("XDG_RUNTIME_DIR");
if (!path)
@@ -161,6 +172,7 @@ createAnonymousFile(off_t size)
if (fd < 0)
return -1;
#endif
ret = posix_fallocate(fd, 0, size);
if (ret != 0)
{
@@ -186,7 +198,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Creating a buffer file for %d B failed: %m",
length);
return GLFW_FALSE;
return NULL;
}
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
@@ -195,7 +207,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: mmap failed: %m");
close(fd);
return GLFW_FALSE;
return NULL;
}
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
@@ -260,11 +272,13 @@ static void createDecorations(_GLFWwindow* window)
const GLFWimage image = { 1, 1, data };
GLFWbool opaque = (data[3] == 255);
if (!_glfw.wl.viewporter)
if (!_glfw.wl.viewporter || !window->decorated || window->wl.decorations.serverSide)
return;
if (!window->wl.decorations.buffer)
window->wl.decorations.buffer = createShmBuffer(&image);
if (!window->wl.decorations.buffer)
return;
createDecoration(&window->wl.decorations.top, window->wl.surface,
window->wl.decorations.buffer, opaque,
@@ -305,6 +319,20 @@ static void destroyDecorations(_GLFWwindow* window)
destroyDecoration(&window->wl.decorations.bottom);
}
static void xdgDecorationHandleConfigure(void* data,
struct zxdg_toplevel_decoration_v1* decoration,
uint32_t mode)
{
_GLFWwindow* window = data;
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
if (!window->wl.decorations.serverSide)
createDecorations(window);
}
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
xdgDecorationHandleConfigure,
};
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
{
@@ -477,9 +505,6 @@ static GLFWbool createSurface(_GLFWwindow* window,
if (!window->wl.transparent)
setOpaqueRegion(window);
if (window->decorated && !window->monitor)
createDecorations(window);
return GLFW_TRUE;
}
@@ -500,7 +525,8 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refres
monitor->wl.output);
}
setIdleInhibitor(window, GLFW_TRUE);
destroyDecorations(window);
if (!window->wl.decorations.serverSide)
destroyDecorations(window);
}
static GLFWbool createShellSurface(_GLFWwindow* window)
@@ -536,11 +562,13 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
{
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
setIdleInhibitor(window, GLFW_FALSE);
createDecorations(window);
}
else
{
wl_shell_surface_set_toplevel(window->wl.shellSurface);
setIdleInhibitor(window, GLFW_FALSE);
createDecorations(window);
}
wl_surface_commit(window->wl.surface);
@@ -629,6 +657,27 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
xdgSurfaceHandleConfigure
};
static void setXdgDecorations(_GLFWwindow* window)
{
if (_glfw.wl.decorationManager)
{
window->wl.xdg.decoration =
zxdg_decoration_manager_v1_get_toplevel_decoration(
_glfw.wl.decorationManager, window->wl.xdg.toplevel);
zxdg_toplevel_decoration_v1_add_listener(window->wl.xdg.decoration,
&xdgDecorationListener,
window);
zxdg_toplevel_decoration_v1_set_mode(
window->wl.xdg.decoration,
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
}
else
{
window->wl.decorations.serverSide = GLFW_FALSE;
createDecorations(window);
}
}
static GLFWbool createXdgSurface(_GLFWwindow* window)
{
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
@@ -676,11 +725,15 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
{
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
setIdleInhibitor(window, GLFW_FALSE);
setXdgDecorations(window);
}
else
{
setIdleInhibitor(window, GLFW_FALSE);
setXdgDecorations(window);
}
if (strlen(window->wl.appId))
xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId);
wl_surface_commit(window->wl.surface);
wl_display_roundtrip(_glfw.wl.display);
@@ -688,6 +741,69 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
return GLFW_TRUE;
}
static void
setCursorImage(_GLFWcursorWayland* cursorWayland)
{
struct wl_cursor_image* image;
struct wl_buffer* buffer;
struct wl_surface* surface = _glfw.wl.cursorSurface;
if (!cursorWayland->cursor) {
buffer = cursorWayland->buffer;
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
} else
{
image = cursorWayland->cursor->images[cursorWayland->currentImage];
buffer = wl_cursor_image_get_buffer(image);
if (image->delay) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ((double)image->delay) / 1000.0);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
} else {
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
}
if (!buffer)
return;
cursorWayland->width = image->width;
cursorWayland->height = image->height;
cursorWayland->xhot = image->hotspot_x;
cursorWayland->yhot = image->hotspot_y;
}
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
cursorWayland->xhot,
cursorWayland->yhot);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursorWayland->width, cursorWayland->height);
wl_surface_commit(surface);
}
static void
incrementCursorImage(_GLFWwindow* window)
{
if (window && window->wl.decorations.focus == mainWindow) {
_GLFWcursor* cursor = window->wl.currentCursor;
if (cursor && cursor->wl.cursor)
{
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
setCursorImage(&cursor->wl);
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
return;
}
}
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
}
void
animateCursorImage(id_type timer_id, void *data) {
incrementCursorImage(_glfw.wl.pointerFocus);
}
static void
handleEvents(double timeout)
{
@@ -757,6 +873,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
{
window->wl.justCreated = GLFW_TRUE;
window->wl.transparent = fbconfig->transparent;
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
if (!createSurface(window, wndconfig))
return GLFW_FALSE;
@@ -835,6 +952,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
window->context.destroy(window);
destroyDecorations(window);
if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
if (window->wl.decorations.buffer)
wl_buffer_destroy(window->wl.decorations.buffer);
@@ -855,6 +974,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
free(window->wl.title);
free(window->wl.monitors);
if (window->wl.frameCallbackData.current_wl_callback)
wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
}
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
@@ -948,7 +1069,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
int* left, int* top,
int* right, int* bottom)
{
if (window->decorated && !window->monitor)
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
{
if (top)
*top = _GLFW_DECORATION_TOP;
@@ -970,6 +1091,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = (float) window->wl.scale;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
if (_glfw.wl.wmBase)
@@ -1090,7 +1216,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
else if (window->wl.shellSurface)
wl_shell_surface_set_toplevel(window->wl.shellSurface);
setIdleInhibitor(window, GLFW_FALSE);
if (window->decorated)
if (!_glfw.wl.decorationManager)
createDecorations(window);
}
_glfwInputWindowMonitor(window, monitor);
@@ -1227,6 +1353,8 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
int xhot, int yhot, int count)
{
cursor->wl.buffer = createShmBuffer(image);
if (!cursor->wl.buffer)
return GLFW_FALSE;
cursor->wl.width = image->width;
cursor->wl.height = image->height;
cursor->wl.xhot = xhot;
@@ -1248,14 +1376,15 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
return GLFW_FALSE;
}
cursor->wl.image = standardCursor->images[0];
cursor->wl.cursor = standardCursor;
cursor->wl.currentImage = 0;
return GLFW_TRUE;
}
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
// If it's a standard cursor we don't need to do anything here
if (cursor->wl.image)
if (cursor->wl.cursor)
return;
if (cursor->wl.buffer)
@@ -1361,10 +1490,7 @@ static GLFWbool isPointerLocked(_GLFWwindow* window)
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
{
struct wl_buffer* buffer;
struct wl_cursor* defaultCursor;
struct wl_cursor_image* image;
struct wl_surface* surface = _glfw.wl.cursorSurface;
if (!_glfw.wl.pointer)
return;
@@ -1373,7 +1499,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
// If we're not in the correct window just save the cursor
// the next time the pointer enters the window the cursor will change
if (window != _glfw.wl.pointerFocus)
if (window != _glfw.wl.pointerFocus || window->wl.decorations.focus != mainWindow)
return;
// Unlock possible pointer lock if no longer disabled.
@@ -1383,7 +1509,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
if (window->cursorMode == GLFW_CURSOR_NORMAL)
{
if (cursor)
image = cursor->wl.image;
setCursorImage(&cursor->wl);
else
{
defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
@@ -1394,33 +1520,14 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
"Wayland: Standard cursor not found");
return;
}
image = defaultCursor->images[0];
}
if (image)
{
buffer = wl_cursor_image_get_buffer(image);
if (!buffer)
return;
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
image->hotspot_x,
image->hotspot_y);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
image->width, image->height);
wl_surface_commit(surface);
}
else
{
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
surface,
cursor->wl.xhot,
cursor->wl.yhot);
wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
wl_surface_damage(surface, 0, 0,
cursor->wl.width, cursor->wl.height);
wl_surface_commit(surface);
_GLFWcursorWayland cursorWayland = {
defaultCursor,
NULL,
0, 0,
0, 0,
0
};
setCursorImage(&cursorWayland);
}
}
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
@@ -1435,18 +1542,480 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
}
}
static void send_text(char *text, int fd)
{
if (text) {
size_t len = strlen(text), pos = 0;
double start = glfwGetTime();
while (pos < len && glfwGetTime() - start < 2.0) {
ssize_t ret = write(fd, text + pos, len - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
break;
}
if (ret > 0) {
start = glfwGetTime();
pos += ret;
}
}
}
close(fd);
}
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd) {
send_text(_glfw.wl.clipboardString, fd);
}
static void _glfwSendPrimarySelectionText(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source,
const char *mime_type, int fd) {
send_text(_glfw.wl.primarySelectionString, fd);
}
static char* read_offer_string(int data_pipe) {
wl_display_flush(_glfw.wl.display);
size_t sz = 0, capacity = 0;
char *buf = NULL;
struct pollfd fds;
fds.fd = data_pipe;
fds.events = POLLIN;
double start = glfwGetTime();
#define bail(...) { \
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
free(buf); buf = NULL; \
close(data_pipe); \
return NULL; \
}
while (glfwGetTime() - start < 2) {
int ret = poll(&fds, 1, 2000);
if (ret == -1) {
if (errno == EINTR) continue;
bail("Wayland: Failed to poll clipboard data from pipe with error: %s", strerror(errno));
}
if (!ret) {
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
}
if (capacity <= sz || capacity - sz <= 64) {
capacity += 4096;
buf = realloc(buf, capacity);
if (!buf) {
bail("Wayland: Failed to allocate memory to read clipboard data");
}
}
ret = read(data_pipe, buf + sz, capacity - sz - 1);
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
}
if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; }
sz += ret;
start = glfwGetTime();
}
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
#undef bail
}
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
close(pipefd[1]);
return read_offer_string(pipefd[0]);
}
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(pipefd[1]);
return read_offer_string(pipefd[0]);
}
static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) {
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
_glfw.wl.dataSourceForClipboard = NULL;
wl_data_source_destroy(wl_data_source);
}
static void primary_selection_source_canceled(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source) {
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
_glfw.wl.dataSourceForPrimarySelection = NULL;
zwp_primary_selection_source_v1_destroy(primary_selection_source);
}
static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) {
}
const static struct wl_data_source_listener data_source_listener = {
.send = _glfwSendClipboardText,
.cancelled = data_source_canceled,
.target = data_source_target,
};
const static struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
.send = _glfwSendPrimarySelectionText,
.cancelled = primary_selection_source_canceled,
};
static void prune_unclaimed_data_offers() {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
}
}
}
static void prune_unclaimed_primary_selection_offers() {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[i].id);
memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
}
}
}
static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer)
{
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == data_offer) {
_glfw.wl.dataOffers[i].offer_type = CLIPBOARD;
} else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer
}
}
prune_unclaimed_data_offers();
}
static void mark_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1* primary_selection_device,
struct zwp_primary_selection_offer_v1 *primary_selection_offer) {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) {
_glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION;
} else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
_glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer
}
}
prune_unclaimed_primary_selection_offers();
}
static void set_offer_mimetype(struct _GLFWWaylandDataOffer* offer, const char* mime) {
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
offer->mime = "text/plain;charset=utf-8";
else if (!offer->mime && strcmp(mime, "text/plain") == 0)
offer->mime = "text/plain";
else if (strcmp(mime, clipboard_mime()) == 0)
offer->is_self_offer = 1;
else if (strcmp(mime, URI_LIST_MIME) == 0)
offer->has_uri_list = 1;
}
static void handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) {
set_offer_mimetype(&_glfw.wl.dataOffers[i], mime);
break;
}
}
}
static void handle_primary_selection_offer_mimetype(void *data, struct zwp_primary_selection_offer_v1* id, const char *mime) {
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id == id) {
set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime);
break;
}
}
}
static void data_offer_source_actions(void *data, struct wl_data_offer* id, uint32_t actions) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) {
_glfw.wl.dataOffers[i].source_actions = actions;
break;
}
}
}
static void data_offer_action(void *data, struct wl_data_offer* id, uint32_t action) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) {
_glfw.wl.dataOffers[i].dnd_action = action;
break;
}
}
}
static const struct wl_data_offer_listener data_offer_listener = {
.offer = handle_offer_mimetype,
.source_actions = data_offer_source_actions,
.action = data_offer_action,
};
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
.offer = handle_primary_selection_offer_mimetype,
};
static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
size_t smallest_idx = SIZE_MAX, pos = 0;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) {
smallest_idx = _glfw.wl.dataOffers[i].idx;
pos = i;
}
if (_glfw.wl.dataOffers[i].id == NULL) {
_glfw.wl.dataOffers[i].id = id;
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
goto end;
}
}
if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id);
memset(_glfw.wl.dataOffers + pos, 0, sizeof(_glfw.wl.dataOffers[0]));
_glfw.wl.dataOffers[pos].id = id;
_glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter;
end:
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
}
static void handle_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
size_t smallest_idx = SIZE_MAX, pos = 0;
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].idx && _glfw.wl.primarySelectionOffers[i].idx < smallest_idx) {
smallest_idx = _glfw.wl.primarySelectionOffers[i].idx;
pos = i;
}
if (_glfw.wl.primarySelectionOffers[i].id == NULL) {
_glfw.wl.primarySelectionOffers[i].id = id;
_glfw.wl.primarySelectionOffers[i].idx = ++_glfw.wl.primarySelectionOffersCounter;
goto end;
}
}
if (_glfw.wl.primarySelectionOffers[pos].id) zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[pos].id);
memset(_glfw.wl.primarySelectionOffers + pos, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
_glfw.wl.primarySelectionOffers[pos].id = id;
_glfw.wl.primarySelectionOffers[pos].idx = ++_glfw.wl.primarySelectionOffersCounter;
end:
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL);
}
static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id == id) {
_glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP;
_glfw.wl.dataOffers[i].surface = surface;
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
wl_data_offer_accept(id, serial, mime);
} else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer
}
}
prune_unclaimed_data_offers();
}
static void drag_leave(void *data, struct wl_data_device *wl_data_device) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
}
}
}
static void drop(void *data, struct wl_data_device *wl_data_device) {
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
if (uri_list) {
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
int count;
char** paths = parseUriList(data, &count);
_GLFWwindow* window = _glfw.windowListHead;
while (window)
{
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) {
_glfwInputDrop(window, count, (const char**) paths);
break;
}
window = window->next;
}
for (int k = 0; k < count; k++)
free(paths[k]);
free(paths);
free(uri_list);
}
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
break;
}
}
}
static void motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
}
const static struct wl_data_device_listener data_device_listener = {
.data_offer = handle_data_offer,
.selection = mark_selection_offer,
.enter = drag_enter,
.motion = motion,
.drop = drop,
.leave = drag_leave,
};
const static struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
.data_offer = handle_primary_selection_offer,
.selection = mark_primary_selection_offer,
};
static void
clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) {
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
}
wl_callback_destroy(callback);
}
static void
primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) {
zwp_primary_selection_device_v1_set_selection(_glfw.wl.primarySelectionDevice, data, serial);
}
wl_callback_destroy(callback);
}
void _glfwSetupWaylandDataDevice() {
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
}
void _glfwSetupWaylandPrimarySelectionDevice() {
_glfw.wl.primarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device(_glfw.wl.primarySelectionDeviceManager, _glfw.wl.seat);
if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL);
}
static inline GLFWbool _glfwEnsureDataDevice() {
if (!_glfw.wl.dataDeviceManager)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot use clipboard, data device manager is not ready");
return GLFW_FALSE;
}
if (!_glfw.wl.dataDevice)
{
if (!_glfw.wl.seat)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot use clipboard, seat is not ready");
return GLFW_FALSE;
}
if (!_glfw.wl.dataDevice)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot use clipboard, failed to create data device");
return GLFW_FALSE;
}
}
return GLFW_TRUE;
}
void _glfwPlatformSetClipboardString(const char* string)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Clipboard setting not implemented yet");
if (!_glfwEnsureDataDevice()) return;
free(_glfw.wl.clipboardString);
_glfw.wl.clipboardString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForClipboard)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
if (!_glfw.wl.dataSourceForClipboard)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create data source");
return;
}
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
const static struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
}
const char* _glfwPlatformGetClipboardString(void)
{
// TODO
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Clipboard getting not implemented yet");
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
free(_glfw.wl.pasteString);
_glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
return _glfw.wl.pasteString;
}
}
return NULL;
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
if (!_glfw.wl.primarySelectionDevice) {
static GLFWbool warned_about_primary_selection_device = GLFW_FALSE;
if (!warned_about_primary_selection_device) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy no primary selection device available");
warned_about_primary_selection_device = GLFW_TRUE;
}
return;
}
if (_glfw.wl.primarySelectionString == string) return;
free(_glfw.wl.primarySelectionString);
_glfw.wl.primarySelectionString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForPrimarySelection)
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
if (!_glfw.wl.dataSourceForPrimarySelection)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create primary selection source");
return;
}
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
const static struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
if (_glfw.wl.primarySelectionOffers[i].is_self_offer) {
return _glfw.wl.primarySelectionString;
}
free(_glfw.wl.pasteString);
_glfw.wl.pasteString = read_primary_selection_offer(_glfw.wl.primarySelectionOffers[i].id, _glfw.wl.primarySelectionOffers[i].mime);
return _glfw.wl.pasteString;
}
}
return NULL;
}
@@ -1518,6 +2087,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
}
static void
frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time) {
_GLFWwindow* window = (_GLFWwindow*) data;
if (callback == window->wl.frameCallbackData.current_wl_callback) {
window->wl.frameCallbackData.callback(window->wl.frameCallbackData.id);
window->wl.frameCallbackData.current_wl_callback = NULL;
}
wl_callback_destroy(callback);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////
@@ -1539,3 +2117,16 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}
GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, void(*callback)(unsigned long long id)) {
_GLFWwindow* window = (_GLFWwindow*) handle;
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
window->wl.frameCallbackData.id = id;
window->wl.frameCallbackData.callback = callback;
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
if (window->wl.frameCallbackData.current_wl_callback) {
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
wl_surface_commit(window->wl.surface);
}
}

14
glfw/x11_monitor.c vendored
View File

@@ -433,7 +433,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
}
}
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
{
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
{
@@ -449,6 +449,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short));
XRRFreeGamma(gamma);
return GLFW_TRUE;
}
else if (_glfw.x11.vidmode.available)
{
@@ -460,6 +461,13 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
XF86VidModeGetGammaRamp(_glfw.x11.display,
_glfw.x11.screen,
ramp->size, ramp->red, ramp->green, ramp->blue);
return GLFW_TRUE;
}
else
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Gamma ramp access not supported by server");
return GLFW_FALSE;
}
}
@@ -492,6 +500,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
(unsigned short*) ramp->green,
(unsigned short*) ramp->blue);
}
else {
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Gamma ramp access not supported by server");
}
}

207
glfw/x11_window.c vendored
View File

@@ -161,29 +161,6 @@ static Bool isSelPropNewValueNotify(Display* display, XEvent* event, XPointer po
event->xproperty.atom == notification->xselection.property;
}
// Translates a GLFW standard cursor to a font cursor shape
//
static int translateCursorShape(int shape)
{
switch (shape)
{
case GLFW_ARROW_CURSOR:
return XC_left_ptr;
case GLFW_IBEAM_CURSOR:
return XC_xterm;
case GLFW_CROSSHAIR_CURSOR:
return XC_crosshair;
case GLFW_HAND_CURSOR:
return XC_hand1;
case GLFW_HRESIZE_CURSOR:
return XC_sb_h_double_arrow;
case GLFW_VRESIZE_CURSOR:
return XC_sb_v_double_arrow;
}
return 0;
}
// Translates an X event modifier state mask
//
static int translateState(int state)
@@ -206,23 +183,6 @@ static int translateState(int state)
return mods;
}
// Return the GLFW window corresponding to the specified X11 window
//
static _GLFWwindow* findWindowByHandle(Window handle)
{
_GLFWwindow* window;
if (XFindContext(_glfw.x11.display,
handle,
_glfw.x11.context,
(XPointer*) &window) != 0)
{
return NULL;
}
return window;
}
// Sends an EWMH or ICCCM event to the window manager
//
static void sendEventToWM(_GLFWwindow* window, Atom type,
@@ -390,56 +350,6 @@ static void updateWindowMode(_GLFWwindow* window)
}
}
// Splits and translates a text/uri-list into separate file paths
// NOTE: This function destroys the provided string
//
static char** parseUriList(char* text, int* count)
{
const char* prefix = "file://";
char** paths = NULL;
char* line;
*count = 0;
while ((line = strtok(text, "\r\n")))
{
text = NULL;
if (line[0] == '#')
continue;
if (strncmp(line, prefix, strlen(prefix)) == 0)
{
line += strlen(prefix);
// TODO: Validate hostname
while (*line != '/')
line++;
}
(*count)++;
char* path = calloc(strlen(line) + 1, 1);
paths = realloc(paths, *count * sizeof(char*));
paths[*count - 1] = path;
while (*line)
{
if (line[0] == '%' && line[1] && line[2])
{
const char digits[3] = { line[1], line[2], '\0' };
*path = strtol(digits, NULL, 16);
line += 2;
}
else
*path = *line;
path++;
line++;
}
}
return paths;
}
// Encode a Unicode code point to a UTF-8 stream
// Based on cutef8 by Jeff Bezanson (Public Domain)
@@ -582,6 +492,15 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
const _GLFWwndconfig* wndconfig,
Visual* visual, int depth)
{
int width = wndconfig->width;
int height = wndconfig->height;
if (wndconfig->scaleToMonitor)
{
width *= _glfw.x11.contentScaleX;
height *= _glfw.x11.contentScaleY;
}
// Create a colormap based on the visual used by the current context
window->x11.colormap = XCreateColormap(_glfw.x11.display,
_glfw.x11.root,
@@ -607,7 +526,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
window->x11.handle = XCreateWindow(_glfw.x11.display,
_glfw.x11.root,
0, 0,
wndconfig->width, wndconfig->height,
width, height,
0, // Border width
depth, // Color depth
InputOutput,
@@ -710,7 +629,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
XFree(hints);
}
updateNormalHints(window, wndconfig->width, wndconfig->height);
updateNormalHints(window, width, height);
// Set ICCCM WM_CLASS property
{
@@ -954,6 +873,7 @@ static const char* getSelectionString(Atom selection)
Atom actualType;
int actualFormat;
unsigned long itemCount, bytesAfter;
double start = glfwGetTime();
XEvent notification, dummy;
XConvertSelection(_glfw.x11.display,
@@ -968,7 +888,10 @@ static const char* getSelectionString(Atom selection)
SelectionNotify,
&notification))
{
waitForX11Event(-1);
double time = glfwGetTime();
if (time - start > 2)
return "";
waitForX11Event(2.0 - (time - start));
}
if (notification.xselection.property == None)
@@ -999,12 +922,16 @@ static const char* getSelectionString(Atom selection)
for (;;)
{
start = glfwGetTime();
while (!XCheckIfEvent(_glfw.x11.display,
&dummy,
isSelPropNewValueNotify,
(XPointer) &notification))
{
waitForX11Event(-1);
double time = glfwGetTime();
if (time - start > 2)
return "";
waitForX11Event(2.0 - (time - start));
}
XFree(data);
@@ -1243,8 +1170,10 @@ static void processEvent(XEvent *event)
return;
}
window = findWindowByHandle(event->xany.window);
if (window == NULL)
if (XFindContext(_glfw.x11.display,
event->xany.window,
_glfw.x11.context,
(XPointer*) &window) != 0)
{
// This is an event for a window that has already been destroyed
return;
@@ -1370,12 +1299,20 @@ static void processEvent(XEvent *event)
case EnterNotify:
{
// XEnterWindowEvent is XCrossingEvent
const int x = event->xcrossing.x;
const int y = event->xcrossing.y;
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
// ignore the defined cursor for hidden cursor mode
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
updateCursorImage(window);
_glfwInputCursorEnter(window, GLFW_TRUE);
_glfwInputCursorPos(window, x, y);
window->x11.lastCursorPosX = x;
window->x11.lastCursorPosY = y;
return;
}
@@ -2179,6 +2116,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
*yscale = _glfw.x11.contentScaleY;
}
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
{
return 0.5;
}
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
{
if (window->x11.overrideRedirect)
@@ -2357,6 +2299,10 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
Atom* states;
unsigned long i;
GLFWbool maximized = GLFW_FALSE;
if (!_glfw.x11.NET_WM_STATE ||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT ||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
return maximized;
const unsigned long count =
_glfwGetWindowPropertyX11(window->x11.handle,
_glfw.x11.NET_WM_STATE,
@@ -2680,8 +2626,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display,
translateCursorShape(shape));
int native = 0;
if (shape == GLFW_ARROW_CURSOR)
native = XC_left_ptr;
else if (shape == GLFW_IBEAM_CURSOR)
native = XC_xterm;
else if (shape == GLFW_CROSSHAIR_CURSOR)
native = XC_crosshair;
else if (shape == GLFW_HAND_CURSOR)
native = XC_hand1;
else if (shape == GLFW_HRESIZE_CURSOR)
native = XC_sb_h_double_arrow;
else if (shape == GLFW_VRESIZE_CURSOR)
native = XC_sb_v_double_arrow;
else
return GLFW_FALSE;
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
if (!cursor->x11.handle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -2730,6 +2692,29 @@ const char* _glfwPlatformGetClipboardString(void)
return getSelectionString(_glfw.x11.CLIPBOARD);
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.PRIMARY,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of primary selection");
}
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
return getSelectionString(_glfw.x11.PRIMARY);
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface)
@@ -2901,32 +2886,6 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
return window->x11.handle;
}
GLFWAPI void glfwSetX11SelectionString(const char* string)
{
_GLFW_REQUIRE_INIT();
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = _glfw_strdup(string);
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.PRIMARY,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of primary selection");
}
}
GLFWAPI const char* glfwGetX11SelectionString(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return getSelectionString(_glfw.x11.PRIMARY);
}
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}

31
glfw/xkb_glfw.c vendored
View File

@@ -33,7 +33,7 @@
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
#define map_key(key) { \
#define map_key(key) \
switch(key) { \
S(space, SPACE); \
S(apostrophe, APOSTROPHE); \
@@ -46,6 +46,7 @@
S(bracketleft, LEFT_BRACKET); \
S(backslash, BACKSLASH); \
S(bracketright, RIGHT_BRACKET); \
S(plus, PLUS); \
S(grave, GRAVE_ACCENT); \
S(Escape, ESCAPE); \
S(Return, ENTER); \
@@ -93,10 +94,6 @@
D(A, Z, A, Z); \
R(F1, F25, F1, F25); \
R(KP_0, KP_9, KP_0, KP_9); \
default: \
break; \
} \
}
static int
glfw_key_for_sym(xkb_keysym_t key) {
@@ -105,6 +102,13 @@ glfw_key_for_sym(xkb_keysym_t key) {
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
#define D(s, e, gs, ...) R(s, e, gs)
map_key(key)
S(KP_Up, UP);
S(KP_Down, DOWN);
S(KP_Left, LEFT);
S(KP_Right, RIGHT);
default:
break;
}
return GLFW_KEY_UNKNOWN;
#undef F
#undef D
@@ -119,6 +123,9 @@ glfw_xkb_sym_for_key(int key) {
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
#define D(...)
map_key(key)
default:
break;
}
return GLFW_KEY_UNKNOWN;
#undef F
#undef D
@@ -249,6 +256,7 @@ load_compose_tables(_GLFWXKBData *xkb) {
if (!xkb->states.composeState) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
}
xkb_compose_table_unref(compose_table);
}
GLFWbool
@@ -329,12 +337,14 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
static KeyEvent key_event = {};
static inline xkb_keysym_t
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym) {
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) {
*compose_completed = 0;
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
switch (xkb_compose_state_get_status(composeState)) {
case XKB_COMPOSE_COMPOSED:
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
*compose_completed = 1;
return xkb_compose_state_get_one_sym(composeState);
case XKB_COMPOSE_COMPOSING:
case XKB_COMPOSE_CANCELLED:
@@ -432,9 +442,9 @@ glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed) {
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
ev->glfw_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym),
(ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")),
format_mods(ev->glfw_modifiers), key_event.text
format_mods(ev->glfw_modifiers), ev->text
);
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, key_event.text, 0);
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, ev->text, 0);
} else debug("↳ discarded\n");
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
}
@@ -465,8 +475,9 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
const char *text_type = "composed_text";
glfw_sym = compose_symbol(sg->composeState, syms[0]);
if (glfw_sym == XKB_KEY_NoSymbol) {
int compose_completed;
glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed);
if (glfw_sym == XKB_KEY_NoSymbol && !compose_completed) {
debug("compose not complete, ignoring.\n");
return;
}

View File

@@ -32,7 +32,7 @@ from .search import BadRegex, Search
try:
from .highlight import initialize_highlighter, highlight_collection
except ImportError:
initialize_highlighter = None
initialize_highlighter = highlight_collection = None
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)

View File

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

View File

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

View File

@@ -43,7 +43,7 @@ class Resize(Handler):
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
opts, items = parse_subcommand_cli(resize_window, cmdline)
payload = resize_window(global_opts, opts, items)
send = {'cmd': resize_window.name, 'version': version, 'payload': payload}
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
self.write(encode_send(send))
def on_kitty_cmd_response(self, response):

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -10,6 +10,7 @@ from functools import partial
from gettext import gettext as _
from weakref import WeakValueDictionary
from .child import cached_process_data
from .cli import create_opts, parse_args
from .conf.utils import to_cmdline
from .config import initial_window_size_func, prepare_config_file_for_editing
@@ -19,10 +20,11 @@ from .constants import (
)
from .fast_data_types import (
ChildMonitor, background_opacity_of, change_background_opacity,
create_os_window, current_os_window, destroy_global_data,
get_clipboard_string, glfw_post_empty_event, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
set_clipboard_string, set_in_sequence_mode, toggle_fullscreen
change_os_window_state, create_os_window, current_os_window,
destroy_global_data, get_clipboard_string, glfw_post_empty_event,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, set_clipboard_string, set_in_sequence_mode,
toggle_fullscreen
)
from .keys import get_shortcut, shortcut_matches
from .layout import set_draw_minimal_borders
@@ -31,9 +33,9 @@ from .rgb import Color, color_from_int
from .session import create_session
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
from .utils import (
get_editor, get_primary_selection, log_error, open_url, parse_address_spec,
remove_socket_file, safe_print, set_primary_selection, single_instance,
startup_notification_handler
get_editor, get_primary_selection, is_path_in_temp_dir, log_error,
open_url, parse_address_spec, remove_socket_file, safe_print,
set_primary_selection, single_instance, startup_notification_handler
)
@@ -47,6 +49,31 @@ def listen_on(spec):
return s.fileno()
def data_for_at(w, arg, add_wrap_markers=False):
def as_text(**kw):
kw['add_wrap_markers'] = add_wrap_markers
return w.as_text(**kw)
if arg == '@selection':
return w.text_for_selection()
if arg == '@ansi':
return as_text(as_ansi=True, add_history=True)
if arg == '@text':
return as_text(add_history=True)
if arg == '@screen':
return as_text()
if arg == '@ansi_screen':
return as_text(as_ansi=True)
if arg == '@alternate':
return as_text(alternate_screen=True)
if arg == '@alternate_scrollback':
return as_text(alternate_screen=True, add_history=True)
if arg == '@ansi_alternate':
return as_text(as_ansi=True, alternate_screen=True)
if arg == '@ansi_alternate_scrollback':
return as_text(as_ansi=True, alternate_screen=True, add_history=True)
class DumpCommands: # {{{
def __init__(self, args):
@@ -80,6 +107,7 @@ class Boss:
set_draw_minimal_borders(opts)
self.window_id_map = WeakValueDictionary()
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.startup_cursor_text_color = opts.cursor_text_color
self.pending_sequences = None
self.cached_values = cached_values
self.os_window_map = {}
@@ -103,6 +131,11 @@ class Boss:
if new_os_window_trigger is not None:
self.keymap.pop(new_os_window_trigger, None)
self.add_os_window(startup_session, os_window_id=os_window_id)
if args.start_as != 'normal':
if args.start_as == 'fullscreen':
self.toggle_fullscreen()
else:
change_os_window_state(args.start_as)
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
if os_window_id is None:
@@ -118,14 +151,15 @@ class Boss:
return os_window_id
def list_os_windows(self):
active_tab, active_window = self.active_tab, self.active_window
active_tab_manager = self.active_tab_manager
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'is_focused': tm is active_tab_manager,
'tabs': list(tm.list_tabs(active_tab, active_window)),
}
with cached_process_data():
active_tab, active_window = self.active_tab, self.active_window
active_tab_manager = self.active_tab_manager
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'is_focused': tm is active_tab_manager,
'tabs': list(tm.list_tabs(active_tab, active_window)),
}
@property
def all_tab_managers(self):
@@ -213,9 +247,16 @@ class Boss:
def new_os_window(self, *args):
self._new_os_window(args)
def new_os_window_with_cwd(self, *args):
@property
def active_window_for_cwd(self):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
if w is not None and w.overlay_for is not None and w.overlay_for in self.window_id_map:
w = self.window_id_map[w.overlay_for]
return w
def new_os_window_with_cwd(self, *args):
w = self.active_window_for_cwd
cwd_from = w.child.pid_for_cwd if w is not None else None
self._new_os_window(args, cwd_from)
def add_child(self, window):
@@ -339,6 +380,9 @@ class Boss:
reset = action == 'reset'
how = 3 if action == 'scrollback' else 2
for w in windows:
if action == 'scroll':
w.screen.scroll_until_cursor()
continue
w.screen.cursor.x = w.screen.cursor.y = 0
if reset:
w.screen.reset()
@@ -422,10 +466,10 @@ class Boss:
_('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity'))
os_window_id = window.os_window_id
if opacity[0] in '+-':
opacity = background_opacity_of(os_window_id)
if opacity is None:
old_opacity = background_opacity_of(os_window_id)
if old_opacity is None:
return
opacity += float(opacity)
opacity = old_opacity + float(opacity)
elif opacity == 'default':
opacity = self.opts.background_opacity
else:
@@ -518,6 +562,8 @@ class Boss:
if key_action is not None:
f = getattr(self, key_action.func, None)
if f is not None:
if self.args.debug_keyboard:
print('Keypress matched action:', f.__name__)
passthrough = f(*key_action.args)
if passthrough is not True:
return True
@@ -531,6 +577,8 @@ class Boss:
f = getattr(tab, key_action.func, getattr(window, key_action.func, None))
if f is not None:
passthrough = f(*key_action.args)
if self.args.debug_keyboard:
print('Keypress matched action:', f.__name__)
if passthrough is not True:
return True
return False
@@ -608,17 +656,20 @@ class Boss:
return output
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
orig_args, args = list(args), list(args)
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
if window is None:
w = self.active_window
tab = self.active_tab
else:
w = window
tab = w.tabref()
if end_kitten.no_ui:
end_kitten(None, getattr(w, 'id', None), self)
return
if w is not None and tab is not None and w.overlay_for is None:
orig_args, args = list(args), list(args)
from kittens.runner import create_kitten_handler
end_kitten = create_kitten_handler(kitten, orig_args)
args[0:0] = [config_dir, kitten]
if input_data is None:
type_of_input = end_kitten.type_of_input
@@ -768,25 +819,74 @@ class Boss:
if tm is not None:
tm.next_tab(-1)
prev_tab = previous_tab
def process_stdin_source(self, window=None, stdin=None):
w = window or self.active_window
env = None
if stdin:
add_wrap_markers = stdin.endswith('_wrap')
if add_wrap_markers:
stdin = stdin[:-len('_wrap')]
stdin = data_for_at(w, stdin, add_wrap_markers=add_wrap_markers)
if stdin is not None:
pipe_data = w.pipe_data(stdin, has_wrap_markers=add_wrap_markers) if w else {}
if pipe_data:
env = {
'KITTY_PIPE_DATA':
'{scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}'.format(**pipe_data)
}
stdin = stdin.encode('utf-8')
return env, stdin
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
w = window or self.active_window
env, stdin = self.process_stdin_source(w, stdin)
cmdline = []
for arg in cmd:
if arg == '@selection':
arg = data_for_at(w, arg)
if not arg:
continue
cmdline.append(arg)
overlay_for = w.id if as_overlay and w.overlay_for is None else None
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
def pipe(self, source, dest, exe, *args):
cmd = [exe] + list(args)
window = self.active_window
cwd_from = window.child.pid_for_cwd if window else None
def create_window():
return self.special_window_for_cmd(
cmd, stdin=source, as_overlay=dest == 'overlay', cwd_from=cwd_from)
if dest == 'overlay' or dest == 'window':
tab = self.active_tab
if tab is not None:
return tab.new_special_window(create_window())
elif dest == 'tab':
tm = self.active_tab_manager
if tm is not None:
tm.new_tab(special_window=create_window(), cwd_from=cwd_from)
elif dest == 'os_window':
self._new_os_window(create_window(), cwd_from=cwd_from)
else:
import subprocess
env, stdin = self.process_stdin_source(stdin=source, window=window)
if stdin:
p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE)
p.communicate(stdin)
else:
subprocess.Popen(cmd)
def args_to_special_window(self, args, cwd_from=None):
args = list(args)
stdin = None
w = self.active_window
def data_for_at(arg):
if arg == '@selection':
return w.text_for_selection()
if arg == '@ansi':
return w.as_text(as_ansi=True, add_history=True)
if arg == '@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('@') and args[0] != '@':
stdin = data_for_at(args[0]) or None
stdin = data_for_at(w, args[0]) or None
if stdin is not None:
stdin = stdin.encode('utf-8')
del args[0]
@@ -794,13 +894,13 @@ class Boss:
cmd = []
for arg in args:
if arg == '@selection':
arg = data_for_at(arg)
arg = data_for_at(w, arg)
if not arg:
continue
cmd.append(arg)
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
def _new_tab(self, args, cwd_from=None):
def _new_tab(self, args, cwd_from=None, as_neighbor=False):
special_window = None
if args:
if isinstance(args, SpecialWindowInstance):
@@ -809,15 +909,22 @@ class Boss:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager
if tm is not None:
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor)
def _create_tab(self, args, cwd_from=None):
as_neighbor = False
if args and args[0].startswith('!'):
as_neighbor = 'neighbor' in args[0][1:].split(',')
args = args[1:]
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
def new_tab(self, *args):
self._new_tab(args)
self._create_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)
w = self.active_window_for_cwd
cwd_from = w.child.pid_for_cwd if w is not None else None
self._create_tab(args, cwd_from=cwd_from)
def _new_window(self, args, cwd_from=None):
tab = self.active_tab
@@ -831,10 +938,10 @@ class Boss:
self._new_window(args)
def new_window_with_cwd(self, *args):
w = self.active_window
w = self.active_window_for_cwd
if w is None:
return self.new_window(*args)
cwd_from = w.child.pid if w is not None else None
cwd_from = w.child.pid_for_cwd if w is not None else None
self._new_window(args, cwd_from=cwd_from)
def move_tab_forward(self):
@@ -847,11 +954,20 @@ class Boss:
if tm is not None:
tm.move_tab(-1)
def patch_colors(self, spec, configured=False):
def patch_colors(self, spec, cursor_text_color, configured=False):
if configured:
for k, v in spec.items():
if hasattr(self.opts, k):
setattr(self.opts, k, color_from_int(v))
if cursor_text_color is not False:
self.opts.cursor_text_color = cursor_text_color
for tm in self.all_tab_managers:
tm.tab_bar.patch_colors(spec)
patch_global_colors(spec, configured)
def safe_delete_temp_file(self, path):
if is_path_in_temp_dir(path):
try:
os.remove(path)
except FileNotFoundError:
pass

View File

@@ -64,7 +64,7 @@ vec4 calculate_foreground() {
// Since strike and text are the same color, we simply add the alpha values
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
// Underline color might be different, so alpha blend
vec4 ans = alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
vec4 ans = alpha_blend(fg, combined_alpha * effective_text_alpha, decoration_fg, underline_alpha * effective_text_alpha);
return mix(ans, cursor_color_vec, cursor_alpha);
}
#endif
@@ -101,12 +101,12 @@ void main() {
#else
// SIMPLE
#ifdef TRANSPARENT
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
final_color = alpha_blend_premul(fg.rgb, fg.a, background, bg_alpha);
final_color = vec4(final_color.rgb, final_color.a);
#else
// since background alpha is 1.0, it is effectively pre-multiplied
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
final_color = vec4(final_color.rgb, final_color.a);
#endif
#endif

View File

@@ -5,13 +5,12 @@
#define REVERSE_SHIFT {REVERSE_SHIFT}
#define STRIKE_SHIFT {STRIKE_SHIFT}
#define DIM_SHIFT {DIM_SHIFT}
#define CURSOR_TEXT_COLOR {CURSOR_TEXT_COLOR}
// Inputs {{{
layout(std140) uniform CellRenderData {
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted;
uint xnum, ynum, cursor_fg_sprite_idx;
float cursor_x, cursor_y, cursor_w;
@@ -182,8 +181,9 @@ void main() {
// Cursor
cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0);
foreground = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, foreground);
decoration_fg = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, decoration_fg);
vec3 final_cursor_text_color = mix(color_to_vec(cursor_text_color), bg, cursor_text_uses_bg);
foreground = choose_color(cell_has_block_cursor, final_cursor_text_color, foreground);
decoration_fg = choose_color(cell_has_block_cursor, final_cursor_text_color, decoration_fg);
cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO);
#endif
// }}}

View File

@@ -372,7 +372,7 @@ parse_input(ChildMonitor *self) {
}
if (UNLIKELY(kill_signal_received)) {
global_state.close_all_windows = true;
global_state.terminate = true;
} else {
count = self->count;
for (size_t i = 0; i < count; i++) {
@@ -565,7 +565,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
}
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
}
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) {
if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window);
else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at);
}
Tab *tab = os_window->tabs + os_window->active_tab;
*active_window_bg = OPT(background);
for (unsigned int i = 0; i < tab->num_windows; i++) {
@@ -599,6 +602,8 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
static inline void
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows) {
// ensure all pixels are cleared to background color at least once in every buffer
if (os_window->clear_count++ < 3) blank_os_window(os_window);
Tab *tab = os_window->tabs + os_window->active_tab;
BorderRects *br = &tab->border_rects;
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, os_window);
@@ -620,6 +625,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
os_window->focused_at_last_render = os_window->is_focused;
os_window->is_damaged = false;
if (global_state.is_wayland && OPT(sync_to_monitor)) wayland_request_frame_render(os_window);
#undef WD
#undef TD
}
@@ -635,6 +641,10 @@ render(double now) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
if (global_state.is_wayland && w->wayland_render_state != RENDER_FRAME_READY && OPT(sync_to_monitor)) {
if (w->wayland_render_state == RENDER_FRAME_NOT_REQUESTED) wayland_request_frame_render(w);
continue;
}
bool needs_render = w->is_damaged;
make_os_window_context_current(w);
if (w->viewport_size_dirty) {
@@ -727,7 +737,6 @@ process_pending_resizes(double now) {
static inline void
close_all_windows() {
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
global_state.close_all_windows = false;
}
static inline bool
@@ -783,7 +792,13 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
}
#endif
parse_input(self);
if (global_state.close_all_windows) close_all_windows();
if (global_state.terminate) {
global_state.terminate = false;
close_all_windows();
#ifdef __APPLE__
request_application_quit();
#endif
}
has_open_windows = process_pending_closes(self);
}
if (PyErr_Occurred()) return NULL;
@@ -864,7 +879,7 @@ read_bytes(int fd, Screen *screen) {
while(true) {
len = read(fd, screen->read_buf + orig_sz, available_buffer_space);
if (len < 0) {
if (errno == EINTR) continue;
if (errno == EINTR || errno == EAGAIN) continue;
if (errno != EIO) perror("Call to read() from child fd failed");
return false;
}
@@ -981,7 +996,8 @@ io_loop(void *data) {
// The I/O thread loop
size_t i;
int ret;
bool has_more, data_received;
bool has_more, data_received, has_pending_wakeups = false;
double last_main_loop_wakeup_at = -1, now = -1;
Screen *screen;
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyChildMon");
@@ -1000,7 +1016,14 @@ io_loop(void *data) {
fds[EXTRA_FDS + i].events = (screen->read_buf_sz < READ_BUF_SZ ? POLLIN : 0) | (screen->write_buf_used ? POLLOUT : 0);
screen_mutex(unlock, read); screen_mutex(unlock, write);
}
ret = poll(fds, self->count + EXTRA_FDS, -1);
if (has_pending_wakeups) {
now = monotonic();
double time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at);
if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, (int)ceil(1000 * time_delta));
else ret = 0;
} else {
ret = poll(fds, self->count + EXTRA_FDS, -1);
}
if (ret > 0) {
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
if (fds[1].revents && POLLIN) {
@@ -1044,8 +1067,17 @@ io_loop(void *data) {
perror("Call to poll() failed");
}
}
if (data_received) wakeup_main_loop();
#define WAKEUP { wakeup_main_loop(); last_main_loop_wakeup_at = now; has_pending_wakeups = false; }
// we only wakeup the main loop after input_delay as wakeup is an expensive operation
// on some platforms, such as cocoa
if (data_received) {
if ((now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
else has_pending_wakeups = true;
} else {
if (has_pending_wakeups && (now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
}
}
#undef WAKEUP
children_mutex(lock);
for (i = 0; i < self->count; i++) children[i].needs_removal = true;
remove_children(self);

View File

@@ -16,6 +16,7 @@
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++) {
@@ -28,6 +29,13 @@ serialize_string_tuple(PyObject *src) {
return ans;
}
static inline void
free_string_tuple(char** data) {
size_t i = 0;
while(data[i]) free(data[i++]);
free(data);
}
extern char **environ;
static inline void
@@ -107,6 +115,9 @@ spawn(PyObject *self UNUSED, PyObject *args) {
for (int c = 3; c < 201; c++) close(c);
environ = env;
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
// which does not reset signal handlers on its own
signal(SIGPIPE, SIG_DFL);
execvp(exe, argv);
// Report the failure and exec a shell instead, so that we are not left
// with a forked but not exec'ed process
@@ -125,8 +136,8 @@ spawn(PyObject *self UNUSED, PyObject *args) {
break;
}
#undef exit_on_err
free(argv);
free(env);
free_string_tuple(argv);
free_string_tuple(env);
if (PyErr_Occurred()) return NULL;
return PyLong_FromLong(pid);
}

View File

@@ -4,17 +4,28 @@
import fcntl
import os
from collections import defaultdict
from contextlib import contextmanager
import kitty.fast_data_types as fast_data_types
from .constants import is_macos, shell_path, terminfo_dir
if is_macos:
from kitty.fast_data_types import cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process
from kitty.fast_data_types import (
cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process,
process_group_map as _process_group_map
)
def cwd_of_process(pid):
return os.path.realpath(_cwd(pid))
def process_group_map():
ans = defaultdict(list)
for pid, pgid in _process_group_map():
ans[pgid].append(pid)
return ans
else:
def cmdline_of_process(pid):
@@ -27,6 +38,46 @@ else:
def _environ_of_process(pid):
return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8')
def process_group_map():
ans = defaultdict(list)
for x in os.listdir('/proc'):
try:
pid = int(x)
except Exception:
continue
try:
raw = open('/proc/' + x + '/stat', 'rb').read().decode('utf-8')
except EnvironmentError:
continue
try:
q = int(raw.split(' ', 5)[4])
except Exception:
continue
ans[q].append(pid)
return ans
def processes_in_group(grp):
gmap = getattr(process_group_map, 'cached_map', None)
if gmap is None:
try:
gmap = process_group_map()
except Exception:
gmap = {}
return gmap.get(grp, [])
@contextmanager
def cached_process_data():
try:
process_group_map.cached_map = process_group_map()
except Exception:
process_group_map.cached_map = {}
try:
yield
finally:
process_group_map.cached_map = None
def parse_environ_block(data):
"""Parse a C environ block of environment variables into a dictionary."""
@@ -142,6 +193,28 @@ class Child:
os.close(self.terminal_ready_fd)
self.terminal_ready_fd = -1
@property
def foreground_processes(self):
try:
pgrp = os.tcgetpgrp(self.child_fd)
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
def process_desc(pid):
ans = {'pid': pid}
try:
ans['cmdline'] = cmdline_of_process(pid)
except Exception:
pass
try:
ans['cwd'] = cwd_of_process(pid) or None
except Exception:
pass
return ans
return list(map(process_desc, foreground_processes))
except Exception:
return []
@property
def cmdline(self):
try:
@@ -162,3 +235,21 @@ class Child:
return cwd_of_process(self.pid)
except Exception:
pass
@property
def pid_for_cwd(self):
try:
pgrp = os.tcgetpgrp(self.child_fd)
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
if len(foreground_processes) == 1:
return foreground_processes[0]
except Exception:
pass
return self.pid
@property
def foreground_cwd(self):
try:
return cwd_of_process(self.pid_for_cwd) or None
except Exception:
pass

View File

@@ -275,7 +275,10 @@ def print_help_for_seq(seq, usage, message, appname):
if print_help_for_seq.allow_pager and sys.stdout.isatty():
import subprocess
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
p.communicate(text.encode('utf-8'))
try:
p.communicate(text.encode('utf-8'))
except KeyboardInterrupt:
raise SystemExit(1)
raise SystemExit(p.wait())
else:
print(text)
@@ -464,7 +467,7 @@ def options_spec():
dest=cls
default={appname}
condition=not is_macos
Set the class part of the :italic:`WM_CLASS` window property
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
--name
@@ -474,7 +477,9 @@ Set the name part of the :italic:`WM_CLASS` property (defaults to using the valu
--title -T
Set the window title. This will override any title set by the program running inside kitty. So
only use this if you are running a program that does not set titles.
only use this if you are running a program that does not set titles. If combined
with :option:`{appname} --session` the title will be used for all windows created by the
session, that do not set their own titles.
--config -c
@@ -546,6 +551,13 @@ specify this address. This option will be ignored, unless you set
option as it is read automatically from the environment.
--start-as
type=choices
default=normal
choices=normal,fullscreen,maximized,minimized
Control how the initial kitty window is created.
# Debugging options
--version -v

View File

@@ -32,6 +32,9 @@ class UnknownLayout(ValueError):
hide_traceback = True
cmap = {}
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
def w(func):
@@ -45,6 +48,7 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
func.no_response = no_response
func.string_return_is_error = string_return_is_error
func.args_count = 0 if not argspec else args_count
cmap[func.name] = func
return func
return w
@@ -122,7 +126,7 @@ def set_font_size(boss, window, payload):
' and :italic:`\\u21fa` to send unicode characters. If you use the :option:`kitty @ send-text --match` option'
' the text will be sent to all matched windows. By default, text is sent to'
' only the currently active window.',
options_spec=MATCH_WINDOW_OPTION + '''\n
options_spec=MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
--stdin
type=bool-set
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
@@ -138,7 +142,7 @@ are sent as is, not interpreted for escapes.
)
def cmd_send_text(global_opts, opts, args):
limit = 1024
ret = {'match': opts.match, 'is_binary': False}
ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab}
def pipe():
ret['is_binary'] = True
@@ -205,6 +209,13 @@ def send_text(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if payload['match_tab']:
windows = []
tabs = tuple(boss.match_tabs(payload['match_tab']))
if not tabs:
raise MatchError(payload['match_tab'], 'tabs')
for tab in tabs:
windows += tuple(tab)
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
for window in windows:
if window is not None:
@@ -278,7 +289,7 @@ def set_tab_title(boss, window, payload):
# }}}
# set_layout {{{
# goto_layout {{{
@cmd(
'Set the window layout',
'Set the window layout in the specified tab (or the active tab if not specified).'
@@ -313,6 +324,34 @@ def goto_layout(boss, window, payload):
# }}}
# last_used_layout {{{
@cmd(
'Switch to the last used layout',
'Switch to the last used window layout in the specified tab (or the active tab if not specified).'
' You can use special match value :italic:`all` to set the layout in all tabs.',
options_spec=MATCH_TAB_OPTION,
)
def cmd_last_used_layout(global_opts, opts, args):
return {'match': opts.match}
def last_used_layout(boss, window, payload):
match = payload['match']
if match:
if match == 'all':
tabs = tuple(boss.all_tabs)
else:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise MatchError(match, 'tabs')
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
for tab in tabs:
if tab:
tab.last_used_layout()
# }}}
# close_window {{{
@cmd(
'Close the specified window(s)',
@@ -363,7 +402,7 @@ reset the layout to its default configuration.
--self
type=bool-set
If specified close the window this command is run in, rather than the active window.
If specified resize the window this command is run in, rather than the active window.
''',
argspec='',
string_return_is_error=True
@@ -541,7 +580,9 @@ def focus_window(boss, window, payload):
raise MatchError(match)
for window in windows:
if window:
boss.set_active_window(window)
os_window_id = boss.set_active_window(window)
if os_window_id:
focus_os_window(os_window_id, True)
break
# }}}
@@ -550,10 +591,20 @@ def focus_window(boss, window, payload):
@cmd(
'Focus the specified tab',
'The active window in the specified tab will be focused.',
options_spec=MATCH_TAB_OPTION,
options_spec=MATCH_TAB_OPTION + '''
--no-response
type=bool-set
default=false
Don't wait for a response indicating the success of the action. Note that
using this option means that you will not be notified of failures.
''',
argspec='',
no_response=True,
)
def cmd_focus_tab(global_opts, opts, args):
if opts.no_response:
global_opts.no_command_response = True
return {'match': opts.match}
@@ -640,7 +691,7 @@ this option, any color arguments are ignored and --configured and --all are impl
)
def cmd_set_colors(global_opts, opts, args):
from .rgb import color_as_int, Color
colors = {}
colors, cursor_text_color = {}, False
if not opts.reset:
for spec in args:
if '=' in spec:
@@ -648,15 +699,17 @@ def cmd_set_colors(global_opts, opts, args):
else:
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
colors.update(parse_config(f))
cursor_text_color = colors.pop('cursor_text_color', False)
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
return {
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': colors, 'reset': opts.reset
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color
}
def set_colors(boss, window, payload):
from .rgb import color_as_int
from .rgb import color_as_int, Color
if payload['all']:
windows = tuple(boss.all_windows)
else:
@@ -673,10 +726,14 @@ def set_colors(boss, window, payload):
windows += tuple(tab)
if payload['reset']:
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
payload['cursor_text_color'] = boss.startup_cursor_text_color
profiles = tuple(w.screen.color_profile for w in windows)
from .fast_data_types import patch_color_profiles
patch_color_profiles(payload['colors'], profiles, payload['configured'])
boss.patch_colors(payload['colors'], payload['configured'])
cursor_text_color = payload.get('cursor_text_color', False)
if isinstance(cursor_text_color, (tuple, list, Color)):
cursor_text_color = color_as_int(Color(*cursor_text_color))
patch_color_profiles(payload['colors'], cursor_text_color, profiles, payload['configured'])
boss.patch_colors(payload['colors'], cursor_text_color, payload['configured'])
default_bg_changed = 'background' in payload['colors']
for w in windows:
if default_bg_changed:
@@ -787,9 +844,6 @@ def kitten(boss, window, payload):
# }}}
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
def cli_params_for(func):
return (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name)

View File

@@ -12,6 +12,7 @@
#include <AvailabilityMacros.h>
// Needed for _NSGetProgname
#include <crt_externs.h>
#include <objc/runtime.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -99,6 +100,21 @@ cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_FALSE;
}
// Implementation of applicationDockMenu: for the app delegate
static NSMenu *dockMenu = nil;
static NSMenu *
get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
if (!dockMenu) {
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
dockMenu = [[NSMenu alloc] init];
NSMenuItem *newWindowItem = [dockMenu addItemWithTitle:@"New OS window"
action:@selector(new_os_window:)
keyEquivalent:@""];
[newWindowItem setTarget:global_menu_target];
}
return dockMenu;
}
void
cocoa_create_global_menu(void) {
NSString* app_name = find_app_name();
@@ -183,6 +199,12 @@ cocoa_create_global_menu(void) {
}
[bar release];
class_addMethod(
object_getClass([NSApp delegate]),
@selector(applicationDockMenu:),
(IMP)get_dock_menu,
"@@:@");
}
void
@@ -224,6 +246,29 @@ cocoa_focus_window(void *w) {
[window makeKeyWindow];
}
bool
cocoa_toggle_fullscreen(void *w, bool traditional) {
NSWindow *window = (NSWindow*)w;
bool made_fullscreen = true;
NSWindowStyleMask sm = [window styleMask];
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
if (traditional) {
if (!(in_fullscreen)) {
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
} else {
made_fullscreen = false;
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
}
[window setStyleMask: sm];
} else {
if (in_fullscreen) made_fullscreen = false;
[window toggleFullScreen: nil];
}
return made_fullscreen;
}
static PyObject*
cocoa_get_lang(PyObject UNUSED *self) {
NSString* locale = nil;
@@ -269,6 +314,12 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
}
}
static void
cleanup() {
if (dockMenu) [dockMenu release];
dockMenu = nil;
}
static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
@@ -278,5 +329,9 @@ static PyMethodDef module_methods[] = {
bool
init_cocoa(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
if (Py_AtExit(cleanup) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the cocoa_window at exit handler");
return false;
}
return true;
}

View File

@@ -100,8 +100,8 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
static PyObject*
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured; PyObject *cursor_text_color;
if (!PyArg_ParseTuple(args, "O!OO!p", &PyDict_Type, &spec, &cursor_text_color, &PyTuple_Type, &profiles, &change_configured)) return NULL;
char key[32] = {0};
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
snprintf(key, sizeof(key) - 1, "color%zu", i);
@@ -131,6 +131,22 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
#undef S
if (cursor_text_color != Py_False) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
self->overridden.cursor_text_color = 0x111111;
self->overridden.cursor_text_uses_bg = 3;
if (cursor_text_color != Py_None) {
self->overridden.cursor_text_color = (PyLong_AsUnsignedLong(cursor_text_color) << 8) | 2;
self->overridden.cursor_text_uses_bg = 1;
}
if (change_configured) {
self->configured.cursor_text_color = self->overridden.cursor_text_color;
self->configured.cursor_text_uses_bg = self->overridden.cursor_text_uses_bg;
}
self->dirty = true;
}
}
Py_RETURN_NONE;
}
@@ -149,6 +165,14 @@ colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
}
}
float
cursor_text_as_bg(ColorProfile *self) {
if (self->overridden.cursor_text_uses_bg & 1) {
return self->overridden.cursor_text_uses_bg & 2 ? 1.f : 0.f;
}
return self->configured.cursor_text_uses_bg & 2 ? 1.f : 0.f;
}
static PyObject*
as_dict(ColorProfile *self, PyObject *args UNUSED) {
@@ -174,7 +198,7 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) {
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
}}
D(default_fg, foreground); D(default_bg, background);
D(cursor_color, cursor); D(highlight_fg, selection_foreground);
D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground);
D(highlight_bg, selection_background);
#undef D
@@ -236,7 +260,11 @@ set_color(ColorProfile *self, PyObject *args) {
static PyObject*
set_configured_colors(ColorProfile *self, PyObject *args) {
#define set_configured_colors_doc "Set the configured colors"
if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
if (!PyArg_ParseTuple(
args, "II|IIIII",
&(self->configured.default_fg), &(self->configured.default_bg),
&(self->configured.cursor_color), &(self->configured.cursor_text_color), &(self->configured.cursor_text_uses_bg),
&(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
self->dirty = true;
Py_RETURN_NONE;
}
@@ -286,6 +314,7 @@ default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
CGETSET(default_fg)
CGETSET(default_bg)
CGETSET(cursor_color)
CGETSET(cursor_text_color)
CGETSET(highlight_fg)
CGETSET(highlight_bg)
@@ -293,6 +322,7 @@ static PyGetSetDef getsetters[] = {
GETSET(default_fg)
GETSET(default_bg)
GETSET(cursor_color)
GETSET(cursor_text_color)
GETSET(highlight_fg)
GETSET(highlight_bg)
{NULL} /* Sentinel */

View File

@@ -35,7 +35,7 @@ completion_scripts = {
'zsh': '''
_kitty() {
local src
# Send all words upto the word the cursor is currently on
# Send all words up to the word the cursor is currently on
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
if [[ $? == 0 ]]; then
eval ${src}
@@ -44,7 +44,7 @@ _kitty() {
compdef _kitty kitty
''',
'bash': '''
kitty_completions() {
_kitty_completions() {
local src
local limit
# Send all words up to the word the cursor is currently on
@@ -55,7 +55,15 @@ kitty_completions() {
fi
}
complete -o nospace -F kitty_completions kitty
complete -o nospace -F _kitty_completions kitty
''',
'fish': '''
function __kitty_completions
# Send all words up to the one before the cursor
commandline -cop | kitty +complete fish
end
complete -f -c kitty -a "(__kitty_completions)"
''',
}
@@ -86,6 +94,11 @@ def bash_input_parser(data):
return words, new_word
@input_parser
def fish_input_parser(data):
return data.rstrip().splitlines(), True
@output_serializer
def zsh_output_serializer(ans):
lines = []
@@ -95,6 +108,10 @@ def zsh_output_serializer(ans):
cmd += ['-S', '""']
if description in ans.files_groups:
cmd.append('-f')
common_prefix = os.path.commonprefix(tuple(matches))
if common_prefix:
cmd.extend(('-p', shlex.quote(common_prefix)))
matches = {k[len(common_prefix):]: v for k, v in matches.items()}
cmd.append('--')
for word, description in matches.items():
cmd.append(shlex.quote(word))
@@ -117,6 +134,16 @@ def bash_output_serializer(ans):
# }}}
@output_serializer
def fish_output_serializer(ans):
lines = []
for matches in ans.match_groups.values():
for word in matches:
lines.append(shlex.quote(word))
# debug('\n'.join(lines))
return '\n'.join(lines)
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
cmds = ['@' + c for c in cmap]
ans.match_groups['Entry points'] = {
@@ -197,21 +224,40 @@ def complete_remote_command(ans, cmd_name, words, new_word):
complete_alias_map(ans, words, new_word, alias_map)
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=lambda filename: True):
def path_completion(prefix=''):
prefix = prefix.replace(r'\ ', ' ')
dirs, files = [], []
base = '.'
if prefix.endswith('/'):
base = prefix
elif '/' in prefix:
base = os.path.dirname(prefix)
for x in os.scandir(base):
q = os.path.relpath(x.path)
if x.is_dir():
if q.startswith(prefix):
dirs.append(q.rstrip(os.sep) + os.sep)
src = os.path.expandvars(os.path.expanduser(base))
src_prefix = os.path.abspath(os.path.expandvars(os.path.expanduser(prefix))) if prefix else ''
try:
items = os.scandir(src)
except FileNotFoundError:
items = ()
for x in items:
abspath = os.path.abspath(x.path)
if prefix and not abspath.startswith(src_prefix):
continue
if prefix:
q = prefix + abspath[len(src_prefix):].lstrip(os.sep)
q = os.path.expandvars(os.path.expanduser(q))
else:
if q.startswith(prefix) and predicate(q):
files.append(q)
q = os.path.relpath(abspath)
if x.is_dir():
dirs.append(q.rstrip(os.sep) + os.sep)
else:
files.append(q)
return dirs, files
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=None):
dirs, files = path_completion(prefix or '')
files = filter(predicate, files)
if dirs:
ans.match_groups['Directories'] = dict.fromkeys(dirs)
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
@@ -232,6 +278,17 @@ def complete_icat_args(ans, opt, prefix):
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
def config_file_predicate(filename):
return filename.endswith('.conf')
def complete_diff_args(ans, opt, prefix):
if opt is None:
complete_files_and_dirs(ans, prefix, 'Files')
elif opt['dest'] == 'config':
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
def complete_kitten(ans, kitten, words, new_word):
try:
cd = get_kitten_cli_docs(kitten)
@@ -247,7 +304,8 @@ def complete_kitten(ans, kitten, words, new_word):
for alias in opt['aliases']:
option_map[alias] = opt
complete_alias_map(ans, words, new_word, option_map, {
'icat': complete_icat_args
'icat': complete_icat_args,
'diff': complete_diff_args,
}.get(kitten))

View File

@@ -16,7 +16,7 @@ from .conf.utils import (
parse_config_base, python_string, to_bool, to_cmdline
)
from .config_data import all_options, parse_mods, type_map
from .constants import cache_dir, defconf
from .constants import cache_dir, defconf, is_macos
from .utils import log_error
named_keys = {
@@ -35,14 +35,22 @@ named_keys = {
def parse_shortcut(sc):
parts = sc.split('+')
mods = parse_mods(parts[:-1], sc)
if mods is None:
return None, None, None
mods = 0
if len(parts) > 1:
mods = parse_mods(parts[:-1], sc)
if mods is None:
return None, None, None
key = parts[-1].upper()
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
is_native = False
if key is None:
key = defines.key_for_native_key_name(parts[-1])
if parts[-1].startswith('0x'):
try:
key = int(parts[-1], 16)
except Exception:
pass
else:
key = defines.key_for_native_key_name(parts[-1])
is_native = key is not None
return mods, is_native, key
@@ -107,7 +115,7 @@ def float_parse(func, rest):
@func_with_args('change_font_size')
def parse_change_font_size(func, rest):
vals = rest.split(' ', 1)
vals = rest.strip().split(' ', 1)
if len(vals) != 2:
log_error('Invalid change_font_size specification: {}, treating it as default'.format(rest))
args = [True, None, 0]
@@ -117,13 +125,13 @@ def parse_change_font_size(func, rest):
if amt[0] in '+-':
args[1] = amt[0]
amt = amt[1:]
args[2] = float(amt)
args[2] = float(amt.strip())
return func, args
@func_with_args('clear_terminal')
def clear_terminal(func, rest):
vals = rest.split(' ', 1)
vals = rest.strip().split(' ', 1)
if len(vals) != 2:
log_error('clear_terminal needs two arguments, using defaults')
args = ['reset', 'active']
@@ -132,8 +140,72 @@ def clear_terminal(func, rest):
return func, args
@func_with_args('neighboring_window')
def neighboring_window(func, rest):
rest = rest.lower()
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
if rest not in ('left', 'right', 'top', 'bottom'):
log_error('Invalid neighbor specification: {}'.format(rest))
rest = 'right'
return func, [rest]
@func_with_args('resize_window')
def resize_window(func, rest):
vals = rest.strip().split(' ', 1)
if len(vals) > 2:
log_error('resize_window needs one or two arguments, using defaults')
args = ['wider', 1]
else:
quality = vals[0].lower()
if quality not in ('taller', 'shorter', 'wider', 'narrower'):
log_error('Invalid quality specification: {}'.format(quality))
quality = 'wider'
increment = 1
if len(vals) == 2:
try:
increment = int(vals[1])
except Exception:
log_error('Invalid increment specification: {}'.format(vals[1]))
args = [quality, increment]
return func, args
@func_with_args('move_window')
def move_window(func, rest):
rest = rest.lower()
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
try:
rest = int(rest)
except Exception:
if rest not in ('left', 'right', 'top', 'bottom'):
log_error('Invalid move_window specification: {}'.format(rest))
rest = 0
return func, [rest]
@func_with_args('pipe')
def pipe(func, rest):
import shlex
rest = shlex.split(rest)
if len(rest) < 3:
log_error('Too few arguments to pipe function')
rest = ['none', 'none', 'true']
return func, rest
@func_with_args('nth_window')
def nth_window(func, rest):
try:
num = int(rest)
except Exception:
log_error('Invalid nth_window number: {}'.format(rest))
num = 1
return func, [num]
def parse_key_action(action):
parts = action.split(' ', 1)
parts = action.strip().split(' ', 1)
func = parts[0]
if len(parts) == 1:
return KeyAction(func, ())
@@ -144,7 +216,8 @@ def parse_key_action(action):
func, args = parser(func, rest)
except Exception:
log_error('Ignoring invalid key action: {}'.format(action))
return KeyAction(func, args)
else:
return KeyAction(func, args)
all_key_actions = set()
@@ -261,6 +334,15 @@ special_handlers = {}
def special_handler(func):
special_handlers[func.__name__.partition('_')[2]] = func
return func
def deprecated_handler(*names):
def special_handler(func):
for name in names:
special_handlers[name] = func
return func
return special_handler
@special_handler
@@ -285,6 +367,15 @@ def handle_clear_all_shortcuts(key, val, ans):
ans['key_definitions'] = [None]
@deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar')
def handle_deprecated_hide_window_decorations_aliases(key, val, ans):
if not hasattr(handle_deprecated_hide_window_decorations_aliases, key):
handle_deprecated_hide_window_decorations_aliases.key = True
log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key))
if to_bool(val):
ans['hide_window_decorations'] = True
def expandvars(val, env):
def sub(m):
@@ -296,7 +387,7 @@ def expandvars(val, env):
result = m.group()
return result
return re.sub(r'$\{(\S+)\}', sub, val)
return re.sub(r'\$\{(\S+?)\}', sub, val)
@special_handler
@@ -501,4 +592,6 @@ def load_config(*paths, overrides=None):
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
log_error('Cannot use both macos_titlebar_color and background_opacity')
opts.macos_titlebar_color = 0
if (is_macos and getattr(opts, 'macos_hide_titlebar', False)) or (not is_macos and getattr(opts, 'x11_hide_window_decorations', False)):
opts.hide_window_decorations = True
return opts

View File

@@ -95,6 +95,21 @@ for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
prefix. Note that you should only use an XKB key name for keys that are not present
in the list of GLFW keys.
Finally, you can use raw system key codes to map keys. To see the system key code
for a key, start kitty with the :option:`kitty --debug-keyboard` option. Then kitty will
output some debug text for every key event. In that text look for ``native_code``
the value of that becomes the key name in the shortcut. For example:
.. code-block:: none
on_key_input: glfw key: 65 native_code: 0x61 action: PRESS mods: 0x0 text: 'a'
Here, the key name for the :kbd:`A` key is :kbd:`0x61` and you can use it with::
map ctrl+0x61 something
to map :kbd:`ctrl+a` to something.
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
assigned in the default configuration.
@@ -125,13 +140,19 @@ For example::
'shortcuts.tab': [
_('Tab management'), '',
_('''\
You can also create shortcuts to go to specific tabs, with 1 being the first tab::
You can also create shortcuts to go to specific tabs, with 1 being the first
tab, 2 the second tab and -1 being the previously active tab::
map ctrl+alt+1 goto_tab 1
map ctrl+alt+2 goto_tab 2
Just as with :code:`new_window` above, you can also pass the name of arbitrary
commands to run when using new_tab and use :code:`new_tab_with_cwd`.
commands to run when using new_tab and use :code:`new_tab_with_cwd`. Finally,
if you want the new tab to open next to the current tab rather than at the
end of the tabs list, use::
map ctrl+t new_tab !neighbor [optional cmd to run]
''')],
'shortcuts.layout': [
_('Layout management'), '',
@@ -140,17 +161,22 @@ You can also create shortcuts to switch to specific layouts::
map ctrl+alt+t goto_layout tall
map ctrl+alt+s goto_layout stack
Similarly, to switch back to the previous layout::
map ctrl+alt+p last_used_layout
''')],
'shortcuts.fonts': [
_('Font sizes'), _('''\
You can change the font size for all top-level kitty windows at a time
You can change the font size for all top-level kitty OS windows at a time
or only the current one.
'''), _('''\
To setup shortcuts for specific font sizes::
map kitty_mod+f6 change_font_size all 10.0
To setup shortcuts to change only the current window's font size::
To setup shortcuts to change only the current OS window's font size::
map kitty_mod+f6 change_font_size current 10.0
''')],
@@ -290,9 +316,24 @@ o('cursor_stop_blinking_after', 15.0, option_type=positive_float)
g('scrollback') # {{{
o('scrollback_lines', 2000, option_type=positive_int, long_text=_('''
def scrollback_lines(x):
x = int(x)
if x < 0:
x = 2 ** 32 - 1
return x
def scrollback_pager_history_size(x):
ans = int(max(0, float(x)) * 1024 * 1024)
return min(ans, 4096 * 1024 * 1024 - 1)
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
Number of lines of history to keep in memory for scrolling back. Memory is allocated
on demand.'''))
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
very large scrollback is not recommended as it can slow down resizing of the terminal
and also use large amounts of RAM.'''))
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
Program with which to view scrollback in a new window. The scrollback buffer is
@@ -301,10 +342,26 @@ use can handle ANSI escape sequences for colors and text formatting.
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
representing which line should be at the top of the screen.'''))
o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size, long_text=_('''
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
This separate buffer is not available for interactive scrolling but will be
piped to the pager program when viewing scrollback buffer in a separate window.
The current implementation stores one character in 4 bytes, so approximatively
2500 lines per megabyte at 100 chars per line. A value of zero or less disables
this feature. The maximum allowed size is 4GB.'''))
o('wheel_scroll_multiplier', 5.0, long_text=_('''
Modify the amount scrolled by the mouse wheel. Note this is only used for low
precision scrolling devices, not for high precision scrolling on platforms such
as macOS and Wayland. Use negative numbers to change scroll direction.'''))
o('touch_scroll_multiplier', 1.0, long_text=_('''
Modify the amount scrolled by a touchpad. Note this is only used for high
precision scrolling devices on platforms such as macOS and Wayland.
Use negative numbers to change scroll direction.'''))
# }}}
# }}}
g('mouse') # {{{
@@ -337,7 +394,7 @@ operating system's default URL handler.'''))
o('copy_on_select', False, long_text=_('''
Copy to clipboard on select. With this enabled, simply selecting text with
the mouse will cause the text to be copied to clipboard. Useful on platforms
such as macOS/Wayland that do not have the concept of primary selections. Note
such as macOS that do not have the concept of primary selections. Note
that this is a security risk, as all programs, including websites open in your
browser can read the contents of the clipboard.'''))
@@ -350,9 +407,9 @@ Characters considered part of a word when double clicking. In addition to these
any character that is marked as an alpha-numeric character in the unicode
database will be matched.'''))
o('click_interval', 0.5, option_type=positive_float, long_text=_('''
The interval between successive clicks to detect
double/triple clicks (in seconds)'''))
o('click_interval', -1.0, option_type=float, long_text=_('''
The interval between successive clicks to detect double/triple clicks (in seconds).
Negative numbers will use the system default instead, if available, or fallback to 0.5.'''))
o('mouse_hide_wait', 3.0, option_type=positive_float, long_text=_('''
Hide mouse cursor after the specified number of seconds
@@ -432,7 +489,7 @@ def to_layout_names(raw):
parts = [x.strip().lower() for x in raw.split(',')]
ans = []
for p in parts:
if p == '*':
if p in ('*', 'all'):
ans.extend(sorted(all_layouts))
continue
name = p.partition(':')[0]
@@ -444,7 +501,7 @@ def to_layout_names(raw):
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
The enabled window layouts. A comma separated list of layout names. The special
value :code:`*` means all layouts. The first listed layout will be used as the
value :code:`all` means all layouts. The first listed layout will be used as the
startup layout. For a list of available layouts, see the :ref:`layouts`.
'''))
@@ -489,6 +546,12 @@ o('inactive_text_alpha', 1.0, option_type=unit_float, long_text=_('''
Fade the text in inactive windows by the specified amount (a number between
zero and one, with zero being fully faded).
'''))
o('hide_window_decorations', False, long_text=_('''
Hide the window decorations (title-bar and window borders).
Whether this works and exactly what effect it has depends on the
window manager/operating system.
'''))
# }}}
g('tabbar') # {{{
@@ -523,8 +586,8 @@ Which edge to show the tab bar on, top or bottom'''))
o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_('''
The margin to the left and right of the tab bar (in pts)'''))
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator'), long_text=_('''
The tab bar style, can be one of: :code:`fade` or :code:`separator`. In the fade style,
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'hidden'), long_text=_('''
The tab bar style, can be one of: :code:`fade`, :code:`separator` or :code:`hidden`. In the fade style,
each tab's edges fade into the background color, in the separator style, tabs are
separated by a configurable separator.
'''))
@@ -545,6 +608,13 @@ entries to this list.
o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_('''
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
o('tab_title_template', '{title}', long_text=_('''
A template to render the tab title. The default just renders
the title. If you wish to include the tab-index as well,
use something like: :code:`{index}: {title}`. Useful
if you have shortcuts mapped for :code:`goto_tab N`.
'''))
o('active_tab_foreground', '#000', option_type=to_color, long_text=_('''
Tab bar colors and styles'''))
o('active_tab_background', '#eee', option_type=to_color)
@@ -655,7 +725,7 @@ Specify environment variables to set in all child processes. Note that
environment variables are expanded recursively, so if you use::
env MYVAR1=a
env MYVAR2=${MYVAR}/${HOME}/b
env MYVAR2=${MYVAR1}/${HOME}/b
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
'''))
@@ -689,9 +759,13 @@ program, even one running on a remote server via SSH can read your clipboard.
'''))
o('term', 'xterm-kitty', long_text=_('''
The value of the TERM environment variable to set. Changing this can break
many terminal programs, only change it if you know what you are doing, not
because you read some advice on Stack Overflow to change it.
The value of the TERM environment variable to set. Changing this can break many
terminal programs, only change it if you know what you are doing, not because
you read some advice on Stack Overflow to change it. The TERM variable if used
by various programs to get information about the capabilities and behavior of
the terminal. If you change it, depending on what programs you run, and how
different the terminal you are changing it to is, various things from
key-presses, to colors, to various advanced features may not work.
'''))
# }}}
@@ -716,18 +790,9 @@ an arbitrary color, such as :code:`#12af59` or :code:`red`. WARNING: This option
using a hack, as there is no proper Cocoa API for it. It sets the background
color of the entire window and makes the titlebar transparent. As such it is
incompatible with :opt:`background_opacity`. If you want to use both, you are
probably better off just hiding the titlebar with :opt:`macos_hide_titlebar`.
probably better off just hiding the titlebar with :opt:`hide_window_decorations`.
'''))
o('macos_hide_titlebar', False, long_text=_('''
Hide the kitty window's title bar on macOS.'''))
o('x11_hide_window_decorations', False, long_text=_('''
Hide the window decorations (title bar and window borders) on X11 and Wayland.
Whether this works and exactly what effect it has depends on the window
manager, as it is the job of the window manager/compositor to draw window
decorations.'''))
o('macos_option_as_alt', True, long_text=_('''
Use the option key as an alt key. With this set to no, kitty will use
the macOS native :kbd:`Option+Key` = unicode character behavior. This will
@@ -749,6 +814,23 @@ o('macos_window_resizable', True, long_text=_('''
Disable this if you want kitty top-level (OS) windows to not be resizable
on macOS.
'''))
o('macos_thicken_font', 0, option_type=positive_float, long_text=_('''
Draw an extra border around the font with the given width, to increase
legibility at small font sizes. For example, a value of 0.75 will
result in rendering that looks similar to sub-pixel antialiasing at
common font sizes.
'''))
o('macos_traditional_fullscreen', False, long_text=_('''
Use the traditional full-screen transition, that is faster, but less pretty.
'''))
# Disabled by default because of https://github.com/kovidgoyal/kitty/issues/794
o('macos_custom_beam_cursor', False, long_text=_('''
Enable/disable custom mouse cursor for macOS that is easier to see on both
light and dark backgrounds. WARNING: this might make your mouse cursor
invisible on dual GPU machines.'''))
# }}}
g('shortcuts') # {{{
@@ -766,7 +848,9 @@ g('shortcuts.clipboard') # {{{
if is_macos:
k('copy_to_clipboard', 'cmd+c', 'copy_to_clipboard', _('Copy to clipboard'), add_to_docs=False)
k('paste_from_clipboard', 'cmd+v', 'paste_from_clipboard', _('Paste from clipboard'), add_to_docs=False)
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'))
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'), long_text=_('''
There is also a :code:`copy_or_interrupt` action that can be optionally mapped to :kbd:`Ctrl+c`.
It will copy only if there is a selection and send an interrupt otherwise.'''))
k('paste_from_clipboard', 'kitty_mod+v', 'paste_from_clipboard', _('Paste from clipboard'))
k('paste_from_selection', 'kitty_mod+s', 'paste_from_selection', _('Paste from selection'))
k('paste_from_selection', 'shift+insert', 'paste_from_selection', _('Paste from selection'))
@@ -795,12 +879,15 @@ k('scroll_page_down', 'kitty_mod+page_down', 'scroll_page_down', _('Scroll page
k('scroll_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top'))
k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom'))
k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_('''
You can send the contents of the current screen + history buffer as stdin to an arbitrary program 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::
map kitty_mod+y new_window @ansi less +G -R
You can pipe the contents of the current screen + history buffer as
:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example,
the following opens the scrollback buffer in less in an overlay window::
map f1 pipe @ansi overlay less +G -R
For more details on piping screen and buffer contents to external programs,
see :doc:`pipe`.
'''))
@@ -850,6 +937,8 @@ g('shortcuts.tab') # {{{
if is_macos:
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'))
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
if is_macos:
k('previous_tab', 'ctrl+shift+tab', 'previous_tab', _('Previous tab'))
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
@@ -907,12 +996,21 @@ k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset
long_text=_('''
You can create shortcuts to clear/reset the terminal. For example::
# Reset the terminal
map kitty_mod+f9 clear_terminal reset active
# Clear the terminal screen by erasing all contents
map kitty_mod+f10 clear_terminal clear active
# Clear the terminal scrollback by erasing it
map kitty_mod+f11 clear_terminal scrollback active
# Scroll the contents of the screen into the scrollback
map kitty_mod+f12 clear_terminal scroll active
These will reset screen/clear screen/clear screen+scrollback respectively. If you want to
operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
If you want to operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
It is also possible to remap Ctrl+L to both scroll the current screen contents into the scrollback buffer
and clear the screen, instead of just clearing the screen::
map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c
'''))
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
add_to_default=False, long_text=_('''

View File

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

View File

@@ -16,7 +16,7 @@
// *Bell*: Beeps.
#define BEL 0x07
// *Backspace*: Backspace one column, but not past the begining of the
// *Backspace*: Backspace one column, but not past the beginning of the
// line.
#define BS 0x08

View File

@@ -171,6 +171,14 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
else start++;
if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); return NULL; }
if (new_font == old_font) { CFRelease(new_font); continue; }
CFStringRef name = CTFontCopyPostScriptName(new_font);
CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0);
CFRelease(name);
if (cr == kCFCompareEqualTo) {
CFRelease(new_font);
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
return NULL;
}
return new_font;
}
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
@@ -213,6 +221,15 @@ is_glyph_empty(PyObject *s, glyph_index g) {
return bounds.size.width <= 0;
}
int
get_glyph_width(PyObject *s, glyph_index g) {
CTFace *self = (CTFace*)s;
CGGlyph gg = g;
CGRect bounds;
CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1);
return bounds.size.width;
}
static inline float
scaled_point_sz(FONTS_DATA_HANDLE fg) {
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
@@ -364,14 +381,18 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
CGContextSetShouldAntialias(render_ctx, true);
CGContextSetShouldSmoothFonts(render_ctx, true);
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
CGContextSetGrayStrokeColor(render_ctx, 1, 1);
CGContextSetLineWidth(render_ctx, global_state.opts.macos_thicken_font);
CGContextSetTextDrawingMode(render_ctx, kCGTextFillStroke);
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
CGContextSetTextPosition(render_ctx, 0, height - baseline);
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
CGContextRelease(render_ctx);
CGColorSpaceRelease(gray_color_space);
}
static inline bool
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg) {
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
unsigned int canvas_width = cell_width * num_cells;
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs);
if (allow_resize) {
@@ -382,7 +403,7 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
CGFloat sz = CTFontGetSize(ct_font);
sz *= canvas_width / right;
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg);
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
CFRelease(new_font);
return ret;
}
@@ -408,10 +429,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
}
bool
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
CTFace *self = (CTFace*)s;
for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint;
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg);
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
}

View File

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

View File

@@ -40,6 +40,7 @@
#define NS_TO_US (1000)
#ifdef __APPLE__
#include <libproc.h>
#include <mach/mach_time.h>
static mach_timebase_info_data_t timebase = {0};
@@ -54,6 +55,25 @@ user_cache_dir() {
return PyUnicode_FromString(buf);
}
static PyObject*
process_group_map() {
int num_of_processes = proc_listallpids(NULL, 0);
size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024);
pid_t *buf = malloc(bufsize);
if (!buf) return PyErr_NoMemory();
num_of_processes = proc_listallpids(buf, (int)bufsize);
PyObject *ans = PyTuple_New(num_of_processes);
if (ans == NULL) { free(buf); return PyErr_NoMemory(); }
for (int i = 0; i < num_of_processes; i++) {
long pid = buf[i], pgid = getpgid(buf[i]);
PyObject *t = Py_BuildValue("ll", pid, pgid);
if (t == NULL) { free(buf); Py_DECREF(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, t);
}
free(buf);
return ans;
}
#else
#include <time.h>
static inline double monotonic_() {
@@ -126,6 +146,7 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
if (!read_with_timeout) flags |= O_NONBLOCK;
static char ctty[L_ctermid+1];
int fd = open(ctermid(ctty), flags);
if (fd == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
struct termios *termios_p = calloc(1, sizeof(struct termios));
if (!termios_p) return PyErr_NoMemory();
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
@@ -176,6 +197,7 @@ static PyMethodDef module_methods[] = {
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
#ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS),
METHODB(process_group_map, METH_NOARGS),
#endif
#ifdef WITH_PROFILER
{"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},

View File

@@ -173,11 +173,20 @@ typedef struct {
line_attrs_type *line_attrs;
} HistoryBufSegment;
typedef struct {
index_type bufsize, maxsz;
Py_UCS4 *buffer;
index_type start, end;
index_type bufend;
bool rewrap_needed;
} PagerHistoryBuf;
typedef struct {
PyObject_HEAD
index_type xnum, ynum, num_segments;
HistoryBufSegment* segments;
HistoryBufSegment *segments;
PagerHistoryBuf *pagerhist;
Line *line;
index_type start_of_data, count;
} HistoryBuf;
@@ -201,7 +210,7 @@ typedef struct {
} CursorRenderInfo;
typedef struct {
color_type default_fg, default_bg, cursor_color, highlight_fg, highlight_bg;
color_type default_fg, default_bg, cursor_color, cursor_text_color, cursor_text_uses_bg, highlight_fg, highlight_bg;
} DynamicColor;
typedef struct {
@@ -254,7 +263,7 @@ const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, siz
Line* alloc_line();
Cursor* alloc_cursor();
LineBuf* alloc_linebuf(unsigned int, unsigned int);
HistoryBuf* alloc_historybuf(unsigned int, unsigned int);
HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int);
ColorProfile* alloc_color_profile();
PyObject* create_256_color_table();
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
@@ -267,7 +276,8 @@ void cursor_copy_to(Cursor *src, Cursor *dest);
void cursor_reset_display_attrs(Cursor*);
void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count);
void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
const char* cursor_as_sgr(Cursor*, Cursor*);
const char* cell_as_sgr(GPUCell *, GPUCell *);
const char* cursor_as_sgr(const Cursor *);
double monotonic();
PyObject* cm_thread_write(PyObject *self, PyObject *args);
@@ -275,13 +285,14 @@ bool schedule_write_to_child(unsigned long id, unsigned int num, ...);
bool set_iutf8(int, bool);
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
float cursor_text_as_bg(ColorProfile *self);
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
void colorprofile_push_dynamic_colors(ColorProfile*);
void colorprofile_pop_dynamic_colors(ColorProfile*);
void set_mouse_cursor(MouseShape);
void enter_event();
void mouse_event(int, int);
void mouse_event(int, int, int);
void focus_in_event();
void wakeup_io_loop(bool);
void scroll_event(double, double, int);

View File

@@ -31,10 +31,15 @@ static PyObject*
init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) {
static bool done = false;
static const char* libname = "libstartup-notification-1.so";
// some installs are missing the .so symlink, so try the full name
static const char* libname2 = "libstartup-notification-1.so.0";
static const char* libname3 = "libstartup-notification-1.so.0.0.0";
if (!done) {
done = true;
libsn_handle = dlopen(libname, RTLD_LAZY);
if (libsn_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY);
if (libsn_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY);
if (libsn_handle == NULL) {
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libname, dlerror());
return NULL;

View File

@@ -24,11 +24,12 @@ pyspacing(int val) {
#undef S
}
static inline PyObject*
pattern_as_dict(FcPattern *pat) {
PyObject *ans = PyDict_New();
if (ans == NULL) return NULL;
#define PS(x) PyUnicode_FromString((char*)x)
#define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace")
#define G(type, get, which, conv, name) { \
type out; PyObject *p; \
if (get(pat, which, 0, &out) == FcResultMatch) { \
@@ -114,6 +115,7 @@ _fc_match(FcPattern *pat) {
FcResult result;
FcConfigSubstitute(NULL, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
/* printf("fc_match = %s\n", FcNameUnparse(pat)); */
match = FcFontMatch(NULL, pat, &result);
if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; }
ans = pattern_as_dict(match);
@@ -145,16 +147,22 @@ end:
static PyObject*
fc_match(PyObject UNUSED *self, PyObject *args) {
char *family = NULL;
int bold = 0, italic = 0, allow_bitmapped_fonts = 0;
int bold = 0, italic = 0, allow_bitmapped_fonts = 0, monospaced = 0;
double size_in_pts = 0, dpi = 0;
FcPattern *pat = NULL;
PyObject *ans = NULL;
if (!PyArg_ParseTuple(args, "|zpppdd", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
if (!PyArg_ParseTuple(args, "|zppppdd", &family, &bold, &italic, &monospaced, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();
if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family");
if (monospaced) {
// pass the family,monospace as the family parameter to fc-match,
// which will fallback to using monospace if the family does not match.
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family");
AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing");
}
if (!allow_bitmapped_fonts) {
AP(FcPatternAddBool, FC_OUTLINE, true, "outline");
AP(FcPatternAddBool, FC_SCALABLE, true, "scalable");

View File

@@ -14,6 +14,7 @@
#define MISSING_GLYPH 4
#define MAX_NUM_EXTRA_GLYPHS 8
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
#define MAX_NUM_EXTRA_GLYPHS_PUA 4
typedef void (*send_sprite_to_gpu_func)(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
@@ -395,7 +396,7 @@ calc_cell_metrics(FontGroup *fg) {
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px);
if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac);
if (OPT(adjust_column_width_frac) != 0.f) cell_width *= OPT(adjust_column_width_frac);
int line_height_adjustment = cell_height - before_cell_height;
if (cell_height < 4) fatal("line height too small after adjustment");
if (cell_height > 1000) fatal("line height too large after adjustment");
@@ -635,8 +636,13 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
return ans;
}
static inline bool
is_private_use(char_type ch) {
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
static inline void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, bool center_glyph) {
static SpritePosition* sprite_position[16];
int error = 0;
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
@@ -651,7 +657,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
clear_canvas(fg);
bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg);
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
if (PyErr_Occurred()) PyErr_Print();
for (unsigned int i = 0; i < num_cells; i++) {
@@ -901,17 +907,17 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
static inline void
merge_groups_for_pua_space_ligature() {
if (G(group_idx) == 1) {
while (G(group_idx) > 0) {
Group *g = G(groups), *g1 = G(groups) + 1;
g->num_cells += g1->num_cells;
g->num_glyphs += g1->num_glyphs;
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
G(group_idx) = 0;
G(group_idx)--;
}
}
static inline void
render_groups(FontGroup *fg, Font *font) {
render_groups(FontGroup *fg, Font *font, bool center_glyph) {
unsigned idx = 0;
ExtraGlyphs ed;
while (idx <= G(group_idx)) {
@@ -924,7 +930,7 @@ render_groups(FontGroup *fg, Font *font) {
int last = -1;
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
idx++;
}
}
@@ -970,12 +976,12 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
#undef G
static inline void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph) {
switch(font_idx) {
default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx]);
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
render_groups(fg, &fg->fonts[font_idx]);
render_groups(fg, &fg->fonts[font_idx], center_glyph);
break;
case BLANK_FONT:
while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; }
@@ -989,16 +995,12 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
}
}
static inline bool
is_private_use(char_type ch) {
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
}
void
render_line(FONTS_DATA_HANDLE fg_, Line *line) {
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph);
FontGroup *fg = (FontGroup*)fg_;
ssize_t run_font_idx = NO_FONT;
bool center_glyph = false;
index_type first_cell_in_run, i;
attrs_type prev_width = 0;
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
@@ -1006,21 +1008,46 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line) {
CPUCell *cpu_cell = line->cpu_cells + i;
GPUCell *gpu_cell = line->gpu_cells + i;
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell);
if (is_private_use(cpu_cell->ch) && i + 1 < line->xnum && (line->cpu_cells[i+1].ch == ' ' || line->cpu_cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
// We have a private use char followed by a space char, render it as a two cell ligature.
GPUCell *space_cell = line->gpu_cells + i+1;
// Ensure the space cell uses the foreground colors from the PUA cell
// This is needed because there are stupid applications like
// powerline that use PUA+space with different foreground colors
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
space_cell->fg = gpu_cell->fg; space_cell->decoration_fg = gpu_cell->decoration_fg;
RENDER;
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, 2, cell_font_idx, true);
run_font_idx = NO_FONT;
first_cell_in_run = i + 2;
prev_width = line->gpu_cells[i+1].attrs & WIDTH_MASK;
i++;
continue;
if (is_private_use(cpu_cell->ch)
&& cell_font_idx != BOX_FONT
&& cell_font_idx != MISSING_FONT) {
int desired_cells = 1;
if (cell_font_idx > 0) {
Font *font = (fg->fonts + cell_font_idx);
glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch);
int width = get_glyph_width(font->face, glyph_id);
desired_cells = ceilf((float)width / fg->cell_width);
}
int num_spaces = 0;
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
&& num_spaces < desired_cells
&& i + num_spaces + 1 < line->xnum) {
num_spaces++;
// We have a private use char followed by space(s), render it as a multi-cell ligature.
GPUCell *space_cell = line->gpu_cells + i + num_spaces;
// Ensure the space cell uses the foreground/background colors from the PUA cell.
// This is needed because there are stupid applications like
// powerline that use PUA+space with different foreground colors
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
space_cell->fg = gpu_cell->fg;
space_cell->bg = gpu_cell->bg;
space_cell->decoration_fg = gpu_cell->decoration_fg;
}
if (num_spaces) {
center_glyph = true;
RENDER;
center_glyph = false;
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph);
run_font_idx = NO_FONT;
first_cell_in_run = i + num_spaces + 1;
prev_width = line->gpu_cells[i+num_spaces].attrs & WIDTH_MASK;
i += num_spaces;
continue;
}
}
prev_width = gpu_cell->attrs & WIDTH_MASK;
if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx;
@@ -1077,10 +1104,10 @@ send_prerendered_sprites(FontGroup *fg) {
do_increment(fg, &error);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->logical_dpi_x, fg->logical_dpi_y);
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); }
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
if (y > 0) { fatal("too many prerendered sprites for your GPU"); }
if (y > 0) { fatal("too many pre-rendered sprites for your GPU or the font size is too large"); }
do_increment(fg, &error);
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
@@ -1280,6 +1307,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT;
FontGroup *fg = font_groups;
ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell);
if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; }
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
return fg->fonts[ans].face;
}

View File

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

View File

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

View File

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

View File

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

5606
kitty/gl-wrapper.c generated

File diff suppressed because it is too large Load Diff

87
kitty/gl-wrapper.h generated
View File

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

13
kitty/glfw-wrapper.c generated
View File

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

41
kitty/glfw-wrapper.h generated
View File

@@ -1,8 +1,6 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
@@ -168,6 +166,7 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
#define GLFW_KEY_PLUS 163 /* non-US #2 */
/* Function keys */
#define GLFW_KEY_ESCAPE 256
@@ -734,6 +733,10 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
* [attribute](@ref GLFW_CLIENT_API_attrib).
*/
#define GLFW_CONTEXT_CREATION_API 0x0002200B
/*! @brief Window content area scaling window
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
*/
#define GLFW_SCALE_TO_MONITOR 0x0002200C
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
#define GLFW_COCOA_FRAME_NAME 0x00023002
@@ -741,6 +744,8 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
#define GLFW_X11_CLASS_NAME 0x00024001
#define GLFW_X11_INSTANCE_NAME 0x00024002
#define GLFW_WAYLAND_APP_ID 0x00025001
/*! @} */
#define GLFW_NO_API 0
@@ -1380,6 +1385,10 @@ typedef struct GLFWgamepadstate
*/
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
typedef int (*glfwInit_func)();
glfwInit_func glfwInit_impl;
#define glfwInit glfwInit_impl
@@ -1536,6 +1545,10 @@ typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*);
glfwGetWindowContentScale_func glfwGetWindowContentScale_impl;
#define glfwGetWindowContentScale glfwGetWindowContentScale_impl
typedef double (*glfwGetDoubleClickInterval_func)(GLFWwindow*);
glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl;
#define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl
typedef float (*glfwGetWindowOpacity_func)(GLFWwindow*);
glfwGetWindowOpacity_func glfwGetWindowOpacity_impl;
#define glfwGetWindowOpacity glfwGetWindowOpacity_impl
@@ -1840,6 +1853,10 @@ typedef void* (*glfwGetCocoaWindow_func)(GLFWwindow*);
glfwGetCocoaWindow_func glfwGetCocoaWindow_impl;
#define glfwGetCocoaWindow glfwGetCocoaWindow_impl
typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*);
glfwGetNSGLContext_func glfwGetNSGLContext_impl;
#define glfwGetNSGLContext glfwGetNSGLContext_impl
typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*);
glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl;
#define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl
@@ -1848,6 +1865,10 @@ typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwind
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
#define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl
typedef GLFWcocoatogglefullscreenfun (*glfwSetCocoaToggleFullscreenIntercept_func)(GLFWwindow*, GLFWcocoatogglefullscreenfun);
glfwSetCocoaToggleFullscreenIntercept_func glfwSetCocoaToggleFullscreenIntercept_impl;
#define glfwSetCocoaToggleFullscreenIntercept glfwSetCocoaToggleFullscreenIntercept_impl
typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReopen_func)(GLFWapplicationshouldhandlereopenfun);
glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl;
#define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl
@@ -1864,16 +1885,20 @@ typedef int32_t (*glfwGetX11Window_func)(GLFWwindow*);
glfwGetX11Window_func glfwGetX11Window_impl;
#define glfwGetX11Window glfwGetX11Window_impl
typedef void (*glfwSetX11SelectionString_func)(const char*);
glfwSetX11SelectionString_func glfwSetX11SelectionString_impl;
#define glfwSetX11SelectionString glfwSetX11SelectionString_impl
typedef void (*glfwSetPrimarySelectionString_func)(GLFWwindow*, const char*);
glfwSetPrimarySelectionString_func glfwSetPrimarySelectionString_impl;
#define glfwSetPrimarySelectionString glfwSetPrimarySelectionString_impl
typedef const char* (*glfwGetX11SelectionString_func)();
glfwGetX11SelectionString_func glfwGetX11SelectionString_impl;
#define glfwGetX11SelectionString glfwGetX11SelectionString_impl
typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*);
glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl;
#define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl
typedef int (*glfwGetXKBScancode_func)(const char*, int);
glfwGetXKBScancode_func glfwGetXKBScancode_impl;
#define glfwGetXKBScancode glfwGetXKBScancode_impl
typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc);
glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl;
#define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl
const char* load_glfw(const char* path);

View File

@@ -10,10 +10,12 @@
#include "glfw-wrapper.h"
extern bool cocoa_make_window_resizable(void *w, bool);
extern void cocoa_focus_window(void *w);
extern bool cocoa_toggle_fullscreen(void *w, bool);
extern void cocoa_create_global_menu(void);
extern void cocoa_set_hide_from_tasks(void);
extern void cocoa_set_titlebar_color(void *w, color_type color);
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
#endif
@@ -25,9 +27,13 @@ static void set_os_window_dpi(OSWindow *w);
void
update_os_window_viewport(OSWindow *window, bool notify_boss) {
int w, h;
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
int w, h, fw, fh;
glfwGetFramebufferSize(window->handle, &fw, &fh);
glfwGetWindowSize(window->handle, &w, &h);
if (fw == window->viewport_width && fh == window->viewport_height && w == window->window_width && h == window->window_height) {
return; // no change, ignore
}
window->viewport_width = fw; window->viewport_height = fh;
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
window->viewport_x_ratio = (double)window->viewport_width / (double)w;
window->viewport_y_ratio = (double)window->viewport_height / (double)h;
@@ -43,6 +49,11 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
window->window_height = MAX(h, 100);
if (notify_boss) {
call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
if (dpi_changed && global_state.is_wayland) {
// Fake resize event needed on weston to ensure surface is
// positioned correctly after DPI change
glfwSetWindowSize(window->handle, window->window_width, window->window_height);
}
}
}
@@ -106,6 +117,17 @@ framebuffer_size_callback(GLFWwindow *w, int width, int height) {
window->has_pending_resizes = true; global_state.has_pending_resizes = true;
window->last_resize_event_at = monotonic();
unjam_event_loop();
#ifdef __APPLE__
// Cocoa starts a sub-loop inside wait events which means main_loop
// stays stuck and no rendering happens. This causes the window to be
// blank. This is particularly bad for semi-transparent windows since
// they are rendered as invisible, so for that case we manually render.
if (global_state.callback_os_window->is_semi_transparent) {
make_os_window_context_current(global_state.callback_os_window);
blank_os_window(global_state.callback_os_window);
swap_window_buffers(global_state.callback_os_window);
}
#endif
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
global_state.callback_os_window = NULL;
}
@@ -157,9 +179,9 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
show_mouse_cursor(w);
double now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now;
if (button >= 0 && (unsigned int)button < sizeof(global_state.callback_os_window->mouse_button_pressed)/sizeof(global_state.callback_os_window->mouse_button_pressed[0])) {
if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) {
global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false;
if (is_window_ready_for_callbacks()) mouse_event(button, mods);
if (is_window_ready_for_callbacks()) mouse_event(button, mods, action);
}
global_state.callback_os_window = NULL;
}
@@ -173,7 +195,7 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
global_state.callback_os_window->cursor_blink_zero_time = now;
global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio;
global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio;
if (is_window_ready_for_callbacks()) mouse_event(-1, 0);
if (is_window_ready_for_callbacks()) mouse_event(-1, 0, -1);
global_state.callback_os_window = NULL;
}
@@ -278,17 +300,17 @@ set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
Py_RETURN_NONE;
}
static GLFWwindow *current_os_window_ctx = NULL;
void
make_os_window_context_current(OSWindow *w) {
if (w->handle != current_os_window_ctx) {
current_os_window_ctx = w->handle;
GLFWwindow *current_context = glfwGetCurrentContext();
if (w->handle != current_context) {
glfwMakeContextCurrent(w->handle);
}
}
#ifndef __APPLE__
static GLFWmonitor*
current_monitor(GLFWwindow *window) {
// Find the monitor that has the maximum overlap with this window
@@ -323,14 +345,15 @@ current_monitor(GLFWwindow *window) {
return bestmonitor;
}
#endif
static inline void
get_window_dpi(GLFWwindow *w, double *x, double *y) {
GLFWmonitor *monitor = NULL;
if (w) monitor = current_monitor(w);
if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
float xscale = 1, yscale = 1;
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
if (w) glfwGetWindowContentScale(w, &xscale, &yscale);
else glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale);
if (!xscale) xscale = 1.0;
if (!yscale) yscale = 1.0;
#ifdef __APPLE__
double factor = 72.0;
#else
@@ -345,11 +368,56 @@ set_os_window_dpi(OSWindow *w) {
get_window_dpi(w->handle, &w->logical_dpi_x, &w->logical_dpi_y);
}
static bool
toggle_fullscreen_for_os_window(OSWindow *w) {
int width, height, x, y;
glfwGetWindowSize(w->handle, &width, &height);
glfwGetWindowPos(w->handle, &x, &y);
#ifdef __APPLE__
if (OPT(macos_traditional_fullscreen)) {
if (cocoa_toggle_fullscreen(glfwGetCocoaWindow(w->handle), true)) {
w->before_fullscreen.is_set = true;
w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y;
return true;
}
if (w->before_fullscreen.is_set) {
glfwSetWindowSize(w->handle, w->before_fullscreen.w, w->before_fullscreen.h);
glfwSetWindowPos(w->handle, w->before_fullscreen.x, w->before_fullscreen.y);
}
return false;
} else {
return cocoa_toggle_fullscreen(glfwGetCocoaWindow(w->handle), false);
}
#else
GLFWmonitor *monitor;
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
// make fullscreen
monitor = current_monitor(w->handle);
if (monitor == NULL) { PyErr_Print(); return false; }
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
w->before_fullscreen.is_set = true;
w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y;
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
return true;
} else {
// make windowed
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
return false;
}
#endif
}
#ifdef __APPLE__
static int
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED) {
return ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) ? 1 : 0;
}
static GLFWwindow *application_quit_canary = NULL;
static int
@@ -360,6 +428,14 @@ on_application_reopen(int has_visible_windows) {
unjam_event_loop();
return false;
}
static int
intercept_cocoa_fullscreen(GLFWwindow *w) {
if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return 0;
toggle_fullscreen_for_os_window(global_state.callback_os_window);
global_state.callback_os_window = NULL;
return 1;
}
#endif
void
@@ -399,9 +475,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
glfwWindowHint(GLFW_DEPTH_BITS, 0);
glfwWindowHint(GLFW_STENCIL_BITS, 0);
#ifdef __APPLE__
if (OPT(macos_hide_titlebar)) glfwWindowHint(GLFW_DECORATED, false);
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);
glfwSetApplicationShouldHandleReopen(on_application_reopen);
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
#endif
}
@@ -409,9 +485,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
#ifndef __APPLE__
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
if (OPT(x11_hide_window_decorations)) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
}
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
#endif
if (global_state.num_os_windows >= MAX_CHILDREN) {
@@ -476,6 +551,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
}}
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
#undef CC
if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window);
is_first_window = false;
}
OSWindow *w = add_os_window();
@@ -488,12 +564,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
}
w->logical_dpi_x = dpi_x; w->logical_dpi_y = dpi_y;
w->fonts_data = fonts_data;
current_os_window_ctx = glfw_window;
w->shown_once = true;
push_focus_history(w);
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
#ifdef __APPLE__
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
#endif
send_prerendered_sprites_for_window(w);
if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo);
@@ -535,7 +611,6 @@ destroy_os_window(OSWindow *w) {
show_mouse_cursor(w->handle);
glfwSetCursor(w->handle, NULL);
glfwDestroyWindow(w->handle);
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
}
w->handle = NULL;
#ifdef __APPLE__
@@ -575,6 +650,12 @@ bool
application_quit_requested() {
return !application_quit_canary || glfwWindowShouldClose(application_quit_canary);
}
void
request_application_quit() {
if (application_quit_canary)
glfwSetWindowShouldClose(application_quit_canary, true);
}
#endif
// Global functions {{{
@@ -674,6 +755,24 @@ get_clipboard_string(PYNOARG) {
return Py_BuildValue("s", "");
}
void
ring_audio_bell(OSWindow *w) {
static double last_bell_at = -1;
double now = monotonic();
if (now - last_bell_at <= 0.1) return;
last_bell_at = now;
if (w->handle) {
glfwWindowBell(w->handle);
}
}
static PyObject*
ring_bell(PYNOARG) {
OSWindow *w = current_os_window();
ring_audio_bell(w);
Py_RETURN_NONE;
}
static PyObject*
get_content_scale_for_window(PYNOARG) {
OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows;
@@ -693,40 +792,22 @@ set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
static PyObject*
toggle_fullscreen(PYNOARG) {
GLFWmonitor *monitor;
OSWindow *w = current_os_window();
if (!w) Py_RETURN_NONE;
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
// make fullscreen
monitor = current_monitor(w->handle);
if (monitor == NULL) return NULL;
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
w->before_fullscreen.is_set = true;
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
Py_RETURN_TRUE;
} else {
// make windowed
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
#ifdef __APPLE__
if (glfwGetCocoaWindow) cocoa_make_window_resizable(glfwGetCocoaWindow(w->handle), OPT(macos_window_resizable));
#endif
Py_RETURN_FALSE;
}
if (toggle_fullscreen_for_os_window(w)) { Py_RETURN_TRUE; }
Py_RETURN_FALSE;
}
void
ring_audio_bell(OSWindow *w) {
static double last_bell_at = -1;
double now = monotonic();
if (now - last_bell_at <= 0.1) return;
last_bell_at = now;
if (w->handle) {
glfwWindowBell(w->handle);
}
static PyObject*
change_os_window_state(PyObject *self UNUSED, PyObject *args) {
char *state;
if (!PyArg_ParseTuple(args, "s", &state)) return NULL;
OSWindow *w = current_os_window();
if (!w || !w->handle) Py_RETURN_NONE;
if (strcmp(state, "maximized") == 0) glfwMaximizeWindow(w->handle);
else if (strcmp(state, "minimized") == 0) glfwIconifyWindow(w->handle);
else { PyErr_SetString(PyExc_ValueError, "Unknown window state"); return NULL; }
Py_RETURN_NONE;
}
void
@@ -749,9 +830,15 @@ hide_mouse(OSWindow *w) {
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}
bool
is_mouse_hidden(OSWindow *w) {
return w->handle && glfwGetInputMode(w->handle, GLFW_CURSOR) == GLFW_CURSOR_HIDDEN;
}
void
swap_window_buffers(OSWindow *w) {
glfwSwapBuffers(w->handle);
swap_window_buffers(OSWindow *os_window) {
glfwSwapBuffers(os_window->handle);
}
void
@@ -821,9 +908,10 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
static PyObject*
get_primary_selection(PYNOARG) {
if (glfwGetX11SelectionString) {
return Py_BuildValue("y", glfwGetX11SelectionString());
} else log_error("Failed to load glfwGetX11SelectionString");
if (glfwGetPrimarySelectionString) {
OSWindow *w = current_os_window();
if (w) return Py_BuildValue("y", glfwGetPrimarySelectionString(w->handle));
} else log_error("Failed to load glfwGetPrimarySelectionString");
Py_RETURN_NONE;
}
@@ -831,8 +919,11 @@ static PyObject*
set_primary_selection(PyObject UNUSED *self, PyObject *args) {
char *text;
if (!PyArg_ParseTuple(args, "s", &text)) return NULL;
if (glfwSetX11SelectionString) glfwSetX11SelectionString(text);
else log_error("Failed to load glfwSetX11SelectionString");
if (glfwSetPrimarySelectionString) {
OSWindow *w = current_os_window();
if (w) glfwSetPrimarySelectionString(w->handle, text);
}
else log_error("Failed to load glfwSetPrimarySelectionString");
Py_RETURN_NONE;
}
@@ -915,6 +1006,23 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
}
#endif
void
wayland_frame_request_callback(id_type os_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
if (global_state.os_windows[i].id == os_window_id) {
global_state.os_windows[i].wayland_render_state = RENDER_FRAME_READY;
break;
}
}
}
void
wayland_request_frame_render(OSWindow *w) {
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
w->wayland_render_state = RENDER_FRAME_REQUESTED;
}
// Boilerplate {{{
static PyMethodDef module_methods[] = {
@@ -924,8 +1032,10 @@ static PyMethodDef module_methods[] = {
METHODB(set_default_window_icon, METH_VARARGS),
METHODB(get_clipboard_string, METH_NOARGS),
METHODB(get_content_scale_for_window, METH_NOARGS),
METHODB(ring_bell, METH_NOARGS),
METHODB(set_clipboard_string, METH_VARARGS),
METHODB(toggle_fullscreen, METH_NOARGS),
METHODB(change_os_window_state, METH_VARARGS),
METHODB(glfw_window_hint, METH_VARARGS),
METHODB(os_window_should_close, METH_VARARGS),
METHODB(os_window_swap_buffers, METH_VARARGS),
@@ -1020,6 +1130,7 @@ init_glfw(PyObject *m) {
ADDC(GLFW_KEY_GRAVE_ACCENT);
ADDC(GLFW_KEY_WORLD_1);
ADDC(GLFW_KEY_WORLD_2);
ADDC(GLFW_KEY_PLUS);
// --- Function keys -----------------------------------------------------------
ADDC(GLFW_KEY_ESCAPE);

View File

@@ -368,7 +368,10 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
close(fd);
if (tt == 't') unlink(fname);
if (tt == 't') {
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
else unlink(fname);
}
else if (tt == 's') shm_unlink(fname);
break;
default:

View File

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

47
kitty/key_encoding.py generated
View File

@@ -127,7 +127,8 @@ ENCODING = {
'WORLD 2': 'x',
'X': 'p',
'Y': 'q',
'Z': 'r'
'Z': 'r',
'PLUS': 'Bi'
}
KEY_MAP = {
32: 'A',
@@ -180,6 +181,7 @@ KEY_MAP = {
96: 'v',
161: 'w',
162: 'x',
163: 'Bi',
256: 'y',
257: 'z',
258: '0',
@@ -250,10 +252,8 @@ KEY_MAP = {
346: 'Bg',
347: 'Bh'
}
# END_ENCODING }}}
text_keys = string.ascii_uppercase + string.ascii_lowercase + string.digits + '`~!@#$%^&*()_-+=[{]}\\|<,>./?;:\'" '
@@ -324,28 +324,35 @@ mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
key_rmap = {}
g = globals()
config_key_map = {}
config_mod_map = {'SHIFT': SHIFT, 'ALT': ALT, 'OPTION': ALT, '': ALT, '': SUPER, 'CMD': SUPER, 'SUPER': SUPER, 'CTRL': CTRL, 'CONTROL': CTRL}
config_mod_map = {
'SHIFT': SHIFT,
'ALT': ALT,
'OPTION': ALT,
'': ALT,
'': SUPER,
'CMD': SUPER,
'SUPER': SUPER,
'CTRL': CTRL,
'CONTROL': CTRL
}
for key_name, enc in ENCODING.items():
key_name = key_name.replace(' ', '_')
g[key_name] = config_key_map[key_name] = key_name
key_rmap[enc] = key_name
config_key_map.update({
'`': g['GRAVE_ACCENT'],
'-': g['MINUS'],
'=': g['EQUAL'],
'[': g['LEFT_BRACKET'],
']': g['RIGHT_BRACKET'],
'\\': g['BACKSLASH'],
';': g['SEMICOLON'],
"'": g['APOSTROPHE'],
',': g['COMMA'],
'.': g['PERIOD'],
'/': g['SLASH'],
'ESC': g['ESCAPE'],
'`': g['GRAVE_ACCENT'],
'-': g['MINUS'],
'=': g['EQUAL'],
'[': g['LEFT_BRACKET'],
']': g['RIGHT_BRACKET'],
'\\': g['BACKSLASH'],
';': g['SEMICOLON'],
"'": g['APOSTROPHE'],
',': g['COMMA'],
'.': g['PERIOD'],
'/': g['SLASH'],
'ESC': g['ESCAPE'],
'+': g['PLUS'],
})
enter_key = KeyEvent(PRESS, 0, g['ENTER'])

View File

@@ -103,13 +103,9 @@ check_if_special(int key, int mods, int scancode) {
qkey = SPECIAL_INDEX(qkey);
special = needs_special_handling[qkey];
}
#ifdef __APPLE__
(void)scancode;
#else
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
}
#endif
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
}
return special;
}
@@ -127,7 +123,7 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
void
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
Window *w = active_window();
debug("on_key_input: glfw key: %d native_code: %d action: %s mods: 0x%x text: '%s' state: %d ",
debug("on_key_input: glfw key: %d native_code: 0x%x action: %s mods: 0x%x text: '%s' state: %d ",
key, scancode,
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
mods, text, state);
@@ -222,15 +218,13 @@ PYWRAP1(key_for_native_key_name) {
const char *name;
int case_sensitive = 0;
PA("s|p", &name, case_sensitive);
#ifdef __APPLE__
Py_RETURN_NONE;
#else
#ifndef __APPLE__
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
int scancode = glfwGetXKBScancode(name, case_sensitive);
if (scancode) return Py_BuildValue("i", scancode);
}
Py_RETURN_NONE;
#endif
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {

12043
kitty/keys.h generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,23 +6,14 @@ import string
from . import fast_data_types as defines
from .key_encoding import KEY_MAP
from .terminfo import key_as_bytes
from .terminfo import key_as_bytes, modify_key_bytes
from .utils import base64_encode
from .constants import is_macos
def modify_key_bytes(keybytes, amt):
ans = bytearray(keybytes)
amt = str(amt).encode('ascii')
if ans[-1] == ord('~'):
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
if ans[1] == ord('O'):
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
raise ValueError('Unknown key type')
def modify_complex_key(name, amt):
return modify_key_bytes(key_as_bytes(name), amt)
if not isinstance(name, bytes):
name = key_as_bytes(name)
return modify_key_bytes(name, amt)
control_codes = {
@@ -40,6 +31,7 @@ shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt')
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL
ctrl_alt_shift_mod = ctrl_alt_mod | defines.GLFW_MOD_SHIFT
SHIFTED_KEYS = {
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
@@ -55,6 +47,32 @@ SHIFTED_KEYS = {
control_alt_codes = {
defines.GLFW_KEY_SPACE: b'\x1b\0',
}
control_alt_shift_codes = {}
ASCII_C0_SHIFTED = {
# ^@
'2': b'\x00',
# ^^
'6': b'\x1e',
# ^_
'MINUS': b'\x1f',
# ^?
'SLASH': b'\x7f',
}
control_shift_keys = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
def create_modifier_variants(keycode, terminfo_name_or_bytes, add_shifted_key=True):
kn = terminfo_name_or_bytes
smkx_key_map[keycode] = kn if isinstance(kn, bytes) else key_as_bytes(kn)
if add_shifted_key:
SHIFTED_KEYS[keycode] = modify_complex_key(kn, 2)
alt_codes[keycode] = modify_complex_key(kn, 3)
shift_alt_codes[keycode] = modify_complex_key(kn, 4)
control_codes[keycode] = modify_complex_key(kn, 5)
control_shift_keys[keycode] = modify_complex_key(kn, 6)
control_alt_codes[keycode] = modify_complex_key(kn, 7)
control_alt_shift_codes[keycode] = modify_complex_key(kn, 8)
for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1',
@@ -68,24 +86,16 @@ for kf, kn in {
defines.GLFW_KEY_PAGE_UP: 'kpp',
defines.GLFW_KEY_PAGE_DOWN: 'knp',
}.items():
smkx_key_map[kf] = key_as_bytes(kn)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
control_alt_codes[kf] = modify_complex_key(kn, 7)
create_modifier_variants(kf, kn, add_shifted_key=False)
for f in range(1, 13):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
SHIFTED_KEYS[kf] = modify_complex_key(kn, 2)
alt_codes[kf] = modify_complex_key(kn, 3)
shift_alt_codes[kf] = modify_complex_key(kn, 4)
control_codes[kf] = modify_complex_key(kn, 5)
control_alt_codes[kf] = modify_complex_key(kn, 7)
create_modifier_variants(kf, kn)
for f in range(13, 26):
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
kn = 'kf{}'.format(f)
smkx_key_map[kf] = key_as_bytes(kn)
create_modifier_variants(defines.GLFW_KEY_MENU, b'\x1b[29~')
f = {k: k for k in '0123456789'}
f.update({
'COMMA': ',',
@@ -216,17 +226,6 @@ SHIFTED_PRINTABLE.update(pmap(
"{|}~"
))
ASCII_C0_SHIFTED = {
# ^@
'2': b'\x00',
# ^^
'6': b'\x1e',
# ^_
'MINUS': b'\x1f',
# ^?
'SLASH': b'\x7f',
}
CTRL_SHIFT_KEYS = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase}
all_control_alt_keys = set(CTRL_ALT_KEYS) | set(control_alt_codes)
@@ -238,8 +237,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
# Map Ctrl-key to ascii control code
data.extend(control_codes[key])
elif mods == ctrl_shift_mod and key in CTRL_SHIFT_KEYS:
data.extend(CTRL_SHIFT_KEYS[key])
elif mods == ctrl_shift_mod and key in control_shift_keys:
data.extend(control_shift_keys[key])
elif mods in alt_mods:
if key in alt_codes:
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
@@ -252,6 +251,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
data.append(0x1b), data.extend(control_codes[key])
else:
data.extend(control_alt_codes[key])
elif mods == ctrl_alt_shift_mod and key in control_alt_shift_codes:
data.extend(control_alt_shift_codes[key])
else:
key_map = cursor_key_mode_map[smkx]
x = key_map.get(key)
@@ -276,7 +277,7 @@ def interpret_key_event(key, scancode, mods, window, action):
def get_shortcut(keymap, mods, key, scancode):
mods &= 0b1111
ans = keymap.get((mods, False, key))
if ans is None and not is_macos:
if ans is None:
ans = keymap.get((mods, True, scancode))
return ans

View File

@@ -228,15 +228,34 @@ class Layout: # {{{
active_window_idx = idx_for_id(windows[active_window_idx].id, all_windows)
return self.set_active_window(all_windows, active_window_idx)
def move_window(self, all_windows, active_window_idx, delta=1):
def neighbors(self, all_windows, active_window_idx):
w = all_windows[active_window_idx]
windows = process_overlaid_windows(all_windows)[1]
if len(windows) < 2 or abs(delta) == 0:
ans = self.neighbors_for_window(w, windows)
for values in ans.values():
values[:] = [idx_for_id(w.id, all_windows) for w in values]
return ans
def move_window(self, all_windows, active_window_idx, delta=1):
# delta can be either a number or a string such as 'left', 'top', etc
# for neighborhood moves
w = all_windows[active_window_idx]
windows = process_overlaid_windows(all_windows)[1]
if len(windows) < 2 or not delta:
return active_window_idx
idx = idx_for_id(w.id, windows)
if idx is None:
idx = idx_for_id(w.overlay_window_id, windows)
nidx = (idx + len(windows) + delta) % len(windows)
if isinstance(delta, int):
nidx = (idx + len(windows) + delta) % len(windows)
else:
delta = delta.lower()
delta = {'up': 'top', 'down': 'bottom'}.get(delta, delta)
neighbors = self.neighbors_for_window(w, windows)
if not neighbors.get(delta):
return active_window_idx
nidx = idx_for_id(neighbors[delta][0].id, windows)
nw = windows[nidx]
nidx = idx_for_id(nw.id, all_windows)
idx = active_window_idx
@@ -368,6 +387,9 @@ class Layout: # {{{
def do_layout(self, windows, active_window_idx):
raise NotImplementedError()
def neighbors_for_window(self, window, windows):
return {'left': [], 'right': [], 'top': [], 'bottom': []}
def resolve_borders(self, windows, active_window):
if draw_minimal_borders:
needs_borders_map = {w.id: (w is active_window or w.needs_attention) for w in windows}
@@ -469,6 +491,13 @@ class Tall(Layout): # {{{
# left bottom blank rect
self.bottom_blank_rect(windows[0])
def neighbors_for_window(self, window, windows):
if window is windows[0]:
return {'left': [], 'right': windows[1:], 'top': [], 'bottom': []}
idx = windows.index(window)
return {'left': [windows[0]], 'right': [], 'top': [] if idx <= 1 else [windows[idx-1]],
'bottom': [] if window is windows[-1] else [windows[idx+1]]}
def minimal_borders(self, windows, active_window, needs_borders_map):
last_i = len(windows) - 1
for i, w in enumerate(windows):
@@ -520,6 +549,14 @@ class Fat(Tall): # {{{
self.bottom_blank_rect(windows[0])
# bottom blank rect
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
def neighbors_for_window(self, window, windows):
if window is windows[0]:
return {'left': [], 'bottom': windows[1:], 'top': [], 'right': []}
idx = windows.index(window)
return {'top': [windows[0]], 'bottom': [], 'left': [] if idx <= 1 else [windows[idx-1]],
'right': [] if window is windows[-1] else [windows[idx+1]]}
# }}}
@@ -680,6 +717,56 @@ class Grid(Layout): # {{{
(right_neighbor_id is not None and not needs_borders_map[right_neighbor_id]) or next_col_has_different_count,
bottom_neighbor_id is not None and not needs_borders_map[bottom_neighbor_id]
)
def neighbors_for_window(self, window, windows):
n = len(windows)
if n < 4:
return Tall.neighbors_for_window(self, window, windows)
try:
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
except Exception:
n = -1
if n != len(windows):
# Something bad happened
return Layout.neighbors_for_window(self, window, windows)
blank_row = [None for i in range(ncols)]
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
wi = iter(windows)
pos_map = {}
col_counts = []
id_map = {}
for col in range(ncols):
rows = special_rows if col == special_col else nrows
for row in range(rows):
w = next(wi)
matrix[row][col] = wid = w.id
pos_map[wid] = row, col
id_map[wid] = w
col_counts.append(rows)
row, col = pos_map[window.id]
def neighbors(row, col):
try:
ans = matrix[row][col]
except IndexError:
ans = None
return [] if ans is None else [id_map[ans]]
def side(row, col, delta):
neighbor_col = col + delta
if col_counts[neighbor_col] == col_counts[col]:
return neighbors(row, neighbor_col)
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
return {
'top': neighbors(row-1, col) if row else [],
'bottom': neighbors(row + 1, col),
'left': side(row, col, -1) if col else [],
'right': side(row, col, 1) if col < ncols - 1 else [],
}
# }}}
@@ -740,6 +827,15 @@ class Vertical(Layout): # {{{
yield no_borders
else:
yield self.only_between_border
def neighbors_for_window(self, window, windows):
idx = windows.index(window)
before = [] if window is windows[0] else [windows[idx-1]]
after = [] if window is windows[-1] else [windows[idx+1]]
if self.main_is_horizontal:
return {'left': before, 'right': after, 'top': [], 'bottom': []}
return {'top': before, 'bottom': after, 'left': [], 'right': []}
# }}}
@@ -768,6 +864,7 @@ class Horizontal(Vertical): # {{{
self.simple_blank_rects(windows[0], windows[-1])
# bottom blank rect
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
# }}}

View File

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

View File

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

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