Compare commits

..

733 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
Kovid Goyal
4f05024550 version 0.12.0 2018-09-01 09:16:29 +05:30
Kovid Goyal
4d2d9d5574 Do not change wheel event reporting when not using high resolution scroll events 2018-08-31 15:28:48 +05:30
Kovid Goyal
14f44e79f7 Remove debug printf 2018-08-31 07:56:21 +05:30
Kovid Goyal
ab960ea12d Linux: Fix slow startup on some systems caused by GLFW searching for joysticks. Since kitty does not use joysticks, disable joystick support.
Fixes #830
2018-08-31 07:51:19 +05:30
Kovid Goyal
d276317e3a Remote control: Allow changing the current window layout with a new goto-layout command
Fixes #845
2018-08-30 10:23:20 +05:30
Kovid Goyal
8b3b0064a9 ... 2018-08-29 18:57:51 +05:30
Kovid Goyal
66b890bfd3 Merge branch 'alternate_screen_fast_scrolling' of https://github.com/Luflosi/kitty 2018-08-29 17:56:29 +05:30
Luflosi
2454ecd586 Allow for faster scrolling in alternate screen mode 2018-08-29 14:04:12 +02:00
Kovid Goyal
b891f4f874 Better error message for attempts to use CSI codes to resize the window
nvim does this for some odd reason
2018-08-29 10:09:06 +05:30
Kovid Goyal
92d06b3088 title stack should be LIFO not FIFO 2018-08-29 10:00:15 +05:30
Kovid Goyal
3067103b18 Implement window title stack
Used by new versions of vim
2018-08-29 09:56:52 +05:30
Kovid Goyal
0e248b3faa Write escape codes to children atomically
Ensure a partial escape code is never written
2018-08-29 09:01:52 +05:30
Kovid Goyal
d1a76bbcd9 Merge branch 'typo' of https://github.com/Luflosi/kitty 2018-08-29 07:30:01 +05:30
Luflosi
1d91edfb00 dont -> don't 2018-08-29 03:18:25 +02:00
Kovid Goyal
cf7ecd5e66 Do not import fast_data_types at the top level of utility modules 2018-08-28 10:27:06 +05:30
Kovid Goyal
63a2ecb89c Merge branch 'writePlist_deprecation' of https://github.com/Luflosi/kitty 2018-08-28 04:54:17 +05:30
Luflosi
8e6b58b1db Fix deprecation warning on macOS 2018-08-27 23:54:45 +02:00
Kovid Goyal
0f594ef950 Ensure multiplication is not unsigned 2018-08-27 15:47:32 +05:30
Kovid Goyal
0c1b76c17a Fix completion of kitten names not working 2018-08-27 13:23:17 +05:30
Kovid Goyal
1c11ba8dbd ... 2018-08-27 13:11:30 +05:30
Kovid Goyal
529d5b2226 Dont store the options spec at the top level of the module 2018-08-27 13:06:17 +05:30
Kovid Goyal
9fe16867c6 Do not import config_data at the top-level of the cli module
Importing it is slow and it is not needed in many cases, for instance
when used from kittens
2018-08-27 12:59:10 +05:30
Kovid Goyal
1d275c6b35 Only import the ast module on demand 2018-08-27 12:43:20 +05:30
Kovid Goyal
d19b29ced0 Fix incorrect calculation of pending_scroll_pixels 2018-08-27 06:51:25 +05:30
Kovid Goyal
bbefcb2cd2 Fix spaces after file/dir names and config directive names in bash completion
Since bash has no concept of completion groups with customizable
behavior, unlike zsh, we tell it to never add trailing spaces and
add the spaces ourselves.
2018-08-26 18:22:40 +05:30
Kovid Goyal
c864cba4a2 Completion of image names for the icat kitten 2018-08-26 11:49:56 +05:30
Kovid Goyal
42546f4371 Implement completion for the kitty command
Implemented for bash and zsh. Completion scripts are just thin wrappers
around kitty + complete which knows how to complete kitty command lines.
Should make it easy to add completion for other shells in the future.
2018-08-26 10:39:51 +05:30
Kovid Goyal
ff09bdcbf1 Completion for kittens 2018-08-26 09:42:26 +05:30
Kovid Goyal
f36a9fffa3 Completion for bash 2018-08-26 09:16:02 +05:30
Kovid Goyal
933cced76c Implement completion for remote control commands 2018-08-25 14:51:59 +05:30
Kovid Goyal
c51e7d3bde More work on completions 2018-08-25 11:27:22 +05:30
Kovid Goyal
d3b31224cd More work on completions 2018-08-24 12:15:27 +05:30
Kovid Goyal
ec9bb2302c ... 2018-08-24 11:03:30 +05:30
Kovid Goyal
009ef54de7 Implement high precision scrolling with the trackpad on platforms such as macOS and Wayland that implement it.
Fixes #819. Note that I have no idea whether the code works well on
retina screens, might have to multiple the pixel count by the scale
factor on those screens?
2018-08-24 11:00:58 +05:30
Kovid Goyal
0e0e25f986 Avoid double filter for completions in zsh 2018-08-24 08:13:59 +05:30
Kovid Goyal
8b02b89bd6 Add an is_focused field to the output of kitty @ ls 2018-08-23 16:07:26 +05:30
Kovid Goyal
8ba3c4ba50 Group completion matches 2018-08-23 15:34:54 +05:30
Kovid Goyal
16764c79b2 Start work on completion for the kitty command 2018-08-23 15:10:13 +05:30
Kovid Goyal
96c444d041 Workaround for bug in glfw causing crash when hotplugging monitors
Fixes #812
2018-08-21 12:52:16 +05:30
Kovid Goyal
ec1c72e9a3 Fix compiler warning 2018-08-20 10:55:49 +05:30
Kovid Goyal
f9b1f6be5c Fix #809 2018-08-20 10:24:09 +05:30
Kovid Goyal
9201d27875 Document kitty's extension to the OSC 52 protocol 2018-08-13 07:27:08 +05:30
Kovid Goyal
ecbe79e04f Update glfw from upstream 2018-08-12 21:36:19 +05:30
Kovid Goyal
6e34a7559b Fix one-pixel line appearing at window edges at some window sizes when displaying images with background opacity enabled
Fixes #741
2018-08-12 07:22:25 +05:30
Kovid Goyal
ea8b17565d Make ctrl+tab next_tab by default on macOS 2018-08-11 07:55:43 +05:30
Kovid Goyal
a143faaf05 macOS: Fix control+tab key combination not working
Fixes #801
2018-08-11 07:18:24 +05:30
Kovid Goyal
acdb56ad5d ... 2018-08-11 06:52:05 +05:30
Kovid Goyal
1f90238b18 If malloc fails remove timer from queue 2018-08-09 08:52:22 +05:30
Kovid Goyal
89b636376e Possible fix for #784 2018-08-09 08:26:25 +05:30
Kovid Goyal
59f39ae3ab Fix detection of xprop on systems with older xprop
Fixes #798
2018-08-08 15:31:15 +05:30
Kovid Goyal
a92b20f85a Only do manual focus follows mouse on macOS 2018-08-07 07:56:56 +05:30
Kovid Goyal
e71c432b7c ... 2018-08-06 20:13:16 +05:30
Kovid Goyal
b6a5d09d4f Allow enabling remote control in only some kitty windows 2018-08-06 20:04:12 +05:30
Kovid Goyal
9235d2b8e4 ... 2018-08-06 18:38:23 +05:30
Kovid Goyal
605847fd75 Add --no-response to kitty @ focus-window 2018-08-06 18:17:45 +05:30
Kovid Goyal
6222273adc ... 2018-08-06 12:27:20 +05:30
Kovid Goyal
6a759ea7d5 ... 2018-08-06 12:26:57 +05:30
Kovid Goyal
d8a7114dba macOS: Disable the custom mouse cursor. Using a custom cursor fails on dual GPU machines.
I give up, Apple users will just have to live with the limitations of their choice of OS.
See #794
2018-08-06 12:22:01 +05:30
Kovid Goyal
ab818ed89f IBUS: Fix update of pre-edit text not working correctly
Fixes #793
2018-08-06 11:30:33 +05:30
Kovid Goyal
1bb6540b64 Better debug output for pre-edit text 2018-08-06 11:08:12 +05:30
Kovid Goyal
ddd8209c45 Fix crash when pressing enter while using IBUS 2018-08-06 10:35:46 +05:30
Kovid Goyal
1dc3dfe58f Distinguish shifted Pgup and PgDn keys in normal and application mode
Fixes #791
2018-08-05 17:17:15 +05:30
Kovid Goyal
634d262b64 When using the --listen-on option have kitty @ automatically connect to the socket even when --to is not specified
The listen on value is set in the env of the child processes, so kitty @
can read it when run from inside kitty.
2018-08-04 22:06:47 +05:30
Kovid Goyal
d964146f8c Use ctermid() instead of hardcoding /dev/tty 2018-08-04 20:58:01 +05:30
Kovid Goyal
05b817f5f5 Micro-optimization 2018-08-04 20:34:12 +05:30
Kovid Goyal
eb2ec1833c Use a pipe rather than a signal to wait for terminal ready
Simpler code, more robust since there is no longer a race between the
installation of the signal handler and the dispatch of the signal
2018-08-04 20:24:06 +05:30
Kovid Goyal
094ddd9333 Round-trip the zwj unicode character
Rendering of sequences containing zwj is still not implemented, since it
can cause the collapse of an unbounded number of characters into a
single cell. However, kitty at least preserves the zwj by storing it as
a combining character.
2018-08-04 18:29:45 +05:30
Kovid Goyal
e05d48a574 Also prevent long running diff workers from causing a hang on exit 2018-08-04 17:51:33 +05:30
Kovid Goyal
927bfcd37a Use only a single diff executor 2018-08-04 17:46:05 +05:30
Kovid Goyal
42cabace47 Better fix for long running highlight workers 2018-08-04 17:41:37 +05:30
Kovid Goyal
9a1f14d05c diff kitten: Do not highlight extremely large files
python's asyncio module has no way to tell a process executor object to
kill its worker processes, so highlighting very large files cause the
kitten to hang around o exit till the highlighting is done
2018-08-04 17:30:45 +05:30
Kovid Goyal
4189aa8f14 Add a --no-response argument to kitty @ new-window
See #786
2018-08-04 15:57:43 +05:30
Kovid Goyal
5fa5bf8142 Add a test for unicode regional indicator widths 2018-08-04 10:56:35 +05:30
Kovid Goyal
000c1cf306 Implement support for emoji skin tone modifiers
Fixes #787
2018-08-04 10:06:25 +05:30
Kovid Goyal
28447d3389 Only launch child processes after window size is available in the termios structure
The delay is minimal ~ 1ms and there are apparently some applications
such as kakoune that rely on the window size being available on startup.

Since kitty always sizes windows with some size on startup, we can
afford to wait.

Fixes #786
2018-08-04 09:19:11 +05:30
Kovid Goyal
ab7f3310c4 Dont crash if XRRGetCrtcInfo() returns NULL 2018-08-03 16:17:25 +05:30
Kovid Goyal
e5a720c6fa Fix detection of URLs in HTML source code (URLs inside quotes)
Fixes #785
2018-08-03 12:28:23 +05:30
Kovid Goyal
8d20dbe81d Use the ! modifier when resizing images via ImageMagick
Should hopefully make ImageMagick respect the specified width and height
exactly.
2018-08-01 12:03:36 +05:30
Kovid Goyal
1fc666d82c Add an Info.plist key to enable support for dark mode in Mojave
Mojave uses tis key to determine how to draw the titlebar.
See https://developer.apple.com/documentation/appkit/nsappearancecustomization/choosing_a_specific_appearance_for_your_app?language=objc
2018-08-01 03:53:17 +05:30
Kovid Goyal
1b2a1a1e77 Add a check that tic works as expected
Apparently on FreeBSD it exits with no errors but fails to write the
compiled terminfo file.
2018-07-31 09:01:23 +05:30
Kovid Goyal
9af3081f45 The focus_follows_mouse option now also works across top-level kitty OS windows
Fixes #772
2018-07-30 21:26:20 +05:30
Kovid Goyal
78def6e6bf kitty @ new-window: Add a new option --window-type
To create top-level OS windows. Fixes #770
2018-07-30 08:40:26 +05:30
Kovid Goyal
b4269199b4 Fix #771 2018-07-30 07:58:34 +05:30
Kovid Goyal
cb289125ab Use _GNU_SOURCE instead of POSIX_SOURCE for fileno()
Maybe that will work with FreeBSD
2018-07-29 21:43:02 +05:30
Kovid Goyal
a4e3ce31dd Merge branch 'fix_window_decorations' of https://github.com/danbtl/kitty 2018-07-29 20:05:11 +05:30
Daniel Beitler
8a725d3d3e Fix hiding window decorations under X11/Wayland 2018-07-29 10:12:07 -04:00
Kovid Goyal
c48fa0f078 Silence the OpenGL deprecation warnings on macOS 10.14 2018-07-29 19:35:43 +05:30
Kovid Goyal
31aa782362 Update URL to kitty home page in Info.plist 2018-07-29 19:03:41 +05:30
Kovid Goyal
892637683c Fix controlling terminal not being set on BSD
Fixes #764
2018-07-29 13:48:14 +05:30
Kovid Goyal
1cf6f28fe8 When compiling python bytecode for the packages optimize it to level-2 and use relative paths in the pyc files 2018-07-29 13:20:21 +05:30
Kovid Goyal
29f9394257 ... 2018-07-29 13:06:26 +05:30
Kovid Goyal
8fd92df7ae Also fix launcher for netbsd 2018-07-29 12:54:24 +05:30
Kovid Goyal
374f7abb5d Fix package launcher for FreeBSD
Fixes #763
2018-07-29 12:52:24 +05:30
Kovid Goyal
3d1e604a6c Make the glfw strdup implementation more efficient 2018-07-29 12:21:56 +05:30
Kovid Goyal
1ca49b9380 Fix #761 2018-07-29 12:15:21 +05:30
Kovid Goyal
9bd9cf8052 typo 2018-07-29 11:18:12 +05:30
Kovid Goyal
ed934a0206 Fix #758 2018-07-29 11:07:36 +05:30
Kovid Goyal
dc891760ec Fix pthread_setname_np on openbsd as well
openbsd apparently follows freebsd for this function
2018-07-29 10:34:54 +05:30
Kovid Goyal
97401f843a Sigh. Apparently _XOPEN_SOURCE is needed on older linux systems for fileno() 2018-07-28 23:54:23 +05:30
Kovid Goyal
833865cb28 Re-enable brew based build on travis
New harfbuzz release will hopefully work again
2018-07-28 23:54:23 +05:30
Kovid Goyal
02635d506e Dont use _XOPEN_SOURCE when building
Apparently it has exactly opposite semantics for glibc and whatever libcs
are used on BSD. What a clusterfuck.
2018-07-28 22:56:18 +05:30
Kovid Goyal
f018e1a075 Nicer check for is_binary 2018-07-28 14:02:01 +05:30
Kovid Goyal
c79cde43ae ... 2018-07-28 14:00:18 +05:30
Kovid Goyal
8a812c9d02 diff kitten: Fix error when only RHS file is a binary file
Fixes #752
2018-07-28 13:59:32 +05:30
Kovid Goyal
3df78de3f8 Update pending mode escape code parsing to match latest shed color 2018-07-28 10:56:34 +05:30
Kovid Goyal
bd4831e2ee Some explicit includes needed for FreeBSD 2018-07-26 19:50:38 +05:30
Kovid Goyal
341a34ea9e Assume file extensions are up to 7 chars long
See .desktop files
2018-07-26 18:37:13 +05:30
Kovid Goyal
011866a101 Add a keyboard shortcut to reset the terminal
It takes parameters so you can define your own shortcuts to clear the screen/scrollback also. Fixes #747
2018-07-26 17:29:13 +05:30
Kovid Goyal
1486af8770 More debug output when using --debug-keyboard 2018-07-26 08:41:16 +05:30
Kovid Goyal
f06e04ffc5 Fix #745 2018-07-26 07:03:41 +05:30
Kovid Goyal
d72098bdf8 Preserve the mouse selection even when the contents of the screen are scrolled or overwritten provided the new text does not intersect the selected lines.
Requires a small CPU usage overhead when scrolling/drawing characters
even in the absence of a selection. I couldn't isolate the overhead from
statistical noise in my testing.
2018-07-25 16:16:57 +05:30
Kovid Goyal
57871e1983 Restore sys.path after importing custom kitten 2018-07-25 09:51:04 +05:30
Kovid Goyal
21dbb0e820 Improve the help message for the kitten remote control cmd 2018-07-25 09:49:01 +05:30
Kovid Goyal
007f377f16 Allow running kitten via the remote control system
Fixes #738
2018-07-25 09:45:11 +05:30
Kovid Goyal
da15477aaf Make the choice of default editor a bit more robust 2018-07-25 08:57:44 +05:30
Kovid Goyal
4f34ad0a8d ... 2018-07-24 14:14:34 +05:30
Kovid Goyal
6ed9dccde2 Remove unused parameter 2018-07-24 13:51:45 +05:30
Kovid Goyal
ce2b3265be Remote control: Allow matching windows by the environment variables of their child process as well 2018-07-24 12:40:59 +05:30
Kovid Goyal
14ea69c9c7 Pass pid of child process as env var to kittens 2018-07-24 11:28:34 +05:30
Kovid Goyal
72e2307c16 Code to get the env vars of a process 2018-07-24 11:27:10 +05:30
Kovid Goyal
0dd3334811 Implement getting cmdline of process on macOS as well 2018-07-24 11:07:53 +05:30
Kovid Goyal
5485436ea1 ... 2018-07-24 10:18:37 +05:30
Kovid Goyal
9e044750ce When running kittens use the cwd of the child process as the kittens' cwd 2018-07-24 10:14:17 +05:30
Kovid Goyal
f84cb2f07a ... 2018-07-24 09:32:21 +05:30
Kovid Goyal
e0ff6bcc5d Implement protocol for atomic screen updates
See https://gitlab.com/gnachman/iterm2/wikis/synchronized-updates-spec
2018-07-23 13:24:49 +05:30
Kovid Goyal
1695f6800c Expand links to kitty docs in sample kitty.conf 2018-07-20 21:21:52 +05:30
Kovid Goyal
810f149501 Fix #731 2018-07-20 21:04:07 +05:30
Kovid Goyal
05d51d8693 Fix #730 2018-07-20 15:55:39 +05:30
Kovid Goyal
ac4c819f57 Add a warning not to edit the apc parsers header files 2018-07-20 09:45:05 +05:30
Kovid Goyal
e7cc654bb4 Make error message when creating glfw window fails a little more useful 2018-07-20 09:36:42 +05:30
Kovid Goyal
bf37e3f77f Format generated header using clang-format 2018-07-19 18:56:10 +05:30
Kovid Goyal
af9f7bb0d2 Generate the code to parse APC escape codes
Allows it to be re-used for other APC codes
if needed.
2018-07-19 18:43:26 +05:30
Kovid Goyal
0b7abf5690 Fix test that needed to be changed for new prerendered sprites 2018-07-19 14:21:09 +05:30
Kovid Goyal
490ae248f8 Render all cursor shapes in the cell shader 2018-07-19 14:05:15 +05:30
Kovid Goyal
e552bcb0c6 Prerender cursor sprites 2018-07-19 14:03:39 +05:30
Kovid Goyal
2c27316c4a Get rid of the cursor program
Cursors will now be drawn in the cell program using a sprite
2018-07-19 12:46:25 +05:30
Kovid Goyal
c49dd12855 Pass in cursor shape to cell shader 2018-07-19 12:13:39 +05:30
Kovid Goyal
544f97a3fe ... 2018-07-19 11:18:31 +05:30
Kovid Goyal
6a909e0700 Get rid of the last branch in the cell shader 2018-07-19 10:56:28 +05:30
Kovid Goyal
40a6ad9a1f ... 2018-07-19 06:07:04 +05:30
Kovid Goyal
039f2b49e8 Remove workaround for #654 as it has been upstreamed 2018-07-19 05:11:03 +05:30
Kovid Goyal
5ae2406a03 macOS: Allow scrolling window contents using mouse wheel/trackbar even when the window is not the active window
Fixes #729
2018-07-18 10:34:36 +05:30
Kovid Goyal
2c3235d452 Remove need to call super-class on_resize method 2018-07-15 16:19:54 +05:30
Kovid Goyal
d2ea71a403 Remove unused code 2018-07-15 16:14:40 +05:30
Kovid Goyal
feb97b2313 Use asyncio for jobs in the diff kitten 2018-07-15 16:09:52 +05:30
Kovid Goyal
0b662ecb9a Refactor the kittens tui loop to use asyncio
Gets us timers and various jobs for free, and makes it easier to
integrate with libraries that use asyncio from the larger python
ecosystem.
2018-07-15 15:27:35 +05:30
Kovid Goyal
3d0da77c80 Revert change to set PYTHONIOENCODING on macOS
It is not needed in python 3.7 as the POSIX locale is
now UTF-8 by default
2018-07-15 12:19:48 +05:30
Kovid Goyal
9918397190 Add an option :opt:env to set environment variables in child processes from kitty.conf 2018-07-15 12:16:43 +05:30
Kovid Goyal
b3e2562c00 macOS bundle: Set the PYTHONIOENCODING env var to utf-8
There are apparently still some situations where python ends up with
ascii as its I/O encoding on macOS. See #724
2018-07-15 10:38:40 +05:30
Kovid Goyal
255cacf912 kitty shell prompt: fallback to plain ascii prompt in case of unicode errors 2018-07-14 06:35:43 +05:30
Kovid Goyal
8268f9f612 Add a section about remote control to the main page 2018-07-14 06:06:59 +05:30
Kovid Goyal
92b8798cad Add note about ctrl+alt to select in columns 2018-07-14 06:02:17 +05:30
Kovid Goyal
fbb4db8a83 Allocate talk data on the heap
Most users will talk to 0, 1, 2 peers simultaneously at the most,
so allocate memory for them dynamically.
2018-07-13 17:13:59 +05:30
Kovid Goyal
19a672caf2 Allow kitty to handle talking to more simultaneous peers 2018-07-13 16:46:08 +05:30
Kovid Goyal
5a85c890ba Add a printf annotation of the log_error function 2018-07-13 16:39:47 +05:30
Kovid Goyal
55556aaac4 Ensure queued display events are dispatched when polling/waiting for events 2018-07-13 10:56:12 +05:30
Kovid Goyal
31853ee20c Handle errno better when polling for events 2018-07-13 10:33:18 +05:30
Kovid Goyal
037bfeff82 Give IBUS a maximum of three seconds to respond to a key press
Previously was using the default timeout, which is 25 seconds
2018-07-12 18:18:21 +05:30
Kovid Goyal
bf1c919547 When de-activating overlay line, mark it dirty 2018-07-12 18:18:21 +05:30
Kovid Goyal
202ec17a08 Add a changelog entry for IBus support
Fixes #469
2018-07-12 18:18:21 +05:30
Kovid Goyal
98ede457a0 Do not hand on startup if the ibus daemon is hung 2018-07-12 18:18:21 +05:30
Kovid Goyal
3c772c3576 ... 2018-07-12 18:18:21 +05:30
Kovid Goyal
5b1eaa741c IME input: Show the pre-edit text while the IME is in progress
Text is show in reverse video
2018-07-12 18:18:21 +05:30
Kovid Goyal
1a1aa2d6aa Nicer debug print for key debugging 2018-07-12 18:18:20 +05:30
Kovid Goyal
6e9aecdff3 Fix Shift key not working with IBUS
Fixes #721
2018-07-12 18:18:20 +05:30
Kovid Goyal
95187632bf Allow using GLFW_IM_MODULE to activate ibus 2018-07-12 18:18:20 +05:30
Kovid Goyal
e3e28a023c Fix exporting to glfw 2018-07-12 18:18:20 +05:30
Kovid Goyal
65bb71779a Install dbus on travis 2018-07-12 18:18:20 +05:30
Kovid Goyal
29dd766a67 Fix unused vars warning 2018-07-12 18:18:20 +05:30
Kovid Goyal
851145e69e Add some debug prints for IBUS key handling 2018-07-12 18:18:20 +05:30
Kovid Goyal
b2e300c440 Simplify key round-tripping to IBUS
Only send to IBUS after full xkb processing. This is needed as otherwise
modifier state tracking does not work reliably. If you use alt+tab to
switch away from the window, the window never receives the release event
for the alt modifier.

The downside is that now if the uer has both XCompose and ibus active,
the who knows what will happen.
2018-07-12 18:18:20 +05:30
Kovid Goyal
38998a4869 Notify IBUS when focus is gained/lost 2018-07-12 18:18:19 +05:30
Kovid Goyal
21f1fd55f8 Have the IBUS popup display at the cursor position 2018-07-12 18:18:19 +05:30
Kovid Goyal
c6c6a18375 Handle keys returned by IBUS 2018-07-12 18:18:19 +05:30
Kovid Goyal
56063b283e Implement text commit from the IME system 2018-07-12 18:18:19 +05:30
Kovid Goyal
d27e7ead49 Print an error if too many timers/watches are added 2018-07-12 18:18:19 +05:30
Kovid Goyal
342164ede1 Add API for the application to notify the IME system of state changes 2018-07-12 18:18:19 +05:30
Kovid Goyal
1061e40b98 Bi-directional glfw updates 2018-07-12 18:18:19 +05:30
Kovid Goyal
377115a594 Add a GLFW specific env var to toggle IBUS 2018-07-12 18:18:19 +05:30
Kovid Goyal
e405b68da1 ... 2018-07-12 18:18:18 +05:30
Kovid Goyal
d875d7baed Implement the handler for ibus signals 2018-07-12 18:18:18 +05:30
Kovid Goyal
9c48e4965c Send key events to ibus 2018-07-12 18:18:18 +05:30
Kovid Goyal
8675659fcb Fix remaining sync BUS calls
Also work on IBUS integration
2018-07-12 18:18:18 +05:30
Kovid Goyal
3b226618ad Ensure no ibus comms take place till the connection is fully setup 2018-07-12 18:18:18 +05:30
Kovid Goyal
6879a492dc Give watches and timers names to ease debugging 2018-07-12 18:18:18 +05:30
Kovid Goyal
4070255dde Simplify watch->fd matching 2018-07-12 18:18:18 +05:30
Kovid Goyal
dd9e484336 Make the DBUS APIs async 2018-07-12 18:18:18 +05:30
Kovid Goyal
78cda7aa74 Implement dispatch for the IBUS connection 2018-07-12 18:18:17 +05:30
Kovid Goyal
9ced447fa3 Make timer dispatch more efficient 2018-07-12 18:18:17 +05:30
Kovid Goyal
8e665ffedc Implement DBUS timeout functions 2018-07-12 18:18:17 +05:30
Kovid Goyal
99ea6c08a7 Implement timers for the linux event loops
Needed for dbus integration. Also cleanup the event handling code.
X11 and Wayland now share most of their event polling and dispatch logic.
2018-07-12 18:18:17 +05:30
Kovid Goyal
b0bfa2b2fc ... 2018-07-12 18:18:17 +05:30
Kovid Goyal
3797294838 Use an id for watches 2018-07-12 18:18:17 +05:30
Kovid Goyal
3d6995d1e6 Work on integration of DBUS into event loop 2018-07-12 18:18:17 +05:30
Kovid Goyal
e91eb27e56 Refactor linux backend event loops
Allow waiting for events on an arbitrary number of fds. Needed
for async DBUS integration.
2018-07-12 18:18:17 +05:30
Kovid Goyal
3a4b614ae0 IBUS does not need a connection to the DBUS session bus 2018-07-12 18:18:16 +05:30
Kovid Goyal
26a6d6bef9 Set startup IBUS cursor and focus state 2018-07-12 18:18:16 +05:30
Kovid Goyal
dca81589a7 Finish code to connect to IBUS daemon 2018-07-12 18:18:16 +05:30
Kovid Goyal
5b7c697dfe Connect to IBUS 2018-07-12 18:18:16 +05:30
Kovid Goyal
18d6b8a0ad Code to read the IBUS address 2018-07-12 18:18:16 +05:30
Kovid Goyal
cdee601137 Start work on IBUS support 2018-07-12 18:18:16 +05:30
Kovid Goyal
e74c08ff39 Disable brew based travis build
Building the latest release of harfbuzz is failing
2018-07-12 17:42:33 +05:30
Kovid Goyal
5bfe2fb8d3 Make using a fixed color for the text under the cursor the default 2018-07-12 16:52:38 +05:30
Kovid Goyal
55fc5c4358 Add an option :opt:cursor_text_color to render text under the cursor in a fixed color.
See #126
2018-07-12 12:50:42 +05:30
Kovid Goyal
d6a603d536 Change the default publish action from build+ to all 2018-07-10 22:05:33 +05:30
Kovid Goyal
5c85d915cc ... 2018-07-10 22:00:34 +05:30
Kovid Goyal
57f8519a52 version 0.11.3 2018-07-10 21:42:01 +05:30
Kovid Goyal
056be45dd5 ... 2018-07-10 21:30:30 +05:30
Kovid Goyal
3ee22e545e macOS: Workaround for switching from fullscreen to windowed mode with the titlebar hidden causing window resizing to not work.
Fixes #711
2018-07-10 21:29:30 +05:30
Kovid Goyal
db5aa36f44 Fix deprecation warning from newer versions of wayland-scanner 2018-07-10 19:48:37 +05:30
Kovid Goyal
cc767061cc Linux: Fix compilation with python 3.7
Fixes #714
2018-07-10 18:47:05 +05:30
Kovid Goyal
1d22b75090 Fix a regression in the previous release that caused pasting large amounts of text to be duplicated
Fixes #709
2018-07-08 16:25:08 +05:30
Kovid Goyal
4313531432 macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
Fixes #359. Also, update GLFW from upstream for retina cursor setting
support.
2018-07-07 10:43:30 +05:30
Kovid Goyal
1faddeb402 Also return image dimensions when loading PNG 2018-07-07 08:04:11 +05:30
Kovid Goyal
2b035739f8 Make the PNG reader code re-useable 2018-07-07 07:46:15 +05:30
Kovid Goyal
aeed20087e Fix segfault when opening more tabs than would fit with even one character titles on the tab bar. Fixes #706 2018-07-07 05:35:56 +05:30
Kovid Goyal
a239f41495 minimal borders for the grid layout 2018-07-06 20:23:56 +05:30
Kovid Goyal
b36d6967a5 DRYer 2018-07-06 17:54:01 +05:30
Kovid Goyal
7c1d13b7db Minimal borders for vertical and horizontal layouts 2018-07-06 17:46:08 +05:30
Kovid Goyal
3e99949db8 Implement minimal borders in the Fat layout 2018-07-06 17:35:23 +05:30
Kovid Goyal
aa12a65a8f Draw only the minimum borders needed for inactive windows
Fixes #699

Note that currently this is only implemented by the Tall layout.
2018-07-06 17:30:53 +05:30
Kovid Goyal
18804efb7e Let the layout control which window borders are drawn 2018-07-06 17:14:25 +05:30
Kovid Goyal
a5d27196ea Remove unnecessary code 2018-07-06 13:17:51 +05:30
Kovid Goyal
5af6d88ffb Support drawing individual border widths 2018-07-06 13:17:51 +05:30
Kovid Goyal
472a3bc69f Refactor layout dimension to use per window decoration sizes 2018-07-06 13:17:51 +05:30
Kovid Goyal
f0f1c911ea Track window neighbors 2018-07-06 13:17:50 +05:30
Kovid Goyal
fcb0033d25 When drag-scrolling stop the scroll when the mouse button is released. 2018-07-06 13:01:49 +05:30
Kovid Goyal
c8b1e76783 When clicking in the margin/border area of a window map the click to the nearest cell in the window. Avoids selection with the mouse failing when starting the selection just outside the window. 2018-07-06 12:32:08 +05:30
Kovid Goyal
b50f621e61 DRYer 2018-07-06 10:55:03 +05:30
Kovid Goyal
77c4f5fecc When dragging to select with the mouse "grab" the mouse so that if it strays into neighboring windows, the selection is still updated
Fixes #624
2018-07-06 10:33:01 +05:30
Kovid Goyal
e27ea3337a Prevent integer wrap on xlimit 2018-07-06 09:50:34 +05:30
Kovid Goyal
23fbf6f157 Fix triple-click to select line not working when the entire line is filled
Fixes #703
2018-07-06 08:17:48 +05:30
Kovid Goyal
321e5081ed Merge branch 'reproducible-docs' of https://github.com/jamessan/kitty 2018-07-05 20:37:57 +05:30
James McCoy
33cac29f87 seq_as_rst: Sort "Choices" so they are consistently formatted 2018-07-05 11:00:54 -04:00
Kovid Goyal
12be438dea ... 2018-07-05 12:30:11 +05:30
Kovid Goyal
00092db6ad Use an enum for border colors 2018-07-05 11:52:32 +05:30
Kovid Goyal
619c3a0025 macOS: Add an option macos_window_resizable to control if kitty top-level windows are resizable using the mouse or not
Fixes #698
2018-07-03 06:49:28 +05:30
Kovid Goyal
f12697c897 Better error when kitty config dir is a file
See #697
2018-07-03 06:30:29 +05:30
Kovid Goyal
4bf0e0ea23 version 0.11.2 2018-07-01 07:58:46 +05:30
Kovid Goyal
c7879d06c9 Output native key names in --debug-config 2018-06-30 18:48:39 +05:30
Kovid Goyal
05227e7dfb Fix --debug-config failing when native keys present 2018-06-30 18:42:19 +05:30
Kovid Goyal
9805d164d0 Remove unknown flags from brew install for harfbuzz on travis 2018-06-30 18:34:27 +05:30
Kovid Goyal
05384c46e7 ... 2018-06-30 18:25:45 +05:30
Kovid Goyal
c2679eff9a Fix compiler warning
The compiler warning is harmless, since we exec immediately after
using the const char* but since I have a no warning policy...
2018-06-30 18:15:08 +05:30
Kovid Goyal
3ec66c0b9f Fix regression in previous release that caused --debug-config to fail when printing shortcuts
Forgot to adjust the print shortcuts code for native shortcuts.
Fixes #695
2018-06-30 17:48:25 +05:30
Kovid Goyal
9277b80fad Remove unused code 2018-06-30 12:01:56 +05:30
Kovid Goyal
568a4c9b5d Re-raise KeyError idiomatically 2018-06-29 13:49:08 +05:30
Kovid Goyal
4de18688ca Merge branch 'fix/conf-utils-keyparser' of https://github.com/arubertoson/kitty 2018-06-29 13:42:21 +05:30
Marcus Albertsson
12975347ee Fix: Exception in conf utils
The bug was related to trying to split a string with unstript spaces in
the action parameter. It resulted in a unbound error as there was no
previous raise when getting the key from args_funcs. The fix is twofold,
it makes sure that we raise the keyerror if we're trying to grab a non
existing key and we're stripping the spaces from the action before
splitting.

This should fix both the unbound error and the not being able to resolve
correct actions.
2018-06-29 08:56:25 +01:00
Kovid Goyal
e35c3cc913 Add an option single_window_margin_width to allow different margins when only a single window is visible in the layout
Fixes #688
2018-06-29 09:56:05 +05:30
Kovid Goyal
65fef81f29 hints kitten: Add an option to select multiple items
Fixes #687
2018-06-29 06:49:16 +05:30
Kovid Goyal
e3f60e6fa7 Update CHANGELOG 2018-06-28 20:34:56 +05:30
Kovid Goyal
0c88e11cd7 More useful exception when unexpected output encountered from a kitten 2018-06-27 14:13:40 +05:30
Kovid Goyal
d689c21283 Fix pasting large amounts of text very slow.
Fixes #682

Apparently when refactoring child.py I somehow lost the code to make the
child fd non-blocking.
2018-06-26 18:20:46 +05:30
Kovid Goyal
5362d1a7a6 Micro-optimization 2018-06-26 16:02:05 +05:30
Kovid Goyal
3b1490020d ... 2018-06-26 12:35:38 +05:30
Kovid Goyal
b8ba25b81f Add a link to the kitty source code for packagers 2018-06-26 12:03:07 +05:30
Kovid Goyal
8bfdc47998 macOS: Fix an error in remote control when using --listen-on. Fixes #679 2018-06-25 12:33:54 +05:30
Kovid Goyal
2491fafe98 Update glfw from upstream 2018-06-23 14:59:36 +05:30
Kovid Goyal
b5bc466aef Merge branch 'master' of https://github.com/Ma-Shell/kitty 2018-06-23 13:54:47 +05:30
Marcel Schumacher
cfd6ea4301 bugfix for get-text with extent=screen 2018-06-23 10:13:37 +02:00
Kovid Goyal
2cdf41f190 ... 2018-06-23 13:20:53 +05:30
Kovid Goyal
9169d0aa9b ... 2018-06-23 10:34:35 +05:30
Kovid Goyal
05a25e3531 redirect to kitty website 2018-06-23 10:30:34 +05:30
Kovid Goyal
2852ce9a4d Update glfw from upstream 2018-06-23 10:23:21 +05:30
Kovid Goyal
20f8f2c401 Removed unused code
The template is no longer needed since I switched my global
template to py3
2018-06-23 10:03:37 +05:30
Kovid Goyal
0995187080 Remove unused code
linux-installer.sh was accidentally committed to the kitty repo
2018-06-23 09:58:20 +05:30
Kovid Goyal
d10eac1b02 Merge branch 'fix-new_os_window' of https://github.com/blueyed/kitty 2018-06-23 08:28:11 +05:30
Daniel Hahler
2cc2f803f1 create_session: handle args=None
Fixes `new_os_window`:

    scancode: 0x39 release: 0 clean_sym: n composed_sym: N mods: ctrl+shift glfw_key: N
    Traceback (most recent call last):
      File "…/Vcs/kitty/kitty/boss.py", line 431, in dispatch_special_key
        return self.dispatch_action(key_action)
      File "…/Vcs/kitty/kitty/boss.py", line 487, in dispatch_action
        passthrough = f(*key_action.args)
      File "…/Vcs/kitty/kitty/boss.py", line 199, in new_os_window
        self._new_os_window(args)
      File "…/Vcs/kitty/kitty/boss.py", line 195, in _new_os_window
        startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
      File "…/Vcs/kitty/kitty/session.py", line 127, in create_session
        if args.hold:
2018-06-23 02:57:47 +02:00
Kovid Goyal
66a52ac588 Also ignore failure to read readline history in the ask kitten 2018-06-22 17:23:55 +05:30
Kovid Goyal
32f22e0ada kitty shell: Ignore failure to read readline history file 2018-06-22 17:23:01 +05:30
Kovid Goyal
c037f53534 ... 2018-06-22 15:35:24 +05:30
Kovid Goyal
aba84581f5 When running kitty from within kitty, use a full path
Fixes inability to run kitty from the shell when the users shell scripts
reset PATH. See #668
2018-06-22 15:33:07 +05:30
Kovid Goyal
82f6fbff77 Add a --hold command line option to stay open after the child process exits
Fixes #667
2018-06-22 13:52:03 +05:30
Kovid Goyal
d16215ab6e Initialize glfw for --debug-config as well 2018-06-22 13:30:03 +05:30
Kovid Goyal
45b0233f60 Update GLFW from upstream
Use the new init hint for keyboard debugging
2018-06-22 13:26:41 +05:30
Kovid Goyal
1ce4366972 ... 2018-06-22 12:44:08 +05:30
Kovid Goyal
c8fc21d336 Linux: Allow using XKB key names to bind shortcuts to keys not supported by GLFW
Useful to bind keys such as the play/pause or volume buttons. Also can
be used to bind non-ascii keys on international keyboards. Fixes #665
2018-06-22 12:41:50 +05:30
Kovid Goyal
5dd3243674 Update glfw from upstream 2018-06-22 10:21:51 +05:30
Kovid Goyal
815dd99f7c Keep the Debian PC police happy
Fixes #661
2018-06-21 13:12:45 +05:30
Kovid Goyal
bf0dcb1d2e Update version of glad used to generate OpenGL wrapper
Also fix #654
2018-06-20 12:43:26 +05:30
Kovid Goyal
031fc10859 Merge branch 'title-command-line-parameter' of https://github.com/lamby/kitty 2018-06-19 13:48:55 +05:30
Chris Lamb
a644e4c31e Support "-T" as an alias for "--title".
Thank you for developing Kitty.

Currently kitty is not available on the list of "alternatives" for a
terminal emulator on Debian GNU/Linux and derivatives. This is because
Debian Policy states that packages that provide a terminal emulator must
provide, inter alia, support the command-line option -T title, which
creates a new terminal window with the specified window title [0].

This PR therefore adds -T as an alias for --title, thus bringing kitty into
compliance and allowing cleaner integration into the Debian desktop.

This issue was originally filed as #900704.

  [0] https://www.debian.org/doc/debian-policy/#packages-providing-a-terminal-emulator
  [1] https://bugs.debian.org/900704

Signed-off-by: Chris Lamb <lamby@debian.org>
2018-06-19 10:10:43 +02:00
Kovid Goyal
0398dbdcbe Fix extra blank lines when using log_error() 2018-06-19 12:51:54 +05:30
Kovid Goyal
84b2fbba28 --debug-font-fallback also prints out what basic fonts were matched
Fixes #656
2018-06-19 12:48:33 +05:30
Kovid Goyal
491f233d33 diff kitten: When triggering a search scroll to the first match automatically 2018-06-19 10:30:52 +05:30
Kovid Goyal
5e38af6d24 When closing a kitty window reset the mouse cursor to its default shape and ensure it is visible. Fixes #655 2018-06-19 10:19:17 +05:30
Kovid Goyal
4c0f68150a Merge branch 'master' of https://github.com/Luflosi/kitty 2018-06-18 18:41:12 +05:30
Luflosi
3f25f70783 Ignore .DS_Store 2018-06-18 14:39:12 +02:00
Kovid Goyal
d7007f77aa DRYer 2018-06-18 12:18:04 +05:30
Kovid Goyal
94273941ad ... 2018-06-18 12:10:42 +05:30
Kovid Goyal
c09542ea6a Speed up reading of command responses 2018-06-18 11:42:32 +05:30
Kovid Goyal
c5583d380d No need to use select() for TTYIO
Instead make read() block with timeout via termios
2018-06-18 11:10:16 +05:30
Kovid Goyal
f820f72768 Add a kitty @ get-colors command 2018-06-18 10:02:05 +05:30
Kovid Goyal
6db1f8e3e3 Dont read more data than needed in TTYIO 2018-06-18 08:36:47 +05:30
Kovid Goyal
2aba3e249c Linux installer: Workaround for bug in 3.0 <= python <= 3.5 2018-06-18 07:21:44 +05:30
180 changed files with 35264 additions and 28028 deletions

2
.gitignore vendored
View File

@@ -9,9 +9,11 @@ logo/*.iconset
compile_commands.json
glad/out
asan-launcher
asan-launcher.dSYM
kitty-profile
dev
__pycache__
glfw/wayland-*-client-protocol.[ch]
docs/_build
docs/generated
.DS_Store

View File

@@ -17,6 +17,7 @@ matrix:
- libxinerama-dev
- libxcursor-dev
- libxcb-xkb-dev
- libdbus-1-dev
- os: linux
dist: trusty
@@ -37,6 +38,7 @@ matrix:
- libxcursor-dev
- libxcb-xkb-dev
- libpng-dev
- libdbus-1-dev
- python3-pil
- os: linux
@@ -58,6 +60,7 @@ matrix:
- libxcursor-dev
- libxcb-xkb-dev
- libpng-dev
- libdbus-1-dev
- python3-pil
- os: linux
@@ -79,15 +82,17 @@ matrix:
- libxcursor-dev
- libxcb-xkb-dev
- libpng-dev
- libdbus-1-dev
- python3-pil
- clang
- os: osx
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:
@@ -102,8 +107,8 @@ install: |
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
if [[ "$USE_BREW" == "1" ]]; then
brew update;
brew upgrade python;
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype;
# brew upgrade python;
brew install harfbuzz --without-graphite2 --without-icu4c --without-freetype;
brew install imagemagick
brew install optipng
brew install librsvg

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: %YEAR%, %USER% <%MAIL%>
%HERE%

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):
@@ -26,10 +27,28 @@ def runpy(args):
exec(args[1])
def hold(args):
import subprocess
ret = subprocess.Popen(args[1:]).wait()
sys.stdin.read()
raise SystemExit(ret)
def complete(args):
from kitty.complete import main
main(args[1:], entry_points, namespaced_entry_points)
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):
@@ -61,6 +80,8 @@ entry_points = {
'+': namespaced,
}
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
namespaced_entry_points['hold'] = hold
namespaced_entry_points['complete'] = complete
def setup_openssl_environment():

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:
``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,35 +57,57 @@ 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
----------------------------------
While kitty does use python, it is not a traditional python package, so please
The released |kitty| source code is available as a `tarball`_ from
`the GitHub releases page <https://github.com/kovidgoyal/kitty/releases>`_.
While |kitty| does use python, it is not a traditional python package, so please
do not install it in site-packages.
Instead run::
python3 setup.py linux-package
This will install kitty into the directory :file:`linux-package`. You can run kitty
This will install |kitty| into the directory :file:`linux-package`. You can run |kitty|
with :file:`linux-package/bin/kitty`. All the files needed to run kitty will be in
:file:`linux-package/lib/kitty`. The terminfo file will be installed into
:file:`linux-package/share/terminfo`. Simply copy these files into :file:`/usr` to install
kitty. In other words, :file:`linux-package` is the staging area into which kitty is
|kitty|. In other words, :file:`linux-package` is the staging area into which |kitty| is
installed. You can choose a different staging area, by passing the ``--prefix``
argument to :file:`setup.py`.
You should probably split kitty into two packages, :file:`kitty-terminfo` that
You should probably split |kitty| into two packages, :file:`kitty-terminfo` that
installs the terminfo file and :file:`kitty` that installs the main program.
This allows users to install the terminfo file on servers into which they ssh,
without needing to install all of kitty.
without needing to install all of |kitty|.
.. note::
You need a couple of extra dependencies to build linux-package.
:file:`tic` to compile terminfo files, usually found in the
development package of :file:`ncurses`. Also, if you are building form
development package of :file:`ncurses`. Also, if you are building from
a git checkout instead of the released source code tarball, you will
need :file:`sphinx-build` from the `Sphinx documentation generator
<http://www.sphinx-doc.org/>`_.
This applies to creating packages for kitty for macOS package managers such as
This applies to creating packages for |kitty| for macOS package managers such as
brew or MacPorts as well.

View File

@@ -3,6 +3,423 @@ 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]
------------------------------
- Preserve the mouse selection even when the contents of the screen are
scrolled or overwritten provided the new text does not intersect the
selected lines.
- Linux: Implement support for Input Method Extensions (multilingual input
using standard keyboards) via `IBus
<https://github.com/ibus/ibus/wiki/ReadMe>`_ (:iss:`469`)
- Implement completion for the kitty command in bash and zsh. See
:ref:`completion`.
- Render the text under the cursor in a fixed color, configurable via
the option :opt:`cursor_text_color` (:iss:`126`)
- 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`)
- macOS: Allow scrolling window contents using mouse wheel/trackpad even when the
window is not the active window (:iss:`729`)
- Remote control: Allow changing the current window layout with a new
:ref:`at_goto-layout` command (:iss:`845`)
- Remote control: Allow matching windows by the environment variables of their
child process as well
- Allow running kittens via the remote control system (:iss:`738`)
- Allow enabling remote control in only some kitty windows
- Add a keyboard shortcut to reset the terminal (:sc:`reset_terminal`). It
takes parameters so you can define your own shortcuts to clear the
screen/scrollback also (:iss:`747`)
- Fix one-pixel line appearing at window edges at some window sizes when
displaying images with background opacity enabled (:iss:`741`)
- diff kitten: Fix error when right hand side file is binary and left hand side
file is text (:pull:`752`)
- kitty @ new-window: Add a new option :option:`kitty @ new-window --window-type`
to create top-level OS windows (:iss:`770`)
- macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows
(:iss:`754`)
- Fix detection of URLs in HTML source code (URLs inside quotes) (:iss:`785`)
- Implement support for emoji skin tone modifiers (:iss:`787`)
- Round-trip the zwj unicode character. Rendering of sequences containing zwj
is still not implemented, since it can cause the collapse of an unbounded
number of characters into a single cell. However, kitty at least preserves
the zwj by storing it as a combining character.
- macOS: Disable the custom mouse cursor. Using a custom cursor fails on dual
GPU machines. I give up, Apple users will just have to live with the
limitations of their choice of OS. (:iss:`794`)
- macOS: Fix control+tab key combination not working (:iss:`801`)
- Linux: Fix slow startup on some systems caused by GLFW searching for
joysticks. Since kitty does not use joysticks, disable joystick support.
(:iss:`830`)
0.11.3 [2018-07-10]
------------------------------
- Draw only the minimum borders needed for inactive windows. That is only the borders
that separate the inactive window from a neighbor. Note that setting
a non-zero window margin overrides this and causes all borders to be drawn.
The old behavior of drawing all borders can be restored via the
:opt:`draw_minimal_borders` setting in kitty.conf. (:iss:`699`)
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
top-level windows are resizable using the mouse or not (:iss:`698`)
- macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
(:iss:`359`)
- macOS: Workaround for switching from fullscreen to windowed mode with the
titlebar hidden causing window resizing to not work. (:iss:`711`)
- Fix triple-click to select line not working when the entire line is filled
(:iss:`703`)
- When dragging to select with the mouse "grab" the mouse so that if it strays
into neighboring windows, the selection is still updated (:pull:`624`)
- When clicking in the margin/border area of a window, map the click to the
nearest cell in the window. Avoids selection with the mouse failing when
starting the selection just outside the window.
- When drag-scrolling stop the scroll when the mouse button is released.
- Fix a regression in the previous release that caused pasting large amounts
of text to be duplicated (:iss:`709`)
0.11.2 [2018-07-01]
------------------------------
- Linux: Allow using XKB key names to bind shortcuts to keys not supported by GLFW (:pull:`665`)
- kitty shell: Ignore failure to read readline history file. Happens if the
user migrates their kitty cache directory between systems with incompatible
readline implementations.
- macOS: Fix an error in remote control when using --listen-on (:iss:`679`)
- hints kitten: Add a :option:`kitty +kitten hints --multiple` option to select
multiple items (:iss:`687`)
- Fix pasting large amounts of text very slow (:iss:`682`)
- Add an option :opt:`single_window_margin_width` to allow different margins
when only a single window is visible in the layout (:iss:`688`)
- Add a :option:`kitty --hold` command line option to stay open after the child process exits (:iss:`667`)
- diff kitten: When triggering a search scroll to the first match automatically
- :option:`kitty --debug-font-fallback` also prints out what basic fonts were matched
- When closing a kitty window reset the mouse cursor to its default shape and ensure it is visible (:iss:`655`).
- Remote control: Speed-up reading of command responses
- Linux installer: Fix installer failing on systems with python < 3.5
- Support "-T" as an alias for "--title" (:pull:`659`)
- Fix a regression in the previous release that broke using :option:`kitty
--debug-config` with custom key mappings (:iss:`695`)
0.11.1 [2018-06-17]
------------------------------
@@ -89,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
@@ -224,7 +641,7 @@ Changelog
- When triple-clicking select all wrapped lines belonging to a single logical line.
- hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL.
- hints kitten: Detect bracketed URLs and don't include the closing bracket in the URL.
- When calling pass_selection_to_program use the current directory of the child
process as the cwd of the program.

View File

@@ -85,6 +85,8 @@ pygments_style = 'sphinx'
rst_prolog = '''
.. |kitty| replace:: *kitty*
.. |version| replace:: VERSION
.. _tarball: https://github.com/kovidgoyal/kitty/releases/download/vVERSION/kitty-VERSION.tar.xz
.. role:: green
.. role:: italic
.. role:: bold
@@ -92,7 +94,7 @@ rst_prolog = '''
.. role:: title
.. role:: env
'''
'''.replace('VERSION', str_version)
# -- Options for HTML output -------------------------------------------------

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
================== ============
@@ -372,9 +417,9 @@ Key Value Default Description
**Keys for deleting images**
-----------------------------------------------------------
``d`` Single character. ``a`` What to delete.
``(a, A, c, C, p,
P, q, Q, x, X, y,
Y, z, Z)``.
``(a, A, c, C, i,
I, p, P, q, Q, x, X,
y, Y, z, Z)``.
======= ==================== ========= =================

View File

@@ -30,7 +30,7 @@ kitty - the fast, featureful, GPU based terminal emulator
the window/tab layout, working directories and programs to run on startup.
* Cross-platform: |kitty| works on Linux and macOS, but because it uses only
OpenGL for rendering, it should be trivial to port to other platforms.
OpenGL for rendering, it should be trivial to port to other Unix-like platforms.
* Allows you to open :ref:`the scrollback buffer <scrollback>` in a
separate window using arbitrary programs of your choice. This is useful for
@@ -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.
@@ -122,8 +124,8 @@ Action Shortcut
======================== =======================
New tab :sc:`new_tab`
Close tab :sc:`close_tab`
Next tab :sc:`next_tab`
Previous tab :sc:`previous_tab`
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
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,9 +180,11 @@ 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`
Pass current selection to program :sc:`pass_selection_to_program`
Edit |kitty| config file :sc:`edit_config_file`
Open a |kitty| shell :sc:`kitty_shell`
@@ -180,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
@@ -200,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::
@@ -249,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
-------------------
@@ -257,6 +295,16 @@ Configuring kitty
painting frames-per-second. For details and a sample :file:`kitty.conf`,
see the :doc:`configuration docs <conf>`.
Remote control
------------------
|kitty| has a very powerful system that allows you to control it from the
:doc:`shell prompt, even over SSH <remote-control>`. You can change colors,
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
@@ -303,6 +351,8 @@ Mouse features
* You can also hold down :kbd:`ctrl+shift` and click on a URL to open it in a browser.
* You can double click to select a word and triple click to select a line.
* You can right click to extend a previous selection
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
columns
Font control
@@ -332,12 +382,66 @@ 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
---------------------------------
The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
.. _completion:
Completion for kitty
---------------------------------
|kitty| comes with completion for the ``kitty`` command for popular shells.
bash
~~~~~~~~
Add the following to your :file:`~/.bashrc`
.. code-block:: sh
source <(kitty + complete setup bash)
zsh
~~~~~~~~~
Add the following to your :file:`~/.zshrc`
.. code-block:: sh
autoload -Uz compinit
compinit
# Completion for kitty
kitty + complete setup zsh | source /dev/stdin
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
------------------

View File

@@ -72,8 +72,8 @@ class Reporter: # {{{
def get_latest_release_data():
print('Checking for latest release on GitHub...')
req = urllib.Request('https://api.github.com/repos/kovidgoyal/kitty/releases/latest', headers={'Accept': 'application/vnd.github.v3+json'})
res = urllib.urlopen(req)
data = json.load(res)
res = urllib.urlopen(req).read().decode('utf-8')
data = json.loads(res)
html_url = data['html_url'].replace('/tag/', '/download/').rstrip('/')
for asset in data.get('assets', ()):
name = asset['name']

View File

@@ -5,7 +5,7 @@ See :ref:`extended-key-protocol` for more information and `this table in JSON
format <https://github.com/kovidgoyal/kitty/blob/master/key_encoding.json>`_.
===================== ======================
Name Encoded representation (base64)
Name Encoded representation
0 ``G``
1 ``H``
2 ``I``
@@ -126,4 +126,3 @@ X ``p``
Y ``q``
Z ``r``
===================== ======================

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

@@ -18,5 +18,5 @@ typing a leading period and then the two character index and pressing Enter.
In :guilabel:`Name` mode you instead type words from the character name and use the arrow
keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you dont like to use arrow
a leading period and the index for the match if you don't like to use arrow
keys.

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:
@@ -184,3 +186,25 @@ changes even after it exits. To avoid this, kitty introduces a new pair of
These escape codes save/restore the so called *dynamic colors*, default
background, default foreground, selection background, selection foreground and
cursor color.
Pasting to clipboard
----------------------
|kitty| implements the OSC 52 escape code protocol to get/set the clipboard
contents (controlled via the :opt:`clipboard_control` setting). There is one
difference in kitty's implementation compared to some other terminal emulators.
|kitty| allows sending arbitrary amounts of text to the clipboard. It does so
by modifying the protocol slightly. Successive OSC 52 escape codes to set the
clipboard will concatenate, so::
<ESC>]52;c;<payload1><ESC>\
<ESC>]52;c;<payload2><ESC>\
will result in the clipboard having the contents ``payload1 + payload2``. To
send a new string to the clipboard send an OSC 52 sequence with an invalid payload
first, for example::
<ESC>]52;c;!<ESC>\
Here ``!`` is not valid base64 encoded text, so it clears the clipboard.

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.
@@ -103,7 +103,7 @@ instance of |kitty| using the :option:`kitty @ --to` command line argument to ``
kitty @ --to unix:/tmp/mykitty ls
Note that is all you want to do is run a single |kitty| "daemon" and have subsequent
Note that if all you want to do is run a single |kitty| "daemon" and have subsequent
|kitty| invocations appear as new top-level windows, you can use the simpler :option:`kitty --single-instance`
option, see ``kitty --help`` for that.
@@ -116,7 +116,23 @@ shell with completion for |kitty| command names and options.
You can even open the |kitty| shell inside a running |kitty| using a simple
keyboard shortcut (:sc:`kitty_shell` by default). This has the added
advantage that you dont need to use ``allow_remote_control`` to make it work.
advantage that you don't need to use ``allow_remote_control`` to make it work.
Allowing only some windows to control kitty
----------------------------------------------
If you do not want to allow all programs running in |kitty| to control it, you can selectively
enable remote control for only some |kitty| windows. Simply create a shortcut
such as::
map ctrl+k new_window @ some_program
Then programs running in windows created with that shortcut can use ``kitty @``
to control kitty. Note that any program with the right level of permissions can
still write to the pipes of any other program on the same computer and
therefore can control |kitty|. It can, however, be useful to block programs
running on other computers (for example, over ssh) or as other users.
.. include:: generated/cli-kitty-at.rst

264
gen-apc-parsers.py Executable file
View File

@@ -0,0 +1,264 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import subprocess
from collections import defaultdict
def resolve_keys(keymap):
ans = defaultdict(list)
for ch, (attr, atype) in keymap.items():
if atype not in ('int', 'uint'):
atype = 'flag'
ans[atype].append(ch)
return ans
def enum(keymap):
lines = []
for ch, (attr, atype) in keymap.items():
lines.append(f"{attr}='{ch}'")
return '''
enum KEYS {{
{}
}};
'''.format(',\n'.join(lines))
def parse_key(keymap):
lines = []
for attr, atype in keymap.values():
vs = atype.upper() if atype in ('uint', 'int') else 'FLAG'
lines.append(f'case {attr}: value_state = {vs}; break;')
return ' \n'.join(lines)
def parse_flag(keymap, type_map, command_class):
lines = []
for ch in type_map['flag']:
attr, allowed_values = keymap[ch]
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values)
lines.append(f'''
case {attr}: {{
g.{attr} = screen->parser_buf[pos++] & 0xff;
if ({q}) {{
REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr});
return;
}};
}}
break;
''')
return ' \n'.join(lines)
def parse_number(keymap):
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
return '; '.join(int_keys), '; '.join(uint_keys)
def cmd_for_report(report_name, keymap, type_map, payload_allowed):
def group(atype, conv):
flag_fmt, flag_attrs = [], []
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
for ch in type_map[atype]:
flag_fmt.append('s' + cv)
attr = keymap[ch][0]
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
return ' '.join(flag_fmt), ', '.join(flag_attrs)
flag_fmt, flag_attrs = group('flag', '')
int_fmt, int_attrs = group('int', '(int)')
uint_fmt, uint_attrs = group('uint', '(unsigned int)')
fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
if payload_allowed:
ans = [f'REPORT_VA_COMMAND("s {{{fmt} sI}} y#", "{report_name}",']
else:
ans = [f'REPORT_VA_COMMAND("s {{{fmt}}}", "{report_name}",']
ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
if payload_allowed:
ans.append(', "payload_sz", g.payload_sz, payload, g.payload_sz')
ans.append(');')
return '\n'.join(ans)
def generate(function_name, callback_name, report_name, keymap, command_class, initial_key='a', payload_allowed=True):
type_map = resolve_keys(keymap)
keys_enum = enum(keymap)
handle_key = parse_key(keymap)
flag_keys = parse_flag(keymap, type_map, command_class)
int_keys, uint_keys = parse_number(keymap)
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
if payload_allowed:
payload_after_value = "case ';': state = PAYLOAD; break;"
payload = payload = ', PAYLOAD'
parr = 'static uint8_t payload[4096];'
payload_case = f'''
case PAYLOAD: {{
sz = screen->parser_buf_pos - pos;
const char *err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz);
if (err != NULL) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: %s", err); return; }}
pos = screen->parser_buf_pos;
}}
break;
'''
callback = f'{callback_name}(screen, &g, payload)'
else:
payload_after_value = payload = parr = payload_case = ''
callback = f'{callback_name}(screen, &g)'
return f'''
static inline void
{function_name}(Screen *screen, PyObject UNUSED *dump_callback) {{
unsigned int pos = 1;
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
enum PARSER_STATES state = KEY, value_state = FLAG;
static {command_class} g;
unsigned int i, code;
uint64_t lcode;
bool is_negative;
memset(&g, 0, sizeof(g));
size_t sz;
{parr}
{keys_enum}
enum KEYS key = '{initial_key}';
while (pos < screen->parser_buf_pos) {{
switch(state) {{
case KEY:
key = screen->parser_buf[pos++];
state = EQUAL;
switch(key) {{
{handle_key}
default:
REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key);
return;
}}
break;
case EQUAL:
if (screen->parser_buf[pos++] != '=') {{
REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]);
return;
}}
state = value_state;
break;
case FLAG:
switch(key) {{
{flag_keys}
default:
break;
}}
state = AFTER_VALUE;
break;
case INT:
#define READ_UINT \\
for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) {{ \\
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \\
}} \\
if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \\
if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
code = lcode;
is_negative = false;
if(screen->parser_buf[pos] == '-') {{ is_negative = true; pos++; }}
#define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
READ_UINT;
switch(key) {{
{int_keys};
default: break;
}}
state = AFTER_VALUE;
break;
#undef I
case UINT:
READ_UINT;
#define U(x) case x: g.x = code; break
switch(key) {{
{uint_keys};
default: break;
}}
state = AFTER_VALUE;
break;
#undef U
#undef READ_UINT
case AFTER_VALUE:
switch (screen->parser_buf[pos++]) {{
default:
REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x",
screen->parser_buf[pos - 1]);
return;
case ',':
state = KEY;
break;
{payload_after_value}
}}
break;
{payload_case}
}} // end switch
}} // end while
switch(state) {{
case EQUAL:
REPORT_ERROR("Malformed {command_class} control block, no = after key"); return;
case INT:
case UINT:
REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return;
case FLAG:
REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return;
default:
break;
}}
{report_cmd}
{callback};
}}
'''
def write_header(text, path):
with open(path, 'w') as f:
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
print('#pragma once', file=f)
print(text, file=f)
subprocess.check_call(['clang-format', '-i', path])
def graphics_parser():
flag = frozenset
keymap = {
'a': ('action', flag('tTqpd')),
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
't': ('transmission_type', flag('dfts')),
'o': ('compressed', flag('z')),
'f': ('format', 'uint'),
'm': ('more', 'uint'),
'i': ('id', 'uint'),
'w': ('width', 'uint'),
'h': ('height', 'uint'),
'x': ('x_offset', 'uint'),
'y': ('y_offset', 'uint'),
'v': ('data_height', 'uint'),
's': ('data_width', 'uint'),
'S': ('data_sz', 'uint'),
'O': ('data_offset', 'uint'),
'c': ('num_cells', 'uint'),
'r': ('num_lines', 'uint'),
'X': ('cell_x_offset', 'uint'),
'Y': ('cell_y_offset', 'uint'),
'z': ('z_index', 'int'),
}
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
write_header(text, 'kitty/parse-graphics-command.h')
graphics_parser()

View File

@@ -20,6 +20,7 @@ non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
non_characters |= frozenset(range(0xfdd0, 0xfdf0))
if len(non_characters) != 66:
raise SystemExit('non_characters table incorrect')
emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1))
def get_data(fname, folder='UCD'):
@@ -41,7 +42,8 @@ def get_data(fname, folder='UCD'):
class_maps = {}
name_map = {}
word_search_map = defaultdict(set)
marks = set()
zwj = 0x200d
marks = set(emoji_skin_tone_modifiers) | {zwj}
not_assigned = set(range(0, sys.maxunicode))
@@ -85,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)
@@ -195,12 +202,13 @@ def gen_emoji():
p('\treturn false;\n}')
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset()):
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()):
static = 'static inline ' if static else ''
chars = set()
for c in classes:
chars |= class_maps[c]
chars |= extra_chars
chars -= exclude
p(f'{static}bool\n{name}(char_type code) {{')
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
p('\tswitch(code) {')
@@ -251,8 +259,16 @@ def gen_ucd():
cz = {c for c in class_maps if c[0] in 'CZ'}
with create_header('kitty/unicode-data.c') as p:
p('#include "unicode-data.h"')
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters and non-characters', extra_chars=non_characters)
category_test(
'is_combining_char', p,
{c for c in class_maps if c.startswith('M')},
'M category (marks)',
# See https://github.com/harfbuzz/harfbuzz/issues/169
extra_chars=emoji_skin_tone_modifiers | {zwj}
)
category_test(
'is_ignored_char', p, 'Cc Cf Cs'.split(),
'Control characters and non-characters', extra_chars=non_characters, exclude={zwj})
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
category_test('is_CZ_category', p, cz, 'C and Z categories')
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
@@ -265,7 +281,7 @@ def gen_ucd():
p('combining_type mark_for_codepoint(char_type c) {')
rmap = codepoint_to_mark_map(p, mark_map)
p('}\n')
if rmap[0xfe0e] != 1280:
if rmap[0xfe0e] != 1281:
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
rmap[0xfe0e], rmap[0xfe0f]
))
@@ -349,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:

314
glfw/backend_utils.c vendored Normal file
View File

@@ -0,0 +1,314 @@
/*
* backend_utils.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define _GNU_SOURCE
#include "backend_utils.h"
#include "internal.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <float.h>
#include <time.h>
#include <stdio.h>
#ifdef __NetBSD__
#define ppoll pollts
#endif
static inline double
monotonic() {
struct timespec ts = {0};
#ifdef CLOCK_HIGHRES
clock_gettime(CLOCK_HIGHRES, &ts);
#elif CLOCK_MONOTONIC_RAW
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
#else
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
return (((double)ts.tv_nsec) / 1e9) + (double)ts.tv_sec;
}
void
update_fds(EventLoopData *eld) {
for (nfds_t i = 0; i < eld->watches_count; i++) {
Watch *w = eld->watches + i;
eld->fds[i].fd = w->fd;
eld->fds[i].events = w->enabled ? w->events : 0;
}
}
static id_type watch_counter = 0;
id_type
addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) {
if (eld->watches_count >= sizeof(eld->watches)/sizeof(eld->watches[0])) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many watches added");
return 0;
}
Watch *w = eld->watches + eld->watches_count++;
w->name = name;
w->fd = fd; w->events = events; w->enabled = enabled;
w->callback = cb;
w->callback_data = cb_data;
w->id = ++watch_counter;
update_fds(eld);
return w->id;
}
#define removeX(which, item_id, update_func) {\
for (nfds_t i = 0; i < eld->which##_count; i++) { \
if (eld->which[i].id == item_id) { \
eld->which##_count--; \
if (i < eld->which##_count) { \
memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \
} \
update_func(eld); break; \
}}}
void
removeWatch(EventLoopData *eld, id_type watch_id) {
removeX(watches, watch_id, update_fds);
}
void
toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) {
for (nfds_t i = 0; i < eld->watches_count; i++) {
if (eld->watches[i].id == watch_id) {
if (eld->watches[i].enabled != enabled) {
eld->watches[i].enabled = enabled;
update_fds(eld);
}
break;
}
}
}
static id_type timer_counter = 0;
static int
compare_timers(const void *a_, const void *b_) {
const Timer *a = (const Timer*)a_, *b = (const Timer*)b_;
return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0;
}
static inline void
update_timers(EventLoopData *eld) {
if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers);
}
id_type
addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data) {
if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
return 0;
}
Timer *t = eld->timers + eld->timers_count++;
t->interval = interval;
t->name = name;
t->trigger_at = enabled ? monotonic() + interval : DBL_MAX;
t->callback = cb;
t->callback_data = cb_data;
t->id = ++timer_counter;
update_timers(eld);
return t->id;
}
void
removeTimer(EventLoopData *eld, id_type timer_id) {
removeX(timers, timer_id, update_timers);
}
void
toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) {
for (nfds_t i = 0; i < eld->timers_count; i++) {
if (eld->timers[i].id == timer_id) {
double trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : DBL_MAX;
if (trigger_at != eld->timers[i].trigger_at) {
eld->timers[i].trigger_at = trigger_at;
update_timers(eld);
}
break;
}
}
}
void
changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) {
for (nfds_t i = 0; i < eld->timers_count; i++) {
if (eld->timers[i].id == timer_id) {
eld->timers[i].interval = interval;
break;
}
}
}
double
prepareForPoll(EventLoopData *eld, double timeout) {
for (nfds_t i = 0; i < eld->watches_count; i++) eld->fds[i].revents = 0;
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return timeout;
double now = monotonic(), next_repeat_at = eld->timers[0].trigger_at;
if (timeout < 0 || now + timeout > next_repeat_at) {
timeout = next_repeat_at <= now ? 0 : next_repeat_at - now;
}
return timeout;
}
int
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
const long seconds = (long) timeout;
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
struct timespec tv = { seconds, nanoseconds };
return ppoll(fds, nfds, &tv, NULL);
}
static void
dispatchEvents(EventLoopData *eld) {
for (nfds_t i = 0; i < eld->watches_count; i++) {
Watch *ww = eld->watches + i;
struct pollfd *pfd = eld->fds + i;
if (pfd->revents & ww->events) {
ww->ready = 1;
if (ww->callback) ww->callback(ww->fd, pfd->revents, ww->callback_data);
} else ww->ready = 0;
}
}
unsigned
dispatchTimers(EventLoopData *eld) {
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0;
static struct { timer_callback_func func; id_type id; void* data; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])];
unsigned num_dispatches = 0;
double now = monotonic();
for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) {
eld->timers[i].trigger_at = now + eld->timers[i].interval;
dispatches[num_dispatches].func = eld->timers[i].callback;
dispatches[num_dispatches].id = eld->timers[i].id;
dispatches[num_dispatches].data = eld->timers[i].callback_data;
num_dispatches++;
}
// we dispatch separately so that the callbacks can modify timers
for (unsigned i = 0; i < num_dispatches; i++) {
dispatches[i].func(dispatches[i].id, dispatches[i].data);
}
if (num_dispatches) update_timers(eld);
return num_dispatches;
}
static void
drain_wakeup_fd(int fd, int events, void* data) {
static char drain_buf[64];
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
}
void
initPollData(EventLoopData *eld, int wakeup_fd, int display_fd) {
addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL);
addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL);
}
int
pollForEvents(EventLoopData *eld, double timeout) {
int read_ok = 0;
timeout = prepareForPoll(eld, timeout);
int result;
double end_time = monotonic() + timeout;
while(1) {
if (timeout >= 0) {
errno = 0;
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
int saved_errno = errno;
dispatchTimers(eld);
if (result > 0) {
dispatchEvents(eld);
read_ok = eld->watches[0].ready;
break;
}
timeout = end_time - monotonic();
if (timeout <= 0) break;
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
break;
} else {
errno = 0;
result = poll(eld->fds, eld->watches_count, -1);
int saved_errno = errno;
dispatchTimers(eld);
if (result > 0) {
dispatchEvents(eld);
read_ok = eld->watches[0].ready;
}
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
break;
}
}
return read_ok;
}
void
closeFds(int *fds, size_t count) {
while(count--) {
if (*fds > 0) {
close(*fds);
*fds = -1;
}
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;
}

72
glfw/backend_utils.h vendored
View File

@@ -25,42 +25,50 @@
//========================================================================
#pragma once
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <unistd.h>
#ifdef __NetBSD__
#define ppoll pollts
#endif
typedef unsigned long long id_type;
typedef void(*watch_callback_func)(int, int, void*);
typedef void(*timer_callback_func)(id_type, void*);
static inline void
drainFd(int fd) {
static char drain_buf[64];
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
}
typedef struct {
int fd, events, enabled, ready;
watch_callback_func callback;
void *callback_data;
id_type id;
const char *name;
} Watch;
typedef struct {
id_type id;
double interval, trigger_at;
timer_callback_func callback;
void *callback_data;
const char *name;
} Timer;
static inline int
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
const long seconds = (long) timeout;
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
struct timespec tv = { seconds, nanoseconds };
return ppoll(fds, nfds, &tv, NULL);
}
typedef struct {
struct pollfd fds[32];
int wakeupFds[2];
nfds_t watches_count, timers_count;
Watch watches[32];
Timer timers[128];
} EventLoopData;
static inline void
initPollData(struct pollfd *fds, int wakeup_fd, int display_fd) {
fds[0].fd = wakeup_fd; fds[1].fd = display_fd;
fds[0].events = POLLIN; fds[1].events = POLLIN;
}
static inline void
closeFds(int *fds, size_t count) {
while(count--) {
if (*fds > 0) {
close(*fds);
*fds = -1;
}
fds++;
}
}
id_type addWatch(EventLoopData *eld, const char *name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data);
void removeWatch(EventLoopData *eld, id_type watch_id);
void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled);
id_type addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data);
void removeTimer(EventLoopData *eld, id_type timer_id);
void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
void changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval);
double prepareForPoll(EventLoopData *eld, double timeout);
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout);
int pollForEvents(EventLoopData *eld, double timeout);
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
//
@@ -69,7 +73,6 @@ static void changeToResourcesDirectory(void)
static void createKeyTables(void)
{
int scancode;
_glfw.ns.debug_keyboard = getenv("GLFW_DEBUG_KEYBOARD") != NULL;
memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes));
@@ -275,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
//////////////////////////////////////////////////////////////////////////
@@ -295,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];
@@ -346,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;
}

12
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,11 +118,12 @@ typedef struct _GLFWlibraryNS
TISInputSourceRef inputSource;
IOHIDManagerRef hidManager;
id unicodeData;
id listener;
id helper;
id keyUpMonitor;
id keyDownMonitor;
char keyName[64];
char text[256];
GLFWbool debug_keyboard;
short int keycodes[256];
short int scancodes[GLFW_KEY_LAST + 1];
char* clipboardString;

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
//
@@ -209,7 +213,7 @@ static int translateFlags(NSUInteger flags)
return mods;
}
#define debug_key(...) if (_glfw.ns.debug_keyboard) NSLog(__VA_ARGS__)
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) NSLog(__VA_ARGS__)
static inline const char*
format_mods(int mods) {
@@ -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,
@@ -739,7 +775,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
}
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
NSTrackingActiveInKeyWindow |
NSTrackingActiveAlways |
NSTrackingEnabledDuringMouseDrag |
NSTrackingCursorUpdate |
NSTrackingInVisibleRect |
@@ -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),
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));
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
}
@@ -884,58 +914,65 @@ is_ascii_control_char(char x) {
deltaX = [event scrollingDeltaX];
deltaY = [event scrollingDeltaY];
if ([event hasPreciseScrollingDeltas])
{
deltaX *= 0.1;
deltaY *= 0.1;
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);
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);
}
@@ -943,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;
@@ -1045,49 +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
// 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.
- (void)sendEvent:(NSEvent *)event
{
if ([event type] == NSEventTypeKeyUp &&
([event modifierFlags] & NSEventModifierFlagCommand))
GLFWContentView *view = [self contentView];
if (view)
{
[[self keyWindow] sendEvent:event];
_GLFWwindow *window = [view glfwWindow];
if (window && window->ns.toggleFullscreenCallback && window->ns.toggleFullscreenCallback((GLFWwindow*)window) == 1)
return;
}
else
[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
@@ -1197,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];
@@ -1232,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;
@@ -1537,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];
@@ -1747,9 +1747,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
void _glfwPlatformPollEvents(void)
{
if (!initializeAppKit())
return;
for (;;)
{
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
@@ -1759,7 +1756,7 @@ void _glfwPlatformPollEvents(void)
if (event == nil)
break;
[NSApp sendEvent:event];
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
}
[_glfw.ns.autoreleasePool drain];
@@ -1775,7 +1772,7 @@ void _glfwPlatformWaitEvents(void)
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[NSApp sendEvent:event];
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
_glfwPlatformPollEvents();
}
@@ -1787,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();
}
@@ -1886,51 +1882,47 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
NSImage* native;
NSBitmapImageRep* rep;
if (!initializeAppKit())
return GLFW_FALSE;
rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:image->width
pixelsHigh:image->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
bytesPerRow:image->width * 4
bitsPerPixel:32];
if (rep == nil)
return GLFW_FALSE;
memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
[native addRepresentation:rep];
if (native == nil)
return GLFW_FALSE;
for (int i = 0; i < count; i++) {
const GLFWimage *src = image + i;
rep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:src->width
pixelsHigh:src->height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:src->width * 4
bitsPerPixel:32];
if (rep == nil)
return GLFW_FALSE;
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
[native addRepresentation:rep];
[rep release];
}
cursor->ns.object = [[NSCursor alloc] initWithImage:native
hotSpot:NSMakePoint(xhot, yhot)];
[native release];
[rep release];
if (cursor->ns.object == nil)
return GLFW_FALSE;
return GLFW_TRUE;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
{
if (!initializeAppKit())
return GLFW_FALSE;
if (shape == GLFW_ARROW_CURSOR)
cursor->ns.object = [NSCursor arrowCursor];
@@ -1970,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,
@@ -2098,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;
@@ -2165,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);

295
glfw/dbus_glfw.c vendored Normal file
View File

@@ -0,0 +1,295 @@
//========================================================================
// GLFW 3.3 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#include "internal.h"
#include "dbus_glfw.h"
#include <stdlib.h>
#include <string.h>
static inline void
report_error(DBusError *err, const char *fmt, ...) {
static char buf[1024];
va_list args;
va_start(args, fmt);
int n = vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
snprintf(buf + n, sizeof(buf), ". DBUS error: %s", err->message);
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
dbus_error_free(err);
}
static _GLFWDBUSData *dbus_data = NULL;
GLFWbool
glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) {
dbus->eld = eld;
dbus_data = dbus;
return GLFW_TRUE;
}
static void
on_dbus_watch_ready(int fd, int events, void *data) {
DBusWatch *watch = (DBusWatch*)data;
unsigned int flags = 0;
if (events & POLLERR) flags |= DBUS_WATCH_ERROR;
if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP;
if (events & POLLIN) flags |= DBUS_WATCH_READABLE;
if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE;
dbus_watch_handle(watch, flags);
}
static inline int
events_for_watch(DBusWatch *watch) {
int events = 0;
unsigned int flags = dbus_watch_get_flags(watch);
if (flags & DBUS_WATCH_READABLE) events |= POLLIN;
if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT;
return events;
}
static dbus_bool_t
add_dbus_watch(DBusWatch *watch, void *data) {
id_type watch_id = addWatch(dbus_data->eld, data, dbus_watch_get_unix_fd(watch), events_for_watch(watch), dbus_watch_get_enabled(watch), on_dbus_watch_ready, watch);
if (!watch_id) return FALSE;
id_type *idp = malloc(sizeof(id_type));
if (!idp) return FALSE;
*idp = watch_id;
dbus_watch_set_data(watch, idp, free);
return TRUE;
}
static void
remove_dbus_watch(DBusWatch *watch, void *data) {
id_type *idp = dbus_watch_get_data(watch);
if (idp) removeWatch(dbus_data->eld, *idp);
}
static void
toggle_dbus_watch(DBusWatch *watch, void *data) {
id_type *idp = dbus_watch_get_data(watch);
if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch));
}
static void
on_dbus_timer_ready(id_type timer_id, void *data) {
if (data) {
DBusTimeout *t = (DBusTimeout*)data;
dbus_timeout_handle(t);
}
}
static dbus_bool_t
add_dbus_timeout(DBusTimeout *timeout, void *data) {
int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
double interval = ((double)dbus_timeout_get_interval(timeout)) / 1000.0;
if (interval < 0) return FALSE;
id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, on_dbus_timer_ready, timeout);
if (!timer_id) return FALSE;
id_type *idp = malloc(sizeof(id_type));
if (!idp) {
removeTimer(dbus_data->eld, timer_id);
return FALSE;
}
*idp = timer_id;
dbus_timeout_set_data(timeout, idp, free);
return TRUE;
}
static void
remove_dbus_timeout(DBusTimeout *timeout, void *data) {
id_type *idp = dbus_timeout_get_data(timeout);
if (idp) removeTimer(dbus_data->eld, *idp);
}
static void
toggle_dbus_timeout(DBusTimeout *timeout, void *data) {
id_type *idp = dbus_timeout_get_data(timeout);
if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout));
}
DBusConnection*
glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, GLFWbool register_on_bus) {
DBusError err;
dbus_error_init(&err);
DBusConnection *ans = dbus_connection_open_private(path, &err);
if (!ans) {
report_error(&err, err_msg);
return NULL;
}
dbus_connection_set_exit_on_disconnect(ans, FALSE);
dbus_error_free(&err);
if (register_on_bus) {
if (!dbus_bus_register(ans, &err)) {
report_error(&err, err_msg);
return NULL;
}
}
if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path);
dbus_connection_close(ans);
dbus_connection_unref(ans);
return NULL;
}
if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path);
dbus_connection_close(ans);
dbus_connection_unref(ans);
return NULL;
}
return ans;
}
void
glfw_dbus_dispatch(DBusConnection *conn) {
while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
}
void
glfw_dbus_terminate(_GLFWDBUSData *dbus) {
if (dbus_data) {
dbus_data->eld = NULL;
dbus_data = NULL;
}
}
void
glfw_dbus_close_connection(DBusConnection *conn) {
dbus_connection_close(conn);
dbus_connection_unref(conn);
}
GLFWbool
glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) {
DBusError err;
dbus_error_init(&err);
va_list ap;
va_start(ap, failmsg);
int firstarg = va_arg(ap, int);
GLFWbool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? GLFW_TRUE : GLFW_FALSE;
va_end(ap);
if (!ret) report_error(&err, failmsg);
return ret;
}
typedef struct {
dbus_pending_callback callback;
void *user_data;
} MethodResponse;
static const char*
format_message_error(DBusError *err) {
static char buf[1024];
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
return buf;
}
static void
method_reply_received(DBusPendingCall *pending, void *user_data) {
MethodResponse *res = (MethodResponse*)user_data;
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
if (msg) {
DBusError err;
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
else res->callback(msg, NULL, res->user_data);
dbus_message_unref(msg);
}
}
static GLFWbool
call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, va_list ap) {
if (!conn) return GLFW_FALSE;
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
if (!msg) return GLFW_FALSE;
GLFWbool retval = GLFW_FALSE;
MethodResponse *res = malloc(sizeof(MethodResponse));
if (!res) { dbus_message_unref(msg); return GLFW_FALSE; }
res->callback = callback;
res->user_data = user_data;
int firstarg = va_arg(ap, int);
if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
if (callback) {
DBusPendingCall *pending = NULL;
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
dbus_pending_call_set_notify(pending, method_reply_received, res, free);
retval = GLFW_TRUE;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
}
} else {
if (dbus_connection_send(conn, msg, NULL)) {
retval = GLFW_TRUE;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
}
}
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
}
dbus_message_unref(msg);
return retval;
}
GLFWbool
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
GLFWbool retval;
va_list ap;
va_start(ap, user_data);
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
va_end(ap);
return retval;
}
GLFWbool
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) {
GLFWbool retval;
va_list ap;
va_start(ap, method);
retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, ap);
va_end(ap);
return retval;
}
int
glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) {
va_list ap;
va_start(ap, interface);
int ans = -1, num = -1;
while(1) {
num++;
const char *name = va_arg(ap, const char*);
if (!name) break;
if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; }
}
va_end(ap);
return ans;
}

50
glfw/dbus_glfw.h vendored Normal file
View File

@@ -0,0 +1,50 @@
//========================================================================
// GLFW 3.3 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#pragma once
#include <dbus/dbus.h>
#include "backend_utils.h"
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
typedef struct {
EventLoopData* eld;
} _GLFWDBUSData;
GLFWbool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld);
void glfw_dbus_terminate(_GLFWDBUSData *dbus);
DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, GLFWbool register_on_bus);
void glfw_dbus_close_connection(DBusConnection *conn);
GLFWbool
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
GLFWbool
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...);
void glfw_dbus_dispatch(DBusConnection *);
GLFWbool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...);
int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...);

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()
@@ -55,7 +55,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
at_least_version('xkbcommon', 0, 5)
if module == 'x11':
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
ans.ldpaths.extend(pkg_config(dep, '--libs'))
@@ -67,13 +67,23 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.')))
ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code'
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
for p in ans.wayland_protocols:
ans.sources.append(wayland_protocol_file_name(p))
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon'.split():
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
@@ -87,7 +97,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
dest = wayland_protocol_file_name(src, ext)
dest = os.path.join(dest_dir, dest)
if newer(dest, src):
q = 'client-header' if ext == 'h' else 'code'
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
run_tool([env.wayland_scanner, q, src, dest],
desc='Generating {} ...'.format(emphasis(os.path.basename(dest))))
@@ -126,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):
@@ -187,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
@@ -200,14 +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))
@@ -220,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);
@@ -263,34 +269,8 @@ unload_glfw() {
def main():
os.chdir(sys.argv[-1])
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('internal.h', lambda x: x.replace('../include/GLFW/', ''))
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)
os.chdir(os.path.dirname(os.path.abspath(__file__)))
generate_wrappers('glfw3.h')
if __name__ == '__main__':

87
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
@@ -1057,6 +1064,8 @@ extern "C" {
/*! @addtogroup init
* @{ */
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
#define GLFW_DEBUG_KEYBOARD 0x00050002
#define GLFW_ENABLE_JOYSTICKS 0x00050003
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
#define GLFW_COCOA_MENUBAR 0x00051002
@@ -1371,15 +1380,23 @@ 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.
* (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
*
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
* @since Changed in version 4.0. Added `flags` parameter.
*
* @ingroup input
*/
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
/*! @brief The function signature for key callbacks.
*
@@ -1398,7 +1415,9 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
* held down.
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
* @param[in] reserved Reserved for future use.
* @param[in] Used for Input Method events. Zero for normal key events.
* A value of 1 means the pre-edit text for the input event has been changed.
* A value of 2 means the text should be committed.
*
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
@@ -2109,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.
@@ -2190,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.
*
@@ -2918,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.
@@ -4006,8 +4051,8 @@ GLFWAPI int glfwGetKey(GLFWwindow* window, int key);
* `GLFW_RELEASE`.
*
* If the @ref GLFW_STICKY_MOUSE_BUTTONS input mode is enabled, this function
* `GLFW_PRESS` the first time you call it for a mouse button that was pressed,
* even if that mouse button has already been released.
* returns `GLFW_PRESS` the first time you call it for a mouse button that was
* pressed, even if that mouse button has already been released.
*
* @param[in] window The desired window.
* @param[in] button The desired [mouse button](@ref buttons).
@@ -4122,6 +4167,7 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
* @param[in] image The desired cursor image.
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
* @param[in] count The number of images. Used on Cocoa for retina cursors. The first image should be the 1:1 scale image.
* @return The handle of the created cursor, or `NULL` if an
* [error](@ref error_handling) occurred.
*
@@ -4137,11 +4183,11 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
* @sa @ref glfwDestroyCursor
* @sa @ref glfwCreateStandardCursor
*
* @since Added in version 3.1.
* @since Added in version 3.1. Changed in 4.0 to add the count parameter.
*
* @ingroup input
*/
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count);
/*! @brief Creates a cursor with a standard shape.
*
@@ -4226,6 +4272,23 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
*/
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
/*! @brief Notifies the OS Input Method Event system of changes to application input state
*
* Used to notify the IME system of changes in state such as focus gained/lost
* and text cursor position.
*
* @param which: What data to notify. 1 means focus and 2 means cursor position.
* @param a, b, c, d: Interpreted based on the value of which. When which is 1
* a is interpreted as a boolean indicating focus gained/lost. When which is 2
* a, b, c, d are the cursor x, y, width and height values (in the window co-ordinate
* system).
*
* @ingroup input
* @since Added in version 4.0
*/
GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int c, int d);
/*! @brief Sets the mouse button callback.
*
* This function sets the mouse button callback of the specified window, which
@@ -4786,6 +4849,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref gamepad
* @sa @ref glfwUpdateGamepadMappings
* @sa @ref glfwJoystickIsGamepad

426
glfw/ibus_glfw.c vendored Normal file
View File

@@ -0,0 +1,426 @@
//========================================================================
// GLFW 3.3 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <limits.h>
#include "internal.h"
#include "ibus_glfw.h"
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
enum Capabilities {
IBUS_CAP_PREEDIT_TEXT = 1 << 0,
IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
IBUS_CAP_LOOKUP_TABLE = 1 << 2,
IBUS_CAP_FOCUS = 1 << 3,
IBUS_CAP_PROPERTY = 1 << 4,
IBUS_CAP_SURROUNDING_TEXT = 1 << 5
};
static inline GLFWbool
test_env_var(const char *name, const char *val) {
const char *q = getenv(name);
return (q && strcmp(q, val) == 0) ? GLFW_TRUE : GLFW_FALSE;
}
static inline GLFWbool
MIN(size_t a, size_t b) {
return a < b ? a : b;
}
static const char*
get_ibus_text_from_message(DBusMessage *msg) {
/* The message structure is (from dbus-monitor)
variant struct {
string "IBusText"
array [
]
string "ash "
variant struct {
string "IBusAttrList"
array [
]
array [
]
}
}
*/
const char *text = NULL;
const char *struct_id = NULL;
DBusMessageIter iter, sub1, sub2;
dbus_message_iter_init(msg, &iter);
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return NULL;
dbus_message_iter_recurse(&iter, &sub1);
if (dbus_message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) return NULL;
dbus_message_iter_recurse(&sub1, &sub2);
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
dbus_message_iter_get_basic(&sub2, &struct_id);
if (!struct_id || strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) return NULL;
dbus_message_iter_next(&sub2);
dbus_message_iter_next(&sub2);
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
dbus_message_iter_get_basic(&sub2, &text);
return text;
}
static inline void
send_text(const char *text, int state) {
_GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) {
w->callbacks.keyboard((GLFWwindow*) w, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, text, state);
}
}
// Connection handling {{{
static DBusHandlerResult
message_handler(DBusConnection *conn, DBusMessage *msg, void *user_data) {
// To monitor signals from IBUS, use
//  dbus-monitor --address `ibus address` "type='signal',interface='org.freedesktop.IBus.InputContext'"
_GLFWIBUSData *ibus = (_GLFWIBUSData*)user_data;
(void)ibus;
const char *text;
switch(glfw_dbus_match_signal(msg, IBUS_INPUT_INTERFACE, "CommitText", "UpdatePreeditText", "HidePreeditText", "ShowPreeditText", NULL)) {
case 0:
text = get_ibus_text_from_message(msg);
debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)");
send_text(text, 2);
break;
case 1:
text = get_ibus_text_from_message(msg);
send_text(text, 1);
debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)");
break;
case 2:
debug("IBUS: HidePreeditText\n");
break;
case 3:
debug("IBUS: ShowPreeditText\n");
break;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static inline const char*
get_ibus_address_file_name(void) {
const char *addr;
static char ans[PATH_MAX];
addr = getenv("IBUS_ADDRESS");
int offset = 0;
if (addr && addr[0]) {
memcpy(ans, addr, MIN(strlen(addr), sizeof(ans)));
return ans;
}
const char *de = getenv("DISPLAY");
if (!de || !de[0]) de = ":0.0";
char *display = _glfw_strdup(de);
const char *host = display;
char *disp_num = strrchr(display, ':');
char *screen_num = strrchr(display, '.');
if (!disp_num) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon");
free(display);
return NULL;
}
*disp_num = 0;
disp_num++;
if (screen_num) *screen_num = 0;
if (!*host) host = "unix";
memset(ans, 0, sizeof(ans));
const char *conf_env = getenv("XDG_CONFIG_HOME");
if (conf_env && conf_env[0]) {
offset = snprintf(ans, sizeof(ans), "%s", conf_env);
} else {
conf_env = getenv("HOME");
if (!conf_env || !conf_env[0]) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set");
free(display);
return NULL;
}
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
}
char *key = dbus_get_local_machine_id();
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
dbus_free(key);
free(display);
return ans;
}
static inline GLFWbool
read_ibus_address(_GLFWIBUSData *ibus) {
static char buf[1024];
struct stat s;
FILE *addr_file = fopen(ibus->address_file_name, "r");
if (!addr_file) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to open IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
return GLFW_FALSE;
}
int stat_result = fstat(fileno(addr_file), &s);
GLFWbool found = GLFW_FALSE;
while (fgets(buf, sizeof(buf), addr_file)) {
if (strncmp(buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
size_t sz = strlen(buf);
if (buf[sz-1] == '\n') buf[sz-1] = 0;
if (buf[sz-2] == '\r') buf[sz-2] = 0;
found = GLFW_TRUE;
break;
}
}
fclose(addr_file); addr_file = NULL;
if (stat_result != 0) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to stat IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
return GLFW_FALSE;
}
ibus->address_file_mtime = s.st_mtime;
if (found) {
free((void*)ibus->address);
ibus->address = _glfw_strdup(buf + sizeof("IBUS_ADDRESS=") - 1);
return GLFW_TRUE;
}
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not find IBUS_ADDRESS in %s", ibus->address_file_name);
return GLFW_FALSE;
}
void
input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg);
return;
}
const char *path = NULL;
if (!glfw_dbus_get_args(msg, "Failed to get IBUS context path from reply", DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return;
_GLFWIBUSData *ibus = (_GLFWIBUSData*)data;
free((void*)ibus->input_ctx_path);
ibus->input_ctx_path = _glfw_strdup(path);
if (!ibus->input_ctx_path) return;
dbus_bus_add_match(ibus->conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
DBusObjectPathVTable ibus_vtable = {.message_function = message_handler};
dbus_connection_try_register_object_path(ibus->conn, ibus->input_ctx_path, &ibus_vtable, ibus, NULL);
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
ibus->ok = GLFW_TRUE;
glfw_ibus_set_focused(ibus, GLFW_FALSE);
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
debug("Connected to IBUS daemon for IME input management\n");
}
GLFWbool
setup_connection(_GLFWIBUSData *ibus) {
const char *client_name = "GLFW_Application";
const char *address_file_name = get_ibus_address_file_name();
ibus->ok = GLFW_FALSE;
if (!address_file_name) return GLFW_FALSE;
free((void*)ibus->address_file_name);
ibus->address_file_name = _glfw_strdup(address_file_name);
if (!read_ibus_address(ibus)) return GLFW_FALSE;
if (ibus->conn) {
glfw_dbus_close_connection(ibus->conn);
ibus->conn = NULL;
}
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", GLFW_FALSE);
if (!ibus->conn) return GLFW_FALSE;
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
if (!glfw_dbus_call_method_with_reply(
ibus->conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext", DBUS_TIMEOUT_USE_DEFAULT, input_context_created, ibus,
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) {
return GLFW_FALSE;
}
return GLFW_TRUE;
}
void
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
if (ibus->inited) return;
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
ibus->inited = GLFW_TRUE;
setup_connection(ibus);
}
void
glfw_ibus_terminate(_GLFWIBUSData *ibus) {
if (ibus->conn) {
glfw_dbus_close_connection(ibus->conn);
ibus->conn = NULL;
}
#define F(x) if (ibus->x) { free((void*)ibus->x); ibus->x = NULL; }
F(input_ctx_path);
F(address);
F(address_file_name);
#undef F
ibus->ok = GLFW_FALSE;
}
static GLFWbool
check_connection(_GLFWIBUSData *ibus) {
if (!ibus->inited) return GLFW_FALSE;
if (ibus->conn && dbus_connection_get_is_connected(ibus->conn)) {
return ibus->ok;
}
struct stat s;
if (stat(ibus->address_file_name, &s) != 0 || s.st_mtime != ibus->address_file_mtime) {
if (!read_ibus_address(ibus)) return GLFW_FALSE;
setup_connection(ibus);
}
return GLFW_FALSE;
}
void
glfw_ibus_dispatch(_GLFWIBUSData *ibus) {
if (ibus->conn) glfw_dbus_dispatch(ibus->conn);
}
// }}}
static void
simple_message(_GLFWIBUSData *ibus, const char *method) {
if (check_connection(ibus)) {
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
}
}
void
glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused) {
simple_message(ibus, focused ? "FocusIn" : "FocusOut");
}
void
glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) {
if (check_connection(ibus)) {
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID);
}
}
typedef enum
{
IBUS_SHIFT_MASK = 1 << 0,
IBUS_LOCK_MASK = 1 << 1,
IBUS_CONTROL_MASK = 1 << 2,
IBUS_MOD1_MASK = 1 << 3,
IBUS_MOD2_MASK = 1 << 4,
IBUS_MOD3_MASK = 1 << 5,
IBUS_MOD4_MASK = 1 << 6,
IBUS_MOD5_MASK = 1 << 7,
IBUS_BUTTON1_MASK = 1 << 8,
IBUS_BUTTON2_MASK = 1 << 9,
IBUS_BUTTON3_MASK = 1 << 10,
IBUS_BUTTON4_MASK = 1 << 11,
IBUS_BUTTON5_MASK = 1 << 12,
/* The next few modifiers are used by XKB, so we skip to the end.
* Bits 15 - 23 are currently unused. Bit 29 is used internally.
*/
/* ibus mask */
IBUS_HANDLED_MASK = 1 << 24,
IBUS_FORWARD_MASK = 1 << 25,
IBUS_IGNORED_MASK = IBUS_FORWARD_MASK,
IBUS_SUPER_MASK = 1 << 26,
IBUS_HYPER_MASK = 1 << 27,
IBUS_META_MASK = 1 << 28,
IBUS_RELEASE_MASK = 1 << 30,
IBUS_MODIFIER_MASK = 0x5f001fff
} IBusModifierType;
static inline uint32_t
ibus_key_state(unsigned int glfw_modifiers, int action) {
uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0;
#define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i
M(SHIFT, IBUS_SHIFT_MASK);
M(CAPS_LOCK, IBUS_LOCK_MASK);
M(CONTROL, IBUS_CONTROL_MASK);
M(ALT, IBUS_MOD1_MASK);
M(NUM_LOCK, IBUS_MOD2_MASK);
M(SUPER, IBUS_MOD4_MASK);
#undef M
return ans;
}
void
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
uint32_t handled = 0;
KeyEvent *ev = (KeyEvent*)data;
GLFWbool is_release = ev->action == GLFW_RELEASE;
GLFWbool failed = GLFW_FALSE;
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
failed = GLFW_TRUE;
} else {
glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID);
debug("IBUS processed scancode: 0x%x release: %d handled: %u\n", ev->keycode, is_release, handled);
}
glfw_xkb_key_from_ime(ev, handled ? GLFW_TRUE : GLFW_FALSE, failed);
free(ev);
}
GLFWbool
ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus) {
if (!check_connection(ibus)) return GLFW_FALSE;
KeyEvent *ev = malloc(sizeof(KeyEvent));
if (!ev) return GLFW_FALSE;
memcpy(ev, ev_, sizeof(KeyEvent));
uint32_t state = ibus_key_state(ev->glfw_modifiers, ev->action);
if (!glfw_dbus_call_method_with_reply(
ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
3000, key_event_processed, ev,
DBUS_TYPE_UINT32, &ev->ibus_sym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32,
&state, DBUS_TYPE_INVALID)) {
free(ev);
return GLFW_FALSE;
}
return GLFW_TRUE;
}

55
glfw/ibus_glfw.h vendored Normal file
View File

@@ -0,0 +1,55 @@
//========================================================================
// GLFW 3.3 XKB - www.glfw.org
//------------------------------------------------------------------------
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would
// be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
//========================================================================
#pragma once
#include "dbus_glfw.h"
#include <xkbcommon/xkbcommon.h>
typedef struct {
GLFWbool ok, inited;
time_t address_file_mtime;
DBusConnection *conn;
const char *input_ctx_path, *address_file_name, *address;
} _GLFWIBUSData;
typedef struct {
xkb_keycode_t keycode, ibus_keycode;
xkb_keysym_t keysym, ibus_sym;
unsigned int glfw_modifiers;
int action;
GLFWid window_id;
int glfw_keycode;
char text[64];
} KeyEvent;
void glfw_connect_to_ibus(_GLFWIBUSData *ibus);
void glfw_ibus_terminate(_GLFWIBUSData *ibus);
void glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused);
void glfw_ibus_dispatch(_GLFWIBUSData *ibus);
GLFWbool ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus);
void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h);

14
glfw/init.c vendored
View File

@@ -51,6 +51,8 @@ static GLFWerrorfun _glfwErrorCallback;
static _GLFWinitconfig _glfwInitHints =
{
GLFW_TRUE, // hat buttons
GLFW_FALSE, // debug keyboard
GLFW_TRUE, // enable joystick
{
GLFW_TRUE, // macOS menu bar
GLFW_TRUE // macOS bundle chdir
@@ -114,8 +116,9 @@ static void terminate(void)
char* _glfw_strdup(const char* source)
{
const size_t length = strlen(source);
char* result = calloc(length + 1, 1);
strcpy(result, source);
char* result = malloc(length + 1);
memcpy(result, source, length);
result[length] = 0;
return result;
}
@@ -252,9 +255,15 @@ GLFWAPI void glfwInitHint(int hint, int value)
{
switch (hint)
{
case GLFW_ENABLE_JOYSTICKS:
_glfwInitHints.enableJoysticks = value;
return;
case GLFW_JOYSTICK_HAT_BUTTONS:
_glfwInitHints.hatButtons = value;
return;
case GLFW_DEBUG_KEYBOARD:
_glfwInitHints.debugKeyboard = value;
return;
case GLFW_COCOA_CHDIR_RESOURCES:
_glfwInitHints.ns.chdir = value;
return;
@@ -311,4 +320,3 @@ GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun)
_GLFW_SWAP_POINTERS(_glfwErrorCallback, cbfun);
return cbfun;
}

38
glfw/input.c vendored
View File

@@ -286,10 +286,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action,
// Notifies shared code of a scroll event
//
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
{
if (window->callbacks.scroll)
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
}
// Notifies shared code of a mouse button click event
@@ -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";
@@ -799,11 +800,12 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos)
}
}
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count)
{
_GLFWcursor* cursor;
assert(image != NULL);
assert(count > 0);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -811,7 +813,7 @@ GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
cursor->next = _glfw.cursorListHead;
_glfw.cursorListHead = cursor;
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot))
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot, count))
{
glfwDestroyCursor((GLFWcursor*) cursor);
return NULL;
@@ -908,6 +910,18 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboard
return cbfun;
}
GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, int which, int a, int b, int c, int d) {
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT();
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
_glfwPlatformUpdateIMEState(window, which, a, b, c, d);
#else
(void)window; (void)which; (void)a; (void)b; (void)c; (void)d;
#endif
}
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
GLFWmousebuttonfun cbfun)
{
@@ -1362,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);

30
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) || \
@@ -59,6 +61,7 @@
#define _GLFW_MESSAGE_SIZE 1024
typedef int GLFWbool;
typedef unsigned long long GLFWid;
typedef struct _GLFWerror _GLFWerror;
typedef struct _GLFWinitconfig _GLFWinitconfig;
@@ -126,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
@@ -188,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
@@ -242,6 +242,8 @@ struct _GLFWerror
struct _GLFWinitconfig
{
GLFWbool hatButtons;
GLFWbool debugKeyboard;
GLFWbool enableJoysticks;
struct {
GLFWbool menubar;
GLFWbool chdir;
@@ -268,6 +270,7 @@ struct _GLFWwndconfig
GLFWbool maximized;
GLFWbool centerCursor;
GLFWbool focusOnShow;
GLFWbool scaleToMonitor;
struct {
GLFWbool retina;
char frameName[256];
@@ -276,6 +279,9 @@ struct _GLFWwndconfig
char className[256];
char instanceName[256];
} x11;
struct {
char appId[256];
} wl;
};
// Context configuration
@@ -376,6 +382,7 @@ struct _GLFWwindow
GLFWbool focusOnShow;
GLFWbool shouldClose;
void* userPointer;
GLFWid id;
GLFWvidmode videoMode;
_GLFWmonitor* monitor;
_GLFWcursor* cursor;
@@ -524,6 +531,7 @@ struct _GLFWlibrary
_GLFWerror* errorListHead;
_GLFWcursor* cursorListHead;
_GLFWwindow* windowListHead;
GLFWid focusedWindowId;
_GLFWmonitor** monitors;
int monitorCount;
@@ -560,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;
@@ -599,7 +605,7 @@ void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image, int xhot, int yhot);
const GLFWimage* image, int xhot, int yhot, int count);
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
@@ -613,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);
@@ -647,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);
@@ -669,6 +680,7 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
void _glfwPlatformPollEvents(void);
void _glfwPlatformWaitEvents(void);
@@ -712,7 +724,7 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window);
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
@@ -765,5 +777,7 @@ const char* _glfwGetKeyName(int key);
GLFWbool _glfwInitVulkan(int mode);
void _glfwTerminateVulkan(void);
const char* _glfwGetVulkanResultString(VkResult result);
_GLFWwindow* _glfwFocusedWindow();
_GLFWwindow* _glfwWindowForId(GLFWid id);
char* _glfw_strdup(const char* source);

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)
{
}

7
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)
{
}
@@ -267,7 +272,7 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
return GLFW_TRUE;
}

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

@@ -58,6 +58,8 @@
"posix_time.h",
"posix_thread.h",
"xkb_glfw.h",
"dbus_glfw.h",
"ibus_glfw.h",
"backend_utils.h",
"egl_context.h",
"osmesa_context.h",
@@ -69,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",
@@ -78,15 +82,18 @@
"posix_time.c",
"posix_thread.c",
"xkb_glfw.c",
"dbus_glfw.c",
"ibus_glfw.c",
"egl_context.c",
"osmesa_context.c",
"backend_utils.c",
"linux_joystick.c",
"null_joystick.c"
]
},
"wayland_protocols": [
1,
12
17
],
"win32": {
"headers": [
@@ -112,6 +119,8 @@
"headers": [
"x11_platform.h",
"xkb_glfw.h",
"dbus_glfw.h",
"ibus_glfw.h",
"backend_utils.h",
"posix_time.h",
"posix_thread.h",
@@ -126,13 +135,16 @@
"x11_monitor.c",
"x11_window.c",
"xkb_glfw.c",
"dbus_glfw.c",
"ibus_glfw.c",
"posix_time.c",
"posix_thread.c",
"glx_context.c",
"egl_context.c",
"osmesa_context.c",
"backend_utils.c",
"linux_joystick.c",
"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,20 +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 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)
#define IsWindowsXPOrGreater() \
_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
@@ -206,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*);
@@ -223,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
@@ -276,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;
@@ -300,6 +331,7 @@ typedef struct _GLFWlibraryWin32
_GLFWwindow* disabledCursorWindow;
RAWINPUT* rawInput;
int rawInputSize;
UINT mouseTrailSize;
struct {
HINSTANCE instance;
@@ -322,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 {
@@ -337,6 +373,11 @@ typedef struct _GLFWlibraryWin32
PFN_GetDpiForMonitor GetDpiForMonitor_;
} shcore;
struct {
HINSTANCE instance;
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
} ntdll;
} _GLFWlibraryWin32;
// Win32-specific per-monitor data
@@ -392,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);
@@ -405,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);

289
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)
@@ -529,7 +524,18 @@ static void fitToMonitor(_GLFWwindow* window)
static void acquireMonitor(_GLFWwindow* window)
{
if (!_glfw.win32.acquiredMonitorCount)
{
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
// HACK: When mouse trails are enabled the cursor becomes invisible when
// the OpenGL ICD switches to page flipping
if (IsWindowsXPOrGreater())
{
SystemParametersInfo(SPI_GETMOUSETRAILS, 0, &_glfw.win32.mouseTrailSize, 0);
SystemParametersInfo(SPI_SETMOUSETRAILS, 0, 0, 0);
}
}
if (!window->monitor->window)
_glfw.win32.acquiredMonitorCount++;
@@ -546,8 +552,14 @@ static void releaseMonitor(_GLFWwindow* window)
_glfw.win32.acquiredMonitorCount--;
if (!_glfw.win32.acquiredMonitorCount)
{
SetThreadExecutionState(ES_CONTINUOUS);
// HACK: Restore mouse trail length saved in acquireMonitor
if (IsWindowsXPOrGreater())
SystemParametersInfo(SPI_SETMOUSETRAILS, _glfw.win32.mouseTrailSize, 0, 0);
}
_glfwInputMonitorWindow(window->monitor, NULL);
_glfwRestoreVideoModeWin32(window->monitor);
}
@@ -561,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;
@@ -986,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)
@@ -1039,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;
}
@@ -1129,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);
@@ -1168,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)
@@ -1227,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 //////
@@ -1378,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);
}
@@ -1408,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);
@@ -1464,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;
@@ -1485,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);
@@ -1546,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,
@@ -1607,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,
@@ -1643,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)
@@ -1839,7 +1990,7 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
if (!cursor->win32.handle)
@@ -1850,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,

40
glfw/window.c vendored
View File

@@ -48,6 +48,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
if (!focused)
{
int key, button;
_glfw.focusedWindowId = 0;
for (key = 0; key <= GLFW_KEY_LAST; key++)
{
@@ -63,7 +64,28 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
if (window->mouseButtons[button] == GLFW_PRESS)
_glfwInputMouseClick(window, button, GLFW_RELEASE, 0);
}
} else
_glfw.focusedWindowId = window->id;
}
_GLFWwindow* _glfwFocusedWindow() {
if (_glfw.focusedWindowId) {
_GLFWwindow *w = _glfw.windowListHead;
while (w) {
if (w->id == _glfw.focusedWindowId) return w;
w = w->next;
}
}
return NULL;
}
_GLFWwindow* _glfwWindowForId(GLFWid id) {
_GLFWwindow *w = _glfw.windowListHead;
while (w) {
if (w->id == _glfw.focusedWindowId) return w;
w = w->next;
}
return NULL;
}
// Notifies shared code that a window has moved
@@ -185,8 +207,10 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
if (!_glfwIsValidContextConfig(&ctxconfig))
return NULL;
static GLFWid windowIdCounter = 0;
window = calloc(1, sizeof(_GLFWwindow));
window->next = _glfw.windowListHead;
window->id = ++windowIdCounter;
_glfw.windowListHead = window;
window->videoMode.width = width;
@@ -369,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;
@@ -433,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);
@@ -690,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;

114
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)
@@ -164,7 +167,7 @@ static void pointerHandleMotion(void* data,
wl_fixed_t sy)
{
_GLFWwindow* window = _glfw.wl.pointerFocus;
const char* cursorName;
const char* cursorName = NULL;
if (!window)
return;
@@ -308,7 +311,7 @@ static void pointerHandleButton(void* data,
state == WL_POINTER_BUTTON_STATE_PRESSED
? GLFW_PRESS
: GLFW_RELEASE,
_glfw.wl.xkb.modifiers);
_glfw.wl.xkb.states.modifiers);
}
static void pointerHandleAxis(void* data,
@@ -319,11 +322,6 @@ static void pointerHandleAxis(void* data,
{
_GLFWwindow* window = _glfw.wl.pointerFocus;
double x = 0.0, y = 0.0;
// Wayland scroll events are in pointer motion coordinate space (think two
// finger scroll). The factor 10 is commonly used to convert to "scroll
// step means 1.0.
const double scrollFactor = 1.0 / 10.0;
if (!window)
return;
@@ -331,11 +329,11 @@ static void pointerHandleAxis(void* data,
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
x = wl_fixed_to_double(value) * scrollFactor;
x = wl_fixed_to_double(value) * -1;
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
y = wl_fixed_to_double(value) * scrollFactor;
y = wl_fixed_to_double(value) * -1;
_glfwInputScroll(window, x, y);
_glfwInputScroll(window, x, y, 1);
}
static const struct wl_pointer_listener pointerListener = {
@@ -407,6 +405,15 @@ static void keyboardHandleLeave(void* data,
_glfwInputWindowFocus(window, GLFW_FALSE);
}
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, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
}
static void keyboardHandleKey(void* data,
struct wl_keyboard* keyboard,
uint32_t serial,
@@ -419,14 +426,18 @@ static void keyboardHandleKey(void* data,
return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
_glfw.wl.keyRepeatInfo.nextRepeatAt = 0;
GLFWbool repeatable = GLFW_FALSE;
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
{
_glfw.wl.keyRepeatInfo.key = key;
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
repeatable = GLFW_TRUE;
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
}
if (repeatable) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0);
}
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0);
}
static void keyboardHandleModifiers(void* data,
@@ -552,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)
{
@@ -559,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 =
@@ -585,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,
@@ -664,7 +709,10 @@ int _glfwPlatformInit(void)
"Wayland: Failed to connect to display");
return GLFW_FALSE;
}
initPollData(_glfw.wl.eventLoopData.fds, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
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-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);
@@ -678,15 +726,27 @@ int _glfwPlatformInit(void)
wl_display_roundtrip(_glfw.wl.display);
#ifdef __linux__
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
if (_glfw.hints.init.enableJoysticks) {
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
}
#endif
_glfwInitTimerPOSIX();
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,
@@ -713,6 +773,7 @@ void _glfwPlatformTerminate(void)
}
glfw_xkb_release(&_glfw.wl.xkb);
glfw_dbus_terminate(&_glfw.wl.dbus);
if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
@@ -734,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)
@@ -748,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)
@@ -756,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;
}

75
glfw/wl_platform.h vendored
View File

@@ -49,15 +49,18 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#else
#include "null_joystick.h"
#endif
#include "backend_utils.h"
#include "xkb_glfw.h"
#include "egl_context.h"
#include "osmesa_context.h"
#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)
@@ -142,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.
@@ -167,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
@@ -188,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;
@@ -204,10 +256,12 @@ typedef struct _GLFWlibraryWayland
int32_t keyboardRepeatDelay;
struct {
uint32_t key;
double nextRepeatAt;
id_type keyRepeatTimer;
_GLFWwindow* keyboardFocus;
} keyRepeatInfo;
id_type cursorAnimationTimer;
_GLFWXKBData xkb;
_GLFWDBUSData dbus;
_GLFWwindow* pointerFocus;
_GLFWwindow* keyboardFocus;
@@ -229,11 +283,14 @@ typedef struct _GLFWlibraryWayland
PFN_wl_egl_window_resize window_resize;
} egl;
struct {
struct pollfd fds[2];
int wakeupFds[2];
} eventLoopData;
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
@@ -254,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);

767
glfw/wl_window.c vendored

File diff suppressed because it is too large Load Diff

14
glfw/x11_init.c vendored
View File

@@ -37,6 +37,7 @@
#include <stdio.h>
#include <locale.h>
#include <fcntl.h>
#include <unistd.h>
// Check whether the specified atom is supported
@@ -640,8 +641,8 @@ int _glfwPlatformInit(void)
return GLFW_FALSE;
}
initPollData(_glfw.x11.eventLoopData.fds, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
_glfw.x11.eventLoopData.fds[2].events = POLLIN;
initPollData(&_glfw.x11.eventLoopData, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
glfw_dbus_init(&_glfw.x11.dbus, &_glfw.x11.eventLoopData);
_glfw.x11.screen = DefaultScreen(_glfw.x11.display);
_glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen);
@@ -658,8 +659,12 @@ int _glfwPlatformInit(void)
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
#if defined(__linux__)
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
if (_glfw.hints.init.enableJoysticks) {
if (!_glfwInitJoysticksLinux())
return GLFW_FALSE;
if (_glfw.linjs.inotify > 0)
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
}
#endif
_glfwInitTimerPOSIX();
@@ -689,6 +694,7 @@ void _glfwPlatformTerminate(void)
}
glfw_xkb_release(&_glfw.x11.xkb);
glfw_dbus_terminate(&_glfw.x11.dbus);
free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString);

42
glfw/x11_monitor.c vendored
View File

@@ -152,6 +152,11 @@ void _glfwPollMonitorsX11(void)
}
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
if (!ci)
{
XRRFreeOutputInfo(oi);
continue;
}
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
{
widthMM = oi->mm_height;
@@ -321,13 +326,15 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
if (ci)
{
if (xpos)
*xpos = ci->x;
if (ypos)
*ypos = ci->y;
if (xpos)
*xpos = ci->x;
if (ypos)
*ypos = ci->y;
XRRFreeCrtcInfo(ci);
XRRFreeCrtcInfo(ci);
}
XRRFreeScreenResources(sr);
}
}
@@ -406,9 +413,13 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
*mode = vidmodeFromModeInfo(getModeInfo(sr, ci->mode), ci);
if (ci) {
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
if (mi) // mi can be NULL if the monitor has been disconnected
*mode = vidmodeFromModeInfo(mi, ci);
XRRFreeCrtcInfo(ci);
XRRFreeCrtcInfo(ci);
}
XRRFreeScreenResources(sr);
}
else
@@ -422,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)
{
@@ -438,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)
{
@@ -449,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;
}
}
@@ -481,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");
}
}
@@ -501,4 +524,3 @@ GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle)
_GLFW_REQUIRE_INIT_OR_RETURN(None);
return monitor->x11.output;
}

9
glfw/x11_platform.h vendored
View File

@@ -53,6 +53,7 @@
// The libxkb library is used for improved keyboard support
#include "xkb_glfw.h"
#include "backend_utils.h"
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
@@ -103,8 +104,6 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
typedef struct xcb_connection_t xcb_connection_t;
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
@@ -315,6 +314,7 @@ typedef struct _GLFWlibraryX11
} randr;
_GLFWXKBData xkb;
_GLFWDBUSData dbus;
struct {
int count;
@@ -382,10 +382,7 @@ typedef struct _GLFWlibraryX11
PFN_XRenderFindVisualFormat FindVisualFormat;
} xrender;
struct {
struct pollfd fds[3];
int wakeupFds[2];
} eventLoopData;
EventLoopData eventLoopData;
} _GLFWlibraryX11;

318
glfw/x11_window.c vendored
View File

@@ -54,47 +54,32 @@
// This avoids blocking other threads via the per-display Xlib lock that also
// covers GLX functions
//
static GLFWbool waitForEvent(double* timeout)
{
nfds_t count = 2;
GLFWbool _glfwDispatchX11Events(void);
#if defined(__linux__)
if (_glfw.linjs.inotify > 0)
{
count = 3;
_glfw.x11.eventLoopData.fds[2].fd = _glfw.linjs.inotify;
}
#endif
for (;;)
{
for (nfds_t i = 0; i < count; i++) _glfw.x11.eventLoopData.fds[i].revents = 0;
if (timeout)
{
const uint64_t base = _glfwPlatformGetTimerValue();
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, count, *timeout);
*timeout -= (_glfwPlatformGetTimerValue() - base) /
(double) _glfwPlatformGetTimerFrequency();
static void
handleEvents(double timeout) {
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
if (display_read_ok) _glfwDispatchX11Events();
glfw_ibus_dispatch(&_glfw.x11.xkb.ibus);
}
if (result > 0)
{
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
return GLFW_TRUE;
}
if (result == 0)
return GLFW_FALSE;
if (*timeout > 0 && (errno == EINTR || errno == EAGAIN)) continue;
static GLFWbool
waitForX11Event(double timeout) {
// returns true iff there is X11 data waiting to be read, does not run watches and timers
double end_time = glfwGetTime() + timeout;
while(GLFW_TRUE) {
if (timeout >= 0) {
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout);
if (result > 0) return GLFW_TRUE;
timeout = end_time - glfwGetTime();
if (timeout <= 0) return GLFW_FALSE;
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
return GLFW_FALSE;
} else {
const int result = poll(_glfw.x11.eventLoopData.fds, 1, -1);
if (result > 0) return GLFW_TRUE;
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
return GLFW_FALSE;
}
else {
const int result = poll(_glfw.x11.eventLoopData.fds, count, -1);
if (result > 0)
{
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
return GLFW_TRUE;
}
if (result == 0)
return GLFW_FALSE;
if (errno != EINTR && errno != EAGAIN) return GLFW_FALSE;
}
}
}
@@ -105,14 +90,13 @@ static GLFWbool waitForEvent(double* timeout)
static GLFWbool waitForVisibilityNotify(_GLFWwindow* window)
{
XEvent dummy;
double timeout = 0.1;
while (!XCheckTypedWindowEvent(_glfw.x11.display,
window->x11.handle,
VisibilityNotify,
&dummy))
{
if (!waitForEvent(&timeout))
if (!waitForX11Event(0.1))
return GLFW_FALSE;
}
@@ -177,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)
@@ -222,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,
@@ -406,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)
@@ -598,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,
@@ -623,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,
@@ -726,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
{
@@ -970,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,
@@ -984,7 +888,10 @@ static const char* getSelectionString(Atom selection)
SelectionNotify,
&notification))
{
waitForEvent(NULL);
double time = glfwGetTime();
if (time - start > 2)
return "";
waitForX11Event(2.0 - (time - start));
}
if (notification.xselection.property == None)
@@ -1015,12 +922,16 @@ static const char* getSelectionString(Atom selection)
for (;;)
{
start = glfwGetTime();
while (!XCheckIfEvent(_glfw.x11.display,
&dummy,
isSelPropNewValueNotify,
(XPointer) &notification))
{
waitForEvent(NULL);
double time = glfwGetTime();
if (time - start > 2)
return "";
waitForX11Event(2.0 - (time - start));
}
XFree(data);
@@ -1259,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;
@@ -1325,13 +1238,13 @@ static void processEvent(XEvent *event)
// Modern X provides scroll events as mouse button presses
else if (event->xbutton.button == Button4)
_glfwInputScroll(window, 0.0, 1.0);
_glfwInputScroll(window, 0.0, 1.0, 0);
else if (event->xbutton.button == Button5)
_glfwInputScroll(window, 0.0, -1.0);
_glfwInputScroll(window, 0.0, -1.0, 0);
else if (event->xbutton.button == Button6)
_glfwInputScroll(window, 1.0, 0.0);
_glfwInputScroll(window, 1.0, 0.0, 0);
else if (event->xbutton.button == Button7)
_glfwInputScroll(window, -1.0, 0.0);
_glfwInputScroll(window, -1.0, 0.0, 0);
else
{
@@ -1386,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;
}
@@ -1852,7 +1773,7 @@ void _glfwPushSelectionToManagerX11(void)
}
}
waitForEvent(NULL);
waitForX11Event(-1);
}
}
@@ -2142,7 +2063,6 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
_glfw.x11.NET_REQUEST_FRAME_EXTENTS)
{
XEvent event;
double timeout = 0.5;
// Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to
// function before the window is mapped
@@ -2159,7 +2079,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
isFrameExtentsEvent,
(XPointer) window))
{
if (!waitForEvent(&timeout))
if (!waitForX11Event(0.5))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue");
@@ -2196,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)
@@ -2374,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,
@@ -2560,9 +2489,10 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
PropModeReplace, (unsigned char*) &value, 1);
}
void _glfwPlatformPollEvents(void)
{
GLFWbool
_glfwDispatchX11Events(void) {
_GLFWwindow* window;
GLFWbool dispatched = GLFW_FALSE;
#if defined(__linux__)
_glfwDetectJoystickConnectionLinux();
@@ -2574,6 +2504,7 @@ void _glfwPlatformPollEvents(void)
XEvent event;
XNextEvent(_glfw.x11.display, &event);
processEvent(&event);
dispatched = GLFW_TRUE;
}
window = _glfw.x11.disabledCursorWindow;
@@ -2592,25 +2523,25 @@ void _glfwPlatformPollEvents(void)
}
XFlush(_glfw.x11.display);
return dispatched;
}
void _glfwPlatformPollEvents(void)
{
_glfwDispatchX11Events();
handleEvents(0);
}
void _glfwPlatformWaitEvents(void)
{
while (!XPending(_glfw.x11.display))
waitForEvent(NULL);
_glfwPlatformPollEvents();
double timeout = _glfwDispatchX11Events() ? 0 : -1;
handleEvents(timeout);
}
void _glfwPlatformWaitEventsTimeout(double timeout)
{
while (!XPending(_glfw.x11.display))
{
if (!waitForEvent(&timeout))
break;
}
_glfwPlatformPollEvents();
if (_glfwDispatchX11Events()) timeout = 0;
handleEvents(timeout);
}
void _glfwPlatformPostEmptyEvent(void)
@@ -2684,7 +2615,7 @@ int _glfwPlatformGetKeyScancode(int key)
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
const GLFWimage* image,
int xhot, int yhot)
int xhot, int yhot, int count)
{
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
if (!cursor->x11.handle)
@@ -2695,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,
@@ -2745,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)
@@ -2894,6 +2864,10 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
}
}
void
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, which, a, b, c, d);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW native API //////
@@ -2912,28 +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);
}

345
glfw/xkb_glfw.c vendored
View File

@@ -30,10 +30,10 @@
#include "internal.h"
#include "xkb_glfw.h"
static GLFWbool debug_keyboard = GLFW_FALSE;
#define debug(...) if (debug_keyboard) printf(__VA_ARGS__);
#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 @@ static GLFWbool debug_keyboard = GLFW_FALSE;
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 @@ static GLFWbool debug_keyboard = GLFW_FALSE;
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
@@ -171,157 +178,151 @@ glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
#endif
static void
release_keyboard_data(_GLFWXKBData *xkb) {
#define US(group, state, unref) if (xkb->group.state) { unref(xkb->group.state); xkb->group.state = NULL; }
#define UK(keymap) if(xkb->keymap) { xkb_keymap_unref(xkb->keymap); xkb->keymap = NULL; }
US(states, composeState, xkb_compose_state_unref);
UK(keymap);
UK(default_keymap);
US(states, state, xkb_state_unref);
US(states, clean_state, xkb_state_unref);
US(states, default_state, xkb_state_unref);
#undef US
#undef UK
}
void
glfw_xkb_release(_GLFWXKBData *xkb) {
if (xkb->composeState) {
xkb_compose_state_unref(xkb->composeState);
xkb->composeState = NULL;
}
if (xkb->keymap) {
xkb_keymap_unref(xkb->keymap);
xkb->keymap = NULL;
}
if (xkb->default_keymap) {
xkb_keymap_unref(xkb->default_keymap);
xkb->default_keymap = NULL;
}
if (xkb->state) {
xkb_state_unref(xkb->state);
xkb->state = NULL;
}
if (xkb->clean_state) {
xkb_state_unref(xkb->clean_state);
xkb->clean_state = NULL;
}
if (xkb->default_state) {
xkb_state_unref(xkb->default_state);
xkb->default_state = NULL;
}
release_keyboard_data(xkb);
if (xkb->context) {
xkb_context_unref(xkb->context);
xkb->context = NULL;
}
glfw_ibus_terminate(&xkb->ibus);
}
GLFWbool
glfw_xkb_create_context(_GLFWXKBData *xkb) {
xkb->context = xkb_context_new(0);
debug_keyboard = getenv("GLFW_DEBUG_KEYBOARD") != NULL;
if (!xkb->context)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Failed to initialize XKB context");
return GLFW_FALSE;
}
glfw_connect_to_ibus(&xkb->ibus);
return GLFW_TRUE;
}
static const char*
load_keymaps(_GLFWXKBData *xkb, const char *map_str) {
(void)(map_str); // not needed on X11
xkb_glfw_load_keymap(xkb->keymap, map_str);
if (!xkb->keymap) return "Failed to compile XKB keymap";
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
// env var, see
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
static struct xkb_rule_names default_rule_names = {0};
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (!xkb->default_keymap) return "Failed to create default XKB keymap";
return NULL;
}
static const char*
load_states(_GLFWXKBData *xkb) {
xkb_glfw_load_state(xkb->keymap, xkb->states.state);
xkb->states.clean_state = xkb_state_new(xkb->keymap);
xkb->states.default_state = xkb_state_new(xkb->default_keymap);
if (!xkb->states.state || !xkb->states.clean_state || !xkb->states.default_state) return "Failed to create XKB state";
return NULL;
}
static void
load_compose_tables(_GLFWXKBData *xkb) {
/* Look up the preferred locale, falling back to "C" as default. */
struct xkb_compose_table* compose_table = NULL;
const char *locale = getenv("LC_ALL");
if (!locale) locale = getenv("LC_CTYPE");
if (!locale) locale = getenv("LANG");
if (!locale) locale = "C";
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
if (!compose_table) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table for locale %s", locale);
return;
}
xkb->states.composeState = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
if (!xkb->states.composeState) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
}
xkb_compose_table_unref(compose_table);
}
GLFWbool
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
const char* locale = NULL;
struct xkb_state* state = NULL, *clean_state = NULL;
struct xkb_keymap* keymap = NULL;
struct xkb_compose_table* compose_table = NULL;
struct xkb_compose_state* compose_state = NULL;
(void)(map_str); // not needed on X11
GLFWbool ok = GLFW_FALSE;
xkb_glfw_load_keymap(keymap, map_str);
if (!keymap) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to compile XKB keymap");
else {
xkb_glfw_load_state(keymap, state);
clean_state = xkb_state_new(keymap);
if (!state || !clean_state) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB state");
xkb_keymap_unref(keymap); keymap = NULL;
} else {
ok = GLFW_TRUE;
/* Look up the preferred locale, falling back to "C" as default. */
locale = getenv("LC_ALL");
if (!locale) locale = getenv("LC_CTYPE");
if (!locale) locale = getenv("LANG");
if (!locale) locale = "C";
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
if (!compose_table) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table");
} else {
compose_state = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
xkb_compose_table_unref(compose_table); compose_table = NULL;
if (!compose_state) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
}
}
}
const char *err;
release_keyboard_data(xkb);
err = load_keymaps(xkb, map_str);
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
release_keyboard_data(xkb);
return GLFW_FALSE;
}
if (keymap && state && clean_state) {
if (xkb->composeState) xkb_compose_state_unref(xkb->composeState);
xkb->composeState = compose_state;
if (xkb->keymap) xkb_keymap_unref(xkb->keymap);
xkb->keymap = keymap;
if (xkb->state) xkb_state_unref(xkb->state);
xkb->state = state;
if (xkb->clean_state) xkb_state_unref(xkb->clean_state);
xkb->clean_state = clean_state;
err = load_states(xkb);
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
release_keyboard_data(xkb);
return GLFW_FALSE;
}
if (xkb->keymap) {
load_compose_tables(xkb);
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
S(control, XKB_MOD_NAME_CTRL);
S(alt, XKB_MOD_NAME_ALT);
S(shift, XKB_MOD_NAME_SHIFT);
S(super, XKB_MOD_NAME_LOGO);
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
S(control, XKB_MOD_NAME_CTRL);
S(alt, XKB_MOD_NAME_ALT);
S(shift, XKB_MOD_NAME_SHIFT);
S(super, XKB_MOD_NAME_LOGO);
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
#undef S
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
}
xkb->modifiers = 0;
xkb->activeUnknownModifiers = 0;
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
}
if (!xkb->default_keymap && xkb->context) {
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
// env var, see
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
static struct xkb_rule_names default_rule_names = {0};
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
if (xkb->default_keymap) {
xkb->default_state = xkb_state_new(keymap);
if (!xkb->default_state) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB state, fallback key processing is unavailable");
}
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB keymap, fallback key processing is unavailable");
}
}
return ok;
xkb->states.modifiers = 0;
xkb->states.activeUnknownModifiers = 0;
return GLFW_TRUE;
}
static inline xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb) {
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
if (xkb_state_mod_index_is_active(xkb->state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
i++;
}
return ans;
}
static void
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
}
void
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
if (!xkb->keymap) return;
xkb->modifiers = 0;
xkb_state_update_mask(xkb->state, depressed, latched, locked, base_group, latched_group, locked_group);
xkb->states.modifiers = 0;
xkb_state_update_mask(xkb->states.state, depressed, latched, locked, base_group, latched_group, locked_group);
// We have to update the groups in clean_state, as they change for
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
xkb_state_update_mask(xkb->clean_state, 0, 0, 0, base_group, latched_group, locked_group);
#define S(attr, name) if (xkb_state_mod_index_is_active(xkb->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) xkb->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->activeUnknownModifiers = active_unknown_modifiers(xkb);
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
update_modifiers(xkb, &xkb->states);
}
GLFWbool
@@ -333,16 +334,18 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
}
static char text[256];
static KeyEvent key_event = {};
static inline xkb_keysym_t
compose_symbol(_GLFWXKBData *xkb, xkb_keysym_t sym) {
if (sym == XKB_KEY_NoSymbol || !xkb->composeState) return sym;
if (xkb_compose_state_feed(xkb->composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
switch (xkb_compose_state_get_status(xkb->composeState)) {
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(xkb->composeState, text, sizeof(text));
return xkb_compose_state_get_one_sym(xkb->composeState);
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:
return XKB_KEY_NoSymbol;
@@ -361,6 +364,10 @@ glfw_xkb_keysym_name(xkb_keysym_t sym) {
return name;
}
int
glfw_xkb_keysym_from_name(const char *name, GLFWbool case_sensitive) {
return (int)xkb_keysym_from_name(name, case_sensitive ? XKB_KEYSYM_NO_FLAGS : XKB_KEYSYM_CASE_INSENSITIVE);
}
static inline const char*
format_mods(unsigned int mods) {
@@ -400,30 +407,77 @@ format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
return buf;
}
void
glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d) {
int x = 0, y = 0;
switch(which) {
case 1:
glfw_ibus_set_focused(&xkb->ibus, a ? GLFW_TRUE : GLFW_FALSE);
break;
case 2:
_glfwPlatformGetWindowPos(w, &x, &y);
x += a; y += b;
glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, c, d);
break;
}
}
void
glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed) {
_GLFWwindow *window = _glfwWindowForId(ev->window_id);
if (failed && window && window->callbacks.keyboard) {
// notify application to remove any existing pre-edit text
window->callbacks.keyboard((GLFWwindow*) window, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, "", 1);
}
static xkb_keycode_t last_handled_press_keycode = 0;
// We filter out release events that correspond to the last press event
// handled by the IME system. This wont fix the case of multiple key
// presses before a release, but is better than nothing. For that case
// you'd need to implement a ring buffer to store pending key presses.
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
last_handled_press_keycode = 0;
GLFWbool is_release = ev->action == GLFW_RELEASE;
debug("From IBUS: scancode: 0x%x name: %s is_release: %d\n", ev->keycode, glfw_xkb_keysym_name(ev->keysym), is_release);
if (window && !handled_by_ime && !(is_release && ev->keycode == prev_handled_press)) {
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
ev->glfw_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym),
(ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")),
format_mods(ev->glfw_modifiers), ev->text
);
_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;
}
void
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
const xkb_keysym_t *syms, *clean_syms, *default_syms;
xkb_keysym_t glfw_sym;
xkb_keycode_t code_for_sym = scancode;
key_event.ibus_keycode = scancode;
#ifdef _GLFW_WAYLAND
code_for_sym += 8;
#else
key_event.ibus_keycode -= 8;
#endif
debug("scancode: 0x%x release: %d ", scancode, action == GLFW_RELEASE);
int num_syms = xkb_state_key_get_syms(xkb->state, code_for_sym, &syms);
int num_clean_syms = xkb_state_key_get_syms(xkb->clean_state, code_for_sym, &clean_syms);
text[0] = 0;
debug("%s scancode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", scancode);
XKBStateGroup *sg = &xkb->states;
int num_syms = xkb_state_key_get_syms(sg->state, code_for_sym, &syms);
int num_clean_syms = xkb_state_key_get_syms(sg->clean_state, code_for_sym, &clean_syms);
key_event.text[0] = 0;
// According to the documentation of xkb_compose_state_feed it does not
// support multi-sym events, so we ignore them
if (num_syms != 1 || num_clean_syms != 1) {
debug("scancode: 0x%x num_syms: %d num_clean_syms: %d ignoring event\n", scancode, num_syms, num_clean_syms);
debug("num_syms: %d num_clean_syms: %d ignoring event\n", num_syms, num_clean_syms);
return;
}
glfw_sym = clean_syms[0];
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
const char *text_type = "composed_text";
glfw_sym = compose_symbol(xkb, 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;
}
@@ -433,27 +487,40 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
// are active (for example if ISO_Shift_Level_* mods are active
// they are not reported by GLFW so the key should be the shifted
// key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(xkb->state, code_for_sym) & xkb->activeUnknownModifiers;
if (xkb->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", xkb->activeUnknownModifiers));
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(sg->state, code_for_sym) & sg->activeUnknownModifiers;
if (sg->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", sg->activeUnknownModifiers));
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
else glfw_sym = clean_syms[0];
// xkb returns text even if alt and/or super are pressed
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & xkb->modifiers) == 0) xkb_state_key_get_utf8(xkb->state, code_for_sym, text, sizeof(text));
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & sg->modifiers) == 0) xkb_state_key_get_utf8(sg->state, code_for_sym, key_event.text, sizeof(key_event.text));
text_type = "text";
}
if ((1 <= text[0] && text[0] <= 31) || text[0] == 127) text[0] = 0; // dont send text for ascii control codes
if (text[0]) { debug("%s: %s ", text_type, text); }
if ((1 <= key_event.text[0] && key_event.text[0] <= 31) || key_event.text[0] == 127) key_event.text[0] = 0; // don't send text for ascii control codes
if (key_event.text[0]) { debug("%s: %s ", text_type, key_event.text); }
}
int glfw_keycode = glfw_key_for_sym(glfw_sym);
if (glfw_keycode == GLFW_KEY_UNKNOWN && !text[0]) {
int num_default_syms = xkb_state_key_get_syms(xkb->default_state, code_for_sym, &default_syms);
GLFWbool is_fallback = GLFW_FALSE;
if (glfw_keycode == GLFW_KEY_UNKNOWN && !key_event.text[0]) {
int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms);
if (num_default_syms > 0) {
glfw_sym = default_syms[0];
glfw_keycode = glfw_key_for_sym(glfw_sym);
is_fallback = GLFW_TRUE;
}
debug("%sglfw_fallback_key: %s\n", format_mods(xkb->modifiers), _glfwGetKeyName(glfw_keycode));
} else {
debug("%sglfw_key: %s\n", format_mods(xkb->modifiers), _glfwGetKeyName(glfw_keycode));
}
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, xkb->modifiers, text, 0);
debug(
"%s%s: %d (%s) xkb_key: %d (%s)\n",
format_mods(sg->modifiers),
is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_keycode, _glfwGetKeyName(glfw_keycode),
glfw_sym, glfw_xkb_keysym_name(glfw_sym)
);
key_event.action = action; key_event.glfw_modifiers = sg->modifiers;
key_event.keycode = scancode; key_event.keysym = glfw_sym;
key_event.window_id = window->id; key_event.glfw_keycode = glfw_keycode;
key_event.ibus_sym = syms[0];
if (ibus_process_key(&key_event, &xkb->ibus)) {
debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", key_event.ibus_keycode, key_event.ibus_sym, glfw_xkb_keysym_name(key_event.ibus_sym), format_mods(key_event.glfw_modifiers));
} else {
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, sg->modifiers, key_event.text, 0);
}
}

21
glfw/xkb_glfw.h vendored
View File

@@ -32,14 +32,23 @@
#include <xkbcommon/xkbcommon-x11.h>
#endif
#include "ibus_glfw.h"
typedef struct {
struct xkb_context* context;
struct xkb_keymap* keymap;
struct xkb_keymap* default_keymap;
struct xkb_state* state;
struct xkb_state* clean_state;
struct xkb_state* default_state;
struct xkb_compose_state* composeState;
xkb_mod_mask_t activeUnknownModifiers;
unsigned int modifiers;
} XKBStateGroup;
typedef struct {
struct xkb_context* context;
struct xkb_keymap* keymap;
struct xkb_keymap* default_keymap;
XKBStateGroup states;
xkb_mod_index_t controlIdx;
xkb_mod_index_t altIdx;
@@ -53,9 +62,8 @@ typedef struct {
xkb_mod_mask_t superMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
xkb_mod_mask_t activeUnknownModifiers;
unsigned int modifiers;
xkb_mod_index_t unknownModifiers[256];
_GLFWIBUSData ibus;
#ifdef _GLFW_X11
int32_t keyboard_device_id;
@@ -83,3 +91,6 @@ GLFWbool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode);
const char* glfw_xkb_keysym_name(xkb_keysym_t sym);
xkb_keysym_t glfw_xkb_sym_for_key(int key);
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action);
int glfw_xkb_keysym_from_name(const char *name, GLFWbool case_sensitive);
void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d);
void glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed);

View File

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

View File

@@ -50,8 +50,10 @@ class HistoryCompleter:
def __enter__(self):
if self.history_path:
if os.path.exists(self.history_path):
try:
readline.read_history_file(self.history_path)
except Exception:
pass
readline.set_completer(self.complete)
return self

View File

@@ -2,7 +2,9 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from kitty.cli import parse_args
from ..tui.handler import Handler
@@ -13,7 +15,6 @@ class Clipboard(Handler):
def __init__(self, data_to_send, args):
self.args = args
self.print_on_fail = None
self.clipboard_contents = None
self.data_to_send = data_to_send
@@ -63,16 +64,13 @@ def main(args):
data = None
if not sys.stdin.isatty():
data = sys.stdin.buffer.read()
sys.stdin = open('/dev/tty', 'r')
sys.stdin = open(os.ctermid(), 'r')
loop = Loop()
handler = Clipboard(data, args)
loop.loop(handler)
if loop.return_code == 0 and handler.clipboard_contents:
sys.stdout.write(handler.clipboard_contents)
sys.stdout.flush()
if handler.print_on_fail:
print(handler.print_on_fail, file=sys.stderr)
input('Press Enter to quit')
raise SystemExit(loop.return_code)

View File

@@ -133,6 +133,7 @@ def highlight_collection(collection, aliases=None):
jobs = {}
ans = {}
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
highlight_collection.processes = executor._processes
for path, item_type, other_path in collection:
if item_type != 'rename':
for p in (path, other_path):

View File

@@ -3,6 +3,7 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import signal
import sys
import warnings
from collections import defaultdict
@@ -24,14 +25,14 @@ from .collect import (
set_highlight_data
)
from .config import init_config
from .patch import Differ, set_diff_command
from .patch import Differ, set_diff_command, worker_processes
from .render import ImageSupportWarning, LineRef, render_diff
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)
@@ -42,7 +43,7 @@ def generate_diff(collection, context):
for path, item_type, changed_path in collection:
if item_type == 'diff':
is_binary = isinstance(data_for_path(path), bytes)
is_binary = isinstance(data_for_path(path), bytes) or isinstance(data_for_path(changed_path), bytes)
if not is_binary:
d.add_diff(path, changed_path)
@@ -106,10 +107,67 @@ class DiffHandler(Handler):
return
def create_collection(self):
self.start_job('collect', create_collection, self.left, self.right)
def collect_done(collection):
self.collection = collection
self.state = COLLECTED
self.generate_diff()
def collect(left, right):
collection = create_collection(left, right)
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
def generate_diff(self):
self.start_job('diff', generate_diff, self.collection, self.current_context_count)
def diff_done(diff_map):
if isinstance(diff_map, str):
self.report_traceback_on_exit = diff_map
self.quit_loop(1)
return
self.state = DIFFED
self.diff_map = diff_map
self.calculate_statistics()
self.render_diff()
self.scroll_pos = 0
if self.restore_position is not None:
self.current_position = self.restore_position
self.restore_position = None
self.draw_screen()
if initialize_highlighter is not None and not self.highlighting_done:
from .highlight import StyleNotFound
self.highlighting_done = True
try:
initialize_highlighter(self.opts.pygments_style)
except StyleNotFound as e:
self.report_traceback_on_exit = str(e)
self.quit_loop(1)
return
self.syntax_highlight()
def diff(collection, current_context_count):
diff_map = generate_diff(collection, current_context_count)
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
def syntax_highlight(self):
def highlighting_done(hdata):
if isinstance(hdata, str):
self.report_traceback_on_exit = hdata
self.quit_loop(1)
return
set_highlight_data(hdata)
self.render_diff()
self.draw_screen()
def highlight(*a):
result = highlight_collection(*a)
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
def calculate_statistics(self):
self.added_count = self.collection.added_count
@@ -168,12 +226,13 @@ class DiffHandler(Handler):
return
self.cmd.bell()
def scroll_to_next_match(self, backwards=False):
def scroll_to_next_match(self, backwards=False, include_current=False):
if self.current_search is not None:
offset = 0 if include_current else 1
if backwards:
r = range(self.scroll_pos - 1, -1, -1)
r = range(self.scroll_pos - offset, -1, -1)
else:
r = range(self.scroll_pos + 1, len(self.diff_lines))
r = range(self.scroll_pos + offset, len(self.diff_lines))
for i in r:
if i in self.current_search:
self.scroll_lines(i - self.scroll_pos)
@@ -351,7 +410,9 @@ class DiffHandler(Handler):
self.message = sanitize(_('Bad regex: {}').format(query[1:]))
self.cmd.bell()
else:
if not self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols):
if self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols):
self.scroll_to_next_match(include_current=True)
else:
self.state = MESSAGE
self.message = sanitize(_('No matches found'))
self.cmd.bell()
@@ -410,50 +471,6 @@ class DiffHandler(Handler):
self.render_diff()
self.draw_screen()
def on_job_done(self, job_id, job_result):
if 'tb' in job_result:
self.report_traceback_on_exit = job_result['tb']
self.quit_loop(1)
return
if job_id == 'collect':
self.collection = job_result['result']
self.state = COLLECTED
self.generate_diff()
elif job_id == 'diff':
diff_map = job_result['result']
if isinstance(diff_map, str):
self.report_traceback_on_exit = diff_map
self.quit_loop(1)
return
self.state = DIFFED
self.diff_map = diff_map
self.calculate_statistics()
self.render_diff()
self.scroll_pos = 0
if self.restore_position is not None:
self.current_position = self.restore_position
self.restore_position = None
self.draw_screen()
if initialize_highlighter is not None and not self.highlighting_done:
from .highlight import StyleNotFound
self.highlighting_done = True
try:
initialize_highlighter(self.opts.pygments_style)
except StyleNotFound as e:
self.report_traceback_on_exit = str(e)
self.quit_loop(1)
return
self.start_job('highlight', highlight_collection, self.collection, self.opts.syntax_aliases)
elif job_id == 'highlight':
hdata = job_result['result']
if isinstance(hdata, str):
self.report_traceback_on_exit = diff_map
self.quit_loop(1)
return
set_highlight_data(hdata)
self.render_diff()
self.draw_screen()
def on_interrupt(self):
self.quit_loop(1)
@@ -492,6 +509,14 @@ help_text = 'Show a side-by-side diff of the specified files/directories'
usage = 'file_or_directory_left file_or_directory_right'
def terminate_processes(processes):
for pid in processes:
try:
os.kill(pid, signal.SIGKILL)
except Exception:
pass
def main(args):
warnings.showwarning = showwarning
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
@@ -514,6 +539,9 @@ def main(args):
for message in showwarning.warnings:
from kitty.utils import safe_print
safe_print(message, file=sys.stderr)
highlight_processes = getattr(highlight_collection, 'processes', ())
terminate_processes(tuple(highlight_processes))
terminate_processes(tuple(worker_processes))
if loop.return_code != 0:
if handler.report_traceback_on_exit:
print(handler.report_traceback_on_exit, file=sys.stderr)

View File

@@ -14,6 +14,7 @@ from .diff_speedup import changed_center
left_lines = right_lines = None
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
worker_processes = []
def find_differ():
@@ -43,8 +44,10 @@ def run_diff(file1, file2, context=3):
p = subprocess.Popen(
cmd + [path1, path2],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
worker_processes.append(p.pid)
stdout, stderr = p.communicate()
returncode = p.wait()
worker_processes.remove(p.pid)
if returncode in (0, 1):
return True, returncode == 1, stdout.decode('utf-8')
return False, returncode, stderr.decode('utf-8')
@@ -205,9 +208,13 @@ def parse_patch(raw):
class Differ:
diff_executor = None
def __init__(self):
self.jmap = {}
self.jobs = []
if Differ.diff_executor is None:
Differ.diff_executor = self.diff_executor = concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count())
def add_diff(self, file1, file2):
self.jmap[file1] = file2
@@ -216,26 +223,26 @@ class Differ:
def __call__(self, context=3):
global left_lines, right_lines
ans = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
for future in concurrent.futures.as_completed(jobs):
key = jobs[future]
left_path, right_path = key, self.jmap[key]
try:
ok, returncode, output = future.result()
except FileNotFoundError as err:
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
except Exception as e:
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
if not ok:
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
left_lines = lines_for_path(left_path)
right_lines = lines_for_path(right_path)
try:
patch = parse_patch(output)
except Exception:
import traceback
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
else:
ans[key] = patch
executor = Differ.diff_executor
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
for future in concurrent.futures.as_completed(jobs):
key = jobs[future]
left_path, right_path = key, self.jmap[key]
try:
ok, returncode, output = future.result()
except FileNotFoundError as err:
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
except Exception as e:
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
if not ok:
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
left_lines = lines_for_path(left_path)
right_lines = lines_for_path(right_path)
try:
patch = parse_patch(output)
except Exception:
import traceback
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
else:
ans[key] = patch
return ans

View File

@@ -453,7 +453,9 @@ def render_diff(collection, diff_map, args, columns, image_manager):
for i, (path, item_type, other_path) in enumerate(collection):
item_ref = Reference(path)
is_binary = isinstance(data_for_path(path), bytes)
is_img = is_binary and is_image(path) and images_supported()
if not is_binary and item_type == 'diff' and isinstance(data_for_path(other_path), bytes):
is_binary = True
is_img = is_binary and (is_image(path) or is_image(other_path)) and images_supported()
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
if item_type == 'diff':
if is_binary:

View File

@@ -24,7 +24,7 @@ HINT_ALPHABET = string.digits + string.ascii_lowercase
screen_size = screen_size_function()
class Mark(object):
class Mark:
__slots__ = ('index', 'start', 'end', 'text')
@@ -63,8 +63,10 @@ def highlight_mark(m, text, current_input):
)
def render(text, current_input, all_marks):
def render(text, current_input, all_marks, ignore_mark_indices):
for mark in reversed(all_marks):
if mark.index in ignore_mark_indices:
continue
mtext = highlight_mark(mark, text[mark.start:mark.end], current_input)
text = text[:mark.start] + mtext + text[mark.end:]
@@ -78,11 +80,24 @@ class Hints(Handler):
def __init__(self, text, all_marks, index_map, args):
self.text, self.index_map = text, index_map
self.all_marks = all_marks
self.current_input = ''
self.current_text = None
self.ignore_mark_indices = set()
self.args = args
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
self.chosen = None
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
def init_terminal_state(self):
self.cmd.set_cursor_visible(False)
@@ -101,13 +116,17 @@ class Hints(Handler):
changed = True
if changed:
matches = [
m.text for idx, m in self.index_map.items()
m for idx, m in self.index_map.items()
if encode_hint(idx).startswith(self.current_input)
]
if len(matches) == 1:
self.chosen = matches[0]
self.quit_loop(0)
return
self.chosen.append(matches[0].text + self.match_suffix)
if self.multiple:
self.ignore_mark_indices.add(matches[0].index)
self.reset()
else:
self.quit_loop(0)
return
self.current_text = None
self.draw_screen()
@@ -119,15 +138,20 @@ class Hints(Handler):
elif key_event is enter_key and self.current_input:
try:
idx = decode_hint(self.current_input)
self.chosen = 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 = ''
self.current_text = None
self.draw_screen()
else:
self.quit_loop(0)
if self.multiple:
self.reset()
self.draw_screen()
else:
self.quit_loop(0)
elif key_event.key is ESCAPE:
self.quit_loop(1)
self.quit_loop(0 if self.multiple else 1)
def on_interrupt(self):
self.quit_loop(1)
@@ -136,12 +160,11 @@ class Hints(Handler):
self.quit_loop(1)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.draw_screen()
def draw_screen(self):
if self.current_text is None:
self.current_text = render(self.text, self.current_input, self.all_marks)
self.current_text = render(self.text, self.current_input, self.all_marks, self.ignore_mark_indices)
self.cmd.clear_screen()
self.write(self.current_text)
@@ -155,7 +178,7 @@ def regex_finditer(pat, minimum_match_length, text):
yield s, e
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
opening_brackets = ''.join(closing_bracket_map)
postprocessor_map = {}
@@ -174,15 +197,15 @@ def url(text, s, e):
e -= len(url) - idx
while text[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
e -= 1
# truncate url at closing bracket/quote
if s > 0 and e <= len(text) and text[s-1] in opening_brackets:
q = closing_bracket_map[text[s-1]]
idx = text.find(q, s)
if idx > s:
e = idx
# Restructured Text URLs
if e > 3 and text[e-2:e] == '`_':
e -= 2
# Remove trailing bracket if matched by leading bracket
if s > 0 and e < len(text) and text[s-1] in opening_brackets and text[e-1] == closing_bracket_map[text[s-1]]:
e -= 1
# Remove trailing quote if matched by leading quote
if s > 0 and e < len(text) and text[s-1] in '\'"' and text[e-1] == text[s-1]:
e -= 1
return s, e
@@ -240,7 +263,7 @@ def functions_for(args):
)
post_processors.append(url)
elif args.type == 'path':
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})'
post_processors.extend((brackets, quotes))
elif args.type == 'line':
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
@@ -342,6 +365,19 @@ Defaults to the select_by_word_characters setting from kitty.conf.
default=3
type=int
The minimum number of characters to consider a match.
--multiple
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 = ''
@@ -360,7 +396,7 @@ def main(args):
return
else:
text = sys.stdin.buffer.read().decode('utf-8')
sys.stdin = open('/dev/tty')
sys.stdin = open(os.ctermid())
try:
args, items = parse_hints_args(args[1:])
except SystemExit as e:
@@ -377,18 +413,22 @@ def main(args):
def handle_result(args, data, target_window_id, boss):
program = data['program']
matches = tuple(filter(None, data['match']))
if program == '-':
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(data['match'])
for m in matches:
w.paste(m)
elif program == '@':
set_clipboard_string(data['match'])
set_clipboard_string(matches[-1])
else:
cwd = None
w = boss.window_id_map.get(target_window_id)
if w is not None:
cwd = w.cwd_of_child
boss.open_url(data['match'], None if program == 'default' else program, cwd=cwd)
program = None if program == 'default' else program
for m in matches:
boss.open_url(m, program, cwd=cwd)
handle_result.type_of_input = 'screen'

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 ...'
@@ -260,13 +261,20 @@ def main(args=sys.argv):
if args.print_window_size:
screen_size_function.ans = None
with open('/dev/tty') as tty:
with open(os.ctermid()) as tty:
ss = screen_size_function(tty)()
print('{}x{}'.format(ss.width, ss.height), end='')
raise SystemExit(0)
if not sys.stdout.isatty():
sys.stdout = open('/dev/tty', 'w')
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

@@ -2,12 +2,12 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import shutil
import subprocess
import sys
from kitty.constants import is_macos, is_wayland
from kitty.cli import parse_args
from kitty.constants import is_macos, is_wayland
OPTIONS = r'''
--lines
@@ -123,7 +123,8 @@ def main(sys_args):
global args
if is_macos or is_wayland:
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
call_xprop('-version', silent=True) # ensure xprop is available
if not shutil.which('xprop'):
raise SystemExit('The xprop program is required for the panel kitten')
args, items = parse_panel_args(sys_args[1:])
if not items:
raise SystemExit('You must specify the program to run')

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):
@@ -74,7 +74,6 @@ class Resize(Handler):
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.draw_screen()
def draw_screen(self):

View File

@@ -17,23 +17,27 @@ def resolved_kitten(k):
def import_kitten_main_module(config_dir, kitten):
if kitten.endswith('.py'):
path_modified = False
path = os.path.expanduser(kitten)
if not os.path.isabs(path):
path = os.path.join(config_dir, path)
path = os.path.abspath(path)
if os.path.dirname(path):
sys.path.insert(0, os.path.dirname(path))
path_modified = True
with open(path) as f:
src = f.read()
code = compile(src, path, 'exec')
g = {'__name__': 'kitten'}
exec(code, g)
hr = g.get('handle_result', lambda *a, **kw: None)
if path_modified:
del sys.path[0]
return {'start': g['main'], 'end': hr}
else:
kitten = resolved_kitten(kitten)
m = importlib.import_module('kittens.{}.main'.format(kitten))
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
kitten = resolved_kitten(kitten)
m = importlib.import_module('kittens.{}.main'.format(kitten))
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
def create_kitten_handler(kitten, orig_args):
@@ -42,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
@@ -76,8 +81,11 @@ def launch(args):
def deserialize(output):
import json
if output.startswith('OK: '):
prefix, sz, rest = output.split(' ', 2)
return json.loads(rest[:int(sz)])
try:
prefix, sz, rest = output.split(' ', 2)
return json.loads(rest[:int(sz)])
except Exception:
raise ValueError('Failed to parse kitten output: {!r}'.format(output))
def run_kitten(kitten, run_name='__main__'):

View File

@@ -2,11 +2,12 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from kitty.cli import parse_args
from ..tui.operations import styled
from ..tui.operations import styled
OPTIONS = '''\
--title
@@ -17,7 +18,7 @@ The title for the error message.
def real_main(args):
error_message = sys.stdin.buffer.read().decode('utf-8')
sys.stdin = open('/dev/tty')
sys.stdin = open(os.ctermid())
msg = 'Show an error message'
args, items = parse_args(args, OPTIONS, '', msg, 'hints')
print(styled(args.title, fg_intense=True, fg='red', bold=True))

View File

@@ -102,7 +102,7 @@ def main(args):
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
if len(server_args) > 1:
command_to_execute = ["'{}'".format(c.replace("'", """'"'"'""")) for c in server_args[1:]]
command_to_execute = 'cmd=({}); exec "$cmd"'.format(' '.join(command_to_execute))
command_to_execute = 'exec ' + ' '.join(command_to_execute)
else:
command_to_execute = ''
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)

View File

@@ -10,14 +10,19 @@ class Handler:
image_manager_class = None
def _initialize(self, screen_size, quit_loop, wakeup, start_job, debug, image_manager=None):
self.screen_size, self.quit_loop = screen_size, quit_loop
self.wakeup = wakeup
def _initialize(self, screen_size, term_manager, schedule_write, tui_loop, debug, image_manager=None):
self.screen_size = screen_size
self._term_manager = term_manager
self._tui_loop = tui_loop
self._schedule_write = schedule_write
self.debug = debug
self.start_job = start_job
self.cmd = commander(self)
self.image_manager = image_manager
@property
def asyncio_loop(self):
return self._tui_loop.asycio_loop
def add_shortcut(self, action, key, mods=None, is_text=False):
if not hasattr(self, '_text_shortcuts'):
self._text_shortcuts, self._key_shortcuts = {}, {}
@@ -38,7 +43,6 @@ class Handler:
self.initialize()
def __exit__(self, *a):
del self.write_buf[:]
del self.debug.fobj
self.finalize()
if self.image_manager is not None:
@@ -53,8 +57,11 @@ class Handler:
def on_resize(self, screen_size):
self.screen_size = screen_size
def quit_loop(self, return_code=None):
self._tui_loop.quit(return_code)
def on_term(self):
self.quit_loop(1)
self._tui_loop.quit(1)
def on_text(self, text, in_bracketed_paste=False):
pass
@@ -71,12 +78,6 @@ class Handler:
def on_eot(self):
pass
def on_wakeup(self):
pass
def on_job_done(self, job_id, job_result):
pass
def on_kitty_cmd_response(self, response):
pass
@@ -86,7 +87,7 @@ class Handler:
def write(self, data):
if isinstance(data, str):
data = data.encode('utf-8')
self.write_buf.append(data)
self._schedule_write(data)
def print(self, *args, sep=' ', end='\r\n'):
data = sep.join(map(str, args)) + end

View File

@@ -71,13 +71,15 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
from tempfile import NamedTemporaryFile
width, height = m.width, m.height
cmd = ['convert', '-background', 'none', path]
scaled = False
if scale_up:
if width < available_width:
r = available_width / width
width, height = available_width, int(height * r)
if width > available_width or height > available_height:
scaled = True
if scaled or width > available_width or height > available_height:
width, height = fit_image(width, height, available_width, available_height)
cmd += ['-resize', '{}x{}'.format(width, height)]
cmd += ['-resize', '{}x{}!'.format(width, height)]
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
run_imagemagick(path, cmd + [outfile.name])
# ImageMagick sometimes generated rgba images smaller than the specified
@@ -88,7 +90,10 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
if sz < expected_size:
missing = expected_size - sz
if missing % (bytes_per_pixel * width) != 0:
raise ConvertFailed(path, 'ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
raise ConvertFailed(
path, 'ImageMagick failed to convert {} correctly,'
' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
path, sz, expected_size, width, height, bytes_per_pixel))
height -= missing // (bytes_per_pixel * width)
return outfile.name, width, height

View File

@@ -2,6 +2,7 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import asyncio
import codecs
import io
import os
@@ -10,13 +11,12 @@ import selectors
import signal
import sys
from collections import namedtuple
from contextlib import closing, contextmanager
from contextlib import contextmanager
from functools import partial
from queue import Empty, Queue
from kitty.constants import is_macos
from kitty.fast_data_types import (
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty,
safe_pipe
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty
)
from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
@@ -74,29 +74,6 @@ class TermManager:
del self.tty_fd, self.original_termios
class SignalManager:
def __init__(self, on_sigwinch, on_sigterm, on_sigint):
self.on_sigwinch = on_sigwinch
self.on_sigterm = on_sigterm
self.on_sigint = on_sigint
def __enter__(self):
for x in ('winch', 'term', 'int'):
attr = 'on_sig' + x
handler = getattr(self, attr)
old_handler = signal.signal(getattr(signal, 'SIG' + x.upper()), handler)
setattr(self, attr, old_handler)
def __exit__(self, *a):
for x in ('winch', 'term', 'int'):
attr = 'on_sig' + x
val = getattr(self, attr)
if val is None:
val = signal.SIG_DFL
signal.signal(getattr(signal, 'SIG' + x.upper()), val)
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
DRAG = REPEAT
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
@@ -150,22 +127,38 @@ class UnhandledException(Handler):
def on_interrupt(self):
self.quit_loop(1)
on_eot = on_term = on_interrupt
def on_eot(self):
self.quit_loop(1)
class SignalManager:
def __init__(self, loop, on_winch, on_interrupt, on_term):
self.asycio_loop = loop
self.on_winch, self.on_interrupt, self.on_term = on_winch, on_interrupt, on_term
def __enter__(self):
tuple(map(lambda x: self.asycio_loop.add_signal_handler(*x), (
(signal.SIGWINCH, self.on_winch),
(signal.SIGINT, self.on_interrupt),
(signal.SIGTERM, self.on_term)
)))
def __exit__(self, *a):
tuple(map(self.asycio_loop.remove_signal_handler, (
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
class Loop:
def __init__(self,
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
self.wakeup_read_fd, self.wakeup_write_fd = safe_pipe()
# For some reason on macOS the DefaultSelector fails when tty_fd is
# open('/dev/tty')
self.sel = s = selectors.PollSelector()
s.register(self.wakeup_read_fd, selectors.EVENT_READ)
if is_macos:
# On macOS PTY devices are not supported by the KqueueSelector
self.asycio_loop = asyncio.SelectorEventLoop(selectors.PollSelector())
asyncio.set_event_loop(self.asycio_loop)
else:
self.asycio_loop = asyncio.get_event_loop()
self.return_code = 0
self.read_allowed = True
self.read_buf = ''
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
try:
@@ -178,28 +171,8 @@ class Loop:
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
if self.sanitize_bracketed_paste:
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
self.jobs_queue = Queue()
def start_job(self, job_id, func, *args, **kw):
from threading import Thread
t = Thread(target=partial(self._run_job, job_id, func), args=args, kwargs=kw, name='LoopJob')
t.daemon = True
t.start()
def _run_job(self, job_id, func, *args, **kw):
try:
result = func(*args, **kw)
except Exception as err:
import traceback
entry = {'id': job_id, 'exception': err, 'tb': traceback.format_exc()}
else:
entry = {'id': job_id, 'result': result}
self.jobs_queue.put(entry)
self._wakeup_write(b'j')
def _read_ready(self, handler, fd):
if not self.read_allowed:
return
try:
data = os.read(fd, io.DEFAULT_BUFFER_SIZE)
except BlockingIOError:
@@ -219,6 +192,7 @@ class Loop:
finally:
del self.handler
# terminal input callbacks {{{
def _on_text(self, text):
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
text = self.sanitize_ibp_pat.sub('', text)
@@ -292,127 +266,99 @@ class Loop:
elif apc.startswith('G'):
if self.handler.image_manager is not None:
self.handler.image_manager.handle_response(apc)
# }}}
def _write_ready(self, handler, fd):
if len(handler.write_buf) > self.iov_limit:
handler.write_buf[self.iov_limit - 1] = b''.join(handler.write_buf[self.iov_limit - 1:])
del handler.write_buf[self.iov_limit:]
sizes = tuple(map(len, handler.write_buf))
if len(self.write_buf) > self.iov_limit:
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
del self.write_buf[self.iov_limit:]
sizes = tuple(map(len, self.write_buf))
try:
written = os.writev(fd, handler.write_buf)
written = os.writev(fd, self.write_buf)
except BlockingIOError:
return
if not written:
raise EOFError('The output stream is closed')
if written >= sum(sizes):
handler.write_buf = []
self.write_buf = []
self.asycio_loop.remove_writer(fd)
self.waiting_for_writes = False
else:
consumed = 0
for i, buf in enumerate(handler.write_buf):
for i, buf in enumerate(self.write_buf):
if not written:
break
if len(buf) <= written:
written -= len(buf)
consumed += 1
continue
handler.write_buf[i] = buf[written:]
self.write_buf[i] = buf[written:]
break
del handler.write_buf[:consumed]
def _wakeup_ready(self, handler, fd):
data = os.read(fd, 1024)
if b'r' in data:
self._get_screen_size.changed = True
handler.on_resize(self._get_screen_size())
if b't' in data:
handler.on_term()
if b'i' in data:
handler.on_interrupt()
if b'j' in data:
while True:
try:
entry = self.jobs_queue.get_nowait()
except Empty:
break
else:
job_id = entry.pop('id')
handler.on_job_done(job_id, entry)
if b'1' in data:
handler.on_wakeup()
def _wakeup_write(self, val):
while not os.write(self.wakeup_write_fd, val):
pass
def _on_sigwinch(self, signum, frame):
self._wakeup_write(b'r')
def _on_sigterm(self, signum, frame):
self._wakeup_write(b't')
def _on_sigint(self, signum, frame):
self._wakeup_write(b'i')
del self.write_buf[:consumed]
def quit(self, return_code=None):
self.read_allowed = False
if return_code is not None:
self.return_code = return_code
self.asycio_loop.stop()
def wakeup(self):
self._wakeup_write(b'1')
def loop_impl(self, handler, term_manager, image_manager=None):
self.write_buf = []
tty_fd = term_manager.tty_fd
tb = None
self.waiting_for_writes = True
def _modify_output_selector(self, tty_fd, waiting_for_write):
events = selectors.EVENT_READ
if waiting_for_write:
events |= selectors.EVENT_WRITE
self.sel.modify(tty_fd, events)
def schedule_write(data):
self.write_buf.append(data)
if not self.waiting_for_writes:
self.asycio_loop.add_writer(tty_fd, self._write_ready, handler, tty_fd)
self.waiting_for_writes = True
def loop_impl(self, handler, tty_fd, image_manager=None, waiting_for_write=True):
read_ready, write_ready, wakeup_ready = self._read_ready, self._write_ready, self._wakeup_ready
select = self.sel.select
handler._initialize(self._get_screen_size(), self.quit, self.wakeup, self.start_job, debug, image_manager)
def handle_exception(loop, context):
nonlocal tb
loop.stop()
tb = context['message']
exc = context.get('exception')
if exc is not None:
import traceback
tb += '\n' + ''.join(traceback.format_exception(exc.__class__, exc, exc.__traceback__))
self.asycio_loop.set_exception_handler(handle_exception)
handler._initialize(self._get_screen_size(), term_manager, schedule_write, self, debug, image_manager)
with handler:
while True:
has_data_to_write = bool(handler.write_buf)
if not has_data_to_write and not self.read_allowed:
break
if has_data_to_write != waiting_for_write:
waiting_for_write = has_data_to_write
self._modify_output_selector(tty_fd, waiting_for_write)
for key, mask in select():
fd = key.fd
if fd == tty_fd:
if mask & selectors.EVENT_READ:
read_ready(handler, fd)
if mask & selectors.EVENT_WRITE:
write_ready(handler, fd)
else:
wakeup_ready(handler, fd)
self.asycio_loop.add_reader(
tty_fd, self._read_ready, handler, tty_fd)
self.asycio_loop.add_writer(
tty_fd, self._write_ready, handler, tty_fd)
self.asycio_loop.run_forever()
self.asycio_loop.remove_reader(tty_fd)
if self.waiting_for_writes:
self.asycio_loop.remove_writer(tty_fd)
return tb
def loop(self, handler):
tb = None
signal_manager = SignalManager(self._on_sigwinch, self._on_sigterm, self._on_sigint)
with closing(self.sel), TermManager() as term_manager, signal_manager:
self.sel.register(term_manager.tty_fd, selectors.EVENT_READ | selectors.EVENT_WRITE)
def _on_sigwinch():
self._get_screen_size.changed = True
handler.screen_size = self._get_screen_size()
handler.on_resize(handler.screen_size)
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
with TermManager() as term_manager, signal_manager:
self._get_screen_size = screen_size_function(term_manager.tty_fd)
handler.write_buf = []
handler._term_manager = term_manager
image_manager = None
if handler.image_manager_class is not None:
image_manager = handler.image_manager_class(handler)
try:
self.loop_impl(handler, term_manager.tty_fd, image_manager)
tb = self.loop_impl(handler, term_manager, image_manager)
except Exception:
import traceback
tb = traceback.format_exc()
self.return_code = 1
term_manager.extra_finalize = b''.join(handler.write_buf).decode('utf-8')
term_manager.extra_finalize = b''.join(self.write_buf).decode('utf-8')
if tb is not None:
self.return_code = 1
self._report_error_loop(tb, term_manager)
def _report_error_loop(self, tb, term_manager):
handler = UnhandledException(tb)
handler.write_buf = []
handler._term_manager = term_manager
self.loop_impl(handler, term_manager.tty_fd, waiting_for_write=False)
self.loop_impl(UnhandledException(tb), term_manager)

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') + ' '
@@ -455,7 +457,6 @@ class UnicodeInput(Handler):
self.quit_loop(1)
def on_resize(self, new_size):
Handler.on_resize(self, new_size)
self.refresh()

File diff suppressed because one or more lines are too long

View File

@@ -2,7 +2,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from functools import partial
from itertools import chain
from .fast_data_types import (
@@ -10,6 +9,16 @@ from .fast_data_types import (
)
from .utils import load_shaders
try:
from enum import IntFlag
except Exception:
from enum import IntEnum as IntFlag
class BorderColor(IntFlag):
# See the border vertex shader for how these flags become actual colors
default_bg, active, inactive, window_bg, bell = ((1 << i) for i in range(5))
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
@@ -19,15 +28,15 @@ def horizontal_edge(os_window_id, tab_id, color, height, left, right, top):
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
def edge(func, os_window_id, tab_id, color, sz, a, b):
return partial(func, os_window_id, tab_id, color, sz, a, b)
def border(os_window_id, tab_id, color, sz, left, top, right, bottom):
horz = edge(horizontal_edge, os_window_id, tab_id, color, sz, left, right)
horz(top), horz(bottom - sz) # top, bottom edges
vert = edge(vertical_edge, os_window_id, tab_id, color, sz, top, bottom)
vert(left), vert(right - sz) # left, right edges
def draw_edges(os_window_id, tab_id, colors, width, geometry, base_width=0):
left = geometry.left - (width + base_width)
top = geometry.top - (width + base_width)
right = geometry.right + (width + base_width)
bottom = geometry.bottom + (width + base_width)
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top)
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width)
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left)
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width)
def load_borders_program():
@@ -52,28 +61,31 @@ class Borders:
extra_blank_rects,
draw_window_borders=True
):
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 1)
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
for br in chain(current_layout.blank_rects, extra_blank_rects):
add_borders_rect(self.os_window_id, self.tab_id, *br, 1)
add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg)
bw, pw = self.border_width, self.padding_width
fw = bw + pw
if bw + pw <= 0:
return
draw_borders = bw > 0 and draw_window_borders and len(windows) > 1
if draw_borders:
border_data = current_layout.resolve_borders(windows, active_window)
if fw > 0:
for w in windows:
g = w.geometry
if bw > 0 and draw_window_borders:
# Draw the border rectangles
color = 2 if w is active_window else (16 if w.needs_attention else 4)
border(
self.os_window_id, self.tab_id,
color, bw, g.left - fw, g.top - fw, g.right + fw,
g.bottom + fw
)
if pw > 0:
# Draw the background rectangles over the padding region
color = w.screen.color_profile.default_bg
border(
self.os_window_id, self.tab_id,
(color << 8) | 8, pw, g.left - pw, g.top - pw, g.right + pw,
g.bottom + pw
)
for i, w in enumerate(windows):
g = w.geometry
window_bg = w.screen.color_profile.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders:
# Draw the border rectangles
if w is active_window:
color = BorderColor.active
else:
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges(
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw)
if pw > 0:
# Draw the background rectangles over the padding region
colors = (window_bg, window_bg, window_bg, window_bg)
draw_edges(
self.os_window_id, self.tab_id, colors, pw, g)

View File

@@ -10,29 +10,32 @@ 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
from .config_data import MINIMUM_FONT_SIZE
from .constants import (
appname, config_dir, set_boss, supports_primary_selection
appname, config_dir, kitty_exe, set_boss, supports_primary_selection
)
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
from .remote_control import handle_cmd
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
)
@@ -46,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):
@@ -76,8 +104,10 @@ class DumpCommands: # {{{
class Boss:
def __init__(self, os_window_id, opts, args, cached_values, new_os_window_trigger):
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 = {}
@@ -101,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:
@@ -116,11 +151,15 @@ class Boss:
return os_window_id
def list_os_windows(self):
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'tabs': list(tm.list_tabs()),
}
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):
@@ -150,11 +189,18 @@ class Boss:
return
if w is not None:
yield w
return
if field == 'env':
kp, vp = exp.partition('=')[::2]
if vp:
pat = tuple(map(re.compile, (kp, vp)))
else:
pat = re.compile(kp), None
else:
pat = re.compile(exp)
for window in self.all_windows:
if window.matches(field, pat):
yield window
for window in self.all_windows:
if window.matches(field, pat):
yield window
def tab_for_window(self, window):
for tab in self.all_tabs:
@@ -181,26 +227,36 @@ class Boss:
yield tab
def set_active_window(self, window):
for tm in self.os_window_map.values():
for os_window_id, tm in self.os_window_map.items():
for tab in tm:
for w in tab:
if w.id == window.id:
if tab is not self.active_tab:
tm.set_active_tab(tab)
tab.set_active_window(w)
return
return os_window_id
def _new_os_window(self, args, cwd_from=None):
sw = self.args_to_special_window(args, cwd_from) if args else None
if isinstance(args, SpecialWindowInstance):
sw = args
else:
sw = self.args_to_special_window(args, cwd_from) if args else None
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
return self.add_os_window(startup_session)
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):
@@ -313,6 +369,26 @@ class Boss:
if tm is not None:
tm.resize()
def clear_terminal(self, action, only_active):
if only_active:
windows = []
w = self.active_window
if w is not None:
windows.append(w)
else:
windows = self.all_windows
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()
else:
w.screen.erase_in_display(how, False)
def increase_font_size(self): # legacy
cfs = global_font_size()
self.set_font_size(min(self.opts.font_size * 5, cfs + 2.0))
@@ -390,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:
@@ -476,6 +552,8 @@ class Boss:
if w is not None:
tm = self.os_window_map.get(w.os_window_id)
if tm is not None:
tm.update_tab_bar_data()
tm.mark_tab_bar_dirty()
t = tm.tab_for_id(w.tab_id)
if t is not None:
t.relayout_borders()
@@ -484,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
@@ -497,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
@@ -561,7 +643,7 @@ class Boss:
confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay.
cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + get_editor() + [confpath]
cmd = [kitty_exe(), '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + get_editor() + [confpath]
self.new_os_window(*cmd)
def get_output(self, source_window, num_lines=1):
@@ -573,13 +655,21 @@ class Boss:
output += str(s.linebuf.line(i))
return output
def _run_kitten(self, kitten, args=(), input_data=None):
w = self.active_window
tab = self.active_tab
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
@@ -600,15 +690,18 @@ class Boss:
copts = {k: self.opts[k] for k in ('select_by_word_characters', 'open_url_with')}
overlay_window = tab.new_special_window(
SpecialWindow(
['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + args,
stdin=data,
env={
'KITTY_COMMON_OPTS': json.dumps(copts),
'KITTY_CHILD_PID': w.child.pid,
'PYTHONWARNINGS': 'ignore',
'OVERLAID_WINDOW_LINES': str(w.screen.lines),
'OVERLAID_WINDOW_COLS': str(w.screen.columns),
},
overlay_for=w.id))
cwd=w.cwd_of_child,
overlay_for=w.id
))
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
return overlay_window
@@ -647,23 +740,19 @@ class Boss:
break
def kitty_shell(self, window_type):
cmd = ['kitty', '@']
cmd = ['@', kitty_exe(), '@']
if window_type == 'tab':
window = self._new_tab(cmd).active_window
self._new_tab(cmd).active_window
elif window_type == 'os_window':
os_window_id = self._new_os_window(cmd)
window = self.os_window_map[os_window_id].active_window
self.os_window_map[os_window_id].active_window
elif window_type == 'overlay':
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
else:
window = None
tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
else:
window = self._new_window(cmd)
if window is not None:
window.allow_remote_control = True
self._new_window(cmd)
def switch_focus_to(self, window_idx):
tab = self.active_tab
@@ -730,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('@'):
stdin = data_for_at(args[0]) or None
if args[0].startswith('@') and args[0] != '@':
stdin = data_for_at(w, args[0]) or None
if stdin is not None:
stdin = stdin.encode('utf-8')
del args[0]
@@ -756,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):
@@ -771,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
@@ -793,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):
@@ -809,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

@@ -22,8 +22,10 @@ uniform sampler2DArray sprites;
in float effective_text_alpha;
in vec3 sprite_pos;
in vec3 underline_pos;
in vec3 cursor_pos;
in vec3 strike_pos;
in vec3 foreground;
in vec4 cursor_color_vec;
in vec3 decoration_fg;
in float colored_sprite;
#endif
@@ -58,10 +60,12 @@ vec4 calculate_foreground() {
float text_alpha = text_fg.a;
float underline_alpha = texture(sprites, underline_pos).a;
float strike_alpha = texture(sprites, strike_pos).a;
float cursor_alpha = texture(sprites, cursor_pos).a;
// 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
return 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
@@ -97,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

@@ -8,11 +8,12 @@
// 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_x, cursor_y, cursor_w;
uint xnum, ynum, cursor_fg_sprite_idx;
float cursor_x, cursor_y, cursor_w;
uint color_table[256];
};
@@ -53,6 +54,8 @@ uniform float inactive_text_alpha;
uniform float dim_opacity;
out vec3 sprite_pos;
out vec3 underline_pos;
out vec3 cursor_pos;
out vec4 cursor_color_vec;
out vec3 strike_pos;
out vec3 foreground;
out vec3 decoration_fg;
@@ -110,9 +113,18 @@ vec3 choose_color(float q, vec3 a, vec3 b) {
return mix(b, a, q);
}
float is_cursor(uint x, uint y) {
if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0;
return 0.0;
float are_integers_equal(float a, float b) { // return 1 iff equal otherwise 0
float delta = abs(a - b); // delta can be 0, 1 or larger
return step(delta, 0.5); // 0 if 0.5 < delta else 1
}
float is_cursor(uint xi, uint y) {
float x = float(xi);
float y_equal = are_integers_equal(float(y), cursor_y);
float x1_equal = are_integers_equal(x, cursor_x);
float x2_equal = are_integers_equal(x, cursor_w);
float x_equal = step(0.5, x1_equal + x2_equal);
return step(2.0, x_equal + y_equal);
}
// }}}
@@ -141,7 +153,9 @@ void main() {
uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted;
int fg_index = fg_index_map[is_inverted];
int bg_index = 1 - fg_index;
float cursor = is_cursor(c, r);
float cell_has_cursor = is_cursor(c, r);
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
// }}}
@@ -166,8 +180,11 @@ void main() {
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO);
// Cursor
foreground = choose_color(cursor, bg, foreground);
decoration_fg = choose_color(cursor, bg, decoration_fg);
cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0);
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
// }}}
@@ -182,17 +199,17 @@ void main() {
// If the background color is default, set its opacity to background_opacity, otherwise it should be opaque
bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK));
// Cursor must not be affected by background_opacity
bg_alpha = mix(bg_alpha, 1.0, cursor);
bg_alpha = mix(bg_alpha, 1.0, cell_has_block_cursor);
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
#endif
#if defined(SPECIAL) || defined(SIMPLE)
// Selection and cursor
bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg);
background = choose_color(cursor, color_to_vec(cursor_color), bg);
background = choose_color(cell_has_block_cursor, color_to_vec(cursor_color), bg);
#ifdef SPECIAL
// bg_alpha should be 1 if cursor/selection otherwise 0
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cursor));
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cell_has_block_cursor));
#endif
#endif

View File

@@ -25,7 +25,7 @@ extern PyTypeObject Screen_Type;
#define MSG_NOSIGNAL 0
#endif
static void (*parse_func)(Screen*, PyObject*);
static void (*parse_func)(Screen*, PyObject*, double);
typedef struct {
char *data;
@@ -246,9 +246,18 @@ add_child(ChildMonitor *self, PyObject *args) {
}
bool
schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
schedule_write_to_child(unsigned long id, unsigned int num, ...) {
ChildMonitor *self = the_monitor;
bool found = false;
const char *data;
size_t sz = 0;
va_list ap;
va_start(ap, num);
for (unsigned int i = 0; i < num; i++) {
data = va_arg(ap, const char*);
sz += va_arg(ap, size_t);
}
va_end(ap);
children_mutex(lock);
for (size_t i = 0; i < self->count; i++) {
if (children[i].id == id) {
@@ -266,8 +275,14 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
if (screen->write_buf == NULL) { fatal("Out of memory."); }
}
memcpy(screen->write_buf + screen->write_buf_used, data, sz);
screen->write_buf_used += sz;
va_start(ap, num);
for (unsigned int i = 0; i < num; i++) {
data = va_arg(ap, const char*);
size_t dsz = va_arg(ap, size_t);
memcpy(screen->write_buf + screen->write_buf_used, data, dsz);
screen->write_buf_used += dsz;
}
va_end(ap);
if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) {
screen->write_buf_sz = BUFSIZ;
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
@@ -288,7 +303,7 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
unsigned long id, sz;
const char *data;
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; }
if (schedule_write_to_child(id, 1, data, (size_t)sz)) { Py_RETURN_TRUE; }
Py_RETURN_FALSE;
}
@@ -314,13 +329,17 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
static inline void
do_parse(ChildMonitor *self, Screen *screen, double now) {
screen_mutex(lock, read);
if (screen->read_buf_sz) {
if (screen->read_buf_sz || screen->pending_mode.used) {
double time_since_new_input = now - screen->new_input_at;
if (time_since_new_input >= OPT(input_delay)) {
parse_func(screen, self->dump_callback);
if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
screen->read_buf_sz = 0;
bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ;
parse_func(screen, self->dump_callback, now);
if (read_buf_full) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
screen->new_input_at = 0;
if (screen->pending_mode.activated_at) {
double time_since_pending = MAX(0, now - screen->pending_mode.activated_at);
set_maximum_wait(screen->pending_mode.wait_time - time_since_pending);
}
} else set_maximum_wait(OPT(input_delay) - time_since_new_input);
}
screen_mutex(unlock, read);
@@ -353,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++) {
@@ -491,14 +510,6 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
static double last_render_at = -DBL_MAX;
static inline double
cursor_width(double w, bool vert, OSWindow *os_window) {
double dpi = vert ? os_window->fonts_data->logical_dpi_x : os_window->fonts_data->logical_dpi_y;
double ans = w * dpi / 72.0; // as pixels
double factor = 2.0 / (vert ? os_window->viewport_width : os_window->viewport_height);
return ans * factor;
}
extern void cocoa_update_title(PyObject*);
static inline void
@@ -526,23 +537,6 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
ans->is_focused = os_window->is_focused;
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
double left = rd->xstart + cursor->x * rd->dx;
double top = rd->ystart - cursor->y * rd->dy;
unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
double bottom = top - rd->dy;
switch (ans->shape) {
case CURSOR_UNDERLINE:
top = bottom + cursor_width(2.0, false, os_window);
break;
case CURSOR_BLOCK:
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
break;
default:
break;
}
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
}
static inline bool
@@ -571,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++) {
@@ -605,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);
@@ -614,9 +613,6 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
if (w->visible && WD.screen) {
bool is_active_window = i == tab->active_window;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window, true);
if (is_active_window && WD.screen->cursor_render_info.is_visible && (!WD.screen->cursor_render_info.is_focused || WD.screen->cursor_render_info.shape != CURSOR_BLOCK)) {
draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused);
}
if (WD.screen->start_visual_bell_at != 0) {
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
set_maximum_wait(bell_left);
@@ -629,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
}
@@ -644,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) {
@@ -736,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
@@ -792,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;
@@ -873,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;
}
@@ -976,7 +982,12 @@ write_to_child(int fd, Screen *screen) {
written = screen->write_buf_used;
}
}
screen->write_buf_used -= written;
if (written) {
screen->write_buf_used -= written;
if (screen->write_buf_used) {
memmove(screen->write_buf, screen->write_buf + written, screen->write_buf_used);
}
}
screen_mutex(unlock, write);
}
@@ -985,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");
@@ -1004,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) {
@@ -1048,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);
@@ -1060,9 +1088,6 @@ io_loop(void *data) {
// {{{ Talk thread functions
#define MAX_PEERS 256
#define MAX_LISTENERS 2
typedef struct {
char *data;
size_t capacity, used;
@@ -1081,15 +1106,18 @@ static PeerWriteData empty_pwd = {.fd = -1, 0};
typedef struct {
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
struct pollfd fds[MAX_PEERS + MAX_LISTENERS + 1];
PeerReadData reads[MAX_LISTENERS];
PeerWriteData writes[MAX_LISTENERS];
PeerWriteData queued_writes[MAX_LISTENERS];
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
struct pollfd *fds;
PeerReadData *reads;
PeerWriteData *writes;
PeerWriteData *queued_writes;
int wakeup_fds[2];
pthread_mutex_t peer_lock;
} TalkData;
static TalkData talk_data = {0};
typedef struct pollfd PollFD;
#define PEER_LIMIT 256
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
static inline bool
@@ -1101,11 +1129,16 @@ accept_peer(int listen_fd, bool shutting_down) {
return false;
}
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_reads < arraysz(talk_data.reads)) {
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
talk_data.num_talk_fds++;
} else nuke_socket(peer);
} else {
log_error("Too many peers want to talk, ignoring one.");
nuke_socket(peer);
}
return true;
}
@@ -1234,8 +1267,10 @@ move_queued_writes() {
while (talk_data.num_queued_writes) {
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_writes < arraysz(talk_data.writes)) {
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
talk_data.writes[talk_data.num_writes++] = *src;
talk_data.num_talk_fds++;
} else {
@@ -1254,6 +1289,7 @@ talk_loop(void *data) {
set_thread_name("KittyPeerMon");
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
if (!self_pipe(talk_data.wakeup_fds)) { perror("Failed to create wakeup fds for talk thread"); return 0; }
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
#define add_listener(which) \
if (self->which > -1) { \
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
@@ -1284,6 +1320,7 @@ talk_loop(void *data) {
}
end:
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
return 0;
}
@@ -1291,7 +1328,8 @@ static inline bool
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
bool ok = false;
peer_mutex(lock);
if (talk_data.num_queued_writes < arraysz(talk_data.queued_writes)) {
if (talk_data.num_queued_writes < PEER_LIMIT) {
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {

View File

@@ -11,16 +11,31 @@
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
static inline char**
serialize_string_tuple(PyObject *src) {
Py_ssize_t sz = PyTuple_GET_SIZE(src);
char **ans = calloc(sz + 1, sizeof(char*));
if (!ans) fatal("Out of memory");
for (Py_ssize_t i = 0; i < sz; i++) ans[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i));
for (Py_ssize_t i = 0; i < sz; i++) {
const char *pysrc = PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i));
size_t len = strlen(pysrc);
ans[i] = calloc(len + 1, sizeof(char));
if (ans[i] == NULL) fatal("Out of memory");
memcpy(ans[i], pysrc, len);
}
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
@@ -38,18 +53,29 @@ write_to_stderr(const char *text) {
}
}
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
static inline void
wait_for_terminal_ready(int fd) {
char data;
while(1) {
int ret = read(fd, &data, 1);
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
break;
}
}
static PyObject*
spawn(PyObject *self UNUSED, PyObject *args) {
PyObject *argv_p, *env_p;
int master, slave, stdin_read_fd, stdin_write_fd;
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd;
char *cwd, *exe;
if (!PyArg_ParseTuple(args, "ssO!O!iiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
if (!PyArg_ParseTuple(args, "ssO!O!iiiiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd)) return NULL;
char name[2048] = {0};
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
char **argv = serialize_string_tuple(argv_p);
char **env = serialize_string_tuple(env_p);
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
pid_t pid = fork();
switch(pid) {
case 0:
@@ -57,6 +83,17 @@ spawn(PyObject *self UNUSED, PyObject *args) {
// Use only signal-safe functions (man 7 signal-safety)
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
if (setsid() == -1) exit_on_err("setsid() in child process failed");
// Establish the controlling terminal (see man 7 credentials)
int tfd = open(name, O_RDWR);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
#ifdef TIOCSTTY
// On BSD open() does not establish the controlling terminal
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
#endif
close(tfd);
// Redirect stdin/stdout/stderr to the pty
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
if (stdin_read_fd > -1) {
@@ -68,14 +105,19 @@ spawn(PyObject *self UNUSED, PyObject *args) {
}
close(slave);
close(master);
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
close(ready_write_fd);
wait_for_terminal_ready(ready_read_fd);
close(ready_read_fd);
// Close any extra fds inherited from parent
for (int c = 3; c < 201; c++) close(c);
// Establish the controlling terminal (see man 7 credentials)
int tfd = open(name, O_RDWR);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
close(tfd);
environ = env;
// 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
@@ -94,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,38 +4,137 @@
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,
process_group_map as _process_group_map
)
def cwd_of_process(pid):
if is_macos:
from kitty.fast_data_types import cwd_of_process
ans = cwd_of_process(pid)
else:
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):
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
def cwd_of_process(pid):
ans = '/proc/{}/cwd'.format(pid)
return os.path.realpath(ans)
return os.path.realpath(ans)
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 cmdline_of_process(pid):
if is_macos:
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
raise NotImplementedError()
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
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."""
# The block is usually raw data from the target process. It might contain
# trailing garbage and lines that do not look like assignments.
ret = {}
pos = 0
while True:
next_pos = data.find("\0", pos)
# nul byte at the beginning or double nul byte means finish
if next_pos <= pos:
break
# there might not be an equals sign
equal_pos = data.find("=", pos, next_pos)
if equal_pos > pos:
key = data[pos:equal_pos]
value = data[equal_pos + 1:next_pos]
ret[key] = value
pos = next_pos + 1
return ret
def environ_of_process(pid):
return parse_environ_block(_environ_of_process(pid))
def remove_cloexec(fd):
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC)
def default_env():
try:
return default_env.env
except AttributeError:
return os.environ
def set_default_env(val=None):
env = os.environ.copy()
if val:
env.update(val)
default_env.env = env
class Child:
child_fd = pid = None
forked = False
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
self.allow_remote_control = False
if argv and argv[0] == '@':
self.allow_remote_control = True
if len(argv) > 1:
argv = argv[1:]
self.argv = argv
if cwd_from is not None:
try:
@@ -58,12 +157,14 @@ class Child:
remove_cloexec(slave)
fast_data_types.set_iutf8(master, True)
stdin, self.stdin = self.stdin, None
ready_read_fd, ready_write_fd = os.pipe()
remove_cloexec(ready_read_fd)
if stdin is not None:
stdin_read_fd, stdin_write_fd = os.pipe()
remove_cloexec(stdin_read_fd)
else:
stdin_read_fd = stdin_write_fd = -1
env = os.environ.copy()
env = default_env().copy()
env.update(self.env)
env['TERM'] = self.opts.term
env['COLORTERM'] = 'truecolor'
@@ -76,15 +177,44 @@ class Child:
# Some macOS machines need the shell to have argv[0] prefixed by
# hyphen, see https://github.com/kovidgoyal/kitty/issues/247
argv[0] = ('-' + exe.split('/')[-1])
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd)
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
os.close(slave)
self.pid = pid
self.child_fd = master
if stdin is not None:
os.close(stdin_read_fd)
fast_data_types.thread_write(stdin_write_fd, stdin)
os.close(ready_read_fd)
self.terminal_ready_fd = ready_write_fd
fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fcntl.fcntl(self.child_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
return pid
def mark_terminal_ready(self):
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:
@@ -92,9 +222,34 @@ class Child:
except Exception:
return list(self.argv)
@property
def environ(self):
try:
return environ_of_process(self.pid)
except Exception:
return {}
@property
def current_cwd(self):
try:
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

@@ -7,7 +7,6 @@ import re
import sys
from collections import deque
from .config import defaults, load_config
from .conf.utils import resolve_config
from .constants import appname, defconf, is_macos, is_wayland, str_version
@@ -29,134 +28,10 @@ directory is always used and the above searching does not happen.
If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists it is merged before (i.e. with lower
priority) than any user config files. It can be used to specify system-wide
defaults for all users.
'''.replace('{macos_confpath}', ':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else '')
OPTIONS = '''
--class
dest=cls
default={appname}
condition=not is_macos
Set the class part of the :italic:`WM_CLASS` window property
--name
condition=not is_macos
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
--title
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.
--config -c
type=list
{config_help}
--override -o
type=list
Override individual configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
--directory -d
default=.
Change to the specified directory when launching
--detach
type=bool-set
condition=not is_macos
Detach from the controlling terminal, if any
--session
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
See the README file for details and an example.
--single-instance -1
type=bool-set
If specified only a single instance of :italic:`{appname}` will run. New invocations will
instead create a new top-level window in the existing :italic:`{appname}` instance. This
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
:option:`kitty --instance-group` option
--instance-group
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
with the same :option:`kitty --instance-group` will result in new windows being created
in the first :italic:`{appname}` instance within that group
--wait-for-single-instance-window-close
type=bool-set
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
instance and quit immediately. With this option, it will not quit till the newly opened
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
regardless of this option.
--listen-on
Tell kitty to listen on the specified address for control
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can also use abstract
UNIX sockets, not associated with a file, like this: :option:`{appname} --listen-on`=unix:@mykitty.
To control kitty, you can send it commands with :italic:`kitty @` using the :option:`kitty @ --to` option
to specify this address. Note that this option will be ignored, unless you set
:opt:`allow_remote_control` to yes in :file:`kitty.conf`.
# Debugging options
--version -v
type=bool-set
The current {appname} version
--dump-commands
type=bool-set
Output commands received from child process to stdout
--replay-commands
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
can open a new kitty window to replay the commands with::
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
--dump-bytes
Path to file in which to store the raw bytes received from the child process
--debug-gl
type=bool-set
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
instead of ignoring them. Useful when debugging rendering problems
--debug-keyboard
type=bool-set
This option will cause kitty to print out key events as they are received
--debug-font-fallback
type=bool-set
Print out information about the selection of fallback fonts for characters not present in the main font.
--debug-config
type=bool-set
Print out information about the system and kitty configuration.
--execute -e
type=bool-set
!
'''
'''.replace(
'{macos_confpath}',
(':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1
)
def surround(x, start, end):
@@ -213,6 +88,10 @@ def code(x):
return x
def kbd(x):
return x
def env(x):
return italic(x)
@@ -221,7 +100,9 @@ def file(x):
return italic(x)
def parse_option_spec(spec=OPTIONS):
def parse_option_spec(spec=None):
if spec is None:
spec = options_spec()
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
state = NORMAL
lines = spec.splitlines()
@@ -394,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)
@@ -443,7 +327,7 @@ def seq_as_rst(seq, usage, message, appname, heading_char='-'):
if defval is not None:
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
if 'choices' in opt:
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(opt['choices'])), ' ' * 4))
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(sorted(opt['choices']))), ' ' * 4))
a('')
text = '\n'.join(blocks)
@@ -578,6 +462,150 @@ def parse_cmdline(oc, disabled, args=None):
def options_spec():
if not hasattr(options_spec, 'ans'):
OPTIONS = '''
--class
dest=cls
default={appname}
condition=not is_macos
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
--name
condition=not is_macos
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
--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. 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
type=list
{config_help}
--override -o
type=list
Override individual configuration options, can be specified multiple times.
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
--directory -d
default=.
Change to the specified directory when launching
--detach
type=bool-set
condition=not is_macos
Detach from the controlling terminal, if any
--session
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
See the README file for details and an example.
--hold
type=bool-set
Remain open after child process exits. Note that this only affects the first
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
--single-instance -1
type=bool-set
If specified only a single instance of :italic:`{appname}` will run. New invocations will
instead create a new top-level window in the existing :italic:`{appname}` instance. This
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
:option:`kitty --instance-group` option
--instance-group
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
with the same :option:`kitty --instance-group` will result in new windows being created
in the first :italic:`{appname}` instance within that group
--wait-for-single-instance-window-close
type=bool-set
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
instance and quit immediately. With this option, it will not quit till the newly opened
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
regardless of this option.
--listen-on
Tell kitty to listen on the specified address for control
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can
also use abstract UNIX sockets, not associated with a file, like this:
:option:`{appname} --listen-on`=unix:@mykitty. To control kitty, you can send
it commands with :italic:`kitty @` using the :option:`kitty @ --to` option to
specify this address. This option will be ignored, unless you set
:opt:`allow_remote_control` to yes in :file:`kitty.conf`. Note that if you run
:italic:`kitty @` within a kitty window, there is no need to specify the :italic:`--to`
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
type=bool-set
The current {appname} version
--dump-commands
type=bool-set
Output commands received from child process to stdout
--replay-commands
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
can open a new kitty window to replay the commands with::
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
--dump-bytes
Path to file in which to store the raw bytes received from the child process
--debug-gl
type=bool-set
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
instead of ignoring them. Useful when debugging rendering problems
--debug-keyboard
type=bool-set
This option will cause kitty to print out key events as they are received
--debug-font-fallback
type=bool-set
Print out information about the selection of fallback fonts for characters not present in the main font.
--debug-config
type=bool-set
Print out information about the system and kitty configuration.
--execute -e
type=bool-set
!
'''
options_spec.ans = OPTIONS.format(
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
@@ -585,6 +613,10 @@ def options_spec():
return options_spec.ans
def options_for_completion():
return parse_option_spec(options_spec())[0]
def option_spec_as_rst(ospec=options_spec, usage=None, message=None, appname=None, heading_char='-'):
options = parse_option_spec(ospec())
seq, disabled = options
@@ -614,12 +646,16 @@ def print_shortcut(key_sequence, action):
keys = []
for key in key_sequence:
names = []
mods, key = key
mods, is_native, key = key
for name, val in mmap.items():
if mods & val:
names.append(name)
if key:
names.append(krmap[key])
if is_native:
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
names.append(glfw_get_key_name(GLFW_KEY_UNKNOWN, key))
else:
names.append(krmap[key])
keys.append('+'.join(names))
print('\t', ' > '.join(keys), action)
@@ -651,6 +687,7 @@ def flatten_sequence_map(m):
def compare_opts(opts):
from .config import defaults, load_config
print('\nConfig options different from defaults:')
default_opts = load_config()
changed_opts = [
@@ -672,6 +709,7 @@ def compare_opts(opts):
def create_opts(args, debug_config=False):
from .config import load_config
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
if debug_config:
print(version(add_rev=True))

View File

@@ -122,6 +122,10 @@ def draw(*a):
write(' '.join(a))
def screen_manipulate_title_stack(op, which):
write(CSI + '%d;%dt' % (op, which))
def report_device_attributes(mode, char):
x = CSI
if char:

View File

@@ -9,7 +9,9 @@ import sys
from .cli import parse_args
from .config import parse_config, parse_send_text_bytes
from .constants import appname
from .fast_data_types import focus_os_window
from .tabs import SpecialWindow
from .utils import natsort_ints
class MatchError(ValueError):
@@ -25,6 +27,14 @@ class OpacityError(ValueError):
hide_traceback = True
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):
@@ -38,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
@@ -45,18 +56,20 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
MATCH_WINDOW_OPTION = '''\
--match -m
The window to match. Match specifications are of the form:
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num.
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
You can use the :italic:`ls` command to get a list of windows. Note that for
numeric fields such as id, pid and num the expression is interpreted as a number,
not a regular expression. The field num refers to the window position in the current tab,
starting from zero and counting clockwise (this is the same as the order in which the
windows are reported by the :italic:`ls` command). The window id of the current window
is available as the KITTY_WINDOW_ID environment variable.
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
to match on environment variables you can specify only the environment variable name or a name
and value, for example, :italic:`env:MY_ENV_VAR=2`
'''
MATCH_TAB_OPTION = '''\
--match -m
The tab to match. Match specifications are of the form:
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline.
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, env, cmdline.
You can use the :italic:`ls` command to get a list of tabs. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression. When using title or id, first a matching tab is
@@ -71,8 +84,8 @@ for that window is used.
'List all windows. The list is returned as JSON tree. The top-level is a list of'
' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)` and'
' :italic:`command-line` of the process running in the window.\n\n'
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`, '
' :italic:`command-line` and :italic:`environment` of the process running in the window.\n\n'
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
argspec=''
)
@@ -113,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,
@@ -129,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
@@ -196,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:
@@ -269,6 +289,69 @@ def set_tab_title(boss, window, payload):
# }}}
# goto_layout {{{
@cmd(
'Set the window layout',
'Set the 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,
argspec='LAYOUT_NAME'
)
def cmd_goto_layout(global_opts, opts, args):
try:
return {'layout': args[0], 'match': opts.match}
except IndexError:
raise SystemExit('No layout specified')
def goto_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:
try:
tab.goto_layout(payload['layout'], raise_exception=True)
except ValueError:
raise UnknownLayout('The layout {} is unknown or disabled'.format(payload['layout']))
# }}}
# 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)',
@@ -319,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
@@ -380,7 +463,7 @@ def close_tab(boss, window, payload):
'Open new window',
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
' the first matching tab is used. Otherwise the currently active tab is used.'
' Prints out the id of the newly opened window. Any command line arguments'
' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:\n'
':italic:`kitty @ new-window --title Email mutt`',
@@ -399,6 +482,12 @@ type=bool-set
Keep the current window focused instead of switching to the newly opened window
--window-type
default=kitty
choices=kitty,os
What kind of window to open. A kitty window or a top-level OS window.
--new-tab
type=bool-set
Open a new tab
@@ -406,12 +495,23 @@ Open a new tab
--tab-title
When using --new-tab set the title of the tab.
--no-response
type=bool-set
default=false
Don't wait for a response giving the id of the newly opened window. Note that
using this option means that you will not be notified of failures and that
the id of the new window will not be printed out.
''',
argspec='[CMD ...]'
)
def cmd_new_window(global_opts, opts, args):
if opts.no_response:
global_opts.no_command_response = True
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
'window_type': opts.window_type, 'no_response': opts.no_response,
'keep_focus': opts.keep_focus, 'args': args or []}
@@ -426,7 +526,16 @@ def new_window(boss, window, payload):
wid = boss.active_window.id
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(wid)
return None if payload['no_response'] else str(wid)
if payload['window_type'] == 'os':
boss._new_os_window(w)
wid = boss.active_window.id
if payload['keep_focus'] and old_window:
os_window_id = boss.set_active_window(old_window)
if os_window_id:
focus_os_window(os_window_id)
return None if payload['no_response'] else str(wid)
match = payload['match']
if match:
@@ -439,7 +548,7 @@ def new_window(boss, window, payload):
w = tab.new_special_window(w)
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(w.id)
return None if payload['no_response'] else str(w.id)
# }}}
@@ -447,11 +556,19 @@ def new_window(boss, window, payload):
@cmd(
'Focus the specified window',
'Focus the specified window, if no window is specified, focus the window this command is run inside.',
options_spec=MATCH_WINDOW_OPTION,
argspec='',
options_spec=MATCH_WINDOW_OPTION + '''\n\n
--no-response
type=bool-set
default=false
Don't wait for a response from kitty. This means that even if no matching window is found,
the command will exit with a success code.
'''
)
def cmd_focus_window(global_opts, opts, args):
return {'match': opts.match}
if opts.no_response:
global_opts.no_command_response = True
return {'match': opts.match, 'no_response': opts.no_response}
def focus_window(boss, window, payload):
@@ -463,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
# }}}
@@ -472,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}
@@ -529,7 +658,7 @@ def get_text(boss, window, payload):
if payload['extent'] == 'selection':
ans = window.text_for_selection()
else:
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True)
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=payload['extent'] == 'all')
return ans
# }}}
@@ -562,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:
@@ -570,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:
@@ -595,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:
@@ -607,6 +742,38 @@ def set_colors(boss, window, payload):
# }}}
# get_colors {{{
@cmd(
'Get terminal colors',
'Get the terminal colors for the specified window (defaults to active window). Colors will be output to stdout in the same syntax as used for kitty.conf',
options_spec='''\
--configured -c
type=bool-set
Instead of outputting the colors for the specified window, output the currently
configured colors.
''' + '\n\n' + MATCH_WINDOW_OPTION
)
def cmd_get_colors(global_opts, opts, args):
return {'configured': opts.configured, 'match': opts.match}
def get_colors(boss, window, payload):
from .rgb import Color, color_as_sharp, color_from_int
ans = {k: getattr(boss.opts, k) for k in boss.opts if isinstance(getattr(boss.opts, k), Color)}
if not payload['configured']:
windows = (window or boss.active_window,)
if payload['match']:
windows = tuple(boss.match_windows(payload['match']))
if not windows:
raise MatchError(payload['match'])
ans.update({k: color_from_int(v) for k, v in windows[0].current_colors.items()})
all_keys = natsort_ints(ans)
maxlen = max(map(len, all_keys))
return '\n'.join(('{:%ds} {}' % maxlen).format(key, color_as_sharp(ans[key])) for key in all_keys)
# }}}
# set_background_opacity {{{
@cmd(
'Set the background_opacity',
@@ -647,7 +814,34 @@ def set_background_opacity(boss, window, payload):
# }}}
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
# kitten {{{
@cmd(
'Run a kitten',
'Run a kitten over the specified window (active window by default).'
' The :italic:`kitten_name` can be either the name of a builtin kitten'
' or the path to a python file containing a custom kitten. If a relative path'
' is used it is searched for in the kitty config directory.',
options_spec=MATCH_WINDOW_OPTION,
argspec='kitten_name',
)
def cmd_kitten(global_opts, opts, args):
if len(args) < 1:
raise SystemExit('Must specify kitten name')
return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]}
def kitten(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise MatchError(match)
for window in windows:
if window:
boss._run_kitten(payload['kitten'], args=tuple(payload['args']), window=window)
break
# }}}
def cli_params_for(func):

View File

@@ -12,8 +12,7 @@
#include <AvailabilityMacros.h>
// Needed for _NSGetProgname
#include <crt_externs.h>
typedef void* rusage_info_t; // needed for libproc.h
#include <libproc.h>
#include <objc/runtime.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -101,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();
@@ -185,6 +199,12 @@ cocoa_create_global_menu(void) {
}
[bar release];
class_addMethod(
object_getClass([NSApp delegate]),
@selector(applicationDockMenu:),
(IMP)get_dock_menu,
"@@:@");
}
void
@@ -202,19 +222,53 @@ cocoa_update_title(PyObject *pytitle) {
}
bool
cocoa_make_window_resizable(void *w) {
cocoa_make_window_resizable(void *w, bool resizable) {
NSWindow *window = (NSWindow*)w;
@try {
[window setStyleMask:
[window styleMask] | NSWindowStyleMaskResizable];
if (resizable) {
[window setStyleMask:
[window styleMask] | NSWindowStyleMaskResizable];
} else {
[window setStyleMask:
[window styleMask] & ~NSWindowStyleMaskResizable];
}
} @catch (NSException *e) {
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
log_error("Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
return false;
}
return true;
}
void
cocoa_focus_window(void *w) {
NSWindow *window = (NSWindow*)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;
@@ -229,15 +283,6 @@ cocoa_get_lang(PyObject UNUSED *self) {
return Py_BuildValue("s", [locale UTF8String]);
}
static PyObject*
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
long pid = PyLong_AsLong(pid_);
struct proc_vnodepathinfo vpi;
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
}
void
cocoa_set_hide_from_tasks(void) {
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
@@ -269,9 +314,14 @@ 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, ""},
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};
@@ -279,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,45 @@ 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) {
#define as_dict_doc "Return all colors as a dictionary of color_name to integer (names are the same as used in kitty.conf)"
PyObject *ans = PyDict_New();
if (ans == NULL) return PyErr_NoMemory();
for (unsigned i = 0; i < arraysz(self->color_table); i++) {
static char buf[32] = {0};
snprintf(buf, sizeof(buf) - 1, "color%u", i);
PyObject *val = PyLong_FromUnsignedLong(self->color_table[i]);
if (!val) { Py_CLEAR(ans); return PyErr_NoMemory(); }
int ret = PyDict_SetItemString(ans, buf, val);
Py_CLEAR(val);
if (ret != 0) { Py_CLEAR(ans); return NULL; }
}
#define D(attr, name) { \
color_type c = colorprofile_to_color(self, self->overridden.attr, 0xffffffff); \
if (c != 0xffffffff) { \
PyObject *val = PyLong_FromUnsignedLong(c); \
if (!val) { Py_CLEAR(ans); return PyErr_NoMemory(); } \
int ret = PyDict_SetItemString(ans, #name, val); \
Py_CLEAR(val); \
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
}}
D(default_fg, foreground); D(default_bg, background);
D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground);
D(highlight_bg, selection_background);
#undef D
return ans;
}
static PyObject*
as_color(ColorProfile *self, PyObject *val) {
@@ -205,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;
}
@@ -255,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)
@@ -262,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 */
@@ -275,6 +336,7 @@ static PyMemberDef members[] = {
static PyMethodDef methods[] = {
METHOD(update_ansi_color_table, METH_O)
METHOD(reset_color_table, METH_NOARGS)
METHOD(as_dict, METH_NOARGS)
METHOD(color_table_address, METH_NOARGS)
METHOD(as_color, METH_O)
METHOD(reset_color, METH_O)

384
kitty/complete.py Normal file
View File

@@ -0,0 +1,384 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import shlex
import sys
from kittens.runner import get_kitten_cli_docs, all_kitten_names
from .cli import options_for_completion, parse_option_spec
from .cmds import cmap
from .shell import options_for_cmd
parsers, serializers = {}, {}
def debug(*a, **kw):
kw['file'] = sys.stderr
print(*a, **kw)
class Completions:
def __init__(self):
self.match_groups = {}
self.no_space_groups = set()
self.files_groups = set()
# Shell specific code {{{
completion_scripts = {
'zsh': '''
_kitty() {
local src
# 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}
fi
}
compdef _kitty kitty
''',
'bash': '''
_kitty_completions() {
local src
local limit
# Send all words up to the word the cursor is currently on
let limit=1+$COMP_CWORD
src=$(printf "%s\n" "${COMP_WORDS[@]: 0:$limit}" | kitty +complete bash)
if [[ $? == 0 ]]; then
eval ${src}
fi
}
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)"
''',
}
def input_parser(func):
name = func.__name__.split('_')[0]
parsers[name] = func
return func
def output_serializer(func):
name = func.__name__.split('_')[0]
serializers[name] = func
return func
@input_parser
def zsh_input_parser(data):
new_word = data.endswith('\n\n')
words = data.rstrip().splitlines()
return words, new_word
@input_parser
def bash_input_parser(data):
new_word = data.endswith('\n\n')
words = data.rstrip().splitlines()
return words, new_word
@input_parser
def fish_input_parser(data):
return data.rstrip().splitlines(), True
@output_serializer
def zsh_output_serializer(ans):
lines = []
for description, matches in ans.match_groups.items():
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(description)]
if description in ans.no_space_groups:
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))
lines.append(' '.join(cmd) + ';')
# debug('\n'.join(lines))
return '\n'.join(lines)
@output_serializer
def bash_output_serializer(ans):
lines = []
for description, matches in ans.match_groups.items():
needs_space = description not in ans.no_space_groups
for word in matches:
if needs_space:
word += ' '
lines.append('COMPREPLY+=({})'.format(shlex.quote(word)))
# debug('\n'.join(lines))
return '\n'.join(lines)
# }}}
@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'] = {
k: None for k in
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
if not prefix or k.startswith(prefix)
}
def kitty_cli_opts(ans, prefix=None):
matches = {}
for opt in options_for_completion():
if isinstance(opt, str):
continue
aliases = frozenset(x for x in opt['aliases'] if x.startswith(prefix)) if prefix else opt['aliases']
for alias in aliases:
matches[alias] = opt['help'].strip()
ans.match_groups['Options'] = matches
def complete_kitty_cli_arg(ans, opt, prefix):
prefix = prefix or ''
if opt and opt['dest'] == 'override':
from kitty.config import option_names_for_completion
k = 'Config directives'
ans.match_groups[k] = {k+'=': None for k in option_names_for_completion() if k.startswith(prefix)}
ans.no_space_groups.add(k)
def complete_alias_map(ans, words, new_word, option_map, complete_args=None):
expecting_arg = False
opt = None
last_word = words[-1] if words else ''
for w in words:
if expecting_arg:
if w is last_word and not new_word:
if opt is not None:
complete_args(ans, opt, w)
return
expecting_arg = False
continue
opt = option_map.get(w)
if w is last_word and not new_word:
if w.startswith('-'):
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(last_word)}
else:
if complete_args is not None:
complete_args(ans, None, last_word)
return
if opt is None:
if complete_args is not None:
complete_args(ans, None, '' if new_word else last_word)
return # some non-option word encountered
expecting_arg = not opt.get('type', '').startswith('bool-')
if expecting_arg:
if opt is not None and complete_args is not None:
complete_args(ans, opt, '' if new_word else last_word)
else:
prefix = '' if new_word else last_word
if complete_args is not None:
complete_args(ans, None, prefix)
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(prefix)}
def complete_cli(ans, words, new_word, seq, complete_args=lambda *a: None):
option_map = {}
for opt in seq:
if not isinstance(opt, str):
for alias in opt['aliases']:
option_map[alias] = opt
complete_alias_map(ans, words, new_word, option_map, complete_args)
def complete_remote_command(ans, cmd_name, words, new_word):
aliases, alias_map = options_for_cmd(cmd_name)
if not alias_map:
return
complete_alias_map(ans, words, new_word, alias_map)
def path_completion(prefix=''):
prefix = prefix.replace(r'\ ', ' ')
dirs, files = [], []
base = '.'
if prefix.endswith('/'):
base = prefix
elif '/' in prefix:
base = os.path.dirname(prefix)
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:
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')
if files:
ans.match_groups[files_group_name] = dict.fromkeys(files)
ans.files_groups.add(files_group_name)
def complete_icat_args(ans, opt, prefix):
from mimetypes import guess_type
def icat_file_predicate(filename):
mt = guess_type(filename)[0]
if mt and mt.startswith('image/'):
return True
if opt is None:
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)
except SystemExit:
cd = None
if cd is None:
return
options = cd['options']()
seq = parse_option_spec(options)[0]
option_map = {}
for opt in seq:
if not isinstance(opt, str):
for alias in opt['aliases']:
option_map[alias] = opt
complete_alias_map(ans, words, new_word, option_map, {
'icat': complete_icat_args,
'diff': complete_diff_args,
}.get(kitten))
def find_completions(words, new_word, entry_points, namespaced_entry_points):
ans = Completions()
if not words or words[0] != 'kitty':
return ans
words = words[1:]
if not words or (len(words) == 1 and not new_word):
prefix = words[0] if words else ''
completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points)
kitty_cli_opts(ans, prefix)
return ans
if words[0] == '@':
if len(words) == 1 or (len(words) == 2 and not new_word):
prefix = words[1] if len(words) > 1 else ''
ans.match_groups['Remote control commands'] = {c: None for c in cmap if c.startswith(prefix)}
else:
complete_remote_command(ans, words[1], words[2:], new_word)
return ans
if words[0].startswith('@'):
if len(words) == 1 and not new_word:
prefix = words[0]
ans.match_groups['Remote control commands'] = {'@' + c: None for c in cmap if c.startswith(prefix)}
else:
complete_remote_command(ans, words[0][1:], words[1:], new_word)
if words[0] == '+':
if len(words) == 1 or (len(words) == 2 and not new_word):
prefix = words[1] if len(words) > 1 else ''
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
else:
if words[1] == 'kitten':
if len(words) == 2 or (len(words) == 3 and not new_word):
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith('' if len(words) == 2 else words[2]))
else:
complete_kitten(ans, words[2], words[3:], new_word)
return ans
if words[0].startswith('+'):
if len(words) == 1:
if new_word:
if words[0] == '+kitten':
ans.match_groups['Kittens'] = dict.fromkeys(all_kitten_names())
else:
prefix = words[0]
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
else:
if len(words) == 2 and not new_word:
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith(words[1]))
else:
if words[0] == '+kitten':
complete_kitten(ans, words[1], words[2:], new_word)
else:
complete_cli(ans, words, new_word, options_for_completion(), complete_kitty_cli_arg)
return ans
def setup(cstyle):
print(completion_scripts[cstyle])
def main(args, entry_points, namespaced_entry_points):
if not args:
raise SystemExit('Must specify completion style')
cstyle = args[0]
if cstyle == 'setup':
return setup(args[1])
data = sys.stdin.read()
try:
parser = parsers[cstyle]
serializer = serializers[cstyle]
except KeyError:
raise SystemExit('Unknown completion style: {}'.format(cstyle))
words, new_word = parser(data)
ans = find_completions(words, new_word, entry_points, namespaced_entry_points)
print(serializer(ans), end='')

View File

@@ -119,7 +119,16 @@ def merged_opts(all_options, opt, i):
def remove_markup(text):
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', r'\2', text, flags=re.DOTALL)
def sub(m):
if m.group(1) == 'ref':
return {
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
}[m.group(2)]
return m.group(2)
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
def iter_blocks(lines):

View File

@@ -2,7 +2,6 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import ast
import os
import re
import shlex
@@ -40,6 +39,7 @@ def to_cmdline(x):
def python_string(text):
import ast
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''")
@@ -228,19 +228,28 @@ def parse_kittens_shortcut(sc):
def parse_kittens_func_args(action, args_funcs):
parts = action.split(' ', 1)
parts = action.strip().split(' ', 1)
func = parts[0]
if len(parts) == 1:
return func, ()
rest = parts[1]
parser = args_funcs.get(func)
if parser is not None:
try:
func, args = parser(func, rest)
except Exception:
raise ValueError('Unknown key action: {}'.format(action))
try:
parser = args_funcs[func]
except KeyError as e:
raise KeyError(
'Unknown action: {}. Check if map action: '
'{} is valid'.format(func, action)
) from e
try:
func, args = parser(func, rest)
except Exception:
raise ValueError('Unknown key action: {}'.format(action))
if not isinstance(args, (list, tuple)):
args = (args,)
return func, tuple(args)

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