Compare commits

...

800 Commits

Author SHA1 Message Date
Kovid Goyal
72f92b395f version 0.26.5 2022-11-07 15:36:42 +05:30
Kovid Goyal
c9c8eb6e94 ... 2022-11-05 11:37:07 +05:30
Kovid Goyal
59ded41f7a When no OS Window is focused the active_* should return those belonging to the last focused OS Window 2022-11-05 11:34:10 +05:30
Kovid Goyal
10ad56885e Properly specify semantics of active and focused
active means active in parent. focused means actually has keyboard
focus.
2022-11-04 10:38:18 +05:30
Kovid Goyal
d1eb9340ac Output the actual tab id in its repr 2022-11-04 09:29:33 +05:30
Kovid Goyal
a856c64104 Have the semantics of is_focused match that of state:focused otherwise things are liable to get confusing
The previous value is now available as is_active_window
2022-11-03 22:32:07 +05:30
Kovid Goyal
c0b11c5656 Also change the behavior of state:focused matching for tabs 2022-11-03 21:56:23 +05:30
Kovid Goyal
935c4ded6b Remote control: When matching window by state:focused match the window belonging to the OS window that was last focused
Fixes #5602
2022-11-03 21:52:20 +05:30
Kovid Goyal
f5de08d5fa Splits layout: Add a new mappable action to move the active window to the screen edge
Fixes #5643
2022-11-03 17:40:04 +05:30
Kovid Goyal
9d471782dd Increase some more timeouts 2022-11-03 13:13:14 +05:30
Kovid Goyal
dea3df66f8 system python appears broken on macos-latest image
The pip scripts install directory is not on PATH
2022-11-03 13:05:03 +05:30
Kovid Goyal
ec80a1532c Try to make mypy happy with the contortions needed for importlib.resources 2022-11-03 12:41:13 +05:30
Kovid Goyal
93563f1280 More pointless code churn thanks to importlib.resources
They've deprecated a bunch of API but the suggested replacement is
either not present in versions of python that are not EOLed or doesn't
actually work.
2022-11-03 12:13:47 +05:30
Kovid Goyal
e309e54002 Allow using the re module in tab title templates
Fix #5639
2022-11-01 22:25:58 +05:30
Kovid Goyal
0b3228ab16 ... 2022-11-01 12:51:36 +05:30
Kovid Goyal
3f28cf45d4 Dont use the deprecated importlib.resources API 2022-11-01 12:38:24 +05:30
Kovid Goyal
88b829fc9b cleanup comment 2022-11-01 12:22:05 +05:30
Kovid Goyal
458ac6953f Update changelog for last PR 2022-11-01 12:21:09 +05:30
Kovid Goyal
a6855bde3c Merge branch 'no_cursor_update_if_not_changed' of https://github.com/wengxt/kitty 2022-11-01 12:20:18 +05:30
Kovid Goyal
f6f2f00c53 Add a note about wayland semantics 2022-11-01 12:13:23 +05:30
Kovid Goyal
00324c96fc ... 2022-10-31 22:09:01 +05:30
Kovid Goyal
2435a8ccfd Wayland GNOME: Fix incorrect window size when in some circumstances when switching between windows with window decorations disabled
Only call wl_surface_commit() after a resize when the correct size buffer is
attached to the surface. This is ensured by setting a flag on the window that prevents
all surface commits till it is cleared. The flag is cleared at next
eglSwapBuffers().

I dont actually understand if this guarantees that the
buffer size is always correct. For example, if the back buffer is
latched when wl_egl_resize_window() is called, the backbuffer will be
correct only after two swaps (I think). Or maybe the old back buffer is
discarded, I cant find any documentation about it.

All I can say is that doing it this way seems to fix the issue.

Thanks to @jadahl for his help with tracking down the root cause.

Fixes #4802
2022-10-31 21:59:01 +05:30
Kovid Goyal
de122ed727 Bind eglQuerySurface 2022-10-31 21:03:39 +05:30
Kovid Goyal
c2ead407ae ... 2022-10-31 18:14:38 +05:30
Kovid Goyal
16b322616a Fix cursor position at x=0 changing to x=1 on resize
Fixes #5635
2022-10-31 08:03:42 +05:30
Kovid Goyal
e0b4c7edc5 Increase the timeout for shell integration tests
Needed for ARM running non-natively
2022-10-30 11:09:36 +05:30
Kovid Goyal
a8ab4eaf23 Use a larger timeout when running prewarm test 2022-10-30 10:36:10 +05:30
Kovid Goyal
3847837bd0 Forgot to run gen-config.py 2022-10-19 20:50:53 +05:30
Kovid Goyal
1747bbbbcb ssh kitten: Allow using absolute paths for the location of transferred data
Fixes #5607
2022-10-19 20:43:53 +05:30
Kovid Goyal
90e80477e9 Fix background_tint with background_opacity < 1 causing colors in cell area and padding to not match 2022-10-19 19:24:37 +05:30
Kovid Goyal
4f90110a7c Fix ghosting when using background_tint under GNOME+Wayland
The problem was that on Wayland if the buffer contains pixels with alpha
< 1 they are blended with something, even if the window is opaque. Under
mutter that something was the previous frame, under sway it was the
background/whatever is under the window.

So when blending the tint color, use a blend mode that results in opaque
pixels.

Fixes #5605
2022-10-19 19:02:05 +05:30
Kovid Goyal
28c616d3ff Move blend mode selection into draw_tint 2022-10-19 17:13:46 +05:30
Kovid Goyal
f5126c664c Wayland KDE: Fix abort when pasting into Firefox
Dunno why kwin uses drag and drop callbacks when pasting into Firefox on
Wayland, but then this is Wayland, so every stupid thing imaginable will
be done at least once.

Fixes #5603
2022-10-19 11:28:33 +05:30
Kovid Goyal
0f340086c0 Fix resize_draw_strategy=static
The text is now static again but the background image is still scaled, cant be
bothered about that. Fixes #5601
2022-10-19 08:26:31 +05:30
Kovid Goyal
5c34f16ab4 Fix #5600 2022-10-19 07:45:27 +05:30
Kovid Goyal
03fe928e3f show progress while compressing release tarballs 2022-10-17 09:55:09 +05:30
Kovid Goyal
55b53bcac6 version 0.26.4 2022-10-17 08:18:51 +05:30
Kovid Goyal
57d044861f Merge branch 'patch-1' of https://github.com/ms-test/kitty 2022-10-16 13:28:06 +05:30
ms-test
aab149366d Check correct pair member
I haven't looked deeply, but this looked to be a simple typo unless it's checking one both times because of a type invariant. If that is the case, and the check is correct, then a comment and/or code refactoring to call attention to this would likely be a good idea.
2022-10-16 03:53:16 -04:00
Kovid Goyal
896f93822d kitty shell: Also output active tab id
Fixes #5590
2022-10-16 08:52:35 +05:30
Kovid Goyal
6890e265b6 GNOME Wayland: Fix a memory leak in gnome-shell when using client side decorations
destroy CSD buffers that were never attached explicitly, as mutter does
not send release events for these even when the pool is destroyed.
2022-10-12 17:52:47 +05:30
Kovid Goyal
48a4edc199 Merge branch 'action_cwd_doc' of https://github.com/cbosdo/kitty 2022-10-10 14:04:29 +05:30
Cédric Bosdonnat
d63be90a7f Recommend using --cwd=current in open-actions.conf
Editors like Vim are basing path-related commands on the current working
directory. Launching the action with the current CWD in hyperlink opens
makes life easier and would work with every editor.
2022-10-10 10:12:20 +02:00
Kovid Goyal
6af020e4ef Merge branch 'marksisson/simple-docs-change' of https://github.com/marksisson/kitty 2022-10-09 12:18:25 +05:30
Mark Sisson
ca24e9ca67 change linux_package build instructions to use make target 2022-10-09 00:02:38 -05:00
Kovid Goyal
fbfaefd72a Wayland: Fix an abort when changing background colors with wayland_titlebar_color set to background
Fixes #5562
2022-10-07 14:13:12 +05:30
Kovid Goyal
94b3776066 Merge branch 'master' of https://github.com/Diff-fusion/kitty 2022-10-05 09:09:23 +05:30
Felix
15e4be9051 Fix clipboard target properties and memory leak 2022-10-04 17:22:15 +02:00
Kovid Goyal
ae8f3de070 Allow centering the background image
Fixes #5525
2022-10-04 08:26:28 +05:30
Kovid Goyal
c8d18ffe26 Add a note about yet another gnome bug 2022-10-03 22:06:00 +05:30
Kovid Goyal
4d80427908 Wayland: Fix background image scaling using tiled mode on high DPI screens 2022-10-03 21:49:52 +05:30
Kovid Goyal
0e69324469 Fix resize draw strategy == static causing background image/borders to not be drawn 2022-10-03 21:40:00 +05:30
Kovid Goyal
cf22729dfa Fix background_tint not applying to window margins and padding
Fixes #3933
2022-10-03 21:39:57 +05:30
Kovid Goyal
1402c5bbfa Forgot to change check for space for next tab to use its max tab length rather than the current tabs max tab length, since they can now be different 2022-10-03 16:21:50 +05:30
Kovid Goyal
f0f0c8f4fe max_title_length -> max_tab_length 2022-10-03 16:15:24 +05:30
Kovid Goyal
96faac95db DRYer 2022-10-03 15:32:42 +05:30
Kovid Goyal
08041415d1 Document the purpose of for_layout 2022-10-03 13:26:55 +05:30
Kovid Goyal
647b18d345 Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab
Fixes #5548
2022-10-03 13:24:21 +05:30
Kovid Goyal
a116e3cadd Merge branch 'patch-1' of https://github.com/sorrycc/kitty 2022-09-30 07:46:32 +05:30
chencheng (云谦)
52c9b5f5b8 Update custom.css 2022-09-30 08:44:09 +08:00
Kovid Goyal
bbc9f588f1 Merge branch 'unicode-15' of https://github.com/page-down/kitty 2022-09-29 09:17:02 +05:30
pagedown
13a3c6b5b2 Update to Unicode 15.0 2022-09-29 10:13:21 +08:00
pagedown
fc15b20f24 Update to Nerd Fonts 2.2.2 2022-09-29 10:13:10 +08:00
Kovid Goyal
cd8f2e5cc6 another mypy update another round of breakage 2022-09-27 07:19:03 +05:30
Kovid Goyal
267798a277 Update the docs of open_url_with to indicate that open-actions.conf is read by the "default" handler 2022-09-26 20:21:44 +05:30
Kovid Goyal
921ac12e90 Clear both actions caches when config file is reloaded 2022-09-26 20:15:08 +05:30
Kovid Goyal
901e00cab1 Cleanup previous PR
1) Dont use deprecated code
2) Always set the dock icon on startup as the dock icon doesnt change
   till the dock is restarted
3) Update the app icon automatically if the mtime on the custom icon in
   the config dir is newer than the mtime of the sentinel file apple
   puts inside the application bundle to indicate it has a custom icon
2022-09-25 18:11:17 +05:30
Kovid Goyal
d0769f3979 ... 2022-09-25 17:36:32 +05:30
Kovid Goyal
7e4dd8cf1e Merge branch 'macos-set-icon' of https://github.com/page-down/kitty 2022-09-25 14:40:36 +05:30
Kovid Goyal
5ea0519f62 Pass through python used for type check to the test code from before the env is sanitized 2022-09-25 14:36:12 +05:30
pagedown
a1029418f8 macOS: Allow to set custom app icon automatically 2022-09-25 15:25:43 +08:00
Kovid Goyal
98eacb2067 panel kitten: Do not depend on xprop 2022-09-24 15:44:54 +05:30
Kovid Goyal
2cef9abbd3 Get rid of one call to xprop in the panel kitten 2022-09-24 15:17:11 +05:30
Kovid Goyal
0fa158c809 Merge branch 'docs' of https://github.com/page-down/kitty 2022-09-24 14:38:42 +05:30
pagedown
c54a6b398b Add some missing no_response for rc commands 2022-09-24 12:43:13 +08:00
pagedown
27ea367123 Docs: Remove the extra backslashes from the install commands 2022-09-24 12:39:05 +08:00
Kovid Goyal
42178a4570 X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines
Fixes #5528

And let me just re-iterate, GNOME/GTK is developed by morons.
2022-09-24 08:56:50 +05:30
Kovid Goyal
e98ecc1c4f Another auto-indent fix 2022-09-24 08:34:07 +05:30
Kovid Goyal
0d64246209 fix vim autoindent 2022-09-24 08:31:14 +05:30
Kovid Goyal
a0495219ab ... 2022-09-24 08:30:40 +05:30
Kovid Goyal
9d98aa3159 Merge branch 'patch-2' of https://github.com/sashashura/kitty 2022-09-24 07:29:42 +05:30
Alex
5c82f4beca build: harden ci.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-09-23 22:14:57 +02:00
Alex
13539bd8c6 build: harden codeql-analysis.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-09-23 22:14:16 +02:00
Kovid Goyal
26b8ab9adf Use a regex for bracketed paste sanitization 2022-09-23 22:18:03 +05:30
Kovid Goyal
dbb97a62bf version 0.26.3 2022-09-22 13:48:20 +05:30
Kovid Goyal
b1fa1c9820 Make test a bit more robust 2022-09-21 23:16:34 +05:30
Kovid Goyal
2dc9488909 switch python autoformatter to isort+black+autoflake 2022-09-21 13:40:37 +05:30
Kovid Goyal
e2a251224d ssh kitten: Better error message when tar is missing 2022-09-21 07:34:15 +05:30
Kovid Goyal
2a2dca8a58 ... 2022-09-18 08:26:40 +05:30
Kovid Goyal
e1f3cb85c0 Window for id should not fail when not called from a callback 2022-09-18 08:24:02 +05:30
Kovid Goyal
13cf38d6c6 Update changelog 2022-09-17 18:21:28 +05:30
Kovid Goyal
5180a41e87 Fix a use-after-free when handling fake mouse clicks and the action causes windows to be removed/re-allocated
Fixes #5506
2022-09-17 18:21:10 +05:30
Kovid Goyal
1c44da2b4a Nicer error message when trying to open the config file and no editor is available 2022-09-13 17:24:11 +05:30
Kovid Goyal
bcd78c3940 Fix a regression that caused kitty not to restore SIGPIPE after python nukes it when launching children. Affects bash which does not sanitize its signal mask.
Fixes #5500
2022-09-13 07:51:32 +05:30
Kovid Goyal
fe2f3d9870 Wayland: Remove the mutter specific ignoring of the first empty surface configure event. It seems to be no longer necessary on current mutter, with the recent Wayland changes 2022-09-11 14:37:50 +05:30
Kovid Goyal
dcf2152a7a Wayland: Generate a XDG_ACTIVATION_TOKEN when opening URLs or running programs in the background via the launch action 2022-09-11 14:25:49 +05:30
Kovid Goyal
58a3baaf0f Wayland: Use XDG_ACTIVATION_TOKEN when present at launch 2022-09-11 13:43:52 +05:30
Kovid Goyal
b247759d30 DRYer 2022-09-11 13:15:42 +05:30
Kovid Goyal
ba8d30896b Try to use the activation protocol to focus windows 2022-09-11 10:39:28 +05:30
Kovid Goyal
4d30ae55f3 Wayland: Mark windows in which a bell as urgent on compositors that support the xdg-activation protocol 2022-09-11 09:33:41 +05:30
Kovid Goyal
983ab3c67d build wayland xdg-activation protocol 2022-09-10 16:08:38 +05:30
Kovid Goyal
0bf228b030 Wayland: Micro-optimization: Assume window will be shown on primary monitor so create it with its scale set to that of the primary monitor 2022-09-10 15:23:56 +05:30
Kovid Goyal
29b3a92ffc Wayland: swap buffers immediately on scale change to ensure attached buffer is a multiple of the new scale
Earlier we were only swapping buffers when ready to draw, but the
Wayland protocol  requires the attached buffer to be a multiple of the
scale. We cannot guarantee an application side swap will be triggered
before the next commit, so instead we blank the new buffer swap it in
the GLFW backend itself.

Fixes #5467
2022-09-10 15:09:46 +05:30
Kovid Goyal
23d73ec933 ... 2022-09-10 12:52:27 +05:30
Kovid Goyal
bc650f6230 Output initial window creation size in debug 2022-09-10 12:28:53 +05:30
Kovid Goyal
5272318d67 Update changelog 2022-09-10 12:10:30 +05:30
Kovid Goyal
5ba887ff68 Wayland: Fix for bug in NVIDIA drivers that prevents transparency working
Fixes #5479
2022-09-10 11:57:18 +05:30
Kovid Goyal
67d70f8b8f Apparently on Wayland we need to set the swap interval on every window not just the first. It's somehow not shared even though the context is shared? Fixes #5495 2022-09-10 10:41:31 +05:30
Kovid Goyal
0fac6e4de9 render frames should be always respected on macOS
Only Wayland suffers from flicker on resize when using render frames.
2022-09-10 10:25:42 +05:30
Kovid Goyal
1e7a11b278 Wayland: When swapping buffers in response to a configure event ensure the window is the current context otherwise the swap fails
The swap failing will mean the buffer is not attached which might cause
issues with some compsitors.
2022-09-10 10:22:09 +05:30
Kovid Goyal
6741ac2087 Sessions: Allow controlling which OS Window is active via the focus_os_window directive 2022-09-10 08:27:36 +05:30
Kovid Goyal
7b2e29a6a8 ... 2022-09-10 07:52:26 +05:30
Kovid Goyal
6f147544d2 Add ! to env var serialization test 2022-09-10 07:21:15 +05:30
Kovid Goyal
89570d3b52 ssh kitten: Fix KITTY_PUBLIC_KEY not being encoded properly when transmitting
Fixes #5496
2022-09-10 07:09:58 +05:30
Kovid Goyal
dd4a65e0c9 ... 2022-09-09 15:41:45 +05:30
Kovid Goyal
79fba9dd9a Use the dynamic property instead of testing identifier serialization 2022-09-09 15:39:00 +05:30
Kovid Goyal
b123c21010 Update changelog
Fixes #5483
2022-09-09 13:13:58 +05:30
Kovid Goyal
9c5d1f9771 Pass null bytes through the clipboard on cocoa 2022-09-09 13:12:12 +05:30
Kovid Goyal
5131224b59 Convenience code for debugging 2022-09-09 12:20:28 +05:30
Kovid Goyal
be89da2271 Handle dyn prefixes 2022-09-09 11:59:49 +05:30
Kovid Goyal
45b408a78b Allow setting arbitrary mime types 2022-09-09 11:14:12 +05:30
Kovid Goyal
0e7da8643e Get cocoa clipboard backend working 2022-09-09 10:59:10 +05:30
Kovid Goyal
410b1816ad More work on cocoa clipboard backend 2022-09-09 07:39:38 +05:30
Kovid Goyal
bcfa2a64e4 Start work on new clipboard api support for macos 2022-09-08 21:45:13 +05:30
Kovid Goyal
91c00fb5ac Implement the new clipboard API for Wayland 2022-09-08 17:29:18 +05:30
Kovid Goyal
c58d217d32 Only warn about cursor size once 2022-09-08 16:28:40 +05:30
Kovid Goyal
a9b6047741 Simplify handling of self offers 2022-09-08 15:34:14 +05:30
Kovid Goyal
6b432bee4f Cleanup sending/receiving mimetype atoms 2022-09-08 12:45:35 +05:30
Kovid Goyal
087f0b449f ... 2022-09-08 12:45:35 +05:30
Kovid Goyal
96160b52b6 Code to get the available mime types in the clipboard 2022-09-08 12:45:34 +05:30
Kovid Goyal
d17a6cd3a3 Get reading from clipboard working 2022-09-08 12:45:34 +05:30
Kovid Goyal
7e1380cc0d Start work on giving GLFW a proper clipboard API 2022-09-08 12:45:34 +05:30
Kovid Goyal
9a99554ed3 Also have confirm() and get_line() return their results on overlay removal 2022-09-08 12:06:25 +05:30
Kovid Goyal
19799bd538 When choosing an option call the callback after the popup window is removed. This ensures the active window is unchanged when the callback is called. Fixes #5488 2022-09-08 12:01:38 +05:30
Kovid Goyal
902e94ceac Fix #5484 2022-09-07 15:09:24 +05:30
Kovid Goyal
5bb2cb06a9 Fix #5477 2022-09-05 21:43:56 +05:30
Kovid Goyal
1559a2e15a Cleanup previous PR 2022-09-05 21:19:58 +05:30
Kovid Goyal
35f94963cd Merge branch 'skip-tests-bash-debug' of https://github.com/jamessan/kitty 2022-09-05 21:18:52 +05:30
James McCoy
f0d497dfe0 Skip bash shell_integration tests if bash is a debug build
Switch to using BASH_VERSINFO rather than BASH_VERSION, since it
provides structured data.

    BASH_VERSINFO
           A readonly array variable whose members hold version information for this instance of bash.  The values assigned to the array members are as follows:
           BASH_VERSINFO[0]        The major version number (the release).
           BASH_VERSINFO[1]        The minor version number (the version).
           BASH_VERSINFO[2]        The patch level.
           BASH_VERSINFO[3]        The build version.
           BASH_VERSINFO[4]        The release status (e.g., beta1).
           BASH_VERSINFO[5]        The value of MACHTYPE.

When release status is not "release", bash builds are in debug mode and
output extra information which disturbs the integration.

Closes #5473
2022-09-05 11:37:37 -04:00
Kovid Goyal
5bdf27fd78 version 0.26.2 2022-09-05 10:49:50 +05:30
Kovid Goyal
f05783e64d Sanitize notifications ids as they are retransmitted over the TTY 2022-09-05 10:41:19 +05:30
Kovid Goyal
c455fea729 Also output screen contents when test fails 2022-09-05 07:52:36 +05:30
Kovid Goyal
5bf5454dbe Nicer fix for #5469 2022-09-04 22:15:17 +05:30
Kovid Goyal
b60d9f73ce Wayland: Fix remembering window size not accurate when client side decorations are present 2022-09-04 14:25:04 +05:30
Kovid Goyal
31fe35cd38 Fix #5469 2022-09-04 13:58:16 +05:30
Kovid Goyal
607bc845fc Allow creating overlay-main windows, which are treated as the active window unlike normal overlays
Fixes #5392
2022-09-03 19:04:31 +05:30
Kovid Goyal
a420d9d19c spacing is a clone safe option 2022-09-02 23:28:58 +05:30
Kovid Goyal
4b81e4936e launch: Allow setting the margin and padding for the newly created window
Fixes #5463
2022-09-02 19:41:05 +05:30
Kovid Goyal
295743a96e Fix ref to launch doc 2022-09-02 16:31:21 +05:30
Kovid Goyal
d3a1848c27 Remove markup from ref_hyperlink text 2022-09-02 16:24:50 +05:30
Kovid Goyal
9cec924f32 hints kitten: Allow using launch as the program to run, to open the result in a new kitty tab/window/etc.
Fixes #5462
2022-09-02 16:23:00 +05:30
Kovid Goyal
67c963ab89 Pass KITTY_LISTEN_ON to programs opened via open_url
Useful if those programs want to talk to kitty via the rc socket
2022-09-02 16:01:30 +05:30
Kovid Goyal
0f7ced25c1 ... 2022-09-01 13:55:48 +05:30
Kovid Goyal
42948ffff0 Merge branch 'docs' of https://github.com/page-down/kitty 2022-09-01 13:55:30 +05:30
pagedown
922ce5bdea Docs: Put example commands in code blocks 2022-09-01 15:35:51 +08:00
pagedown
1b42486026 Docs: Remove the extra empty line in the CLI help
Also keep the example code in a code block, in the HTML documentation.
2022-09-01 15:35:41 +08:00
pagedown
95f1a971b2 Docs: Mention clear_all_shortcuts in the FAQ about show_key kitten 2022-09-01 15:35:17 +08:00
Kovid Goyal
7968c88c67 ... 2022-09-01 07:54:36 +05:30
Kovid Goyal
992e90b0a3 Fix a regression in 0.26.0 that broke mapping of native keys who key codes did not fit in 21 bits
Fixes #5452
2022-08-31 22:19:51 +05:30
Kovid Goyal
afada84f52 Cleanup previous PR
Use wcswidth() for line width calculation. Works with emoji and ignores
escape codes automatically. Add changelog entry.
2022-08-31 08:01:52 +05:30
Kovid Goyal
b978ff8930 Merge branch 'fix/hints-next-line' of https://github.com/trygveaa/kitty 2022-08-31 07:56:38 +05:30
Kovid Goyal
053cede581 Use git rev-parse to get nightly commit hash 2022-08-31 07:22:56 +05:30
Trygve Aaberge
03720402a7 Fix hints sometimes matching next line as part of URL
For URLs where there's fewer characters left to the right edge of the
window than the number of escape characters in the line, the next line
would be included in the URL, as if the URL went all the way to the
right edge.

For example with a 40 chars wide terminal, if you run:

    echo -e '\e[31m1\e[m https://github.com/kovidgoyal/kitty\ntest'

And launch the hints kitten, you'll see that test on the next line will
be included in the URL.

This happened because the calculation for filling the rest of the line
with NUL characters counted the escape characters as well as the visible
characters, so it filled in too few characters.

This is a regression introduced in commit 91966712.
2022-08-30 22:24:58 +02:00
Kovid Goyal
fca0999814 Eureka! Figured out why libedit is breaking in prewarm on macOS via launchd
The prewarm zygote imports the world. shell.py had a top level import
for readline. Which means readline was being imported pre-fork. And of
course as is traditional with Apple libedit is not fork safe. Probably
because it initializes its internal IO routines based on the stdio
handles at time of import which are the handles kitty gets from launchd
2022-08-30 19:35:17 +05:30
Kovid Goyal
78056c659c Another place we cant use readline on macOS 2022-08-30 19:27:55 +05:30
Kovid Goyal
30e1b4680d Only override LANG in default env if it was not set either in the original os env or via the env config directive 2022-08-30 19:17:53 +05:30
Kovid Goyal
89a0c04d19 Fix exec in prewarm forked process sometimes inheriting env vars from grandparent process 2022-08-30 19:03:32 +05:30
Kovid Goyal
1ba027c277 Dont show LC_CTYPE in show_kitty_env_vars when running with system python on macOS 2022-08-30 16:55:17 +05:30
Kovid Goyal
d8e43a3412 macOS: Fix regression in 0.26.0 that caused asking the user for a line of input such as for set_tab_title to not work
Apparently libedit doesn't work in the forked process when the parent
process is run via Launch Services. I cant be bothered to investigate
why, given that libedit is closed source. macOS users will just have to
live without history/completion in the ask kitten until I get around to
writing a replacement for readline/libedit.

And on a personal note, macOS >> Necrotizing fasciitis

Fixes #5447
2022-08-30 16:45:14 +05:30
Kovid Goyal
644e46e91d Reconfigure all stdio streams in the prefork process 2022-08-30 16:22:59 +05:30
Kovid Goyal
e2a1f8dde7 ... 2022-08-30 10:40:59 +05:30
Kovid Goyal
7d09c6b762 Link to start of actual changelog 2022-08-30 10:27:53 +05:30
Kovid Goyal
6604e0d015 Fix regression in 0.26.0 that caused launching kitty without working STDIO handles to result in high CPU usage and prewarming failing
Fixes #5444
2022-08-30 08:15:13 +05:30
Kovid Goyal
bb2ec9cde7 ... 2022-08-30 07:50:36 +05:30
Kovid Goyal
89a2545855 Cleanup previous PR
Allow disabling hyperlinking of rg output. Also parse
--kitten=hyperlink=... form of CLI args.
2022-08-30 07:41:14 +05:30
Kovid Goyal
49f8c0eae6 Merge branch 'hyperlinked-grep-only-matches' of https://github.com/groves/kitty 2022-08-30 06:59:53 +05:30
Kovid Goyal
268d687814 version 0.26.1
Bloody macOS. I have better things to do that play whack-a-mole with all
the bug reports caused macOS not bothering to set LANG.
2022-08-30 01:04:42 +05:30
Charlie Groves
db5b4da388 Add options for linking different parts of ripgrep output 2022-08-29 13:23:42 -04:00
Kovid Goyal
71f8e50460 Update changelog 2022-08-29 21:01:59 +05:30
Kovid Goyal
22fbdbca40 Fix a regression in 0.26.0 that caused kitty to no longer set the LANG environment variable on macOS
Happened because reading the locale uses cocoa APIs and they are not fork
safe, so it was moved to after prewarm forking, but at that point the
default child env had already been set.

Fixes #5439
2022-08-29 20:58:48 +05:30
Kovid Goyal
6253ee2a74 Allow resetting the tab title to default 2022-08-29 20:41:07 +05:30
Kovid Goyal
5b28aed0b1 Allow the set_tab_title action to take an argument 2022-08-29 20:11:18 +05:30
Kovid Goyal
9eabc9ecf1 Merge branch 'bash-inherit_errexit' of https://github.com/grimm26/kovidgoyal-kitty 2022-08-29 19:47:24 +05:30
Mark Keisler
a77852466c bash shell integration: Send STDERR of unsetting inherit_errexit to
/dev/null

Some bash versions may not have that option and rather than checking if
they even have it or if it is set, just quiet the STDERR it may throw.

```
$ kssh myhost
bash: shopt: inherit_errexit: invalid shell option name
myhost:~$ bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```
2022-08-29 08:58:54 -05:00
Kovid Goyal
c68b82e4d0 ssh kitten: Fix executable permission missing from kitty bootstrap script
Fixes #5438
2022-08-29 18:20:09 +05:30
Kovid Goyal
16a4845a72 version 0.26.0 2022-08-29 05:05:11 +05:30
Kovid Goyal
16da031f68 Wait till prompt appears before issuing clear 2022-08-28 21:54:55 +05:30
Kovid Goyal
43b2935e52 Make fish shell integration test for XDG_DATA_DIRS work even if the shells rc files add to XDG_DATA_DIRS.
My Linux distro has now started installing system wide rc files that do
this :((
2022-08-28 21:15:26 +05:30
Charlie Groves
8c7968a4d6 Describe --hyperlink-only-matches in the docs 2022-08-28 07:02:39 -04:00
Kovid Goyal
fba759a47c missed some at_ -> at- 2022-08-28 12:04:00 +05:30
Kovid Goyal
3bcb694b1b Switch over to at-cmd form for references.
Sphinx anyway converts at_cmd to at-cmd for anchors in HTML so using at-cmd
consistently.
2022-08-28 11:54:32 +05:30
Kovid Goyal
555020cfd5 Also allow using at-cmd 2022-08-28 10:26:38 +05:30
Kovid Goyal
391e2b8488 Fix resolution of anchor names for kitty @ command docs 2022-08-28 10:25:00 +05:30
Kovid Goyal
c856d5c058 Fix hyperlinks not present when fetching text from the history buffer
Fixes #5427
2022-08-28 08:54:53 +05:30
Charlie Groves
f5a70adf0b Add a --hyperlink-only-matches flag to hyperlinked_grep
If it's active, only matching lines get links, not file headers or context lines.
2022-08-26 17:05:50 -04:00
Kovid Goyal
e330c38d4a ssh kitten: Delegate to ssh when -T is used
Fixes #5411
2022-08-24 15:23:41 +05:30
Kovid Goyal
d595084395 DRYer 2022-08-24 15:02:36 +05:30
Kovid Goyal
b32ee5ee46 Forgot to change code to remove markup for github links 2022-08-24 15:00:39 +05:30
Kovid Goyal
098dd10789 ... 2022-08-24 14:42:40 +05:30
Kovid Goyal
b5fa643c5d Allow ignoring failures when mapping remote control commands 2022-08-24 14:21:54 +05:30
Kovid Goyal
664dd29970 Merge branch 'rc-no-response' of https://github.com/page-down/kitty 2022-08-24 14:15:13 +05:30
Kovid Goyal
b7a4281985 Allow ignoring failure to close windows/tabs via rc commands 2022-08-24 14:13:31 +05:30
Kovid Goyal
9e9aa52553 Ensure extract_rt_targets is run only once per build 2022-08-24 14:00:44 +05:30
Kovid Goyal
77fbdfbb53 Fix finding local docs when running from source on macOS 2022-08-24 13:54:14 +05:30
Kovid Goyal
214416f1e3 Fix handling of :doc: in CLI help output 2022-08-24 13:43:47 +05:30
pagedown
800437ec6b Remote control: Add --no-response to some commands 2022-08-24 15:14:53 +08:00
Kovid Goyal
957eafbef0 Merge branch 'docs' of https://github.com/page-down/kitty 2022-08-24 12:35:00 +05:30
pagedown
7f228bcbf5 Docs: Unify the list style of available values
Highlight the value and provide hyperlink in the description.
2022-08-24 13:30:52 +08:00
pagedown
fd4a682c5b Fix doc website URL with duplicate slashes 2022-08-24 13:29:51 +08:00
pagedown
ba9afc61a4 Hyperlink doc text role 2022-08-24 13:29:07 +08:00
pagedown
9a70709bba Shorten the reference link to the top of the page
Reduce the length of the link :ref:`shell_integration` in commented conf.
2022-08-24 13:27:19 +08:00
pagedown
c437a36794 Fix GitHub links in commented configuration
Hyperlink GitHub related text roles: iss, pull, disc
2022-08-24 13:27:12 +08:00
pagedown
0e0578ff49 Docs: Use --help long option name in the document
Remove the extra empty line in the CLI help for `--replay-commands`.
2022-08-24 13:27:06 +08:00
pagedown
64a202a125 Docs: Using the lowercase form of the key name consistently 2022-08-24 13:26:50 +08:00
Kovid Goyal
740d1ca146 Merge branch 'high-sierra-compat' of https://github.com/rkitover/kitty 2022-08-20 22:15:10 +05:30
Rafael Kitover
7b75f055ea Fix build on macOS High Sierra
Check for >= 10.14 using Availability.h to use the accessibilityRole
callback which does not seem to be available in 10.13.

Signed-off-by: Rafael Kitover <rkitover@gmail.com>
2022-08-20 16:08:28 +00:00
Kovid Goyal
8d6a3cff25 Fix docs build error 2022-08-20 16:26:49 +05:30
Kovid Goyal
10fdde6647 Hyperlink :envvar: as well 2022-08-20 16:24:46 +05:30
Kovid Goyal
e1c45dc23f ... 2022-08-20 16:20:30 +05:30
Kovid Goyal
2efb8f6dc2 Create an API to easily use remote control commands from kittens 2022-08-20 16:19:16 +05:30
Kovid Goyal
3b77f3b4bc Also hyperlink :term: 2022-08-20 15:40:39 +05:30
Kovid Goyal
31f763282f Document the show help shortcut 2022-08-20 14:03:16 +05:30
Kovid Goyal
39434fb1f6 A new default shortcut to show the kitty documentation 2022-08-20 13:55:10 +05:30
Kovid Goyal
44ccdd36d6 Remove socket prewarming
The potential for breakage is too high, and I am working on an
alternative solution that will be better long term.

Prewarming is still used for kittens launched via keybindings
2022-08-20 13:38:33 +05:30
Kovid Goyal
4cc0138a28 Delete the frozen python dir
I verified that all the files remaining in it are loaded via the python
resource system and so are not needed.
2022-08-20 13:05:12 +05:30
Kovid Goyal
6ca39d9d83 Move code to delete empty folders into bypy
So it applies to both linux and mac builds automatically
2022-08-20 12:46:55 +05:30
Kovid Goyal
26c099639c Remove empty folders from macOS package builds 2022-08-20 12:20:18 +05:30
Kovid Goyal
329043ba08 Cleanup build documentation
Note how to build the kitty docs wherever needed instead of in a
separate section
2022-08-20 12:09:36 +05:30
Kovid Goyal
e289f4959f DRYer 2022-08-20 12:03:33 +05:30
Kovid Goyal
b6760a59fa Another bug in resolve_ref 2022-08-20 11:45:48 +05:30
Kovid Goyal
cd35f92607 Fix resolve_ref() 2022-08-20 11:29:40 +05:30
Kovid Goyal
ea8f223298 Cleanup the wrap implementation
Make it more efficient and add support for hyperlink escapes
2022-08-20 09:17:05 +05:30
Kovid Goyal
a4c3154dab ... 2022-08-20 08:27:24 +05:30
Kovid Goyal
6aa3405c78 Clean up build docs a bit 2022-08-20 08:13:42 +05:30
Kovid Goyal
da618584af Fix some outdated docs 2022-08-19 15:48:17 +05:30
Kovid Goyal
9bc31a5651 Use locally built docs when running from source if they exist 2022-08-19 15:01:20 +05:30
Kovid Goyal
314dd97059 Command line tools shouldnt need to resolve documentation refs
Instead use a new kitty+doc protocol and have kitty navigate to the
docs. Uses a default open action for the protocol, which can be
overriden by the user should they so desire.
2022-08-19 14:35:17 +05:30
Kovid Goyal
0b66f20934 Fix mapping of ref targets with underscores 2022-08-19 14:34:25 +05:30
Kovid Goyal
aba634ce88 Fix building of docs on macOS without warnings
--detach doesnt exist on macOS
2022-08-19 14:16:28 +05:30
Kovid Goyal
033950dae9 Only hyperlink when stdout is a tty 2022-08-19 14:05:53 +05:30
Kovid Goyal
a59e1adee1 Also hyperlink the :ac: role in CLI help output 2022-08-19 14:05:11 +05:30
Kovid Goyal
5350eb29c1 A new mappable action to show kitty docs in the browser 2022-08-19 14:00:30 +05:30
Kovid Goyal
2bb42e67d7 We have to build docs to test building of package now 2022-08-19 13:51:35 +05:30
Kovid Goyal
8745c4cd4a Make the kitty man pages automatically available inside kitty when using a binary build 2022-08-19 12:59:10 +05:30
Kovid Goyal
3288400005 Make the docs and man pages available in the macos bundle as well 2022-08-19 12:38:10 +05:30
Kovid Goyal
1f499a7c6c Add newline to end of generated file 2022-08-19 11:29:47 +05:30
Kovid Goyal
012731c3be Clean _generated.h files 2022-08-19 11:22:30 +05:30
Kovid Goyal
5deed81737 Dont maintain ref_map manually 2022-08-19 11:20:50 +05:30
Kovid Goyal
6f6e23bf61 Update docs 2022-08-19 10:00:06 +05:30
Kovid Goyal
d2d0487356 Hyperlink :ref: roles in CLI help 2022-08-19 09:57:52 +05:30
Kovid Goyal
30df4e5de2 Improve formatting of --stdin-source 2022-08-19 09:34:52 +05:30
Kovid Goyal
3f4588c408 Improve formatting of launch --type help message 2022-08-19 09:30:22 +05:30
Kovid Goyal
4e4ca366d9 Merge branch 'docs' of https://github.com/page-down/kitty 2022-08-18 17:52:24 +05:30
pagedown
040ff0cc9d For running the new code KITTY_PREWARM_SOCKET needs to be dropped 2022-08-18 17:06:07 +08:00
pagedown
b01ad4bb3b ... 2022-08-18 17:05:35 +08:00
pagedown
4476251b76 List the available data names for tab_title_template in commented conf
List syntax starting with * becomes a block of text in the commented
configuration file.
2022-08-18 17:05:24 +08:00
pagedown
0f4cb1955f Use the plus sign to connect shortcut keys consistently 2022-08-18 17:05:00 +08:00
Kovid Goyal
c161ee7b5e Merge branch 'rc-password' of https://github.com/page-down/kitty 2022-08-17 18:41:48 +05:30
pagedown
a5bb2548a6 Do not set KITTY_LISTEN_ON after the window rc password is configured 2022-08-17 19:37:52 +08:00
Kovid Goyal
2898a3ef8c Some documentation cleanup 2022-08-17 06:48:33 +05:30
Kovid Goyal
a0568334b5 Change the default for allow_remote_control back to no
That way if there are any bugs in the implementation, we wont be
exposing users by default. In the future after the implementation has
been battle tested, we can revisit.
2022-08-17 06:42:51 +05:30
Kovid Goyal
4005cfdff2 Disallow passwords that start with - to allow for future addition of options 2022-08-17 00:58:11 +05:30
Kovid Goyal
1d8c5e65b9 Ignore generated .go files 2022-08-17 00:55:12 +05:30
Kovid Goyal
56f45e8b0f Fix unhandled exceptions not being printed to stderr on macOS when run from launch services and using prewarming 2022-08-16 15:28:07 +05:30
Kovid Goyal
48a9584688 Hide the password when asking for rc permission
Can press a key or click on it to reveal it
2022-08-16 14:07:36 +05:30
Kovid Goyal
bfcd324993 ask kitten: allow having hidden text in the message 2022-08-16 13:57:58 +05:30
Kovid Goyal
a75235a260 rcp ars should be a sequnce not a set 2022-08-16 12:10:15 +05:30
Kovid Goyal
e8de2def96 Fix --allow-remote-control not working with allow_remote_control=no 2022-08-16 11:42:39 +05:30
Kovid Goyal
5ffc4c6498 DRYer 2022-08-16 11:37:31 +05:30
Kovid Goyal
d0c50248ea Graphics protocol: Only delete temp files if they have the string tty-graphics-protocol in their file paths.
This prevents deletion of arbitrary files in /tmp via the graphics
protocol.
2022-08-16 11:25:33 +05:30
Kovid Goyal
ca2a121696 Nicer error when listen on socket is not found 2022-08-16 10:59:51 +05:30
Kovid Goyal
02d06d6726 Fix --allow-remote-control without passwords not working 2022-08-16 10:51:54 +05:30
Kovid Goyal
18bf76d49e Dont set KITTY_LISTEN_ON for windows with --allow-remote-control 2022-08-16 10:50:17 +05:30
Kovid Goyal
6961845167 ask kitten: fix button click area offset vertically 2022-08-16 10:35:51 +05:30
Kovid Goyal
4c5f7f50f7 Control KITTY_LISTEN_ON more precisely 2022-08-16 10:16:18 +05:30
Kovid Goyal
572e920466 Allow restricting the remote control actions in specific windows 2022-08-16 07:08:51 +05:30
Kovid Goyal
b81fb3c865 Disable pre-warming when running gen-config.py 2022-08-16 05:41:46 +05:30
Kovid Goyal
814dd8a275 allow_remote_control should be per window not per child 2022-08-15 21:32:14 +05:30
Kovid Goyal
1619687d1d Allow specifying allowed actions when no password is sent 2022-08-15 21:05:53 +05:30
Kovid Goyal
65fc61a507 Remove unused code 2022-08-15 20:54:17 +05:30
Kovid Goyal
d027f524ce Make allow_remote_control a little more fine grained
Also, only respect listen_on if there is a possiblility of rc commands
over it being accepted
2022-08-15 20:51:11 +05:30
Kovid Goyal
df5e6e1563 simpler ignore pattern 2022-08-15 20:15:30 +05:30
Kovid Goyal
5541e3c2ff More robust cleaning of launcher dir 2022-08-14 23:39:26 +05:30
Kovid Goyal
57dfc379ae Fix cocoa being loaded before the fork on macOS 2022-08-14 22:17:27 +05:30
Kovid Goyal
b5467b8e26 Set the cocoa uncaught exception handler during glfw init not at module import time 2022-08-14 19:33:32 +05:30
Kovid Goyal
a9f1928913 ... 2022-08-14 19:16:47 +05:30
Kovid Goyal
ae16fed39b Fix repeated make app failing on macOS 2022-08-14 19:01:58 +05:30
Kovid Goyal
848a795d26 Fix minimal macos bundle build 2022-08-14 13:48:45 +05:30
Kovid Goyal
deb8c3dacd Dont put the launcher source files in the root directory 2022-08-14 13:01:29 +05:30
Kovid Goyal
3cb572b47a Fix build on older gcc 2022-08-14 10:12:17 +05:30
Kovid Goyal
df3846fbb8 Add instructions on how to install the man pages under desktop integration 2022-08-12 23:33:06 +05:30
Kovid Goyal
d73ceb26ae Use the RFC as the reference for base-85 encoding 2022-08-12 15:38:28 +05:30
Kovid Goyal
da9ffc9b93 Allow using IV and tags longer than the minimum lengths 2022-08-12 13:21:57 +05:30
Kovid Goyal
63fa0c4e94 Nicer fix for human_repr of keys defined with kitty_mod 2022-08-12 12:37:15 +05:30
Kovid Goyal
ce91b6d9d9 Switch to using the new SingleKey implementation 2022-08-12 12:21:06 +05:30
Kovid Goyal
c982f24faf Add some tests for hashing and __eq__ 2022-08-12 12:02:22 +05:30
Kovid Goyal
9f865adb83 DRYer 2022-08-12 11:39:26 +05:30
Kovid Goyal
b54dd1cb48 Increase the max key num that is storeable 2022-08-12 11:37:54 +05:30
Kovid Goyal
ef621aa099 DRYer
Also dont store negative values for key. Unset is 0 not -1
2022-08-12 11:35:45 +05:30
Kovid Goyal
df4738f080 ... 2022-08-12 11:24:18 +05:30
Kovid Goyal
8d0a06325c Update config types for new value of KITTY_MOD 2022-08-12 11:07:11 +05:30
Kovid Goyal
f3329fcd34 Fix storage for GLFW_MOD_KITTY 2022-08-12 09:58:50 +05:30
Kovid Goyal
af48547d8b Implement SingleKey._replace 2022-08-12 09:40:58 +05:30
Kovid Goyal
f228f8368a A faster version of SingleKey 2022-08-12 09:17:42 +05:30
Kovid Goyal
9bb2c1a27b Type objects should be static 2022-08-12 08:32:11 +05:30
Kovid Goyal
aa0b07a90d Fix #5371 2022-08-12 08:15:25 +05:30
Kovid Goyal
d4075bfa9a ... 2022-08-12 07:01:40 +05:30
Weng Xuetian
7fd119d0eb Fix message recursion between wayland compositor (#5360)
1. Only update preedit if preedit changed.
2. Only send cursor update if cursor position is not changed.
2022-08-11 15:45:45 -07:00
Kovid Goyal
2b7455e353 Use kitty_mod in debug config output
Fixes #5367
2022-08-11 23:45:23 +05:30
Kovid Goyal
f42d2c63a6 Add type annotations to rc protocol field definitions 2022-08-11 22:14:33 +05:30
Kovid Goyal
6be3ae9efc Start work on standalone remote control script 2022-08-11 21:05:04 +05:30
Kovid Goyal
2fd2e07342 Fix #5366 2022-08-11 15:47:06 +05:30
Kovid Goyal
1e0e32e282 DRYer 2022-08-11 13:42:38 +05:30
Kovid Goyal
b2d8f24499 Clean up the text a bit 2022-08-11 07:47:15 +05:30
Kovid Goyal
12c99741a0 Add an example of restricting the launch command 2022-08-11 07:35:18 +05:30
Kovid Goyal
91dbcf4df0 Link to encryption spec 2022-08-11 07:29:56 +05:30
Kovid Goyal
6ebd82a84c Note which version remote_control_password was added 2022-08-11 06:52:37 +05:30
Kovid Goyal
c07178f43a Avoid transmitting the currently un-needed enc_proto 2022-08-11 06:48:35 +05:30
Kovid Goyal
78a5957863 Highlight the JSON blocks appropriately 2022-08-11 06:46:42 +05:30
Kovid Goyal
cd6882d21e ... 2022-08-11 06:40:44 +05:30
Kovid Goyal
926a2828ab 5 minutes does not actually prevent replay just minimise it 2022-08-11 06:38:59 +05:30
Kovid Goyal
daa7df96d1 ... 2022-08-11 06:38:04 +05:30
Kovid Goyal
84ca83ef69 Document the encryption used by remote_control_password 2022-08-11 06:35:25 +05:30
Kovid Goyal
ac9c85e783 Add a note about password auth over SSH 2022-08-11 06:08:28 +05:30
Kovid Goyal
331f6d4903 Document remote_control_password more thoroughly
Also ignore failures in custom auth functions
2022-08-11 05:58:02 +05:30
Kovid Goyal
2e422e5ba8 Use getpass() to read the password from STDIN when it is a tty 2022-08-11 05:20:05 +05:30
Kovid Goyal
20cffc728e Close tty_fd after dup2 2022-08-11 05:10:15 +05:30
Kovid Goyal
b06cbc9465 Use the OS secure random number generator rather than the OpenSSL one to generate IVs
The OpenSSL one is not fork safe, and in general I trust kernel CSRNG
more than OpenSSL
2022-08-11 05:07:38 +05:30
Kovid Goyal
1348327414 Pressing esc for the permission dialog should be same as reject this request 2022-08-10 20:36:15 +05:30
Kovid Goyal
cd3eeb7a95 Center choice message correctly when it is multi-line
Also fix button wrap calculation. It was not taking the borders into
account.
2022-08-10 20:34:25 +05:30
Kovid Goyal
7be6b28011 Preserve blanks lines and per line formatting in the ask kitten for the message with choices 2022-08-10 20:10:17 +05:30
Kovid Goyal
ef1bee0f48 Shorten text 2022-08-10 20:00:20 +05:30
Kovid Goyal
702da542dd Use a as the default accelerator rather than t 2022-08-10 19:11:18 +05:30
Kovid Goyal
5ba92d06cf Get async response working with sockets 2022-08-10 19:10:04 +05:30
Kovid Goyal
34a7b42063 Dont print traceback when socket is unexpectedly closed 2022-08-10 19:06:57 +05:30
Kovid Goyal
963d1b58f8 Better error message when socket is closed without response 2022-08-10 18:53:15 +05:30
Kovid Goyal
fe598bac8a Quote ^ as it is a special char in some shells 2022-08-10 17:39:05 +05:30
Kovid Goyal
45736d7f1d ... 2022-08-10 17:28:22 +05:30
Kovid Goyal
0fe4f5a686 Fix password based rc not working with generator responses 2022-08-10 17:24:10 +05:30
Kovid Goyal
e9ce5c02d0 Allow remote control commands to be interrupted by ctrl-c while waiting for a response 2022-08-10 17:00:04 +05:30
Kovid Goyal
5916d82580 Send disallowed responses when permission for rc is denied 2022-08-10 16:28:14 +05:30
Kovid Goyal
2c83b9902e Implement the permissions system for password based rc requests 2022-08-10 15:49:50 +05:30
Kovid Goyal
fe07825ad9 Work on new permissions framework for rc commands 2022-08-10 12:36:04 +05:30
Kovid Goyal
56e83d7d07 Write code to decrypt commands received in kitty 2022-08-10 12:36:04 +05:30
Kovid Goyal
c7e3c92a0a Allow listen_on to be used even without allow_remote_control
Since we intend to allow password based access, which works regardless
of the value of allow_remote_control
2022-08-10 12:36:04 +05:30
Kovid Goyal
1a643441f3 Make the kitty public key available as an env var to child processes 2022-08-10 12:36:04 +05:30
Kovid Goyal
2aee746da9 Write the code to encrypt rc messages to kitty 2022-08-10 12:36:04 +05:30
Kovid Goyal
e64b1ba67c Start work on remote control passwords 2022-08-10 12:36:04 +05:30
Kovid Goyal
31be4f041e remove unused code 2022-08-10 12:31:47 +05:30
Kovid Goyal
1f5ebc408d When searching macOS for openssl search for the same version as used by the python we are running 2022-08-10 12:30:36 +05:30
Kovid Goyal
b4d9a22df8 Install the same version of OpenSSL as used by python on brew based CI 2022-08-10 12:21:01 +05:30
Kovid Goyal
d4168e3b80 Dont add openssl to the brewfile
We only need to install it on CI because the python on CI doesnt come
from brew. Normal brewfile based installation will install openssl as a
dependency of python
2022-08-10 12:02:43 +05:30
Kovid Goyal
241c7af855 DRYer 2022-08-10 11:45:07 +05:30
Kovid Goyal
080fcd8fc1 Link against newer rather than older openssl 2022-08-10 11:36:56 +05:30
Kovid Goyal
1aad4c7ec3 Only search for brew openssl on failure 2022-08-10 11:35:43 +05:30
Kovid Goyal
e0e0582b53 Fix #5364
macOS is *the* worst UNIX I have ever seen.
2022-08-10 11:33:30 +05:30
Kovid Goyal
0a6276b007 Implement completion for kitty @ global options 2022-08-09 18:54:38 +05:30
Kovid Goyal
2a2256f7d9 ... 2022-08-09 16:11:23 +05:30
Kovid Goyal
ed81a60065 Add keywords completion for --cwd and --config 2022-08-09 16:07:04 +05:30
Kovid Goyal
5ad55dd165 Allow using file completion for any type of command line arg 2022-08-09 15:57:01 +05:30
Kovid Goyal
6860f7ffd6 fix zsh serialization of directory candidates ending with / 2022-08-09 14:51:14 +05:30
Kovid Goyal
33a3e3fb61 Add completion for kitty @ launch --logo 2022-08-09 14:30:13 +05:30
Kovid Goyal
d4df3f67b6 Allow specifying completion for command line arguments that expect paths 2022-08-09 14:21:12 +05:30
Kovid Goyal
f15a2f0c1f Change --session to resolve relative paths from the kitty config dir 2022-08-09 12:36:16 +05:30
Kovid Goyal
252288aff2 kitten completion: Fix --long-opt <TAB> not working 2022-08-09 12:02:25 +05:30
Kovid Goyal
65a20a2407 Merge branch 'show-key-kitten' of https://github.com/page-down/kitty 2022-08-09 11:32:10 +05:30
pagedown
5f180dbd9a ... 2022-08-09 12:29:22 +08:00
pagedown
ee4e9aa678 Add missing modifier key aliases 2022-08-09 12:15:55 +08:00
pagedown
9f1c9529aa Allow completion for show_key kitten
Use the lowercase form of kitty.
2022-08-09 12:15:48 +08:00
Kovid Goyal
5bba1a0851 get_current_selection should return char* not const char* 2022-08-09 09:05:13 +05:30
Kovid Goyal
e9823eb40c Cleanup previous PR 2022-08-09 08:54:47 +05:30
Kovid Goyal
14fc3f6ac4 Merge branch 'macos-accessibility' of https://github.com/page-down/kitty 2022-08-09 08:52:44 +05:30
pagedown
74714f942a Implement macOS accessibility protocol to provide selected text
Allow "Speak selection" (Option+Esc) to work properly.
2022-08-09 11:02:12 +08:00
pagedown
0d116e6ef0 Use four spaces to indent 2022-08-08 22:31:55 +05:30
pagedown
06108d66b1 Replace tab with four spaces indent 2022-08-08 22:31:55 +05:30
Kovid Goyal
21eece4c6d Ensure no bytecode is written when running shell based tests 2022-08-08 17:50:59 +05:30
Kovid Goyal
4dd696e4e0 Add API to glfw to get the currently active text selection 2022-08-08 15:52:34 +05:30
Kovid Goyal
d04d4444af implement primary selection api on cocoa as stubs, can be useful for #5357 2022-08-06 12:59:15 +05:30
Kovid Goyal
a487a78200 Add type definitions for the crypto data types 2022-08-05 14:39:20 +05:30
Kovid Goyal
f06a72b418 OpenSSL 3.0 apparently has API to get rid of the tag length magic number. Lets hope it works 2022-08-05 14:28:56 +05:30
Kovid Goyal
3270c9a527 Get rid of various magic length constants 2022-08-05 14:17:42 +05:30
Kovid Goyal
a7b7fb560a Add tests for AES256GCM 2022-08-05 13:50:03 +05:30
Kovid Goyal
384eba6cd4 Wrap the OpenSSL API for AES 256 GCM encryption 2022-08-05 13:14:25 +05:30
Kovid Goyal
dc7e3ba1ec DRYer 2022-08-05 08:57:22 +05:30
Kovid Goyal
b1e0adcfdc ... 2022-08-05 08:14:29 +05:30
Kovid Goyal
2a5922ce92 Cleanup previous PR
The reason maxsplit is 1 is because some layout action in the future may need more
sophisticated args processing, for example, shlex.split() instead of
plain split(), or even a full command line parser.
2022-08-05 08:12:05 +05:30
Kovid Goyal
93694925ce Merge branch 'feature/toggle_bias' of https://github.com/mong8se/kitty 2022-08-05 08:11:35 +05:30
Steven Moazami
adfcbb004f Add layout_action to change bias on tall layout
Can specify 1 bias to toggle between that and 50%.

Can specify multiple biases to rotate through them with each call to the
layout_action.

Left a comment regarding working around either a possible bug or a
design decision.
2022-08-04 19:29:17 -04:00
Kovid Goyal
6595898b73 Merge branch 'patch-4' of https://github.com/ctrlcctrlv/kitty 2022-08-04 20:45:29 +05:30
Fredrick Brennan
81b33f1f6f Document how to build docs 2022-08-04 11:12:04 -04:00
Kovid Goyal
a1c4ffec20 Private raw key should be returned as a secret 2022-08-04 20:27:38 +05:30
Kovid Goyal
89854cca8b Test for secret derivation 2022-08-04 20:05:19 +05:30
Kovid Goyal
4fe5211ed7 Code to derive the shared secret with elliptic curve keys 2022-08-04 19:42:38 +05:30
Kovid Goyal
795b32efd7 Bash integration: Fix the inherit_errexit option being set by shell integration
Fixes #5349
2022-08-04 16:23:52 +05:30
Kovid Goyal
153064ef3a Fail if mlock fails 2022-08-04 11:16:36 +05:30
Kovid Goyal
183a31c884 mlock() the returned private key to prevent it from being saved to swap 2022-08-04 11:08:54 +05:30
Kovid Goyal
adf47bed89 Use an actual type for the EC key rather than a capsule 2022-08-04 11:01:14 +05:30
Kovid Goyal
cc297a8984 Merge branch 'patch-1' of https://github.com/TomFaulkner/kitty 2022-08-04 07:39:38 +05:30
Tom Faulkner
4ef17edace Fix doc typo: throught to throughout
Just a typo in the docs. Kitty is great, thanks for all the hard work.
2022-08-03 23:52:00 +00:00
Kovid Goyal
e56fa9d31f Try and get Homebrew installed OpenSSL to work 2022-08-03 23:17:47 +05:30
Kovid Goyal
791d4b30fb We need openssl installed via brew 2022-08-03 22:47:11 +05:30
Kovid Goyal
a9a9f1ac00 Add a mappable action to toggle the mirrored setting for the tall and fat layouts
Fixes #5344
2022-08-03 22:45:38 +05:30
Kovid Goyal
fd6bc55db6 Start work on implementing public key crypto
Will come in handy for various things in the future, so lets just setup
the API now. No new dependencies are needed since Python already depends
on OpenSSL.
2022-08-03 22:37:35 +05:30
Kovid Goyal
97d7329d14 A nicer fix for kitty @ ls | less
We rely on the fact that cfmakeraw() and less's code to put the tty in
raw state diverge. This is obv a hack but then so is kitty @ ls | less

This hack allows

kitty +kitten clipboard --get-clipboard > output

to work.
2022-08-02 18:33:53 +05:30
Kovid Goyal
609d42e2bc Ignore EAGAIN when doing tty io 2022-08-02 17:21:28 +05:30
Kovid Goyal
3463931dad Rewrite clipboard kitten to only change termios settings while it is actually reading from terminal 2022-08-02 17:14:27 +05:30
Kovid Goyal
f637bf2377 socket prewarm: Dont control the tty if STDOUT is not a tty
This is because we could be being piped to a program like less that does
want to control the tty itself
2022-08-02 14:09:34 +05:30
Kovid Goyal
2271eb5e60 Document KITTY_PREWARM_SOCKET_REAL_TTY 2022-08-02 08:38:07 +05:30
Kovid Goyal
a5cbb552f0 Allow prewarming of kitty @ commands 2022-08-01 19:47:28 +05:30
Kovid Goyal
e43ca5b031 Add an implementation using splice() for in-kernel tty copies
Sadly splice is currently not functional in Linux, so the implementation
is turned off
2022-08-01 19:16:51 +05:30
Kovid Goyal
cc7e668ad6 Cleanup editing of proc environ 2022-08-01 19:03:03 +05:30
Kovid Goyal
a01dbbdf04 Fix launcher not building universal 2022-08-01 19:03:03 +05:30
Kovid Goyal
e0ce0a67cc ensure setting KITTY_PREWARM_SOCKET_REAL_TTY does not clobber the environ 2022-08-01 19:03:03 +05:30
Kovid Goyal
0d83a8866b Make setting the real tty name more robust 2022-08-01 19:03:03 +05:30
Kovid Goyal
f7a7e39a36 prewarm wrapper should indicate it is active via the process environment 2022-08-01 19:03:03 +05:30
Kovid Goyal
67cb466ea9 ... 2022-08-01 19:03:03 +05:30
Kovid Goyal
376688ab9f Add a test for SIGTSTP via ctrl-z 2022-08-01 19:03:03 +05:30
Kovid Goyal
8668087d15 flush data when exiting on signal as well
should make the signal tests less flaky
2022-08-01 19:03:03 +05:30
Kovid Goyal
9b1c23987c Fix stdout line_buffering incorrect when parent kitty instance has its stdout redirected to a file 2022-08-01 19:03:03 +05:30
Kovid Goyal
784daa1a4b Update changelog for socket prewarm 2022-08-01 19:03:03 +05:30
Kovid Goyal
06f9b73a7b Workaround mypy bug 2022-08-01 19:03:03 +05:30
Kovid Goyal
62c0272df2 Get test.py working when prewarm socket is in operation 2022-08-01 19:03:03 +05:30
Kovid Goyal
7ce2b4c641 Indicate in kitty_run_data we are prewarmed 2022-08-01 19:03:03 +05:30
Kovid Goyal
08c2de541f Nicer establish_controlling_tty 2022-08-01 19:03:03 +05:30
Kovid Goyal
94551623ec Make JSON reading test a bit more robust 2022-08-01 19:03:02 +05:30
Kovid Goyal
cb0d23bae8 Use sub tests for the various prewarm things 2022-08-01 19:03:02 +05:30
Kovid Goyal
cb5157f2d1 Nicer implementation of eintr_retry that does not prevent type checking of wrapped functions arguments 2022-08-01 19:03:02 +05:30
Kovid Goyal
32dcbbb5e1 ... 2022-08-01 19:03:02 +05:30
Kovid Goyal
4eedf0d36c macOS needs SIGTTOU SIGTTIN explicitly ignored to allow the supervisor to use TIOCSWINSZ 2022-08-01 19:03:02 +05:30
Kovid Goyal
be458303c5 Debounce WINSZ events 2022-08-01 19:03:02 +05:30
Kovid Goyal
78542ede14 Separate test for SIGWINCH handling 2022-08-01 19:03:02 +05:30
Kovid Goyal
df8800b7ff tcsetpgrp can raise EINTR despite python claiming to auto retry 2022-08-01 19:03:02 +05:30
Kovid Goyal
9c30cd8891 Use a process supervisor for socket workers
This simplifies the code and also allows SIGTSTP to work as the worker
process is no longer in an orphaned process group.
2022-08-01 19:03:02 +05:30
Kovid Goyal
dda28efd66 Use the same signal to stop as stopped the child 2022-08-01 19:03:02 +05:30
Kovid Goyal
b78183972c Report child stop to wrapper process 2022-08-01 19:03:02 +05:30
Kovid Goyal
fc06196a3e Restore termios state when stopping 2022-08-01 19:03:02 +05:30
Kovid Goyal
2a1809a2d8 When exiting with a signal run cleanup 2022-08-01 19:03:02 +05:30
Kovid Goyal
ac5c312267 Work on handling child stop events 2022-08-01 19:03:02 +05:30
Kovid Goyal
8f3174038b Fix running of test suite inside kitty 2022-08-01 19:03:02 +05:30
Kovid Goyal
2d8113e0e0 Give up on signal delivery tests 2022-08-01 19:03:02 +05:30
Kovid Goyal
e881850bb4 ... 2022-08-01 19:03:02 +05:30
Kovid Goyal
4cbae1db89 ... 2022-08-01 19:03:02 +05:30
Kovid Goyal
97bce7be18 and again 2022-08-01 19:03:01 +05:30
Kovid Goyal
5d2a276172 ... 2022-08-01 19:03:01 +05:30
Kovid Goyal
762dfe8315 increase timeout since signal delivery on CI is flakey 2022-08-01 19:03:01 +05:30
Kovid Goyal
1c0d1ec8a9 wait for child events explicitly 2022-08-01 19:03:01 +05:30
Kovid Goyal
e650699217 py3.8 compat 2022-08-01 19:03:01 +05:30
Kovid Goyal
5ff2b5be1c Clear out any signals before ending test 2022-08-01 19:03:01 +05:30
Kovid Goyal
f1841d7c81 DRYer 2022-08-01 19:03:01 +05:30
Kovid Goyal
fd6231a778 Fix signal reading in prewarm wrapper 2022-08-01 19:03:01 +05:30
Kovid Goyal
751a52153d Restore python signal handlers properly after the test 2022-08-01 19:03:01 +05:30
Kovid Goyal
61b733bae9 Add a test for SIGCHLD on SIGTSTP 2022-08-01 19:03:01 +05:30
Kovid Goyal
624e96df9b wrapper process should exit with signal if prewarmed worker exits with signal 2022-08-01 19:03:01 +05:30
Kovid Goyal
a3f1a44d83 OK I give up I cant get signal delivery to work reliably 2022-08-01 19:03:01 +05:30
Kovid Goyal
ed1547938e When running tests ensure the signal mask is empty for processes run in the testing pty 2022-08-01 19:03:01 +05:30
Kovid Goyal
f96a13e9e4 mask handled signals when running signal handler 2022-08-01 19:03:01 +05:30
Kovid Goyal
7b70ed96b6 DRYer 2022-08-01 19:03:01 +05:30
Kovid Goyal
9379853d47 Get the SIGINT test working on macOS 2022-08-01 19:03:01 +05:30
Kovid Goyal
6ccfebd9e3 ... 2022-08-01 19:03:01 +05:30
Kovid Goyal
45186a17ca macOS doesnt allow TIOCSWINSZ from any process other than the process connected to the terminal or its parent
So send it over the socket to the zygote and have it resize the
terminal. Sigh.
2022-08-01 19:03:00 +05:30
Kovid Goyal
24bb4585af Allow easily skipping tests when updating on ox 2022-08-01 19:03:00 +05:30
Kovid Goyal
feb96f1be8 Use O_CLOEXEC for temp ctty fds 2022-08-01 19:03:00 +05:30
Kovid Goyal
b7d3d64975 Ignore error condition on master fd of testing pty 2022-08-01 19:03:00 +05:30
Kovid Goyal
6114b49630 Ensure pty is in utf8 2022-08-01 19:03:00 +05:30
Kovid Goyal
87d95caae0 Dont bother sending slave fd over the socket as it doesnt help on macOS anyway 2022-08-01 19:03:00 +05:30
Kovid Goyal
9535bc94ff Use both open and the ioctl to set the controlling terminal 2022-08-01 19:03:00 +05:30
Kovid Goyal
0841be7803 Test sigwinch handling 2022-08-01 19:03:00 +05:30
Kovid Goyal
6cec89181b dont abort when failing to write to a tty 2022-08-01 19:03:00 +05:30
Kovid Goyal
cb78e17594 dont break when socket is pre-closed 2022-08-01 19:03:00 +05:30
Kovid Goyal
285c399ae5 ... 2022-08-01 19:03:00 +05:30
Kovid Goyal
c9b14f6f78 Switch to using select() instead of poll() in prewarm wrapper
This is because poll() is broken on macOS with pty fds
2022-08-01 19:03:00 +05:30
Kovid Goyal
6a101c51d2 Close tty_fd in master on fork() rather than on close() 2022-08-01 19:03:00 +05:30
Kovid Goyal
8998970adc Make the socket prewarm test a bit more robust 2022-08-01 19:03:00 +05:30
Kovid Goyal
991fbacb99 Fix ttyfd passing 2022-08-01 19:03:00 +05:30
Kovid Goyal
72f3e8cd40 Avoid passing around pty paths, instead send the pty fd 2022-08-01 19:03:00 +05:30
Kovid Goyal
cf8113ea24 Even more flexible management of poll data 2022-08-01 19:03:00 +05:30
Kovid Goyal
5d1cf49fb6 Only set control msg metadata when actually sending a control msg 2022-08-01 19:03:00 +05:30
Kovid Goyal
90f3c0f9af Workaround macOS weirdness 2022-08-01 19:02:59 +05:30
Kovid Goyal
d02598688a Show full traceback for failures to fork socket child 2022-08-01 19:02:59 +05:30
Kovid Goyal
1991f1ccd7 Better handling of multiple optional to poll fds 2022-08-01 19:02:59 +05:30
Kovid Goyal
8fad8972d5 ... 2022-08-01 19:02:59 +05:30
Kovid Goyal
fc3e78e226 Also handle SIGQUIT 2022-08-01 19:02:59 +05:30
Kovid Goyal
15aace29a1 Restore python signal handlers in forked workers 2022-08-01 19:02:59 +05:30
Kovid Goyal
12a3a2d7fe Only set switch interval after prewarm fork 2022-08-01 19:02:59 +05:30
Kovid Goyal
7e26a340a3 Try to read from child pty even after zygote signals its death 2022-08-01 19:02:59 +05:30
Kovid Goyal
f0764c24d2 Retry read/write while there is space 2022-08-01 19:02:59 +05:30
Kovid Goyal
d080bf3b9c Forward termination signals 2022-08-01 19:02:59 +05:30
Kovid Goyal
51698dff07 ... 2022-08-01 19:02:59 +05:30
Kovid Goyal
4154926be7 Get the test to pass on macOS 2022-08-01 19:02:59 +05:30
Kovid Goyal
806ca2b224 Fix atexit handlers being called in forked children
In particular the atexit handler for random_unix_socket()
2022-08-01 19:02:59 +05:30
Kovid Goyal
92bbf08343 Fix socket connect call on non-linux 2022-08-01 19:02:59 +05:30
Kovid Goyal
dc84e3de67 Do not hang forever waiting for child death 2022-08-01 19:02:59 +05:30
Kovid Goyal
8011801549 re-init openssl PRNG when forking for prewarm 2022-08-01 19:02:59 +05:30
Kovid Goyal
dfec88ed15 Inherit full outside env 2022-08-01 19:02:59 +05:30
Kovid Goyal
5d3e2c7111 ... 2022-08-01 19:02:58 +05:30
Kovid Goyal
af1c48e0f7 Dont rely on having source of test modules available 2022-08-01 19:02:58 +05:30
Kovid Goyal
d4bc906c5c Fix launcher build with sanitize 2022-08-01 19:02:58 +05:30
Kovid Goyal
62fe467336 ... 2022-08-01 19:02:58 +05:30
Kovid Goyal
ef3e9d1c2e make various compilers happy 2022-08-01 19:02:58 +05:30
Kovid Goyal
09ddbbf600 Finish move of prewarm launcher into its own compilation unit 2022-08-01 19:02:58 +05:30
Kovid Goyal
4195c239c8 Start moving prewarm launcher into its own compilation unit 2022-08-01 19:02:58 +05:30
Kovid Goyal
a6c3c57d40 Test stdio redirection with socket prewarm 2022-08-01 19:02:58 +05:30
Kovid Goyal
de9263a117 Verify uid/gid of connection from a prewarm client 2022-08-01 19:02:58 +05:30
Kovid Goyal
7b7f1ecc54 Add basic tests for socket prewarm 2022-08-01 19:02:58 +05:30
Kovid Goyal
d1b028c27a Dont pass inherited KITTY_PREWARM_SOCKET 2022-08-01 19:02:58 +05:30
Kovid Goyal
055aae2382 Use correct exit codes for signals 2022-08-01 19:02:58 +05:30
Kovid Goyal
dbb084da7a Cleanup closing of socket child 2022-08-01 19:02:58 +05:30
Kovid Goyal
8332cd2f79 Avoid needing to call os.getpid() repeatedly 2022-08-01 19:02:58 +05:30
Kovid Goyal
4e29c0c16b Propagate SIGWINCH 2022-08-01 19:02:58 +05:30
Kovid Goyal
320d45a3f5 Only use prewarm socket if uid and gid match 2022-08-01 19:02:58 +05:30
Kovid Goyal
73795b5257 detect when socket child closes its tty 2022-08-01 19:02:58 +05:30
Kovid Goyal
16e59784c6 handle failures to send data over socket gracefully 2022-08-01 19:02:58 +05:30
Kovid Goyal
65a7ec3f85 move socket launch handling into its own function 2022-08-01 19:02:57 +05:30
Kovid Goyal
360ff35061 Better error logging from prewarm zygote 2022-08-01 19:02:57 +05:30
Kovid Goyal
5098792723 Make the tty pipes non-blocking 2022-08-01 19:02:57 +05:30
Kovid Goyal
a4883e6d41 Transfer data between ttys 2022-08-01 19:02:57 +05:30
Kovid Goyal
44c9d66dd4 Use a map for pid -> socketchild 2022-08-01 19:02:57 +05:30
Kovid Goyal
c7718d078c Fix parsing of child pid and exit code 2022-08-01 19:02:57 +05:30
Kovid Goyal
2525994db4 ... 2022-08-01 19:02:57 +05:30
Kovid Goyal
81e6afa9b3 Cleanup fd transmit 2022-08-01 19:02:57 +05:30
Kovid Goyal
d8f5ccf9e4 Make master tty raw and print error messages with more context 2022-08-01 19:02:57 +05:30
Kovid Goyal
774489c578 ... 2022-08-01 19:02:57 +05:30
Kovid Goyal
39bb0ecfcf more work on socket based prewarm 2022-08-01 19:02:57 +05:30
Kovid Goyal
b222ab1bf6 Start work on socket based prewarm 2022-08-01 19:02:57 +05:30
Kovid Goyal
7a31c7ff50 Start work on everywhere prewarm 2022-08-01 19:02:57 +05:30
Kovid Goyal
132f87d152 show_key: use a better representation of the space key when 2022-08-01 13:25:56 +05:30
Kovid Goyal
aedda4bf9b ... 2022-07-31 10:36:03 +05:30
Kovid Goyal
40bba70bfe Wayland IME: Avoid printing errors for compositors that pointlessly acknowledge previous IME commits rather than just the current one. Apparently kwin is one such. Fixes #5339 2022-07-31 10:22:40 +05:30
Kovid Goyal
8202247b27 DRYer 2022-07-29 12:51:36 +05:30
Kovid Goyal
e2117ee8a4 ssh kitten: Allow completion of ssh options between the destination and command
This contravenes the documented command line parsing behavior of ssh but
matches its actual implementation. Sigh.

Fixes #5322
2022-07-29 08:29:49 +05:30
Kovid Goyal
e3a4150fea Second draw attempt has single fade edge 2022-07-29 08:15:38 +05:30
Kovid Goyal
0ce2a23af3 Allow tab title templates to use the current max title length 2022-07-29 07:43:55 +05:30
Kovid Goyal
6b2337c9c0 Merge branch 'master' of https://github.com/newbee1905/kitty-1 2022-07-26 20:27:59 +05:30
newbee1905
54661b7fc4 reflector: replace unecessary while loop 2022-07-26 21:47:52 +07:00
Kovid Goyal
a1f895b781 Merge branch 'master' of https://github.com/nullchilly/kitty 2022-07-26 19:10:34 +05:30
nullchilly
199c271edc Fix modify_font underline_position 1 2022-07-26 20:08:04 +07:00
Kovid Goyal
9ac744ae63 DRYer 2022-07-26 16:10:43 +05:30
Kovid Goyal
93b05bcd1e Make the calculation of the bound for underline_position more precise
Fixes #5308
2022-07-26 16:07:01 +05:30
Kovid Goyal
546cdbefae Don't expose all Tab methods in title template
They can have side effects so prevent user from foot shot
2022-07-26 13:13:00 +05:30
Kovid Goyal
352c07987b Nicer fix for showing cwd in tab titles 2022-07-26 12:35:07 +05:30
Kovid Goyal
f6a1eb19d7 Only import ReferenceType when type checking 2022-07-26 11:06:19 +05:30
Kovid Goyal
a352804c14 Merge branch 'misc' of https://github.com/page-down/kitty 2022-07-26 11:04:01 +05:30
pagedown
28ae8c4d20 Expand ~ when resolving editor path and applying exe_search_path 2022-07-26 13:21:50 +08:00
pagedown
1eef584382 Replace tr with string built-in in fish integration 2022-07-26 13:21:36 +08:00
pagedown
4e1cd0590b Use builtin to call read in zsh integration and bash integration 2022-07-26 13:20:50 +08:00
pagedown
fa670e1f5d ... 2022-07-26 13:20:41 +08:00
Kovid Goyal
c7fa6f620a py3.8 compat 2022-07-26 08:39:33 +05:30
Kovid Goyal
04690c8c7c Dont incur the cost of checking the cwd of a process on every tabbar update unless actually needed 2022-07-26 08:19:14 +05:30
Kovid Goyal
4345ea6602 Remove debugging code I left in the last commit accidentally 2022-07-26 08:17:44 +05:30
Kovid Goyal
d39c212336 Merge branch 'add-cwd-TabBarData' of https://github.com/nxvxl/kitty 2022-07-26 08:08:14 +05:30
Kovid Goyal
54d2f06abe remote files kitten: Fix working with files whose names have characters that need to be quoted in shell scripts
Fixes #5313
2022-07-26 08:06:25 +05:30
noval
237bfc9a6e add cwd to TabBarData
this option add current working directory to the tab by using active windows child directory.
2022-07-26 09:33:55 +07:00
Kovid Goyal
29b3d49cd5 Note that the crossterm library has added support for the kitty keyboard protocol 2022-07-26 07:45:57 +05:30
Kovid Goyal
fe8892ec54 Update changelog for previous PR 2022-07-21 20:03:46 +05:30
Kovid Goyal
2d8dd7a53e Merge branch 'rc-scroll-window' of https://github.com/page-down/kitty 2022-07-21 20:03:05 +05:30
pagedown
b493599690 Remote control: Allow half-scrolling when scrolling window by page 2022-07-21 21:05:56 +08:00
Kovid Goyal
d9371dc265 ssh kitten: Better handling of exit on keyboard interrupt 2022-07-20 21:40:51 +05:30
Kovid Goyal
ef2a12fbdf Ignore sigint while draining the tty 2022-07-20 19:40:23 +05:30
Kovid Goyal
7215c6d6be ssh kitten: Allow pressing Ctrl-C to abort ssh before the connection is completed
Fixing this involved adding a new mode to kitty where it handles
ctrl-c/z/q by sending signals to the tty foreground process group
instead of delegating to the kernel to do that. Since the pipe may be
full we have no way of knowing when the kernel will get around to
reading the signal byte. So send the signal ourselves.

Fixes #5271
2022-07-20 19:11:14 +05:30
Kovid Goyal
bd9e1f58fe ssh kitten: Make discarding of garbage data after ssh fails more robust
We now roundtrip an UUID via the terminal and only exit after receiving
it back. This guarantees that any pending data in the pipe is discarded.
2022-07-20 13:28:51 +05:30
Kovid Goyal
79dec269e1 ... 2022-07-16 10:25:18 +05:30
Kovid Goyal
d6ffbf4f3c Rebuild before and after publishing if a branch change was needed
Sometimes branches break things
2022-07-16 10:18:05 +05:30
Kovid Goyal
32588939ae Deprecate the adjust_baseline adjust_line_height and adjust_column_width options in favor of modify_font
Unifies handling and allow using pt units for those adjustments.
Note that the behavior of percentage sizes for adjust baseline is
backwards incompatible. It now uses the baseline value as the base
rather than the cell height.
2022-07-15 15:29:42 +05:30
Kovid Goyal
a02e07bfe8 Add modify_fonts to the changelog
Fixes #5265
2022-07-15 12:29:08 +05:30
Kovid Goyal
03df0c3cca Actually implement modify_font 2022-07-15 12:27:42 +05:30
Kovid Goyal
1aa50b73a1 Allow pixel based adjustments as well 2022-07-15 11:45:43 +05:30
Kovid Goyal
c910f483bf Improve formatting of config diff for modify_font 2022-07-15 11:15:57 +05:30
Kovid Goyal
14f4a8d28e Make modify_font available to C code 2022-07-15 11:06:24 +05:30
Kovid Goyal
ee931a17b0 Start work on option to modify font characteristics
Implement parsing of the option
2022-07-15 08:54:53 +05:30
Kovid Goyal
0a2ba3fb66 Merge branch 'fix-macos-launch' of https://github.com/page-down/kitty 2022-07-14 16:45:08 +05:30
pagedown
512db660a1 macOS: Fix unable to open new tab/window when there is no OS window 2022-07-14 18:25:00 +08:00
Kovid Goyal
b0666c9180 test.py now needs kitty built 2022-07-13 20:00:54 +05:30
Kovid Goyal
57d3d09679 DRYer 2022-07-13 19:49:40 +05:30
Kovid Goyal
3a17f54bca Add a note about how to exclude directories in the copy command 2022-07-12 16:23:48 +05:30
Kovid Goyal
4c2800b294 mask handled signals when running the signal handler 2022-07-10 12:52:51 +05:30
Kovid Goyal
8358377ec3 Fix reading multiple signals from buffer on macOS 2022-07-10 12:07:54 +05:30
Kovid Goyal
3ab12c616c Merge branch 'patch-1' of https://github.com/kidonng/kitty 2022-07-09 14:45:48 +05:30
Kid
ef4e784121 Fix link formatting in docs 2022-07-09 16:00:51 +08:00
Kovid Goyal
1ead20a5b9 Add a GitHub icon to the docs footer 2022-07-08 20:36:32 +05:30
Kovid Goyal
31dfee8a95 Nicer error message for zsh versions before 5.1 2022-07-08 20:28:42 +05:30
Kovid Goyal
d6492264c7 Bash integration: Fix declare not creating global variables in .bashrc
Fixes #5254
2022-07-08 18:55:04 +05:30
Kovid Goyal
7281b9dbb1 Ensure local kitty is used when running the tests 2022-07-05 21:09:09 +05:30
Kovid Goyal
4b63ee036e Run the test suite using the kitty binary 2022-07-05 21:05:03 +05:30
Kovid Goyal
fb8eb69c20 ... 2022-07-03 20:51:59 +05:30
Kovid Goyal
4b1fa2609d Match behavior of who when getting num_users by ignoring zombie pids 2022-07-03 14:46:54 +05:30
Kovid Goyal
a8b756f040 ssh kitten: A new option :code:--symlink-strategy to control how symlinks are copied to the remote machine
Fixes #5249
2022-07-03 14:03:56 +05:30
Kovid Goyal
a94a0f3026 Fix fd leak in prewarm zygote 2022-07-03 13:32:09 +05:30
Kovid Goyal
71c902eaf8 Build the launcher with debug symbols when using --debug 2022-07-03 13:28:57 +05:30
Kovid Goyal
6e7af8ce2e Merge branch 'fix-last-cmd-output' of https://github.com/page-down/kitty 2022-07-02 10:18:16 +05:30
pagedown
6c80cd040c Fix missing last line when getting output of the running command 2022-07-02 12:33:58 +08:00
Kovid Goyal
4ed413eaa7 Switch to GA2 2022-06-30 17:00:14 +05:30
Kovid Goyal
86357d2c38 ... 2022-06-29 18:53:11 +05:30
Kovid Goyal
c0d4d01b88 ... 2022-06-29 18:41:39 +05:30
Kovid Goyal
b3fcb53625 ssh kitten: Allow ssh kitten to work from inside tmux, provided the tmux session inherits the correct KITTY env vars
Fixes #5227
2022-06-29 18:40:22 +05:30
Kovid Goyal
4f29dea8ef Utility function to detect when running inside tmux 2022-06-29 17:51:38 +05:30
Kovid Goyal
4f8ef05058 Linux: Update cursor position after all key presses not just pre-edit text changes
Fixes #5241
2022-06-29 11:37:35 +05:30
Kovid Goyal
7fc1735a21 swaps_disallowed is used only on wayland 2022-06-28 20:19:52 +05:30
Kovid Goyal
1e49038747 Fix edit-in-kitty not working with editors that use file replace to save changes 2022-06-28 11:51:03 +05:30
Kovid Goyal
295fbe42ad Merge branch 'misc' of https://github.com/page-down/kitty 2022-06-28 11:42:37 +05:30
Kovid Goyal
60d33f2847 Wayland: Reduce flicker at startup by not using render frames immediately after a resize
Fixes #5235
2022-06-28 11:29:57 +05:30
Kovid Goyal
cd369f633a Wayland: Ensure that opengl buffer swapping does not happen till the xdg surface is configured 2022-06-28 10:47:33 +05:30
pagedown
c3df6d2fe3 Remove executable perms from kitten files 2022-06-28 11:59:47 +08:00
Kovid Goyal
72dfa74b3e Avoid an extra buffer swap during startup
glfw should not be swapping buffers behind the clients back. Since we
control the client, we can ensure that the OpenGL VRAM is cleared before
the window is shown.

Also fixes the issue with the glfw wayland backend where the buffer was
being created before the surface was configured
2022-06-28 09:25:24 +05:30
pagedown
1411269187 Docs: Improve documentation for remote commands
Use text roles in the document to apply the style in the cli output.
Add more links.
Unify the text roles.
Use code blocks instead of quote blocks.
Use '+' to connect the shortcut key names.
2022-06-28 11:52:20 +08:00
pagedown
185d959f25 ssh kitten: Add MacPorts paths to the common locations for login shell 2022-06-28 11:43:19 +08:00
pagedown
1652d41992 Fix edit-in-kitty not working when option editor comes with arguments 2022-06-28 11:41:27 +08:00
pagedown
297dea881d Use the same fish completion function for commands 2022-06-28 11:35:11 +08:00
pagedown
01df828353 Refactor the fish implementation of edit-in-kitty
Clean up global variable and signal handling function before exit.
For commands allowing the use of valid functions or binary executables.
2022-06-28 11:31:42 +08:00
pagedown
617115d447 Docs: edit-in-kitty works with neovim 2022-06-28 11:22:52 +08:00
Kovid Goyal
31f9978599 Merge branch 'patch-1' of https://github.com/AnonymouX47/kitty 2022-06-27 15:54:23 +05:30
My Avatar ;D
dc068c16e8 Update projects using kitty graphics protocol
Add `term-image` to the list of projects using the kitty graphics protocol.
2022-06-27 10:35:39 +01:00
Kovid Goyal
89626a462f ... 2022-06-25 20:46:02 +05:30
Kovid Goyal
a043588b4a Update changelog for last PR 2022-06-25 16:50:46 +05:30
Kovid Goyal
2e08ee16c2 Merge branch 'fix-macos-jis-kbd' of https://github.com/page-down/kitty
Fixes #5232
2022-06-25 16:49:46 +05:30
pagedown
2cc506476b macOS: Fix JIS Eisu and Kana keys not working 2022-06-25 19:01:02 +08:00
Kovid Goyal
0a462ad706 Forgot one place to change over to kitty_run_data 2022-06-24 10:31:55 +05:30
Kovid Goyal
81c1bfe1c5 ... 2022-06-24 09:26:38 +05:30
Kovid Goyal
8ce80e39d9 kitty now needs python >= 3.8 2022-06-24 00:01:35 +05:30
Kovid Goyal
fa823a37ae Backport 6281f498c8 2022-06-23 13:00:28 +05:30
Kovid Goyal
17f67fbe28 Add a note that WezTerm implements the kitty keyboard protocol as well 2022-06-23 11:22:01 +05:30
Kovid Goyal
560b78bc35 Dont leak kitty_run_data on error 2022-06-23 08:59:31 +05:30
Kovid Goyal
f023f047ff Workaround for Python 3.11 breaking sys._xoptions
Apparently in Python-land its acceptable behavior to break backward
compatibility with documented interfaces on a whim. Bloody joke.
https://github.com/python/cpython/pull/28823

Fixes #5223
2022-06-23 08:44:34 +05:30
Kovid Goyal
5673359be2 Merge branch 'sam/modern-whiskers-icon' of https://github.com/samholmes/kitty 2022-06-22 04:11:41 +05:30
Samuel Holmes
666696d142 Add "modern whiskers" alternative icon to FAQ 2022-06-21 11:04:24 -07:00
Kovid Goyal
f95bc48f9b kitty @ set-colors: Fix changing inactive_tab_foreground not working
Fixes #5214
2022-06-19 20:20:58 +05:30
Kovid Goyal
8d18fc09fa Merge branch 'fix-rc-new-window' of https://github.com/page-down/kitty 2022-06-19 18:21:22 +05:30
Kovid Goyal
a3d42a90f2 Get rid of os.waitid() since its not present on macOS we cannot rely on it anyway 2022-06-19 18:11:23 +05:30
pagedown
1da5f2ecc6 Fix the rc command new-window does not open the shell by default 2022-06-19 20:25:12 +08:00
Kovid Goyal
d226e82f68 It is only possible to not have an OS Window on macOS 2022-06-19 09:39:40 +05:30
Kovid Goyal
60cc2cb26c Merge branch 'fix-rc-launch' of https://github.com/page-down/kitty 2022-06-19 09:38:30 +05:30
pagedown
1ac90cf464 Allow remote control to launch a new OS window when there is none 2022-06-19 11:51:58 +08:00
Kovid Goyal
d3ffaebd17 Ignore exceptions in kitten result processors 2022-06-18 08:20:01 +05:30
Kovid Goyal
bf01c549f5 Allow kittens to handle multiple results in a window 2022-06-18 08:18:25 +05:30
Kovid Goyal
6912d8c086 No need for entire screen object when scaling a scroll 2022-06-18 06:55:28 +05:30
Kovid Goyal
a89e1b5573 Fix horizontal wheel events not being reported to client programs when they grab the mouse
Fixes #2819
2022-06-18 06:47:42 +05:30
Kovid Goyal
3a9c6088b2 Add debug output for scroll events 2022-06-18 05:54:26 +05:30
Kovid Goyal
fba629f385 Merge branch 'globinclude-sorted' of https://github.com/amarshall/kitty 2022-06-18 04:59:25 +05:30
Andrew Marshall
90477825c2 Make globinclude sort files before loading
`Path.glob` gives no guarantee about the order results are returned in.
This means that if files included by `globinclude` override each other,
the resulting config was non-deterministic. Resolve this by sorting the
results from `glob` to give consistent, well-defined order.
2022-06-17 18:50:16 -04:00
Kovid Goyal
1e56d39777 Wakeup main loop when refreshing window as well as io loop 2022-06-17 19:33:43 +05:30
Kovid Goyal
2829971412 Merge branch 'expose-wakeup-main-loop' of https://github.com/sirno/kitty 2022-06-17 19:09:07 +05:30
Nicolas Ochsner
c147fda6ca Expose wakeup main loop in python api. 2022-06-17 15:26:16 +02:00
Kovid Goyal
a27dfd8b7c Increase the timeout for prewarm I/O on CI 2022-06-14 21:17:28 +05:30
Kovid Goyal
a7c65efc45 Ensure local kitty is used in tests even when another kitty is present in the system PATH
Fixes #5194
2022-06-14 21:08:58 +05:30
Kovid Goyal
646a524c8e Sort imports 2022-06-14 20:06:12 +05:30
Kovid Goyal
f8d01d2c4f Use the same parser for resize_window actions and session setting 2022-06-14 20:03:21 +05:30
Kovid Goyal
1d846f3744 Cleanup previous PR
Merge branch 'session-resize-window' of https://github.com/sirno/kitty
2022-06-14 19:59:01 +05:30
Nicolas Ochsner
6bbe58f876 Change to pass window specs. 2022-06-14 12:28:41 +02:00
Nicolas Ochsner
36719fb086 Cleanup. 2022-06-14 11:23:35 +02:00
Nicolas Ochsner
78fd7a8458 Enable resizing in session files. 2022-06-14 11:09:26 +02:00
Kovid Goyal
f5976d0d63 Remove unneeded code 2022-06-13 21:33:07 +05:30
Kovid Goyal
b313fc3fbe Forgot to also add SIGUSR1 handler 2022-06-13 21:09:32 +05:30
Kovid Goyal
cf667b8c47 Use a signal handler instead of a pipe for child death notification 2022-06-13 20:40:04 +05:30
Kovid Goyal
7e3bd8586f Dont use SIGTSTP and SIGCONT in the test as they are very flaky 2022-06-13 20:29:58 +05:30
Kovid Goyal
d5df301317 Finer grained polling 2022-06-13 19:41:40 +05:30
Kovid Goyal
628246c3da Workaround for python 3.8 that doesnt have the CLD_* constants 2022-06-13 19:22:37 +05:30
Kovid Goyal
d228acd30a Use a busy loop for signaled process 2022-06-13 19:11:29 +05:30
Kovid Goyal
18cd97d914 Skip SIGSTSTP test when CLD_STOPPED is not available 2022-06-13 19:05:49 +05:30
Kovid Goyal
5f13946bac Allow using our signal handlers in python event loops via an fd
pythons signal fd only return signal numbers not the full siginfo struct
2022-06-13 18:52:23 +05:30
Kovid Goyal
8fb24fbc1e poll() takes times in ms not seconds 2022-06-13 18:51:35 +05:30
Kovid Goyal
f0d4952055 Use an iterator 2022-06-13 08:14:51 +05:30
Kovid Goyal
7113580db2 macOS has no waitid
Le Sigh
2022-06-13 02:19:51 +05:30
Kovid Goyal
163c211219 Use waitid() rather than waitpid() to get full exit status 2022-06-13 00:19:56 +05:30
Kovid Goyal
6993157b3a ... 2022-06-13 00:15:07 +05:30
Kovid Goyal
e2efdf674d Reap prewarmed processes 2022-06-13 00:11:16 +05:30
Kovid Goyal
9fcb8e5b6e Close unneeded fds in forked children 2022-06-12 20:26:20 +05:30
Kovid Goyal
a1a637c7f1 Restore SIGUSR1 handling in prewarmed child 2022-06-12 18:59:55 +05:30
Kovid Goyal
4f87f3e9f4 ... 2022-06-12 18:15:23 +05:30
Kovid Goyal
da6faa656c Use a fork() without exec() to create prewarm process
This has the advantages:

1) Even first kitten use is fast
2) Computer has to do less work overall since prewarm process is itself prewarmed
2022-06-12 18:06:51 +05:30
Kovid Goyal
e1ad75e932 Graphics protocol: Make it explicit what is supposed to happen when two images with the same z-index overlap
Fixes #5187
2022-06-10 22:10:11 +05:30
Kovid Goyal
646bdbd002 Wayland: Fix a regression in the previous release that caused mouse cursor animation and keyboard repeat to stop working when switching seats
Fixes #5188
2022-06-10 21:38:30 +05:30
Kovid Goyal
9184de1997 ... 2022-06-10 18:44:23 +05:30
Kovid Goyal
eabc29ec4d Changelog entry for prewarming 2022-06-09 10:43:00 +05:30
Kovid Goyal
e338cdab31 Increase the timeout when talking to prewarm process 2022-06-09 07:01:42 +05:30
Kovid Goyal
7001d609fa Fix spurious warning from runpy module when prewarmed 2022-06-09 07:01:42 +05:30
Kovid Goyal
c8399ce3d2 clear signal handlers in prewarm controller 2022-06-09 07:01:42 +05:30
Kovid Goyal
3e1d701dce DRYer 2022-06-09 07:01:41 +05:30
Kovid Goyal
a385c39c2e Fix reading of dead prewarmed child pids 2022-06-09 07:01:41 +05:30
Kovid Goyal
5d1cf339a7 Simplify child death tracking
Re-use the existing fd instead of catching signals
2022-06-09 07:01:41 +05:30
Kovid Goyal
fafb8518e0 Fix compilation on platforms without SO_PASSCRED 2022-06-09 07:01:41 +05:30
Kovid Goyal
6807f4d4ea Report the correct cmdline for prewarmed processes 2022-06-09 07:01:41 +05:30
Kovid Goyal
0ed7b2f3c0 Manage fd lifecycles a bit better 2022-06-09 07:01:41 +05:30
Kovid Goyal
116128ebb5 Get prewarm working
Needed to wait in the control process for child to set its controlling
terminal so that closing the slave fd in kitty is safe.
2022-06-09 07:01:41 +05:30
Kovid Goyal
0c870c5fcd handle child death and config reload for prewarmed processes 2022-06-09 07:01:41 +05:30
Kovid Goyal
90bc3ab770 Function to create a randomly named UNIX domain socket 2022-06-09 07:01:41 +05:30
Kovid Goyal
0021b0c242 Prewarm all kittens 2022-06-09 07:01:41 +05:30
Kovid Goyal
6dc076f5d1 Complete move to prewarm.py 2022-06-09 06:54:05 +05:30
Kovid Goyal
2dd1201c3f Dont use a kitten for prewarm 2022-06-09 06:53:51 +05:30
Kovid Goyal
9be68966b4 Also detect ssh kitten run as an overlay 2022-06-08 18:53:54 +05:30
Kovid Goyal
47d482dca9 Clear signal handlers when running processes 2022-06-08 17:50:42 +05:30
Kovid Goyal
18dd13c872 Make init_loop_data a little safer to use 2022-06-08 15:10:52 +05:30
Kovid Goyal
c71bb877c3 Handle SIGHUP gradefully 2022-06-08 09:06:28 +05:30
Kovid Goyal
eee8106f9e Fix a regression that broke completion when using a kitty alias instead of having kitty on PATH 2022-06-08 07:36:09 +05:30
Kovid Goyal
80ad596d3d Yet another place where macos_colorspace was broken 2022-06-08 07:17:34 +05:30
Kovid Goyal
9dbafcc368 Remove unneccessary argument to connection_sharing_args() 2022-06-07 20:40:01 +05:30
Kovid Goyal
9f5f113840 ... 2022-06-07 13:05:07 +05:30
Kovid Goyal
6afbdbe94f Use correct kitty config in prewarmed process 2022-06-07 12:53:39 +05:30
Kovid Goyal
061a0c8cb6 Reset global options object in the tests 2022-06-07 10:58:03 +05:30
Kovid Goyal
ad128a3c29 version 0.25.2 2022-06-07 07:32:35 +05:30
Kovid Goyal
b71d2e04ca Clarify behavior of placement id when image id is zero 2022-06-07 07:22:12 +05:30
Kovid Goyal
cc3c7b0286 Fix waitpid() call on python 3.8 2022-06-06 21:22:04 +05:30
Kovid Goyal
bf6c90a69a Compare realpaths for cwd test 2022-06-06 20:46:49 +05:30
Kovid Goyal
4c9a75c1ba Remove unused code 2022-06-06 20:45:40 +05:30
Kovid Goyal
98f46f8bd7 Start work on prewarming
The prewarm process and its controller are implemented with some basic
tests.
2022-06-06 20:39:36 +05:30
Kovid Goyal
dec62b1929 Allow ignoring SHM close failures
Useful if we export the mmap beyond the lifetime of the shm object
2022-06-06 20:31:13 +05:30
Kovid Goyal
79f7954048 Cleanup previous PR 2022-06-06 17:19:52 +05:30
Kovid Goyal
fc217dafba Merge branch 'master' of https://github.com/suvayu/kitty 2022-06-06 16:55:08 +05:30
Kovid Goyal
a6aff817cf Prepare for prewarm testing 2022-06-06 15:59:34 +05:30
Suvayu Ali
38cb18fe92 diff kitten: tests for directory walking 2022-06-06 11:01:45 +02:00
Suvayu Ali
20b6a97159 Update in response to feedback
- one configuration option: ignore_paths
- use shlex to parse option to support whitespace and in-line comments
- change option type to Tuple[str, ...]
- remove ignored directories from dirnames to prevent scanning
2022-06-06 11:00:45 +02:00
Kovid Goyal
98f6e24106 Move block outside try/except as it doesnt need to be in there 2022-06-06 08:17:15 +05:30
Kovid Goyal
4ed6364b61 Fix compiler warning on 32bit builds 2022-06-06 07:19:52 +05:30
Kovid Goyal
cd7751681d hints kitten: Fix surrounding quotes/brackets and embedded carriage returns not being removed when using line number processing
Fixes #5170
2022-06-05 18:07:16 +05:30
Kovid Goyal
76531d2f7a Python wrapper function to establish controlling tty 2022-06-05 15:36:05 +05:30
Suvayu Ali
eea652f1d0 kittens/diff: move empty pattern check to dir tree walk 2022-06-05 10:11:21 +02:00
Kovid Goyal
f20f4762a8 Use builtin facility for removing cloexec 2022-06-05 12:33:34 +05:30
Kovid Goyal
c004fd1f4d Declare the set of handled signals in only one place 2022-06-05 08:01:26 +05:30
Kovid Goyal
f29039db29 Make the full signal info available in our signal handler 2022-06-05 07:22:38 +05:30
Suvayu Ali
fbf1ec43c7 diff kitten: add options to ignore paths when comparing directories
Tested locally & over SSH:

    $ kitty +kitten diff /local/path /local/path2
    $ kitty +kitten diff /local/path ssh:remote:/path
2022-06-05 00:11:30 +02:00
Kovid Goyal
a42200a430 Use the log_error function to log SIGUSR2 2022-06-04 15:38:56 +05:30
Kovid Goyal
ad576c333c Also restore signal handlers for SIGUSR1 and SIGUSR2 2022-06-04 15:15:39 +05:30
Kovid Goyal
5a0a980648 Make getting cmdline of a PID a bit more convenient 2022-06-04 15:07:08 +05:30
Kovid Goyal
b3578a4fa5 ... 2022-06-04 14:54:32 +05:30
Kovid Goyal
3c952b01d3 Fix reloading in all kitties skipping kitty processes started as kitty +open 2022-06-04 14:53:13 +05:30
Kovid Goyal
b1453a51f6 Better error message for kitty + 2022-06-04 14:47:13 +05:30
Kovid Goyal
2c3a7b5f41 Restore a couple more signal handlers that python nukes 2022-06-04 14:30:08 +05:30
Kovid Goyal
0df5a6c6c8 Utility function to test if PID is alive 2022-06-04 14:14:24 +05:30
Kovid Goyal
bf4dc6365a Add support for sigqueue() and also handle SIGUSR2
I anticipate using sigqueue() for simpler handling of SIGCHLD
notifications from the prewram process to the its parent.
2022-06-04 14:09:45 +05:30
Kovid Goyal
ce3322bf91 Remote control: Fix commands with asynchronous payloads not sending responses
Fixes #5165
2022-06-04 10:13:21 +05:30
Kovid Goyal
835cb31501 Fix incorrect dispatch of poll events when more than one peer is present 2022-06-04 09:26:57 +05:30
Kovid Goyal
762dd065f3 Nicer reverse loop 2022-06-04 09:06:35 +05:30
Kovid Goyal
7b91447d89 Use a full chekcout in CI when building docs
The :commit: docs role requires it
2022-06-04 08:00:00 +05:30
Kovid Goyal
1146c4ea3f Merge branch 'ignore-fig-osc' of https://github.com/grant0417/kitty 2022-06-04 07:44:17 +05:30
grant0417
729f7b8e03 Add custom message for Fig OSC 2022-06-03 18:57:53 -07:00
grant0417
d3ec632b9f Ignore Fig OSC codes 2022-06-03 17:07:44 -07:00
Kovid Goyal
cb01b5ee44 DRYer 2022-06-03 21:38:49 +05:30
Kovid Goyal
0828096e9e ... 2022-06-03 18:30:14 +05:30
Kovid Goyal
ebcbed290f Make loading of kitty options in kitten re-useable 2022-06-03 17:49:58 +05:30
Kovid Goyal
d6afe6f2cb Move entrypoints into their own module 2022-06-03 11:12:13 +05:30
Kovid Goyal
4daf70f636 ... 2022-06-03 11:08:41 +05:30
Kovid Goyal
2f3dd72c84 Weston: Fix client side decorations flickering on slow computers during window resize
Fixes #5162
2022-06-03 11:00:56 +05:30
Kovid Goyal
dca8c65376 Fix display of url open failures not working 2022-06-02 10:41:08 +05:30
Kovid Goyal
12d8175d9a Use inode/executable rather than application/executable 2022-06-02 10:33:56 +05:30
Kovid Goyal
609bcd68dd Fix #5161 2022-06-02 10:32:43 +05:30
Kovid Goyal
313136803c DRYer 2022-06-02 10:21:34 +05:30
Kovid Goyal
b937033411 macOS: Allow opening executables without a file extension with kitty as well
Fixes #5160
2022-06-02 09:25:30 +05:30
Kovid Goyal
55eeb9c11e Dont import kitty.complete in ssh.main 2022-06-02 08:08:33 +05:30
Kovid Goyal
8fbec2dfd5 remove unused arg 2022-06-02 08:07:53 +05:30
Kovid Goyal
c4521290b8 ... 2022-06-01 19:43:41 +05:30
Kovid Goyal
e12a9f3caf Iosevka: Fix incorrect rendering when there is a combining char that does not group with its neighbors
Fixes #5153
2022-06-01 12:00:53 +05:30
Kovid Goyal
56f8a06362 DRYer 2022-06-01 08:47:19 +05:30
Kovid Goyal
5c4abe749b Merge branch 'fontconfig-special-sources' of https://github.com/leungbk/kitty 2022-06-01 08:33:13 +05:30
Brian Leung
62c27b3db7 Add kitty/fontconfig.c to SPECIAL_SOURCES
Fixup of bec620a8d3.
2022-05-31 19:58:42 -07:00
Kovid Goyal
1283a2b7e5 ... 2022-06-01 08:21:23 +05:30
Kovid Goyal
bec620a8d3 Fix #5154 2022-06-01 08:19:11 +05:30
Kovid Goyal
6b0bb2e845 Sphinx v5 needs language to be not None 2022-06-01 06:47:06 +05:30
Kovid Goyal
135818f34b Note about __main__.py with +launch 2022-05-31 07:56:01 +05:30
Kovid Goyal
2a361d1de1 Update changelog for last PR 2022-05-31 07:46:00 +05:30
Kovid Goyal
761a941d43 Merge branch 'kovidgoyal-master' of https://github.com/jimbob3806/kitty 2022-05-31 07:41:40 +05:30
Kovid Goyal
c03630a487 ... 2022-05-31 07:26:04 +05:30
James Reid
0b79cbdb3b Add "user" tab to themes kitten
Add new tab named "user" to the built-in themes kitten - new tab
filters and displays only user-defined color themes, identified by
adding "is_user_defined" property to "Theme" class which defaults
to False, and is set True when a theme is fetched using the
"load_from_dir" method.
2022-05-30 19:25:09 +01:00
James Reid
1dc6b49e02 Remove hard coded tab string in ThemesHandler
Remove hard coded tab string and replace with self.tabs, which is
a reference to the same tab string defined in the __init__ of
ThemesHandler
2022-05-30 19:17:07 +01:00
Kovid Goyal
1962f8e5a0 Advertise edit-in-kitty a bit more 2022-05-30 15:27:23 +05:30
Kovid Goyal
cabfcbcb69 macOS: When pasting text and the clipboard has a filesystem path, paste the full path instead of the text, which is sometimes just the file name
Fixes #5142
2022-05-30 14:40:45 +05:30
Kovid Goyal
48bb43a2c4 fish: completion for edit-in-kitty clone-in-kitty
fish's wrapping facility does not preserve argv[0] so it cannot be used
unlike for zsh and bash
2022-05-30 10:50:19 +05:30
Kovid Goyal
af5e3e0c18 Add a reference to edit-in-kitty to the remote file kitten docs 2022-05-30 08:19:33 +05:30
Kovid Goyal
fb8398c2eb bash: completion for edit/clone-in-kitty 2022-05-30 08:16:51 +05:30
Kovid Goyal
eefd4bb3e3 zsh: Completion for edit-in-kitty and clone-in-kitty 2022-05-30 08:12:09 +05:30
Kovid Goyal
581c388838 ... 2022-05-29 15:02:37 +05:30
Kovid Goyal
7a63ea6480 Drop python 3.7 from CI 2022-05-29 14:54:51 +05:30
Kovid Goyal
65f1329635 Wayland: When the seat is closed remove any pending pointer animation and key repeat timers
Fixes #5145
2022-05-29 14:50:34 +05:30
Kovid Goyal
0df9a5d5c5 Fix compilation on Python 3.11
Python 3.11 deprecates the supposedly "stable API" PySys_AddXOption().
So switch to using PyConfig.xoptions instead. This means dropping
support for python 3.7 since PyConfig is not available there. So it
rolls.

Fixes #5143
2022-05-29 14:37:14 +05:30
Kovid Goyal
6405849a1b Bash integration clone test was accidentally running zsh
Fixes #5144
2022-05-29 08:46:06 +05:30
Kovid Goyal
e9340dfaaf Some more tests for last non empty command output 2022-05-28 15:22:17 +05:30
Kovid Goyal
0509855930 Allow getting the last non-empty command output easily via an action or remote control
Fixes #4973
2022-05-28 15:19:24 +05:30
Kovid Goyal
4c74462763 ssh kitten: Look in common locations for login shell if it is not in PATH 2022-05-28 11:29:09 +05:30
Kovid Goyal
7f69381221 Update changelog 2022-05-28 11:13:35 +05:30
Kovid Goyal
75942fb47e Implement edit-in-kitty for fish 2022-05-28 11:12:11 +05:30
Kovid Goyal
0893d7164a _run_kitten -> run_kitten_with_metadata 2022-05-28 11:00:25 +05:30
Kovid Goyal
27906ea853 Skip login shell detection when login shell is set to nologin 2022-05-28 08:09:30 +05:30
Kovid Goyal
8540ca3990 Workaround for nix package messing with kitty file permissions 2022-05-28 08:03:17 +05:30
Kovid Goyal
e82c5a63b5 Avoid unnecessary use of cat 2022-05-27 14:18:16 +05:30
Kovid Goyal
83602d7e53 Make chunked transmit code re-useable in fish integration script 2022-05-27 13:21:26 +05:30
Kovid Goyal
64e30e5299 Ignore permission errors when stating file to edit 2022-05-27 13:04:02 +05:30
Kovid Goyal
83ce23ff9f Use zsh's builtin stat instead of relying on the stat command 2022-05-27 12:50:52 +05:30
Kovid Goyal
c5e5ec618d Also trap SIGTERM to cleanup 2022-05-27 12:39:22 +05:30
Kovid Goyal
bd84ec03ce Implement edit-in-kitty for bash 2022-05-27 12:38:26 +05:30
Kovid Goyal
d71f18566e forgot to declare data local 2022-05-27 12:29:11 +05:30
Kovid Goyal
76e75d9f89 Ensure self.tdir exists in __del__ 2022-05-27 10:48:16 +05:30
Kovid Goyal
f5695ac4f4 Increase edit file size limit to 8MB 2022-05-27 10:45:36 +05:30
Kovid Goyal
b748cd21cf Allow opening files at a spedific line number with edit-in-kitty 2022-05-27 09:53:43 +05:30
Kovid Goyal
3a8bdf3b48 React instantly to editor window being closed 2022-05-27 09:15:16 +05:30
Kovid Goyal
196c6b07aa Merge branch 'fix-window-menu' of https://github.com/henryhchchc/kitty 2022-05-27 09:12:56 +05:30
Henry Chu
1978cc5b55 Fixed #5132 2022-05-27 11:15:16 +08:00
Kovid Goyal
ea47cee23f Document edit-in-kitty 2022-05-27 07:41:54 +05:30
Kovid Goyal
e6844ad6f2 ssh kitten: Run bash as a login shell
This fell through the cracks in the rewrite for automatic shell
integration. Fixes #5130
2022-05-26 21:24:32 +05:30
Kovid Goyal
02d5b3eafa Fix shebang 2022-05-26 21:21:36 +05:30
Kovid Goyal
151dacf17f Update changelog 2022-05-26 21:21:27 +05:30
Kovid Goyal
117d1b02be Start work on edit-in-kitty 2022-05-26 21:16:42 +05:30
Kovid Goyal
cb55878efd Fix #5129 2022-05-26 19:42:20 +05:30
Kovid Goyal
ea4751eae5 Move logic for handling chunked remote data into a separate function
Use the existing clipboard_max_size as the limit for remote data
2022-05-26 08:52:20 +05:30
241 changed files with 43276 additions and 34953 deletions

View File

@@ -31,7 +31,11 @@ def install_deps():
sys.stdout.flush()
if is_macos:
items = (x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew '))
run('brew', 'install', 'fish', *items)
openssl = 'openssl'
import ssl
if ssl.OPENSSL_VERSION_INFO[0] == 1:
openssl += '@1.1'
run('brew', 'install', 'fish', openssl, *items)
else:
run('sudo apt-get update')
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
@@ -59,7 +63,7 @@ def build_kitty():
def test_kitty():
run('./kitty/launcher/kitty +launch test.py')
run('./test.py')
def package_kitty():

View File

@@ -6,6 +6,9 @@ env:
LC_ALL: en_US.UTF-8
LANG: en_US.UTF-8
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
linux:
name: Linux (python=${{ matrix.pyver }} cc=${{ matrix.cc }} sanitize=${{ matrix.sanitize }})
@@ -20,15 +23,15 @@ jobs:
cc: [gcc, clang]
include:
- python: a
pyver: 3.7
pyver: "3.8"
sanitize: 0
- python: b
pyver: 3.8
pyver: "3.11"
sanitize: 1
- python: c
pyver: 3.9
pyver: "3.9"
sanitize: 1
@@ -65,7 +68,7 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v3
with:
fetch-depth: 10
fetch-depth: 0 # needed for :commit: docs role
- name: Test for trailing whitespace
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
@@ -73,7 +76,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: 3.8
python-version: "3.10"
- name: Install build-only deps
run: pip install -r docs/requirements.txt flake8 mypy types-requests types-docutils
@@ -84,8 +87,11 @@ jobs:
- name: Build kitty package
run: python .github/workflows/ci.py package
- name: Build kitty
run: python setup.py build --debug
- name: Run mypy
run: python test.py mypy
run: ./test.py mypy
- name: Build man page
run: make FAIL_WARN=1 man
@@ -120,7 +126,12 @@ jobs:
- name: Checkout source code
uses: actions/checkout@master
with:
fetch-depth: 10
fetch-depth: 0 # needed for :commit: docs role
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Build kitty
run: python3 .github/workflows/ci.py build
@@ -128,5 +139,11 @@ jobs:
- name: Test kitty
run: python3 .github/workflows/ci.py test
- name: Install deps for docs
run: python3 -m pip install -r docs/requirements.txt
- name: Builds docs
run: make FAIL_WARN=1 docs
- name: Build kitty package
run: python3 .github/workflows/ci.py package

View File

@@ -9,9 +9,16 @@ on:
schedule:
- cron: '0 22 * * 5'
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
CodeQL-Build:
permissions:
contents: read # to fetch code (actions/checkout)
security-events: write # to upload SARIF results (github/codeql-action/analyze)
runs-on: ubuntu-latest
steps:

4
.gitignore vendored
View File

@@ -2,6 +2,8 @@
*.pyc
*.pyo
*_stub.pyi
*_generated.go
*_generated.h
/.dmypy.json
/tags
/build/
@@ -10,7 +12,7 @@
/compile_commands.json
/link_commands.json
/glad/out/
/kitty/launcher/
/kitty/launcher/kitty*
/*.dSYM/
__pycache__/
/glfw/wayland-*-client-protocol.[ch]

View File

@@ -35,6 +35,12 @@ profile:
app:
python3 setup.py kitty.app $(VVAL)
linux-package: FORCE
rm -rf linux-package
python3 setup.py linux-package
FORCE:
man:
$(MAKE) -C docs man

View File

@@ -1,185 +1,7 @@
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from typing import List
def icat(args: List[str]) -> None:
from kittens.runner import run_kitten as rk
sys.argv = args
rk('icat')
def list_fonts(args: List[str]) -> None:
from kitty.fonts.list import main as list_main
list_main(args)
def remote_control(args: List[str]) -> None:
from kitty.remote_control import main as rc_main
rc_main(args)
def runpy(args: List[str]) -> None:
if len(args) < 2:
raise SystemExit('Usage: kitty +runpy "some python code"')
sys.argv = ['kitty'] + args[2:]
exec(args[1])
def hold(args: List[str]) -> None:
import subprocess
ret = 1
try:
ret = subprocess.Popen(args[1:]).wait()
except KeyboardInterrupt:
pass
except FileNotFoundError:
print(f'Could not find {args[1]!r} to execute', file=sys.stderr)
except Exception as e:
print(e, file=sys.stderr)
from kitty.utils import hold_till_enter
hold_till_enter()
raise SystemExit(ret)
def complete(args: List[str]) -> None:
from kitty.complete import main as complete_main
complete_main(args[1:], entry_points, namespaced_entry_points)
def open_urls(args: List[str]) -> None:
setattr(sys, 'cmdline_args_for_open', True)
sys.argv = ['kitty'] + args[1:]
from kitty.main import main as kitty_main
kitty_main()
def launch(args: List[str]) -> None:
import runpy
sys.argv = args[1:]
try:
exe = args[1]
except IndexError:
raise SystemExit(
'usage: kitty +launch script.py [arguments to be passed to script.py ...]\n\n'
'script.py will be run with full access to kitty code. If script.py is '
'prefixed with a : it will be searched for in PATH'
)
if exe.startswith(':'):
import shutil
q = shutil.which(exe[1:])
if not q:
raise SystemExit(f'{exe[1:]} not found in PATH')
exe = q
if not os.path.exists(exe):
raise SystemExit(f'{exe} does not exist')
runpy.run_path(exe, run_name='__main__')
def shebang(args: List[str]) -> None:
script_path = args[1]
cmd = args[2:]
if cmd == ['__ext__']:
cmd = [os.path.splitext(script_path)[1][1:].lower()]
try:
f = open(script_path)
except FileNotFoundError:
raise SystemExit(f'The file {script_path} does not exist')
with f:
if f.read(2) == '#!':
line = f.readline().strip()
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat
if is_macos:
cmd = line.split(' ')
else:
cmd = line.split(' ', maxsplit=1)
os.execvp(cmd[0], cmd + [script_path])
def run_kitten(args: List[str]) -> None:
try:
kitten = args[1]
except IndexError:
from kittens.runner import list_kittens
list_kittens()
raise SystemExit(1)
sys.argv = args[1:]
from kittens.runner import run_kitten as rk
rk(kitten)
def edit_config_file(args: List[str]) -> None:
from kitty.cli import create_default_opts
from kitty.fast_data_types import set_options
from kitty.utils import edit_config_file as f
set_options(create_default_opts())
f()
def namespaced(args: List[str]) -> None:
try:
func = namespaced_entry_points[args[1]]
except KeyError:
pass
else:
func(args[1:])
return
raise SystemExit(f'{args[1]} is not a known entry point. Choices are: ' + ', '.join(namespaced_entry_points))
entry_points = {
# These two are here for backwards compat
'icat': icat,
'list-fonts': list_fonts,
'@': remote_control,
'+': 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
namespaced_entry_points['runpy'] = runpy
namespaced_entry_points['launch'] = launch
namespaced_entry_points['open'] = open_urls
namespaced_entry_points['kitten'] = run_kitten
namespaced_entry_points['edit-config'] = edit_config_file
namespaced_entry_points['shebang'] = shebang
def setup_openssl_environment() -> None:
# Use our bundled CA certificates instead of the system ones, since
# many systems come with no certificates in a useable form or have various
# locations for the certificates.
d = os.path.dirname
ext_dir: str = getattr(sys, 'kitty_extensions_dir')
if 'darwin' in sys.platform.lower():
cert_file = os.path.join(d(d(d(ext_dir))), 'cacert.pem')
else:
cert_file = os.path.join(d(ext_dir), 'cacert.pem')
os.environ['SSL_CERT_FILE'] = cert_file
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
def main() -> None:
if getattr(sys, 'frozen', False) and getattr(sys, 'kitty_extensions_dir', ''):
setup_openssl_environment()
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
func = entry_points.get(first_arg)
if func is None:
if first_arg.startswith('@'):
remote_control(['@', first_arg[1:]] + sys.argv[2:])
elif first_arg.startswith('+'):
namespaced(['+', first_arg[1:]] + sys.argv[2:])
else:
from kitty.main import main as kitty_main
kitty_main()
else:
func(sys.argv[1:])
if __name__ == '__main__':
from kitty.entry_points import main
main()

View File

@@ -143,6 +143,7 @@ def copy_python(env):
iv['sanitize_source_folder'](os.path.join(env.py_dir, x))
py_compile(env.py_dir)
freeze_python(env.py_dir, pdir, env.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
shutil.rmtree(env.py_dir)
def build_launcher(env):
@@ -219,7 +220,7 @@ def create_tarfile(env, compression_level='9'):
print('Compressing archive...')
ans = f'{dist.rpartition(".")[0]}.txz'
start_time = time.time()
subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
subprocess.check_call(['xz', '--verbose', '--threads=0', '-f', f'-{compression_level}', dist])
secs = time.time() - start_time
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
os.rename(f'{dist}.xz', ans)

View File

@@ -369,6 +369,7 @@ class Freeze(object):
iv['sanitize_source_folder'](join(self.python_stdlib, x))
self.compile_py_modules()
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
shutil.rmtree(self.python_stdlib)
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
os.rename(join(dirname(self.contents_dir), 'bin', 'kitty'), join(self.contents_dir, 'MacOS', 'kitty'))
shutil.rmtree(join(dirname(self.contents_dir), 'bin'))

View File

@@ -10,7 +10,7 @@
}
.sidebar-logo {
max-height: 128px;
height: 128px;
}
.major-features li {

View File

@@ -4,18 +4,14 @@
{{ super() }}
{%- if analytics_id %}
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '{{ analytics_id }}']);
_gaq.push(['_setDomainName', 'none']);
_gaq.push(['_setAllowLinker', true]);
_gaq.push(['_trackPageview']);
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ analytics_id }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
gtag('config', '{{ analytics_id }}');
</script>
{% endif -%}

View File

@@ -123,6 +123,7 @@ The full list of actions that can be mapped to key presses is available
================================== =======================
Action Shortcut
================================== =======================
Show this help :sc:`show_kitty_doc`
Copy to clipboard :sc:`copy_to_clipboard` (also :kbd:`⌘+c` on macOS)
Paste from clipboard :sc:`paste_from_clipboard` (also :kbd:`⌘+v` on macOS)
Paste from selection :sc:`paste_from_selection`

View File

@@ -85,7 +85,7 @@ Customizing the installation
.. code-block:: sh
_kitty_install_cmd \\
_kitty_install_cmd \
installer=nightly
If you want to install it in parallel to the released kitty specify a
@@ -93,14 +93,14 @@ Customizing the installation
.. code-block:: sh
_kitty_install_cmd \\
_kitty_install_cmd \
installer=nightly dest=/some/other/location
* You can specify a different install location, with ``dest``:
.. code-block:: sh
_kitty_install_cmd \\
_kitty_install_cmd \
dest=/some/other/location
* You can tell the installer not to launch |kitty| after installing it with
@@ -108,14 +108,14 @@ Customizing the installation
.. code-block:: sh
_kitty_install_cmd \\
_kitty_install_cmd \
launch=n
* You can use a previously downloaded dmg/tarball, with ``installer``:
.. code-block:: sh
_kitty_install_cmd \\
_kitty_install_cmd \
installer=/path/to/dmg or tarball

View File

@@ -29,12 +29,13 @@ Dependencies
Run-time dependencies:
* ``python`` >= 3.6
* ``python`` >= 3.8
* ``harfbuzz`` >= 2.2.0
* ``zlib``
* ``libpng``
* ``liblcms2``
* ``librsync``
* ``openssl``
* ``freetype`` (not needed on macOS)
* ``fontconfig`` (not needed on macOS)
* ``libcanberra`` (not needed on macOS)
@@ -82,14 +83,21 @@ You can run |kitty|, as::
If that works, you can create a symlink to the launcher in :file:`~/bin` or some
other directory on your PATH so that you can run |kitty| using just ``kitty``.
To have the kitty documentation available locally, run::
python3 -m pip install -r docs/requirements.txt && make docs
Building kitty.app on macOS from source
-------------------------------------------
Run::
python3 -m pip install -r docs/requirements.txt && make docs
make app
Building the docs needs to be done only once.
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.
@@ -152,7 +160,7 @@ 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
make linux-package
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
@@ -175,8 +183,11 @@ You should probably split |kitty| into three packages:
:code:`kitty`
Installs the main program
This allows users to install the terminfo and shell integration files on servers
into which they ssh, without needing to install all of |kitty|.
This allows users to install the terminfo and shell integration files on
servers into which they ssh, without needing to install all of |kitty|. The
shell integration files **must** still be present in
:file:`lib/kitty/shell-integration` when installing the kitty main package as
the kitty program expects to find them there.
.. note::
You need a couple of extra dependencies to build linux-package. :file:`tic`

View File

@@ -16,6 +16,7 @@ The :doc:`ssh kitten <kittens/ssh>` is redesigned with powerful new features:
* Automatic :ref:`shell_integration` on remote machines
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote machines
* Easily :ref:`edit files in your local editor <edit_file>` on remote machines
* Automatic :opt:`re-use of existing connections <kitten-ssh.share_connections>` to avoid connection setup latency
@@ -34,6 +35,223 @@ mouse anywhere in the current command to move the cursor there. See
Detailed list of changes
-------------------------------------
0.26.5 [2022-11-07]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Splits layout: Add a new mappable action to move the active window to the screen edge (:iss:`5643`)
- ssh kitten: Allow using absolute paths for the location of transferred data (:iss:`5607`)
- Fix a regression in the previous release that caused a :opt:`resize_draw_strategy` of ``static`` to not work (:iss:`5601`)
- Wayland KDE: Fix abort when pasting into Firefox (:iss:`5603`)
- Wayland GNOME: Fix ghosting when using :opt:`background_tint` (:iss:`5605`)
- Fix cursor position at x=0 changing to x=1 on resize (:iss:`5635`)
- Wayland GNOME: Fix incorrect window size in some circumstances when switching between windows with window decorations disabled (:iss:`4802`)
- Wayland: Fix high CPU usage when using some input methods (:pull:`5369`)
- Remote control: When matching window by `state:focused` and no window currently has keyboard focus, match the window belonging to the OS window that was last focused (:iss:`5602`)
0.26.4 [2022-10-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- macOS: Allow changing the kitty icon by placing a custom icon in the kitty config folder (:pull:`5464`)
- Allow centering the :opt:`background_image` (:iss:`5525`)
- X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`)
- Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab (:iss:`5548`)
- Fix :opt:`background_tint` not applying to window margins and padding (:iss:`3933`)
- Wayland: Fix background image scaling using tiled mode on high DPI screens
- Wayland: Fix an abort when changing background colors with :opt:`wayland_titlebar_color` set to ``background`` (:iss:`5562`)
- Update to Unicode 15.0 (:pull:`5542`)
- GNOME Wayland: Fix a memory leak in gnome-shell when using client side decorations
0.26.3 [2022-09-22]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Wayland: Mark windows in which a bell occurs as urgent on compositors that support the xdg-activation protocol
- Allow passing null bytes through the system clipboard (:iss:`5483`)
- ssh kitten: Fix :envvar:`KITTY_PUBLIC_KEY` not being encoded properly when transmitting (:iss:`5496`)
- Sessions: Allow controlling which OS Window is active via the ``focus_os_window`` directive
- Wayland: Fix for bug in NVIDIA drivers that prevents transparency working (:iss:`5479`)
- Wayland: Fix for a bug that could cause kitty to become non-responsive when
using multiple OS windows in a single instance on some compositors (:iss:`5495`)
- Wayland: Fix for a bug preventing kitty from starting on Hyprland when using a non-unit scale (:iss:`5467`)
- Wayland: Generate a XDG_ACTIVATION_TOKEN when opening URLs or running programs in the background via the launch action
- Fix a regression that caused kitty not to restore SIGPIPE after python nukes it when launching children. Affects bash which does not sanitize its signal mask. (:iss:`5500`)
- Fix a use-after-free when handling fake mouse clicks and the action causes windows to be removed/re-allocated (:iss:`5506`)
0.26.2 [2022-09-05]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow creating :code:`overlay-main` windows, which are treated as the active window unlike normal overlays (:iss:`5392`)
- hints kitten: Allow using :doc:`launch` as the program to run, to open the result in a new kitty tab/window/etc. (:iss:`5462`)
- hyperlinked_grep kitten: Allow control over which parts of ``rg`` output are hyperlinked (:pull:`5428`)
- Fix regression in 0.26.0 that caused launching kitty without working STDIO handles to result in high CPU usage and prewarming failing (:iss:`5444`)
- :doc:`/launch`: Allow setting the margin and padding for newly created windows (:iss:`5463`)
- macOS: Fix regression in 0.26.0 that caused asking the user for a line of input such as for :ac:`set_tab_title` to not work (:iss:`5447`)
- hints kitten: hyperlink matching: Fix hints occasionally matching text on subsequent line as part of hyperlink (:pull:`5450`)
- Fix a regression in 0.26.0 that broke mapping of native keys whose key codes did not fit in 21 bits (:iss:`5452`)
- Wayland: Fix remembering window size not accurate when client side decorations are present
- Fix an issue where notification identifiers were not sanitized leading to
code execution if the user clicked on a notification popup from a malicious
source. Thanks to Carter Sande for discovering this vulnerability.
0.26.1 [2022-08-30]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ssh kitten: Fix executable permission missing from kitty bootstrap script (:iss:`5438`)
- Fix a regression in 0.26.0 that caused kitty to no longer set the ``LANG`` environment variable on macOS (:iss:`5439`)
- Allow specifying a title when using the :ac:`set_tab_title` action (:iss:`5441`)
0.26.0 [2022-08-29]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new option :opt:`remote_control_password` to use fine grained permissions for what can be remote controlled (:disc:`5320`)
- Reduce startup latency by ~30 milliseconds when running kittens via key bindings inside kitty (:iss:`5159`)
- A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`)
- A new shortcut :sc:`show_kitty_doc` to display the kitty docs in a browser
- Graphics protocol: Only delete temp files if they have the string
:code:`tty-graphics-protocol` in their file paths. This prevents deletion of arbitrary files in :file:`/tmp`.
- Deprecate the ``adjust_baseline``, ``adjust_line_height`` and ``adjust_column_width`` options in favor of :opt:`modify_font`
- Wayland: Fix a regression in the previous release that caused mouse cursor
animation and keyboard repeat to stop working when switching seats (:iss:`5188`)
- Allow resizing windows created in session files (:pull:`5196`)
- Fix horizontal wheel events not being reported to client programs when they grab the mouse (:iss:`2819`)
- macOS: Remote control: Fix unable to launch a new OS window or background process when there is no OS window (:iss:`5210`)
- macOS: Fix unable to open new tab or new window when there is no OS window (:iss:`5276`)
- kitty @ set-colors: Fix changing inactive_tab_foreground not working (:iss:`5214`)
- macOS: Fix a regression that caused switching keyboard input using Eisu and
Kana keys not working (:iss:`5232`)
- Add a mappable action to toggle the mirrored setting for the tall and fat
layouts (:pull:`5344`)
- Add a mappable action to switch between predefined bias values for the tall and fat
layouts (:pull:`5352`)
- Wayland: Reduce flicker at startup by not using render frames immediately after a resize (:iss:`5235`)
- Linux: Update cursor position after all key presses not just pre-edit text
changes (:iss:`5241`)
- ssh kitten: Allow ssh kitten to work from inside tmux, provided the tmux
session inherits the correct KITTY env vars (:iss:`5227`)
- ssh kitten: A new option :code:`--symlink-strategy` to control how symlinks
are copied to the remote machine (:iss:`5249`)
- ssh kitten: Allow pressing :kbd:`Ctrl+C` to abort ssh before the connection is
completed (:iss:`5271`)
- Bash integration: Fix declare not creating global variables in .bashrc (:iss:`5254`)
- Bash integration: Fix the inherit_errexit option being set by shell integration (:iss:`5349`)
- :command:`kitty @ scroll-window` allow scrolling by fractions of a screen
(:iss:`5294`)
- remote files kitten: Fix working with files whose names have characters that
need to be quoted in shell scripts (:iss:`5313`)
- Expand ~ in paths configured in :opt:`editor` and :opt:`exe_search_path` (:disc:`5298`)
- Allow showing the working directory of the active window in tab titles
(:pull:`5314`)
- ssh kitten: Allow completion of ssh options between the destination and command (:iss:`5322`)
- macOS: Fix speaking selected text not working (:iss:`5357`)
- Allow ignoring failure to close windows/tabs via rc commands (:disc:`5406`)
- Fix hyperlinks not present when fetching text from the history buffer
(:iss:`5427`)
0.25.2 [2022-06-07]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new command :command:`edit-in-kitty` to :ref:`edit_file`
- Allow getting the last non-empty command output easily via an action or
remote control (:pull:`4973`)
- Fix a bug that caused :opt:`macos_colorspace` to always be ``default`` regardless of its actual value (:iss:`5129`)
- diff kitten: A new option :opt:`kitten-diff.ignore_name` to exclude files and directories from being scanned (:pull:`5171`)
- ssh kitten: Fix bash not being executed as a login shell since kitty 0.25.0 (:iss:`5130`)
- macOS: When pasting text and the clipboard has a filesystem path, paste the
full path instead of the text, which is sometimes just the file name (:pull:`5142`)
- macOS: Allow opening executables without a file extension with kitty as well
(:iss:`5160`)
- Themes kitten: Add a tab to show user defined custom color themes separately
(:pull:`5150`)
- Iosevka: Fix incorrect rendering when there is a combining char that does not
group with its neighbors (:iss:`5153`)
- Weston: Fix client side decorations flickering on slow computers during
window resize (:iss:`5162`)
- Remote control: Fix commands with large or asynchronous payloads like
:command:`kitty @ set-backround-image`, :command:`kitty @ set-window-logo`
and :command:`kitty @ select-window` not working correctly
when using a socket (:iss:`5165`)
- hints kitten: Fix surrounding quotes/brackets and embedded carriage returns
not being removed when using line number processing (:iss:`5170`)
0.25.1 [2022-05-26]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -648,7 +866,7 @@ Detailed list of changes
0.21.2 [2021-06-28]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new :opt:`adjust_baseline` option to adjust the vertical alignment of text
- A new ``adjust_baseline`` option to adjust the vertical alignment of text
inside a line (:pull:`3734`)
- A new :opt:`url_excluded_characters` option to exclude additional characters
@@ -822,7 +1040,7 @@ Detailed list of changes
scrollback buffer onto the screen. Useful, for example, to restore
the screen after showing completions below the shell prompt.
- A new remote control command :ref:`at_env` to change the default
- A new remote control command :ref:`at-env` to change the default
environment passed to newly created windows (:iss:`3529`)
- Linux: Fix binary kitty builds not able to load fonts in WOFF2 format
@@ -847,7 +1065,7 @@ Detailed list of changes
kitty window title bar
- Fix reading :option:`kitty --session` from ``STDIN`` not working when the
:option:`kitty --detach` option is used (:iss:`3523`)
:code:`kitty --detach` option is used (:iss:`3523`)
- Special case rendering of the few remaining Powerline box drawing chars
(:iss:`3535`)
@@ -1990,7 +2208,7 @@ Detailed list of changes
- Allow the user to control the resize debounce time via
:opt:`resize_debounce_time`.
- Remote control: Make the :ref:`at_set-font-size` command more capable.
- Remote control: Make the :ref:`at-set-font-size` command more capable.
It can now increment font size and reset it. It also only acts on the
active top-level window, by default (:iss:`1581`)
@@ -2299,7 +2517,7 @@ Detailed list of changes
- 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
- Wayland: Allow using the :code:`kitty --class` command line flag
to set the app id (:iss:`862`)
- Add completion of the kitty command for the fish shell (:pull:`829`)
@@ -2342,7 +2560,7 @@ Detailed list of changes
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`)
:ref:`at-goto-layout` command (:iss:`845`)
- Remote control: Allow matching windows by the environment variables of their
child process as well

View File

@@ -13,7 +13,7 @@ import subprocess
import sys
import time
from functools import partial
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
from typing import Any, Callable, Dict, Iterable, List, Tuple
from docutils import nodes
from docutils.parsers.rst.roles import set_classes
@@ -85,7 +85,7 @@ master_doc = 'index'
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language: Optional[str] = None
language: str = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
@@ -120,10 +120,23 @@ html_title = 'kitty'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
github_icon_path = 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z' # noqa
html_theme_options: Dict[str, Any] = {
'sidebar_hide_name': True,
'navigation_with_keys': True,
'footer_icons': [
{
"name": "GitHub",
"url": "https://github.com/kovidgoyal/kitty",
"html": f"""
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="{github_icon_path}"></path>
</svg>
""",
"class": "",
},
],
}
@@ -236,7 +249,7 @@ if you specify a program-to-run you can use the special placeholder
from kitty.rc.base import cli_params_for
for cmd_name in sorted(all_command_names()):
func = command_for_name(cmd_name)
p(f'.. _at_{func.name}:\n')
p(f'.. _at-{func.name}:\n')
p('kitty @', func.name)
p('-' * 120)
p('.. program::', 'kitty @', func.name)
@@ -264,7 +277,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
from kitty.rc.base import (
RemoteCommand, all_command_names, command_for_name
)
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
p(name)
@@ -276,7 +289,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
if m is None:
p(line)
else:
fields.append((m.group(1), m.group(2)))
fields.append((m.group(1).split('/')[0], m.group(2)))
if fields:
p('\nFields are:\n')
for (name, desc) in fields:

54
docs/extract-rst-targets.py Executable file
View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from typing import Dict, Iterator
tgt_pat = re.compile(r'^.. _(\S+?):$', re.MULTILINE)
title_pat = re.compile('^(.+)\n[-=^#*]{5,}$', re.MULTILINE)
def find_explicit_targets(text: str) -> Iterator[str]:
for m in tgt_pat.finditer(text):
yield m.group(1)
def find_page_title(text: str) -> str:
for m in title_pat.finditer(text):
return m.group(1)
return ''
def main() -> Dict[str, Dict[str, str]]:
refs = {}
docs = {}
base = os.path.dirname(os.path.abspath(__file__))
for dirpath, dirnames, filenames in os.walk(base):
if 'generated' in dirnames:
dirnames.remove('generated')
for f in filenames:
if f.endswith('.rst'):
with open(os.path.join(dirpath, f)) as stream:
raw = stream.read()
href = os.path.relpath(stream.name, base).replace(os.sep, '/')
href = href.rpartition('.')[0] + '/'
docs[href.rstrip('/')] = find_page_title(raw)
first_line = raw.lstrip('\n').partition('\n')[0]
first_target_added = False
for explicit_target in find_explicit_targets(raw):
# Shorten the reference link to the top of the page.
# Note that anchor links should still be used in HTML docs
# to allow jumping within the same page.
if not first_target_added:
first_target_added = True
if first_line.startswith(f'.. _{explicit_target}:'):
refs[explicit_target] = href
continue
refs[explicit_target] = href + f'#{explicit_target.replace("_", "-")}'
return {'ref': refs, 'doc': docs}
if __name__ == '__main__':
import json
print(json.dumps(main(), indent=2))

View File

@@ -132,8 +132,8 @@ You can also define keyboard shortcuts to set colors, for example::
map f1 set_colors --configured /path/to/some/config/file/colors.conf
Or you can enable :doc:`remote control <remote-control>` for |kitty| and use
:ref:`at_set-colors`. The shortcut mapping technique has the same syntax as the
remote control command, for details, see :ref:`at_set-colors`.
:ref:`at-set-colors`. The shortcut mapping technique has the same syntax as the
remote control command, for details, see :ref:`at-set-colors`.
To change colors when SSHing into a remote host, use the :opt:`color_scheme
<kitten-ssh.color_scheme>` setting for the :doc:`ssh kitten <kittens/ssh>`.
@@ -205,13 +205,15 @@ fonts to be freely resizable, so it does not support bitmapped fonts.
.. note::
If you are trying to use a font patched with `Nerd Fonts
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
fonts. There is no need, simply install the standalone ``Symbols Nerd Font``
fonts. There is no need, simply install the standalone ``Symbols Nerd Font Mono``
(the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
<https://github.com/ryanoasis/nerd-fonts/releases>`__). kitty should pick up
symbols from it automatically, and you can tell it to do so explicitly in
case it doesn't with the :opt:`symbol_map` directive::
symbol_map U+23FB-U+23FE,U+2665,U+26A1,U+2B58,U+E000-U+E00A,U+E0A0-U+E0A3,U+E0B0-U+E0C8,U+E0CA,U+E0CC-U+E0D2,U+E0D4,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E62F,U+E700-U+E7C5,U+F000-U+F2E0,U+F300-U+F31C,U+F400-U+F4A9,U+F500-U+F8FF Symbols Nerd Font
# Nerd Fonts v2.2.2
symbol_map U+23FB-U+23FE,U+2665,U+26A1,U+2B58,U+E000-U+E00A,U+E0A0-U+E0A3,U+E0B0-U+E0C8,U+E0CA,U+E0CC-U+E0D2,U+E0D4,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E634,U+E700-U+E7C5,U+EA60-U+EBEB,U+F000-U+F2E0,U+F300-U+F32F,U+F400-U+F4A9,U+F500-U+F8FF Symbols Nerd Font Mono
Those Unicode symbols beyond the ``E000-F8FF`` Unicode private use area are
not included.
@@ -283,13 +285,40 @@ homepage:
:target: https://github.com/igrmk/whiskers
:width: 256
On macOS you can change the icon by following the steps:
.. image:: https://github.com/samholmes/whiskers/raw/main/whiskers.png
:target: https://github.com/samholmes/whiskers
:width: 256
On macOS you can put :file:`kitty.app.icns` or :file:`kitty.app.png` in the
:ref:`kitty configuration directory <confloc>`, and this icon will be applied
automatically at startup. Unfortunately, Apple's Dock does not change its
cached icon so the custom icon will revert when kitty is quit. Run the
following to force the Dock to update its cached icons:
.. code-block:: sh
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
If you prefer not to keep a custom icon in the kitty config folder, you can
also set it with the following command:
.. code-block:: sh
# Set kitty.icns as the icon for currently running kitty
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' kitty.icns
# Set the icon for app bundle specified by the path
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' /path/to/icon.png /Applications/kitty.app
You can also change the icon manually by following the steps:
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
#. Delete the icon cache and restart Dock::
#. Delete the icon cache and restart Dock:
$ rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
.. code-block:: sh
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
How do I map key presses in kitty to different keys in the terminal program?
@@ -308,7 +337,11 @@ the :sc:`send_text <send_text>` you can use the ``show_key`` kitten. Run::
Then press the key you want to emulate. Note that this kitten will only show
keys that actually reach the terminal program, in particular, keys mapped to
actions in kitty will not be shown. To check those first map them to
:ac:`no_op`.
:ac:`no_op`. You can also start a kitty instance without any shortcuts to
interfere::
kitty -o clear_all_shortcuts=yes kitty +kitten show_key
How do I open a new window or tab with the same working directory as the current window?
--------------------------------------------------------------------------------------------

View File

@@ -30,9 +30,14 @@ Glossary
overlay
An *overlay window* is a :term:`kitty window <window>` that is placed on
top of an existing kitty window, entirely covering it. Overlays are used
throught kitty, for example, to display the :ref:`the scrollback buffer <scrollback>`,
throughout kitty, for example, to display the :ref:`the scrollback buffer <scrollback>`,
to display :doc:`hints </kittens/hints>`, for :doc:`unicode input
</kittens/unicode_input>` etc.
</kittens/unicode_input>` etc. Normal overlays are meant for short
duration popups and so are not considered the :italic:`active window`
when determining the current working directory or getting input text for
kittens, launch commands, etc. To create an overlay considered as a
:italic:`main window` use the :code:`overlay-main` argument to
:doc:`launch`.
hyperlinks
Terminals can have hyperlinks, just like the internet. In kitty you can
@@ -107,6 +112,11 @@ Variables that influence kitty behavior
Set this to the directory path of the kitty source code and its Python code
will be loaded from there. Only works with official binary builds.
.. envvar:: KITTY_RC_PASSWORD
Set this to a pass phrase to use the ``kitty @`` remote control command with
:opt:`remote_control_password`.
Variables that kitty sets when running child programs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -116,53 +126,49 @@ Variables that kitty sets when running child programs
This is only set on macOS. If the country and language from the macOS user
settings form an invalid locale, it will be set to :code:`en_US.UTF-8`.
.. envvar:: PATH
kitty prepends itself to the PATH of its own environment to ensure the
functions calling :program:`kitty` will work properly.
.. envvar:: KITTY_WINDOW_ID
An integer that is the id for the kitty :term:`window` the program is running in.
Can be used with the :doc:`kitty remote control facility <remote-control>`.
.. envvar:: KITTY_PID
An integer that is the process id for the kitty process in which the program
is running. Allows programs to tell kitty to reload its config by sending it
the SIGUSR1 signal.
.. envvar:: KITTY_PUBLIC_KEY
A public key that programs can use to communicate securely with kitty using
the remote control protocol. The format is: :code:`protocol:key data`.
.. envvar:: WINDOWID
The id for the :term:`OS Window <os_window>` the program is running in. Only available
on platforms that have ids for their windows, such as X11 and macOS.
.. envvar:: TERM
The name of the terminal, defaults to ``xterm-kitty``. See :opt:`term`.
.. envvar:: TERMINFO
Path to a directory containing the kitty terminfo database.
.. envvar:: KITTY_INSTALLATION_DIR
Path to the kitty installation directory.
.. envvar:: COLORTERM
Set to the value ``truecolor`` to indicate that kitty supports 16 million
colors.
.. envvar:: KITTY_LISTEN_ON
Set when the :doc:`remote control <remote-control>` facility is enabled and
@@ -170,45 +176,38 @@ Variables that kitty sets when running child programs
Contains the path to the socket. Avoid the need to use :option:`kitty @ --to` when
issuing remote control commands.
.. envvar:: KITTY_PIPE_DATA
Set to data describing the layout of the screen when running child
programs using :option:`launch --stdin-source` with the contents of the
screen/scrollback piped to them.
.. envvar:: KITTY_CHILD_CMDLINE
Set to the command line of the child process running in the kitty
window when calling the notification callback program on terminal bell, see
:opt:`command_on_bell`.
.. envvar:: KITTY_COMMON_OPTS
Set with the values of some common kitty options when running
kittens, so kittens can use them without needing to load :file:`kitty.conf`.
.. envvar:: KITTY_SHELL_INTEGRATION
Set when enabling :ref:`shell_integration`. It is automatically removed by
the shell integration scripts.
.. envvar:: ZDOTDIR
Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing
:program:`zsh` to automatically load the integration script.
.. envvar:: XDG_DATA_DIRS
Set when enabling :ref:`shell_integration` with :program:`fish`, allowing
:program:`fish` to automatically load the integration script.
.. envvar:: ENV
Set when enabling :ref:`shell_integration` with :program:`bash`, allowing

View File

@@ -31,7 +31,7 @@ Some programs and libraries that use the kitty graphics protocol:
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with
image previews, see this `PR <https://github.com/ranger/ranger/pull/1077>`_
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
* `tpix <https://github.com/jesvedberg/tpix>` - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
* `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
information tool
@@ -43,6 +43,7 @@ Some programs and libraries that use the kitty graphics protocol:
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
* `term-image <https://github.com/AnonymouX47/term-image>`_ - A Python library, CLI and TUI to display and browse images in the terminal
Other terminals that have implemented the graphics protocol:
@@ -236,7 +237,8 @@ Value of `t` Meaning
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.
specific temporary directories and the file has the
string :code:`tty-graphics-protocol` in its full file path.
``s`` A *shared memory object*, which on POSIX systems is a
`POSIX shared memory object <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html>`_
and on Windows is a
@@ -378,7 +380,9 @@ id. To do so add the ``p`` key with a number between ``1`` and ``4294967295``.
When you specify a placement id, it will be added to the acknowledgement code
above. Every placement is uniquely identified by the pair of the ``image id``
and the ``placement id``. If you specify a placement id for an image that does
not have an id, it will be ignored. An example response::
not have an id (i.e. has id=0), it will be ignored. In particular this means
there can exist multiple images with ``image id=0, placement id=0``.
An example response::
<ESC>_Gi=<image id>,p=<placement id>;OK<ESC>\
@@ -415,7 +419,9 @@ they are semi-transparent. You can specify z-index values using the ``z`` key.
Negative z-index values mean that the images will be drawn under the text. This
allows rendering of text on top of images. Negative z-index values below
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
colors.
colors. If two images with the same z-index overlap then the image with the
lower id is considered to have the lower z-index. If the images have the same
z-index and the same id, then the behavior is undefined.
.. note:: After placing an image on the screen the cursor must be moved to the
right by the number of cols in the image placement rectangle and down by the

View File

@@ -47,6 +47,12 @@ graphics protocol.
Another terminal file manager, with previews of file contents powered by kitty's
graphics protocol.
.. _tool_term_image:
`term-image <https://github.com/AnonymouX47/term-image>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Tool to browse images in a terminal using kitty's graphics protocol.
.. _tool_koneko:
`koneko <https://github.com/twenty5151/koneko>`_

View File

@@ -35,8 +35,11 @@ inside the kitty terminal to report key events.
In addition to kitty, this protocol is also implemented in:
* The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__
* The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__
* The `notcurses library
<https://github.com/dankamongmen/notcurses/issues/2131>`__
* The `crossterm library
<https://github.com/crossterm-rs/crossterm/pull/688>`__
* The `neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
* The `kakoune text editor <https://github.com/mawww/kakoune/issues/4103>`__
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__

View File

@@ -49,6 +49,24 @@ kittens. Look in the `kittens sub-directory
code for those. Or see below for a list of :ref:`third-party kittens
<external_kittens>`, that other kitty users have created.
kitty API to use with kittens
-------------------------------
Kittens have full access to internal kitty APIs. However these are neither
entirely stable nor documented. You can instead use the kitty
:doc:`Remote control API </remote-control>`. Simply call
:code:`boss.call_remote_control()`, with the same arguments you
would pass to ``kitty @``. For example:
.. code-block:: python
def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
# get the kitty window to which to send text
w = boss.window_id_map.get(target_window_id)
if w is not None:
boss.call_remote_control(w, ('send-text', 'hello world'))
Passing arguments to kittens
------------------------------

View File

@@ -17,12 +17,12 @@ following contents:
# by the hyperlink_grep kitten and nothing else so far.
protocol file
fragment_matches [0-9]+
action launch --type=overlay vim +${FRAGMENT} ${FILE_PATH}
action launch --type=overlay --cwd=current vim +${FRAGMENT} ${FILE_PATH}
# Open text files without fragments in the editor
protocol file
mime text/*
action launch --type=overlay ${EDITOR} ${FILE_PATH}
action launch --type=overlay --cwd=current ${EDITOR} ${FILE_PATH}
Now, run a search with::
@@ -74,6 +74,16 @@ to :program:`rg`. How to do that varies based on the shell:
To learn more about kitty's powerful framework for customizing URL click
actions, see :doc:`here </open_actions>`.
By default, this kitten adds hyperlinks for several parts of ripgrep output:
the per-file header, match context lines, and match lines. You can control
which items are linked with a :command:`--kitten hyperlink` flag. For example,
:command:`--kitten hyperlink=matching_lines` will only add hyperlinks to the
match lines. :command:`--kitten hyperlink=file_headers,context_lines` will link
file headers and context lines but not match lines. :command:`--kitten
hyperlink=none` will cause the command line to be passed to directly to
:command:`rg` so no hyperlinking will be performed. :command:`--kitten
hyperlink` may be specified multiple times.
Hopefully, someday this functionality will make it into some `upstream grep
<https://github.com/BurntSushi/ripgrep/issues/665>`__ program directly removing
the need for this kitten.

View File

@@ -22,6 +22,8 @@ transferred to the remote computer. Note that this happens without needing
to install *any* special software on the server, beyond :program:`ls` that
supports hyperlinks.
.. seealso:: See the :ref:`edit-in-kitty <edit_file>` command
.. seealso:: See the :doc:`transfer` kitten
.. versionadded:: 0.19.0

View File

@@ -143,6 +143,16 @@ transmitted instantly without any roundtrip delay.
is something POSIX sh compliant, and use :code:`python` as the
:opt:`interpreter <kitten-ssh.interpreter>` in :file:`ssh.conf`.
.. note::
This may or may not work when using terminal multiplexers, depending on
whether they passthrough the escape codes and if the values of the
environment variables :envvar:`KITTY_PID` and :envvar:`KITTY_WINDOW_ID` are
correct in the current session (they can be wrong when connecting to a tmux
session running in a different window) and the ssh kitten is run in the
currently active multiplexer window.
.. include:: /generated/conf-kitten-ssh.rst

295
docs/kitty_at_template.py Normal file
View File

@@ -0,0 +1,295 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import argparse
import base64
import hashlib
import json
import os
import sys
import termios
import time
import tty
from contextlib import contextmanager
from ctypes import (
CDLL, POINTER, byref, c_char_p, c_int, c_size_t, c_void_p,
create_string_buffer
)
from ctypes.util import find_library
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat
def build_crypto_tools(): # {{{
class EVP_PKEY_POINTER(c_void_p):
algorithm = 0
def __del__(self):
EVP_PKEY_free(self)
@property
def public(self):
sz = c_size_t(0)
EVP_PKEY_get_raw_public_key(self, None, byref(sz))
buf = create_string_buffer(sz.value)
EVP_PKEY_get_raw_public_key(self, buf, byref(sz))
return buf.raw
def derive_secret(self, pubkey):
pubkey = EVP_PKEY_new_raw_public_key(self.algorithm, None, pubkey, len(pubkey))
ctx = EVP_PKEY_CTX_new(self, None)
EVP_PKEY_derive_init(ctx)
EVP_PKEY_derive_set_peer(ctx, pubkey)
sz = c_size_t(0)
EVP_PKEY_derive(ctx, None, byref(sz))
buf = create_string_buffer(sz.value)
EVP_PKEY_derive(ctx, buf, byref(sz))
return hashlib.sha256(buf.raw).digest()
class EVP_PKEY_CTX_POINTER(c_void_p):
def __del__(self):
EVP_PKEY_CTX_free(self)
class EVP_CIPHER_CTX_POINTER(c_void_p):
def __del__(self):
EVP_CIPHER_CTX_free(self)
class EVP_CIPHER_POINTER(c_void_p):
pass
cl = find_library('crypto')
if not cl:
raise SystemExit('Failed to find libcrypto on your system, make sure OpenSSL is installed')
crypto = CDLL(cl)
libc = CDLL(None)
def create_crypto_func(name, *argtypes, restype=c_int, int_return_ok=lambda x: x == 1):
impl = getattr(crypto, name)
impl.restype = restype
impl.argtypes = argtypes
def func(*a):
res = impl(*a)
if restype is c_int:
if not int_return_ok(res):
print('Call to', name, 'failed with return code:', res, file=sys.stderr)
abort_on_openssl_error()
elif restype is not None and issubclass(restype, c_void_p):
if res.value is None:
print('Call to', name, 'failed with NULL return', file=sys.stderr)
abort_on_openssl_error()
return res
return func
OBJ_txt2nid = create_crypto_func('OBJ_txt2nid', c_char_p, int_return_ok=bool)
EVP_PKEY_CTX_new_id = create_crypto_func('EVP_PKEY_CTX_new_id', c_int, c_void_p, restype=EVP_PKEY_CTX_POINTER)
EVP_PKEY_CTX_new = create_crypto_func('EVP_PKEY_CTX_new', EVP_PKEY_POINTER, c_void_p, restype=EVP_PKEY_CTX_POINTER)
EVP_PKEY_keygen_init = create_crypto_func('EVP_PKEY_keygen_init', EVP_PKEY_CTX_POINTER)
EVP_PKEY_keygen = create_crypto_func('EVP_PKEY_keygen', EVP_PKEY_CTX_POINTER, POINTER(EVP_PKEY_POINTER))
ERR_print_errors_fp = create_crypto_func('ERR_print_errors_fp', c_void_p, restype=None)
EVP_PKEY_free = create_crypto_func('EVP_PKEY_free', EVP_PKEY_POINTER, restype=None)
EVP_PKEY_CTX_free = create_crypto_func('EVP_PKEY_CTX_free', EVP_PKEY_CTX_POINTER, restype=None)
EVP_PKEY_get_raw_public_key = create_crypto_func('EVP_PKEY_get_raw_public_key', EVP_PKEY_POINTER, c_char_p, POINTER(c_size_t))
EVP_PKEY_new_raw_public_key = create_crypto_func('EVP_PKEY_new_raw_public_key', c_int, c_void_p, c_char_p, c_size_t, restype=EVP_PKEY_POINTER)
EVP_PKEY_derive_init = create_crypto_func('EVP_PKEY_derive_init', EVP_PKEY_CTX_POINTER)
EVP_PKEY_derive_set_peer = create_crypto_func('EVP_PKEY_derive_set_peer', EVP_PKEY_CTX_POINTER, EVP_PKEY_POINTER)
EVP_PKEY_derive = create_crypto_func('EVP_PKEY_derive', EVP_PKEY_CTX_POINTER, c_char_p, POINTER(c_size_t))
EVP_CIPHER_CTX_free = create_crypto_func('EVP_CIPHER_CTX_free', EVP_CIPHER_CTX_POINTER, restype=None)
EVP_get_cipherbyname = create_crypto_func('EVP_get_cipherbyname', c_char_p, restype=EVP_CIPHER_POINTER)
EVP_CIPHER_key_length = create_crypto_func('EVP_CIPHER_key_length', EVP_CIPHER_POINTER, int_return_ok=bool)
EVP_CIPHER_iv_length = create_crypto_func('EVP_CIPHER_iv_length', EVP_CIPHER_POINTER, int_return_ok=bool)
EVP_CIPHER_CTX_block_size = create_crypto_func('EVP_CIPHER_CTX_block_size', EVP_CIPHER_CTX_POINTER, int_return_ok=bool)
EVP_CIPHER_CTX_new = create_crypto_func('EVP_CIPHER_CTX_new', restype=EVP_CIPHER_CTX_POINTER)
EVP_EncryptInit_ex = create_crypto_func('EVP_EncryptInit_ex', EVP_CIPHER_CTX_POINTER, EVP_CIPHER_POINTER, c_void_p, c_char_p, c_char_p)
EVP_EncryptUpdate = create_crypto_func('EVP_EncryptUpdate', EVP_CIPHER_CTX_POINTER, c_char_p, POINTER(c_int), c_char_p, c_int)
EVP_EncryptFinal_ex = create_crypto_func('EVP_EncryptFinal_ex', EVP_CIPHER_CTX_POINTER, c_char_p, POINTER(c_int))
EVP_CIPHER_CTX_ctrl = create_crypto_func('EVP_CIPHER_CTX_ctrl', EVP_CIPHER_CTX_POINTER, c_int, c_int, c_char_p)
try:
EVP_CIPHER_CTX_tag_length = create_crypto_func('EVP_CIPHER_CTX_tag_length', EVP_CIPHER_CTX_POINTER, int_return_ok=bool)
except AttributeError: # need openssl >= 3
def EVP_CIPHER_CTX_tag_length(cipher):
return 16
EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG = 0x10, 0x11 # these are defines in the header dont know how to get them programmatically
EVP_CTRL_AEAD_SET_TAG
def abort_on_openssl_error():
stderr = c_void_p.in_dll(libc, 'stderr')
ERR_print_errors_fp(stderr)
raise SystemExit(1)
def elliptic_curve_keypair(algorithm='X25519'):
nid = OBJ_txt2nid(algorithm.encode())
pctx = EVP_PKEY_CTX_new_id(nid, None)
EVP_PKEY_keygen_init(pctx)
key = EVP_PKEY_POINTER()
EVP_PKEY_keygen(pctx, byref(key))
key.algorithm = nid
return key
def encrypt(plaintext, symmetric_key, algorithm='aes-256-gcm'):
cipher = EVP_get_cipherbyname(algorithm.encode())
if len(symmetric_key) != EVP_CIPHER_key_length(cipher):
raise KeyError(f'The symmetric key has length {len(symmetric_key)} != {EVP_CIPHER_key_length(cipher)} needed for {algorithm}')
ctx = EVP_CIPHER_CTX_new()
iv = os.urandom(EVP_CIPHER_iv_length(cipher))
EVP_EncryptInit_ex(ctx, cipher, None, symmetric_key, iv)
bs = EVP_CIPHER_CTX_block_size(ctx)
ciphertext = create_string_buffer(len(plaintext) + 2 * bs)
outlen = c_int(len(ciphertext))
EVP_EncryptUpdate(ctx, ciphertext, byref(outlen), plaintext, len(plaintext))
ans = ciphertext[:outlen.value]
outlen = c_int(len(ciphertext))
EVP_EncryptFinal_ex(ctx, ciphertext, byref(outlen))
if outlen.value:
ans += ciphertext[:outlen.value]
tag = create_string_buffer(EVP_CIPHER_CTX_tag_length(cipher))
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, len(tag), tag)
return iv, ans, tag.raw
return elliptic_curve_keypair, encrypt
# }}}
# utils {{{
def encrypt_cmd(cmd, password, pubkey=None):
elliptic_curve_keypair, encrypt = build_crypto_tools()
if pubkey is None:
pubkey = os.environ['KITTY_PUBLIC_KEY']
v, d = pubkey.split(':', 1)
if v != '1':
raise SystemExit(f'Unsupported encryption protocol: {v}')
pubkey = base64.b85decode(d)
k = elliptic_curve_keypair()
sk = k.derive_secret(pubkey)
cmd['timestamp'] = time.time_ns()
cmd['password'] = password
data = json.dumps(cmd).encode()
iv, encrypted, tag = encrypt(data, sk)
def e(x):
return base64.b85encode(x).decode('ascii')
return {
'encrypted': e(encrypted), 'iv': e(iv), 'tag': e(tag), 'pubkey': e(k.public), 'version': cmd['version']
}
@contextmanager
def raw_mode(fd):
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
yield
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def config_dir():
if 'KITTY_CONFIG_DIRECTORY' in os.environ:
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
locations = []
if 'XDG_CONFIG_HOME' in os.environ:
locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME'])))
locations.append(os.path.expanduser('~/.config'))
if is_macos:
locations.append(os.path.expanduser('~/Library/Preferences'))
for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)):
locations.append(os.path.abspath(os.path.expanduser(loc)))
for loc in locations:
if loc:
q = os.path.join(loc, 'kitty')
if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')):
return q
for loc in locations:
if loc:
q = os.path.join(loc, 'kitty')
if os.path.isdir(q) and os.access(q, os.W_OK):
return q
return ''
def resolve_custom_file(path):
path = os.path.expanduser(path)
path = os.path.expandvars(path)
if not os.path.isabs(path):
cdir = config_dir()
if cdir:
path = os.path.join(cdir, path)
return path
def get_password(opts):
if opts.use_password == 'never':
return ''
ans = ''
if opts.password:
ans = opts.password
if not ans and opts.password_file:
if opts.password_file == '-':
if sys.stdin.isatty():
from getpass import getpass
ans = getpass()
else:
ans = sys.stdin.read().rstrip()
try:
tty_fd = os.open(os.ctermid(), os.O_RDONLY | os.O_CLOEXEC)
except OSError:
pass
else:
with open(tty_fd, closefd=True):
os.dup2(tty_fd, sys.stdin.fileno())
else:
try:
with open(resolve_custom_file(opts.password_file)) as f:
ans = f.read().rstrip()
except OSError:
pass
if not ans and opts.password_env:
ans = os.environ.get(opts.password_env, '')
if not ans and opts.use_password == 'always':
raise SystemExit('No password was found')
if ans and len(ans) > 1024:
raise SystemExit('Specified password is too long')
return ans
# }}}
arg_parser = argparse.ArgumentParser(prog='kitty@', description='Control kitty remotely.')
arg_parser.add_argument('--password', default='', help='''\
A password to use when contacting kitty. This will cause kitty to ask the user
for permission to perform the specified action, unless the password has been
accepted before or is pre-configured in kitty.conf''')
arg_parser.add_argument('--password-file', default='rc-pass', help='''\
A file from which to read the password. Trailing whitespace is ignored. Relative
paths are resolved from the kitty configuration directory. Use - to read from STDIN.
Used if no --password is supplied. Defaults to checking for the
rc-pass file in the kitty configuration directory.''')
arg_parser.add_argument('--password-env', default='KITTY_RC_PASSWORD', help='''\
The name of an environment variable to read the password from.
Used if no --password-file is supplied. Defaults to checking the KITTY_RC_PASSWORD.''')
arg_parser.add_argument('--use-password', default='if-available', choices=('if-available', 'always', 'never'), help='''\
If no password is available, kitty will usually just send the remote control command
without a password. This option can be used to force it to always or never use
the supplied password.''')
args = arg_parser.parse_args()
def populate_cmd(cmd):
raise NotImplementedError()
password = get_password(args)
cmd = {'version': [0, 20, 0]} # use a random version that's fairly old
populate_cmd(cmd)
if password:
encrypt_cmd(cmd, password)
# cmd = {'version': [0, 14, 2], 'cmd': 'ls'}
# cmd = encrypt_cmd(cmd, 'test')
# with open(os.open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC), 'w') as tty_file, raw_mode(tty_file.fileno()):
# print(end=f'\x1bP@kitty-cmd{json.dumps(cmd)}\x1b\\', flush=True, file=tty_file)
# os.read(tty_file.fileno(), 4096)

View File

@@ -68,11 +68,20 @@ for the options is::
└──────────────┴───────────────┘
In addition, you can map keys to increase or decrease the number of full-height
windows, for example::
windows, or toggle the mirrored setting, for example::
map ctrl+[ layout_action decrease_num_full_size_windows
map ctrl+] layout_action increase_num_full_size_windows
map ctrl+/ layout_action mirror toggle
map ctrl+y layout_action mirror true
map ctrl+n layout_action mirror false
You can also map a key to change the bias by providing a list of percentages
and it will rotate through the list as you press the key. If you only provide
one number it'll toggle between that percentage and 50, for example::
map ctrl+. layout_action bias 50 62 70
map ctrl+, layout_action bias 62
The Fat Layout
----------------
@@ -101,8 +110,7 @@ on the bottom of the screen instead of the top. The syntax for the options is::
└─────────┴──────────┴─────────┘
This layout also supports ``decrease_num_full_size_windows`` layout action like
the *Tall* layout, shown above.
This layout also supports the same layout actions as the *Tall* layout, shown above.
The Grid Layout
@@ -159,6 +167,12 @@ define a few extra key bindings in :file:`kitty.conf`::
map shift+right move_window right
map shift+down move_window down
# Move the active window to the indicated screen edge
map ctrl+shift+up layout_action move_to_screen_edge top
map ctrl+shift+left layout_action move_to_screen_edge left
map ctrl+shift+right layout_action move_to_screen_edge right
map ctrl+shift+down layout_action move_to_screen_edge bottom
# Switch focus to the neighboring window in the indicated direction
map ctrl+left neighboring_window left
map ctrl+right neighboring_window right

View File

@@ -64,6 +64,10 @@ some special variables, documented below:
``FRAGMENT``
The fragment (unquoted), if any of the URL or the empty string.
``URL_PATH``
The path, query and fragment portions of the URL, without any
unquoting.
.. note::
You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to
@@ -144,33 +148,7 @@ You can customize these actions by creating a :file:`launch-actions.conf` file
in the :ref:`kitty config directory <confloc>`, just like the
:file:`open-actions.conf` file above. For example:
.. code:: conf
# Open script files based on their shebangs
protocol file
ext sh,command,tool
action launch --hold --type=os-window kitty +shebang $FILE_PATH {SHELL}
# Open shell specific script files
protocol file
ext fish,bash,zsh
action launch --hold --type=os-window kitty +shebang $FILE_PATH __ext__
# Open directories
protocol file
mime inode/directory
action launch --type=os-window --cwd $FILE_PATH
# Open text files without fragments in the editor
protocol file
mime text/*
action launch --type=os-window $EDITOR $FILE_PATH
# Open image files with icat
protocol file
mime image/*
action launch --type=os-window kitty +kitten icat --hold $FILE_PATH
# Open ssh URLs with ssh command
protocol ssh
action launch --type=os-window ssh $URL
.. literalinclude:: ../kitty/open_actions.py
:language: conf
:start-at: # Open script files
:end-before: '''.splitlines()))

View File

@@ -161,8 +161,12 @@ option in :file:`kitty.conf`. For example:
# Set the --class for the new OS window
os_window_class mywindow
launch sh
# Make the current window the active (focused) window
# Resize the current window (see the resize_window action for details)
resize_window wider 2
# Make the current window the active (focused) window in its tab
focus
# Make the current OS Window the globally active window (not supported on Wayland)
focus_os_window
launch emacs
.. note::

View File

@@ -7,13 +7,15 @@ form::
<ESC>P@kitty-cmd<JSON object><ESC>\
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form::
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form:
.. code-block:: json
{
"cmd": "command name",
"version": <kitty version>,
"no_response": <Optional Boolean>,
"payload": <Optional JSON object>,
"version": "<kitty version>",
"no_response": "<Optional Boolean>",
"payload": "<Optional JSON object>"
}
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are
@@ -38,4 +40,41 @@ with the following command line::
echo -en '\eP@kitty-cmd{"cmd":"ls","version":[0,14,2]}\e\\' | socat - unix:/tmp/test | awk '{ print substr($0, 13, length($0) - 14) }' | jq -c '.data | fromjson' | jq .
.. _rc_crypto:
Encrypted communication
--------------------------
.. versionadded:: 0.26.0
When using the :opt:`remote_control_password` option communication to the
terminal is encrypted to keep the password secure. A public key is used from
the :envvar:`KITTY_PUBLIC_KEY` environment variable. Currently, only one
encryption protocol is supported. The protocol number is present in
:envvar:`KITTY_PUBLIC_KEY` as ``1``. The key data in this environment variable
is :rfc:`Base-85 <1924>` encoded. The algorithm used is `Elliptic Curve Diffie
Helman <https://en.wikipedia.org/wiki/Elliptic-curve_DiffieHellman>`__ with
the `X25519 curve <https://en.wikipedia.org/wiki/Curve25519>`__. A time based
nonce is used to minimise replay attacks. The original JSON command has the
fields: ``password`` and ``timestamp`` added. The timestamp is the number of
nanoseconds since the epoch, excluding leap seconds. Commands with a timestamp
more than 5 minutes from the current time are rejected. The command is then
encrypted using AES-256-GCM in authenticated encryption mode, with a symmetric
key that is derived from the ECDH key-pair by running the shared secret through
SHA-256 hashing, once. An IV of at least 96 bits of CSPRNG data is used. The
tag for authenticated encryption **must** be at least 128 bits long. The tag
**must** authenticate only the value of the ``encrypted`` field. A new command
is created and transmitted that contains the fields:
.. code-block:: json
{
"version": "<kitty version>",
"iv": "base85 encoded IV",
"tag": "base85 encoded AEAD tag",
"pubkey": "base85 encoded ECDH public key of sender",
"encrypted": "The original command encrypted and base85 encoded"
}
.. include:: generated/rc.rst

View File

@@ -17,8 +17,9 @@ Start by running |kitty| as::
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.
In order for control to work, :opt:`allow_remote_control` or
:opt:`remote_control_password` must be enabled in :file:`kitty.conf`. Here we
turn it on explicitly at the command line.
Now, in the new |kitty| window, enter the command::
@@ -35,12 +36,12 @@ This will make ``Hello, World`` show up in the window running the :program:`cat`
program. The :option:`kitty @ send-text --match` option is very powerful, it
allows selecting windows by their titles, the command line of the program
running in the window, the working directory of the program running in the
window, etc. See :ref:`kitty @ send-text --help <at_send-text>` for details.
window, etc. See :ref:`kitty @ send-text --help <at-send-text>` for details.
More usefully, you can pipe the output of a command running in one window to
another window, for example::
ls | kitty @ send-text --match title:^Output --stdin
ls | kitty @ send-text --match 'title:^Output' --stdin
This will show the output of :program:`ls` in the output window instead of the
current window. You can use this technique to, for example, show the output of
@@ -49,7 +50,7 @@ are endless.
You can even have things you type show up in a different window. Run::
kitty @ send-text --match title:^Output --stdin
kitty @ send-text --match 'title:^Output' --stdin
And type some text, it will show up in the output window, instead of the current
window. Type :kbd:`Ctrl+D` when you are ready to stop.
@@ -61,7 +62,7 @@ Now, let's open a new tab::
This will open a new tab running the bash shell with the title "My Tab".
We can change the title of the tab to "New Title" with::
kitty @ set-tab-title --match title:^My New Title
kitty @ set-tab-title --match 'title:^My' New Title
Let's change the title of the current tab::
@@ -69,12 +70,12 @@ Let's change the title of the current tab::
Now lets switch to the newly opened tab::
kitty @ focus-tab --match title:^New
kitty @ focus-tab --match 'title:^New'
Similarly, to focus the previously opened output window (which will also switch
back to the old tab, automatically)::
kitty @ focus-window --match title:^Output
kitty @ focus-window --match 'title:^Output'
You can get a listing of available tabs and windows, by running::
@@ -92,11 +93,23 @@ As you can see, it is very easy to control |kitty| using the ``kitty @``
messaging system. This tutorial touches only the surface of what is possible.
See ``kitty @ --help`` for more details.
Note that in the example's above, ``kitty @`` messaging works only when run
In the example's above, ``kitty @`` messaging works only when run
inside a |kitty| window, not anywhere. But, within a |kitty| window it even
works over SSH. If you want to control |kitty| from programs/scripts not running
inside a |kitty| window, you have to implement a couple of extra steps. First
start |kitty| as::
inside a |kitty| window, see the section on :ref:`using a socket for remote control <rc_via_socket>`
below.
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.
.. _rc_via_socket:
Remote control via a socket
--------------------------------
First, start |kitty| as::
kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty
@@ -108,11 +121,6 @@ command line argument to ``kitty @``. For example::
kitty @ --to unix:/tmp/mykitty ls
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.
The builtin kitty shell
--------------------------
@@ -147,6 +155,112 @@ other computers (for example, over SSH) or as other users.
what programs you run in such windows, since they can effectively control
kitty, as if you were running with :opt:`allow_remote_control` turned on.
You can further restrict what is allowed in these windows by using
:option:`kitty @ launch --remote-control-password`.
Fine grained permissions for remote control
----------------------------------------------
.. versionadded:: 0.26.0
The :opt:`allow_remote_control` option discussed so far is a blunt
instrument, granting the ability to any program running on your computer
or even on remote computers via SSH the ability to use remote control.
You can instead define remote control passwords that can be used to grant
different levels of control to different places. You can even write your
own script to decide which remote control requests are allowed. This is
done using the :opt:`remote_control_password` option in :file:`kitty.conf`.
Set :opt:`allow_remote_control` to :code:`password` to use this feature.
Let's see some examples:
.. code-block:: conf
remote_control_password "control colors" get-colors set-colors
Now, using this password, you can, in scripts run the command::
kitty @ --password="control colors" set-colors background=red
Any script with access to the password can now change colors in kitty using
remote control, but only that and nothing else. You can even supply the
password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the
file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly.
See :option:`kitty @ --password-file` and :option:`kitty @ --password-env`.
The :opt:`remote_control_password` can be specified multiple times to create
different passwords with different capabilities. Run the following to get a
list of all action names::
kitty @ --help
You can even use glob patterns to match action names, for example:
.. code-block:: conf
remote_control_password "control colors" *-colors
If no action names are specified, all actions are allowed.
If ``kitty @`` is run with a password that is not present in
:file:`kitty.conf`, then kitty will interactively prompt the user to allow or
disallow the remote control request. The user can choose to allow or disallow
either just that request or all requests using that password. The user's
decision is remembered for the duration of that kitty instance.
.. note::
For password based authentication to work over SSH, you must pass the
:envvar:`KITTY_PUBLIC_KEY` environment variable to the remote host. The
:doc:`ssh kitten <kittens/ssh>` does this for you automatically. When
using a password, :ref:`rc_crypto` is used to ensure the password
is kept secure. This does mean that using password based authentication
is slower as the entire command is encrypted before transmission. This
can be noticeable when using a command like ``kitty @ set-background-image``
which transmits large amounts of image data. Also, the clock on the remote
system must match (within a few minutes) the clock on the local system.
kitty uses a time based nonce to minimise the potential for replay attacks.
.. _rc_custom_auth:
Customizing authorization with your own program
____________________________________________________________
If the ability to control access by action names is not fine grained enough,
you can define your own Python script to examine every remote control command
and allow/disallow it. To do so create a file in the kitty configuration
directory, :file:`~/.config/kitty/my_rc_auth.py` and add the following
to :file:`kitty.conf`:
.. code-block:: conf
remote_control_password "testing custom auth" my_rc_auth.py
:file:`my_rc_auth.py` should define a :code:`is_cmd_allowed` function
as shown below:
.. code-block:: py
def is_cmd_allowed(pcmd, window, from_socket, extra_data):
cmd_name = pcmd['cmd'] # the name of the command
cmd_payload = pcmd['payload'] # the arguments to the command
# examine the cmd_name and cmd_payload and return True to allow
# the command or False to disallow it. Return None to have no
# effect on the command.
# The command payload will vary from command to command, see
# the rc protocol docs for details. Below is an example of
# restricting the launch command to allow only running the
# default shell.
if cmd_name != 'launch':
return None
if cmd_payload.get('args') or cmd_payload.get('env') or cmd_payload.get('copy_cmdline') or cmd_payload.get('copy_env'):
return False
# prints in this function go to the parent kitty process STDOUT
print('Allowing launch command:', cmd_payload)
return True
.. _rc_mapping:
@@ -162,6 +276,12 @@ Then pressing the :kbd:`F1` key will set the active window margins to
:code:`30`. The syntax for what follows :ac:`remote_control` is exactly the same
as the syntax for what follows :code:`kitty @` above.
If you wish to ignore errors from the command, prefix the command with an
``!``. For example, the following will not return an error when no windows
are matched::
map f1 remote_control !focus-window --match XXXXXX
.. note:: You do not need :opt:`allow_remote_control` to use these mappings,
as they are not actual remote programs, but are simply a way to resuse the
remote control infrastructure via keybings.

View File

@@ -33,13 +33,15 @@ Features
* :ref:`clone_shell` with all environment variables and the working directory
copied
* :ref:`Edit files in new kitty windows <edit_file>` even over SSH
* Glitch free window resizing even with complex prompts. Achieved by erasing
the prompt on resize and allowing the shell to redraw it cleanly.
* Sophisticated completion for the :program:`kitty` command in the shell.
* When confirming a quit command if a window is sitting at a shell prompt,
it is optionally, not counted (see :opt:`confirm_os_window_close`)
it is not counted (for details, see :opt:`confirm_os_window_close`)
Configuration
@@ -263,6 +265,33 @@ state (mainly CWD and env vars) and this state is transmitted to kitty and
restored by the shell integration scripts in the cloned window.
.. _edit_file:
Edit files in new kitty windows even over SSH
------------------------------------------------
.. code-block:: sh
edit-in-kitty myfile.txt
edit-in-kitty --type tab --title "Editing My File" myfile.txt
# open myfile.txt at line 75 (works with vim, neovim, emacs, nano, micro)
edit-in-kitty +75 myfile.txt
The :command:`edit-in-kitty` command allows you to seamlessly edit files
in your default :opt:`editor` in new kitty windows. This works even over
SSH (if you use the :doc:`ssh kitten <kittens/ssh>`), allowing you
to easily edit remote files in your local editor with all its bells and
whistles.
The :command:`edit-in-kitty` command takes almost all the same arguments as the
:doc:`launch <launch>` command, so you can open a new tab instead or a new OS
window, etc. Not all arguments are supported, see the discussion in the
:ref:`clone_shell` section above.
In order to avoid remote code execution, kitty will only execute the configured
editor and pass the file path to edit to it.
.. _manual_shell_integration:
Manual shell integration

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!./kitty/launcher/kitty +launch
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -770,6 +770,11 @@ is_useful_apple_global_shortcut(int sc) {
}
}
static bool
is_apple_jis_layout_function_key(NSEvent *event) {
return [event keyCode] == 0x66 /* kVK_JIS_Eisu */ || [event keyCode] == 0x68 /* kVK_JIS_Kana */;
}
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
handle_reopen_callback = callback;
@@ -837,6 +842,14 @@ int _glfwPlatformInit(void)
last_keydown_shortcut_event.timestamp = [event timestamp];
return event;
}
// check for JIS keyboard layout function keys
if (is_apple_jis_layout_function_key(event)) {
debug_key("keyDown triggerred JIS layout function key ignoring\n");
last_keydown_shortcut_event.virtual_key_code = [event keyCode];
last_keydown_shortcut_event.input_source_switch_modifiers = 0;
last_keydown_shortcut_event.timestamp = [event timestamp];
return event;
}
}
last_keydown_shortcut_event.virtual_key_code = 0xffff;
NSWindow *kw = [NSApp keyWindow];
@@ -979,8 +992,6 @@ void _glfwPlatformTerminate(void)
_glfw.ns.appleSettings = nil;
}
free(_glfw.ns.clipboardString);
_glfwTerminateNSGL();
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }

View File

@@ -183,7 +183,6 @@ typedef struct _GLFWlibraryNS
char keyName[64];
char text[256];
char* clipboardString;
CGPoint cascadePoint;
// Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY;

View File

@@ -29,6 +29,9 @@
#include "internal.h"
#include "../kitty/monotonic.h"
#include <Availability.h>
#import <CoreServices/CoreServices.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include <float.h>
#include <string.h>
@@ -1540,6 +1543,39 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
}
- (BOOL)isAccessibilityElement
{
return YES;
}
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector
{
if (selector == @selector(accessibilityRole) || selector == @selector(accessibilitySelectedText)) return YES;
return NO;
}
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400)
- (NSAccessibilityRole)accessibilityRole
{
return NSAccessibilityTextAreaRole;
}
#endif
- (NSString *)accessibilitySelectedText
{
NSString *text = nil;
if (_glfw.callbacks.get_current_selection) {
char *s = _glfw.callbacks.get_current_selection();
if (s) {
text = [NSString stringWithUTF8String:s];
free(s);
}
}
return text;
}
@end
// }}}
@@ -2472,38 +2508,150 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
return made_fullscreen;
}
void _glfwPlatformSetClipboardString(const char* string)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
[pasteboard setString:@(string) forType:NSPasteboardTypeString];
// Clipboard {{{
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
static NSString*
mime_to_uti(const char *mime) {
if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString;
if (@available(macOS 11.0, *)) {
UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released
if (t != nil && !t.dynamic) return t.identifier;
}
return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released
}
const char* _glfwPlatformGetClipboardString(void)
{
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"Cocoa: Failed to retrieve string from pasteboard");
return NULL;
static const char*
uti_to_mime(NSString *uti) {
if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain";
if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String];
if (@available(macOS 11.0, *)) {
UTType *t = [UTType typeWithIdentifier:uti]; // auto-released
if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String];
}
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
if (!object)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Cocoa: Failed to retrieve object from pasteboard");
return NULL;
}
free(_glfw.ns.clipboardString);
_glfw.ns.clipboardString = _glfw_strdup([object UTF8String]);
return _glfw.ns.clipboardString;
return "";
}
static void
list_clipboard_mimetypes(GLFWclipboardwritedatafun write_data, void *object) {
#define w(x) { if (ok) ok = write_data(object, x, strlen(x)); }
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
BOOL has_file_urls = [pasteboard canReadObjectForClasses:@[[NSURL class]] options:options];
BOOL has_strings = [pasteboard canReadObjectForClasses:@[[NSString class]] options:nil];
/* NSLog(@"has_file_urls: %d has_strings: %d", has_file_urls, has_strings); */
bool ok = true;
if (has_strings) w("text/plain");
if (has_file_urls) w("text/local-path-list");
for (NSPasteboardItem * item in pasteboard.pasteboardItems) {
for (NSPasteboardType type in item.types) {
/* NSLog(@"%@", type); */
const char *mime = uti_to_mime(type);
if (mime && mime[0] && ![@(mime) hasPrefix:@"text/plain"]) {
/* NSLog(@"ut: %@ mt: %@ tags: %@", ut, ut.preferredMIMEType, ut.tags); */
w(mime);
}
}
}
#undef w
}
static void
get_text_plain(GLFWclipboardwritedatafun write_data, void *object) {
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options];
bool found = false;
if (objs) {
const NSUInteger count = [objs count];
if (count) {
NSMutableData *path_list = [NSMutableData dataWithCapacity:4096]; // auto-released
NSMutableData *text_list = [NSMutableData dataWithCapacity:4096]; // auto-released
for (NSUInteger i = 0; i < count; i++) {
id obj = objs[i];
if ([obj isKindOfClass:[NSURL class]]) {
NSURL *url = (NSURL*)obj;
if (url.fileURL && url.fileSystemRepresentation) {
if ([path_list length] > 0) [path_list appendBytes:"\n" length:1];
[path_list appendBytes:url.fileSystemRepresentation length:strlen(url.fileSystemRepresentation)];
}
} else if ([obj isKindOfClass:[NSString class]]) {
if ([text_list length] > 0) [text_list appendBytes:"\n" length:1];
[text_list appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]];
}
}
const NSMutableData *text = nil;
if (path_list.length > 0) text = path_list;
else if (text_list.length > 0) text = text_list;
if (text) {
found = true;
write_data(object, text.mutableBytes, text.length);
}
}
}
if (!found) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve text/plain from pasteboard");
}
void
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
if (clipboard_type != GLFW_CLIPBOARD) return;
if (mime_type == NULL) {
list_clipboard_mimetypes(write_data, object);
return;
}
if (strcmp(mime_type, "text/plain") == 0) {
get_text_plain(write_data, object);
return;
}
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
/* NSLog(@"mime: %s uti: %@", mime_type, mime_to_uti(mime_type)); */
NSPasteboardType t = [pasteboard availableTypeFromArray:@[mime_to_uti(mime_type)]];
/* NSLog(@"available type: %@", t); */
if (t != nil) {
NSData *data = [pasteboard dataForType:t]; // auto-released
/* NSLog(@"data: %@", data); */
if (data != nil && data.length > 0) {
write_data(object, data.bytes, data.length);
}
}
}
static NSMutableData*
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime) {
NSMutableData *ans = [NSMutableData dataWithCapacity:8192];
if (ans == nil) return nil;
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
void *iter = chunk.iter;
if (!iter) return ans;
while (true) {
chunk = cd->get_data(mime, iter, cd->ctype);
if (!chunk.sz) break;
[ans appendBytes:chunk.data length:chunk.sz];
if (chunk.free) chunk.free((void*)chunk.free_data);
}
cd->get_data(NULL, iter, cd->ctype);
return ans;
}
void
_glfwPlatformSetClipboard(GLFWClipboardType t) {
if (t != GLFW_CLIPBOARD) return;
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
NSMutableArray<NSPasteboardType> *ptypes = [NSMutableArray arrayWithCapacity:_glfw.clipboard.num_mime_types]; // auto-released
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
[ptypes addObject:mime_to_uti(_glfw.clipboard.mime_types[i])];
}
[pasteboard declareTypes:ptypes owner:nil];
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
NSMutableData *data = get_clipboard_data(&_glfw.clipboard, _glfw.clipboard.mime_types[i]); // auto-released
/* NSLog(@"putting data: %@ for: %s with UTI: %@", data, _glfw.clipboard.mime_types[i], ptypes[i]); */
if (data != nil) [pasteboard setData:data forType:ptypes[i]];
}
}
// }}}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)

12
glfw/context.c vendored
View File

@@ -396,15 +396,6 @@ bool _glfwRefreshContextAttribs(_GLFWwindow* window,
window->context.release = GLFW_RELEASE_BEHAVIOR_FLUSH;
}
// Clearing the front buffer to black to avoid garbage pixels left over from
// previous uses of our bit of VRAM
{
PFNGLCLEARPROC glClear = (PFNGLCLEARPROC)
window->context.getProcAddress("glClear");
glClear(GL_COLOR_BUFFER_BIT);
window->context.swapBuffers(window);
}
glfwMakeContextCurrent((GLFWwindow*) previous);
return true;
}
@@ -487,6 +478,9 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
}
window->context.swapBuffers(window);
#ifdef _GLFW_WAYLAND
_glfwWaylandAfterBufferSwap(window);
#endif
}
GLFWAPI void glfwSwapInterval(int interval)

8
glfw/egl_context.c vendored
View File

@@ -326,6 +326,7 @@ bool _glfwInitEGL(void)
glfw_dlsym(_glfw.egl.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers");
glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString");
glfw_dlsym(_glfw.egl.QuerySurface, _glfw.egl.handle, "eglQuerySurface");
glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress");
if (!_glfw.egl.GetConfigAttrib ||
@@ -428,6 +429,8 @@ bool _glfwInitEGL(void)
extensionSupportedEGL("EGL_KHR_get_all_proc_addresses");
_glfw.egl.KHR_context_flush_control =
extensionSupportedEGL("EGL_KHR_context_flush_control");
_glfw.egl.EXT_present_opaque =
extensionSupportedEGL("EGL_EXT_present_opaque");
return true;
}
@@ -598,6 +601,11 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
if (_glfw.egl.KHR_gl_colorspace)
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
}
// Disabled because it prevents transparency from working on NVIDIA drivers under Wayland
// https://github.com/kovidgoyal/kitty/issues/5479
// We anyway dont use the alpha bits for anything.
/* if (_glfw.egl.EXT_present_opaque) */
/* setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent); */
setAttrib(EGL_NONE, EGL_NONE);

12
glfw/egl_context.h vendored
View File

@@ -132,6 +132,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLS
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
typedef const char* (EGLAPIENTRY * PFN_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*);
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
#define eglGetConfigs _glfw.egl.GetConfigs
@@ -149,6 +150,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglSwapBuffers _glfw.egl.SwapBuffers
#define eglSwapInterval _glfw.egl.SwapInterval
#define eglQueryString _glfw.egl.QueryString
#define eglQuerySurface _glfw.egl.QuerySurface
#define eglGetProcAddress _glfw.egl.GetProcAddress
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
@@ -160,11 +162,11 @@ typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLD
//
typedef struct _GLFWcontextEGL
{
EGLConfig config;
EGLContext handle;
EGLSurface surface;
EGLConfig config;
EGLContext handle;
EGLSurface surface;
void* client;
void* client;
} _GLFWcontextEGL;
@@ -186,6 +188,7 @@ typedef struct _GLFWlibraryEGL
bool EXT_platform_base;
bool EXT_platform_x11;
bool EXT_platform_wayland;
bool EXT_present_opaque;
bool ANGLE_platform_angle;
bool ANGLE_platform_angle_opengl;
bool ANGLE_platform_angle_d3d;
@@ -210,6 +213,7 @@ typedef struct _GLFWlibraryEGL
PFN_eglSwapBuffers SwapBuffers;
PFN_eglSwapInterval SwapInterval;
PFN_eglQueryString QueryString;
PFN_eglQuerySurface QuerySurface;
PFN_eglGetProcAddress GetProcAddress;
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;

View File

@@ -111,7 +111,7 @@ def init_env(
elif module == 'cocoa':
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo UniformTypeIdentifiers'.split():
ans.ldpaths.extend(('-framework', f_))
elif module == 'wayland':
@@ -172,6 +172,8 @@ class Arg:
while self.name.startswith('*'):
self.name = self.name[1:]
self.type = self.type + '*'
if '[' in self.name:
self.type += '[' + self.name.partition('[')[-1]
def __repr__(self) -> str:
return f'Arg({self.type}, {self.name})'
@@ -245,11 +247,15 @@ def generate_wrappers(glfw_header: str) -> None:
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
void glfwSetX11WindowAsDock(int32_t x11_window_id)
void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12])
'''.splitlines():
if line:
functions.append(Function(line.strip(), check_fail=False))

77
glfw/glfw3.h vendored
View File

@@ -512,6 +512,7 @@ typedef enum {
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_NUM_LOCK 0x0080
#define GLFW_MOD_LAST GLFW_MOD_NUM_LOCK
#define GLFW_LOCK_MASK (GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK)
/*! @} */
@@ -1715,7 +1716,21 @@ typedef void (* GLFWjoystickfun)(int,int);
typedef void (* GLFWuserdatafun)(unsigned long long, void*);
typedef void (* GLFWtickcallback)(void*);
typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data);
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
typedef char* (* GLFWcurrentselectionfun)(void);
typedef void (* GLFWclipboarddatafreefun)(void* data);
typedef struct GLFWDataChunk {
const char *data;
size_t sz;
GLFWclipboarddatafreefun free;
void *iter, *free_data;
} GLFWDataChunk;
typedef enum {
GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION
} GLFWClipboardType;
typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype);
typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz);
/*! @brief Video mode type.
*
@@ -1873,6 +1888,7 @@ GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFW
GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled);
GLFWAPI void glfwRemoveTimer(unsigned long long);
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun callback);
/*! @brief Terminates the GLFW library.
*
@@ -2768,6 +2784,7 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags);
GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags);
GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* window);
/*! @brief Destroys the specified window and its context.
*
@@ -5216,64 +5233,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
*/
GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state);
/*! @brief Sets the clipboard to the specified string.
*
* This function sets the system clipboard to the specified, UTF-8 encoded
* string.
*
* @param[in] window Deprecated. Any valid window or `NULL`.
* @param[in] string A UTF-8 encoded string.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @remark @wayland Clipboard is currently unimplemented.
*
* @pointer_lifetime The specified string is copied before this function
* returns.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa @ref glfwGetClipboardString
*
* @since Added in version 3.0.
*
* @ingroup input
*/
GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char* string);
/*! @brief Returns the contents of the clipboard as a string.
*
* This function returns the contents of the system clipboard, if it contains
* or is convertible to a UTF-8 encoded string. If the clipboard is empty or
* if its contents cannot be converted, `NULL` is returned and a @ref
* GLFW_FORMAT_UNAVAILABLE error is generated.
*
* @param[in] window Deprecated. Any valid window or `NULL`.
* @return The contents of the clipboard as a UTF-8 encoded string, or `NULL`
* if an [error](@ref error_handling) occurred.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @remark @wayland Clipboard is currently unimplemented.
*
* @pointer_lifetime The returned string is allocated and freed by GLFW. You
* should not free it yourself. It is valid until the next call to @ref
* glfwGetClipboardString or @ref glfwSetClipboardString, or until the library
* is terminated.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref clipboard
* @sa @ref glfwSetClipboardString
*
* @since Added in version 3.0.
*
* @ingroup input
*/
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_iter);
GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object);
/*! @brief Returns the GLFW time.
*

9
glfw/init.c vendored
View File

@@ -68,6 +68,8 @@ static void terminate(void)
int i;
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
_glfw_free_clipboard_data(&_glfw.clipboard);
_glfw_free_clipboard_data(&_glfw.primary);
while (_glfw.windowListHead)
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);
@@ -386,3 +388,10 @@ GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun)
_GLFW_SWAP_POINTERS(_glfw.callbacks.draw_text, cbfun);
return cbfun;
}
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(_glfw.callbacks.get_current_selection, cbfun);
return cbfun;
}

51
glfw/input.c vendored
View File

@@ -1526,36 +1526,41 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
return true;
}
GLFWAPI void glfwSetClipboardString(GLFWwindow* handle UNUSED, const char* string)
{
assert(string != NULL);
void _glfw_free_clipboard_data(_GLFWClipboardData *cd) {
if (cd->mime_types) {
for (size_t i = 0; i < cd->num_mime_types; i++) free((void*)cd->mime_types[i]);
free((void*)cd->mime_types);
}
memset(cd, 0, sizeof(cd[0]));
}
GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
_GLFW_REQUIRE_INIT();
_glfwPlatformSetClipboardString(string);
_glfwPlatformGetClipboard(clipboard_type, mime_type, write_data, object);
}
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle UNUSED)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetClipboardString();
}
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle UNUSED, const char* string)
{
assert(string != NULL);
GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_data) {
assert(mime_types != NULL);
assert(get_data != NULL);
_GLFW_REQUIRE_INIT();
_glfwPlatformSetPrimarySelectionString(string);
_GLFWClipboardData *cd = NULL;
switch(clipboard_type) {
case GLFW_CLIPBOARD: cd = &_glfw.clipboard; break;
case GLFW_PRIMARY_SELECTION: cd = &_glfw.primary; break;
}
_glfw_free_clipboard_data(cd);
cd->get_data = get_data;
cd->mime_types = calloc(num_mime_types, sizeof(char*));
cd->num_mime_types = 0;
cd->ctype = clipboard_type;
for (size_t i = 0; i < num_mime_types; i++) {
if (mime_types[i]) {
cd->mime_types[cd->num_mime_types++] = _glfw_strdup(mime_types[i]);
}
}
_glfwPlatformSetClipboard(clipboard_type);
}
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle UNUSED)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetPrimarySelectionString();
}
#endif
GLFWAPI monotonic_t glfwGetTime(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(0);

30
glfw/internal.h vendored
View File

@@ -34,6 +34,9 @@
#endif
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#define MAX(x, y) __extension__ ({ \
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
a > b ? a : b;})
#if defined(GLFW_INCLUDE_GLCOREARB) || \
defined(GLFW_INCLUDE_ES1) || \
@@ -52,6 +55,8 @@
#define GLFW_INCLUDE_NONE
#include "glfw3.h"
#define EGL_PRESENT_OPAQUE_EXT 0x31df
#define _GLFW_INSERT_FIRST 0
#define _GLFW_INSERT_LAST 1
@@ -439,6 +444,11 @@ struct _GLFWwindow
bool rawMouseMotion;
_GLFWcontext context;
#ifdef _GLFW_WAYLAND
bool swaps_disallowed;
#else
const bool swaps_disallowed;
#endif
struct {
GLFWwindowposfun pos;
@@ -554,6 +564,13 @@ struct _GLFWmutex
_GLFW_PLATFORM_MUTEX_STATE;
};
typedef struct _GLFWClipboardData {
const char** mime_types;
size_t num_mime_types;
GLFWclipboarditerfun get_data;
GLFWClipboardType ctype;
} _GLFWClipboardData;
// Library global data
//
struct _GLFWlibrary
@@ -568,6 +585,8 @@ struct _GLFWlibrary
int refreshRate;
} hints;
_GLFWClipboardData primary, clipboard;
_GLFWerror* errorListHead;
_GLFWcursor* cursorListHead;
_GLFWwindow* windowListHead;
@@ -614,6 +633,7 @@ struct _GLFWlibrary
GLFWjoystickfun joystick;
GLFWapplicationclosefun application_close;
GLFWdrawtextfun draw_text;
GLFWcurrentselectionfun get_current_selection;
} callbacks;
@@ -666,12 +686,8 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
bool _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
void _glfwPlatformSetClipboard(GLFWClipboardType t);
void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object);
bool _glfwPlatformInitJoysticks(void);
void _glfwPlatformTerminateJoysticks(void);
@@ -847,3 +863,5 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval,
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
char* _glfw_strdup(const char* source);
void _glfw_free_clipboard_data(_GLFWClipboardData *cd);

View File

@@ -83,8 +83,8 @@ process_settings_dict(DBusMessageIter *array_iter, void(process_setting)(const c
HANDLER(process_desktop_settings)
cursor_theme_changed = false;
DBusMessageIter root, array, item, settings;
dbus_message_iter_init(msg, &root);
DBusMessageIter root, array, item, settings;
dbus_message_iter_init(msg, &root);
#define die(...) { _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); return; }
if (dbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) die("Reply to request for desktop settings is not an array");
dbus_message_iter_recurse(&root, &array);

View File

@@ -77,7 +77,8 @@
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
"unstable/primary-selection/primary-selection-unstable-v1.xml",
"unstable/text-input/text-input-unstable-v3.xml"
"unstable/text-input/text-input-unstable-v3.xml",
"staging/xdg-activation/xdg-activation-v1.xml"
],
"sources": [
"wl_init.c",

6
glfw/window.c vendored
View File

@@ -71,7 +71,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused)
_glfw.focusedWindowId = window->id;
}
_GLFWwindow* _glfwFocusedWindow() {
_GLFWwindow* _glfwFocusedWindow(void) {
if (_glfw.focusedWindowId) {
_GLFWwindow *w = _glfw.windowListHead;
while (w) {
@@ -1067,6 +1067,10 @@ GLFWAPI bool glfwIsFullscreen(GLFWwindow* wh, unsigned int flags) {
return _glfwPlatformIsFullscreen((_GLFWwindow*)wh, flags);
}
GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* wh) {
return !(((_GLFWwindow*)wh)->swaps_disallowed);
}
GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* handle, void* pointer)
{
_GLFWwindow* window = (_GLFWwindow*) handle;

View File

@@ -31,13 +31,13 @@ static void
build_blur_kernel(kernel_type *blur_kernel, const size_t size, kernel_type sigma) {
// 1D Normalized Gaussian
const kernel_type half = size / (kernel_type)2;
kernel_type sum = 0;
for (size_t i = 0; i < size; i++) {
kernel_type f = (i - half);
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
sum += blur_kernel[i];
}
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
kernel_type sum = 0;
for (size_t i = 0; i < size; i++) {
kernel_type f = (i - half);
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
sum += blur_kernel[i];
}
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
}
static void
@@ -102,13 +102,34 @@ init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, unsi
return 2 * pair->size_in_bytes;
}
static bool
window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) {
#define Q(which) if (decs.which.buffer.a == q) { decs.which.buffer.a_needs_to_be_destroyed = false; return true; } if (decs.which.buffer.b == q) { decs.which.buffer.b_needs_to_be_destroyed = false; return true; }
Q(left); Q(top); Q(right); Q(bottom);
#undef Q
return false;
}
static void
alloc_buffer_pair(_GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
buffer_release_event(void *data, struct wl_buffer *buffer) {
wl_buffer_destroy(buffer);
_GLFWwindow *window = _glfwWindowForId((uintptr_t)data);
if (window && window_has_buffer(window, buffer)) decs.buffer_destroyed = true;
}
static struct wl_buffer_listener handle_buffer_events = {.release = buffer_release_event};
static void
alloc_buffer_pair(uintptr_t window_id, _GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
pair->data.a = data + *offset;
pair->a = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
pair->a_needs_to_be_destroyed = true;
wl_buffer_add_listener(pair->a, &handle_buffer_events, (void*)window_id);
*offset += pair->size_in_bytes;
pair->data.b = data + *offset;
pair->b = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
pair->b_needs_to_be_destroyed = true;
wl_buffer_add_listener(pair->b, &handle_buffer_events, (void*)window_id);
*offset += pair->size_in_bytes;
pair->front = pair->a; pair->back = pair->b;
pair->data.front = pair->data.a; pair->data.back = pair->data.b;
@@ -287,7 +308,7 @@ create_shm_buffers(_GLFWwindow* window) {
struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, decs.mapping.size);
close(fd);
size_t offset = 0;
#define a(which) alloc_buffer_pair(&decs.which.buffer, pool, decs.mapping.data, &offset)
#define a(which) alloc_buffer_pair(window->id, &decs.which.buffer, pool, decs.mapping.data, &offset)
a(top); a(left); a(bottom); a(right);
#undef a
wl_shm_pool_destroy(pool);
@@ -313,8 +334,8 @@ free_csd_surfaces(_GLFWwindow *window) {
static void
free_csd_buffers(_GLFWwindow *window) {
#define d(which) { \
if (decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
if (decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
if (decs.which.buffer.a_needs_to_be_destroyed && decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
if (decs.which.buffer.b_needs_to_be_destroyed && decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \
}
d(left); d(top); d(right); d(bottom);
@@ -337,9 +358,10 @@ create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDEdge *s) {
}
#define damage_csd(which, xbuffer) \
wl_surface_attach(decs.which.surface, xbuffer, 0, 0); \
wl_surface_attach(decs.which.surface, (xbuffer), 0, 0); \
wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \
wl_surface_commit(decs.which.surface)
wl_surface_commit(decs.which.surface); \
if (decs.which.buffer.a == (xbuffer)) { decs.which.buffer.a_needs_to_be_destroyed = false; } else { decs.which.buffer.b_needs_to_be_destroyed = false; }
bool
ensure_csd_resources(_GLFWwindow *window) {
@@ -352,12 +374,14 @@ ensure_csd_resources(_GLFWwindow *window) {
decs.for_window_state.scale != window->wl.scale ||
!decs.mapping.data
);
const bool needs_update = focus_changed || size_changed || !decs.left.surface;
debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d\n", decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update, size_changed);
const bool needs_update = focus_changed || size_changed || !decs.left.surface || decs.buffer_destroyed;
debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d buffer_destroyed: %d\n",
decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update, size_changed, decs.buffer_destroyed);
if (!needs_update) return false;
if (size_changed) {
if (size_changed || decs.buffer_destroyed) {
free_csd_buffers(window);
if (!create_shm_buffers(window)) return false;
decs.buffer_destroyed = false;
}
int32_t x, y, scale = window->wl.scale < 1 ? 1 : window->wl.scale;
@@ -432,8 +456,5 @@ set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) {
decs.use_custom_titlebar_color = use_custom_color;
decs.titlebar_color = color;
}
if (window->decorated && decs.top.surface) {
update_title_bar(window);
damage_csd(top, decs.top.buffer.front);
}
change_csd_title(window);
}

40
glfw/wl_init.c vendored
View File

@@ -99,7 +99,7 @@ static void pointerHandleEnter(void* data UNUSED,
return;
}
window->wl.decorations.focus = focus;
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.pointerFocus = window;
window->wl.hovered = true;
@@ -144,8 +144,12 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
if (!image)
return;
if (image->width % scale || image->height % scale) {
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
static uint32_t warned_width = 0, warned_height = 0;
if (warned_width != image->width || warned_height != image->height) {
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
warned_width = image->width; warned_height = image->height;
}
}
buffer = wl_cursor_image_get_buffer(image);
@@ -307,7 +311,7 @@ static void pointerHandleButton(void* data UNUSED,
if (window->wl.decorations.focus != CENTRAL_WINDOW)
return;
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
* codes. */
@@ -459,7 +463,7 @@ static void keyboardHandleEnter(void* data UNUSED,
return;
}
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.keyboardFocusId = window->id;
_glfwInputWindowFocus(window, true);
uint32_t* key;
@@ -512,7 +516,7 @@ static void keyboardHandleKey(void* data UNUSED,
return;
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
@@ -535,7 +539,7 @@ static void keyboardHandleModifiers(void* data UNUSED,
uint32_t modsLocked,
uint32_t group)
{
_glfw.wl.serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
}
@@ -573,6 +577,7 @@ static void seatHandleCapabilities(void* data UNUSED,
{
wl_pointer_destroy(_glfw.wl.pointer);
_glfw.wl.pointer = NULL;
if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
}
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard)
@@ -584,6 +589,8 @@ static void seatHandleCapabilities(void* data UNUSED,
{
wl_keyboard_destroy(_glfw.wl.keyboard);
_glfw.wl.keyboard = NULL;
_glfw.wl.keyboardFocusId = 0;
if (_glfw.wl.keyRepeatInfo.keyRepeatTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
}
}
@@ -712,6 +719,9 @@ static void registryHandleGlobal(void* data UNUSED,
_glfwSetupWaylandPrimarySelectionDevice();
}
}
else if (strstr(interface, "xdg_activation_v1") != 0) {
_glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1);
}
}
@@ -875,6 +885,14 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void)
{
if (_glfw.wl.activation_requests.array) {
for (size_t i=0; i < _glfw.wl.activation_requests.sz; i++) {
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
if (r->callback) r->callback(NULL, NULL, r->callback_data);
xdg_activation_token_v1_destroy(r->token);
}
free(_glfw.wl.activation_requests.array);
}
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
@@ -919,6 +937,8 @@ void _glfwPlatformTerminate(void)
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
if (_glfw.wl.dataSourceForClipboard)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
if (_glfw.wl.dataSourceForPrimarySelection)
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
if (_glfw.wl.dataOffers[doi].id) {
destroy_data_offer(&_glfw.wl.dataOffers[doi]);
@@ -932,6 +952,8 @@ void _glfwPlatformTerminate(void)
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.xdg_activation_v1)
xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1);
if (_glfw.wl.registry)
wl_registry_destroy(_glfw.wl.registry);
if (_glfw.wl.display)
@@ -940,10 +962,6 @@ void _glfwPlatformTerminate(void)
wl_display_disconnect(_glfw.wl.display);
}
finalizePollData(&_glfw.wl.eventLoopData);
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)

38
glfw/wl_platform.h vendored
View File

@@ -58,7 +58,9 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#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"
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#include "wl_text_input.h"
#include "wayland-xdg-activation-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@@ -102,6 +104,7 @@ typedef struct _GLFWWaylandBufferPair {
struct { uint8_t *a, *b, *front, *back; } data;
bool has_pending_update;
size_t size_in_bytes, width, height, stride;
bool a_needs_to_be_destroyed, b_needs_to_be_destroyed;
} _GLFWWaylandBufferPair;
typedef struct _GLFWWaylandCSDEdge {
@@ -116,14 +119,22 @@ typedef enum WaylandWindowState {
TOPLEVEL_STATE_NONE = 0,
TOPLEVEL_STATE_MAXIMIZED = 1,
TOPLEVEL_STATE_FULLSCREEN = 2,
TOPLEVEL_STATE_RESIZING = 4,
TOPLEVEL_STATE_ACTIVATED = 8,
TOPLEVEL_STATE_TILED_LEFT = 16,
TOPLEVEL_STATE_TILED_RIGHT = 32,
TOPLEVEL_STATE_TILED_TOP = 64,
TOPLEVEL_STATE_TILED_BOTTOM = 128,
TOPLEVEL_STATE_RESIZING = 4,
TOPLEVEL_STATE_ACTIVATED = 8,
TOPLEVEL_STATE_TILED_LEFT = 16,
TOPLEVEL_STATE_TILED_RIGHT = 32,
TOPLEVEL_STATE_TILED_TOP = 64,
TOPLEVEL_STATE_TILED_BOTTOM = 128,
} WaylandWindowState;
typedef struct glfw_wl_xdg_activation_request {
GLFWid window_id;
GLFWactivationcallback callback;
void *callback_data;
uintptr_t request_id;
void *token;
} glfw_wl_xdg_activation_request;
static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM;
@@ -141,6 +152,7 @@ typedef struct _GLFWwindowWayland
bool hovered;
bool transparent;
struct wl_surface* surface;
bool waiting_for_swap_to_commit;
struct wl_egl_window* native;
struct wl_callback* callback;
@@ -172,7 +184,7 @@ typedef struct _GLFWwindowWayland
struct zwp_idle_inhibitor_v1* idleInhibitor;
struct {
bool serverSide;
bool serverSide, buffer_destroyed;
_GLFWdecorationSideWayland focus;
_GLFWWaylandCSDEdge top, left, right, bottom;
@@ -276,13 +288,14 @@ typedef struct _GLFWlibraryWayland
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
struct zwp_primary_selection_device_v1* primarySelectionDevice;
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
struct xdg_activation_v1* xdg_activation_v1;
int compositorVersion;
int seatVersion;
struct wl_surface* cursorSurface;
GLFWCursorShape cursorPreviousShape;
uint32_t serial;
uint32_t serial, input_serial;
int32_t keyboardRepeatRate;
monotonic_t keyboardRepeatDelay;
@@ -316,12 +329,14 @@ typedef struct _GLFWlibraryWayland
PFN_wl_egl_window_resize window_resize;
} egl;
struct {
glfw_wl_xdg_activation_request *array;
size_t capacity, sz;
} activation_requests;
EventLoopData eventLoopData;
char* pasteString;
char* clipboardString;
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@@ -355,6 +370,7 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
void _glfwSetupWaylandDataDevice(void);
void _glfwSetupWaylandPrimarySelectionDevice(void);
void animateCursorImage(id_type timer_id, void *data);

39
glfw/wl_text_input.c vendored
View File

@@ -9,12 +9,15 @@
#include "internal.h"
#include "wayland-text-input-unstable-v3-client-protocol.h"
#include <stdlib.h>
#include <string.h>
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
static struct zwp_text_input_v3* text_input;
static struct zwp_text_input_manager_v3* text_input_manager;
static char *pending_pre_edit = NULL;
static char *current_pre_edit = NULL;
static char *pending_commit = NULL;
static int last_cursor_left = 0, last_cursor_top = 0, last_cursor_width = 0, last_cursor_height = 0;
uint32_t commit_serial = 0;
static void commit(void) {
@@ -87,15 +90,23 @@ static void
text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, uint32_t serial) {
debug("text-input: done event: serial: %u current_commit_serial: %u\n", serial, commit_serial);
if (serial != commit_serial) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
if (serial > commit_serial) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
return;
}
if (pending_pre_edit) {
send_text(pending_pre_edit, GLFW_IME_PREEDIT_CHANGED);
if ((pending_pre_edit == NULL && current_pre_edit == NULL) ||
(pending_pre_edit && current_pre_edit && strcmp(pending_pre_edit, current_pre_edit) == 0)) {
free(pending_pre_edit); pending_pre_edit = NULL;
} else {
// Clear pre-edit text
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
free(current_pre_edit);
current_pre_edit = pending_pre_edit;
pending_pre_edit = NULL;
if (current_pre_edit) {
send_text(current_pre_edit, GLFW_IME_PREEDIT_CHANGED);
} else {
// Clear pre-edit text
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
}
}
if (pending_commit) {
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
@@ -133,6 +144,7 @@ _glfwWaylandDestroyTextInput(void) {
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
text_input = NULL; text_input_manager = NULL;
free(pending_pre_edit); pending_pre_edit = NULL;
free(current_pre_edit); current_pre_edit = NULL;
free(pending_commit); pending_commit = NULL;
}
@@ -146,10 +158,11 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
zwp_text_input_v3_enable(text_input);
zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
} else {
if (pending_pre_edit) {
free(pending_pre_edit); pending_pre_edit = NULL;
if (current_pre_edit) {
// Clear pre-edit text
send_text(NULL, GLFW_IME_PREEDIT_CHANGED);
free(pending_pre_edit); pending_pre_edit = NULL;
free(current_pre_edit); current_pre_edit = NULL;
}
if (pending_commit) {
free(pending_commit); pending_commit = NULL;
@@ -161,9 +174,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
case GLFW_IME_UPDATE_CURSOR_POSITION: {
const int scale = w->wl.scale;
const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale;
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
commit();
if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) {
last_cursor_left = left;
last_cursor_top = top;
last_cursor_width = width;
last_cursor_height = height;
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
commit();
}
}
break;
}

555
glfw/wl_window.c vendored
View File

@@ -44,6 +44,59 @@
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
static void
activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token, const char *token) {
for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) {
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
if (r->request_id == (uintptr_t)data) {
_GLFWwindow *window = _glfwWindowForId(r->window_id);
if (r->callback) r->callback((GLFWwindow*)window, token, r->callback_data);
remove_i_from_array(_glfw.wl.activation_requests.array, i, _glfw.wl.activation_requests.sz);
break;
}
}
xdg_activation_token_v1_destroy(xdg_token);
}
static const struct
xdg_activation_token_v1_listener activation_token_listener = {
.done = &activation_token_done,
};
static bool
get_activation_token(
_GLFWwindow *window, uint32_t serial, GLFWactivationcallback cb, void *cb_data
) {
#define fail(msg) { _glfwInputError(GLFW_PLATFORM_ERROR, msg); if (cb) cb((GLFWwindow*)window, NULL, cb_data); return false; }
if (_glfw.wl.xdg_activation_v1 == NULL) fail("Wayland: activation requests not supported by this Wayland compositor");
struct xdg_activation_token_v1 *token = xdg_activation_v1_get_activation_token(_glfw.wl.xdg_activation_v1);
if (token == NULL) fail("Wayland: failed to create activation request token");
if (_glfw.wl.activation_requests.capacity < _glfw.wl.activation_requests.sz + 1) {
_glfw.wl.activation_requests.capacity = MAX(64u, _glfw.wl.activation_requests.capacity * 2);
_glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity);
if (!_glfw.wl.activation_requests.array) {
_glfw.wl.activation_requests.capacity = 0;
fail("Wayland: Out of memory while allocation activation request");
}
}
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + _glfw.wl.activation_requests.sz++;
memset(r, 0, sizeof(*r));
static uintptr_t rq = 0;
r->window_id = window->id;
r->callback = cb; r->callback_data = cb_data;
r->request_id = ++rq; r->token = token;
if (serial != 0)
xdg_activation_token_v1_set_serial(token, serial, _glfw.wl.seat);
xdg_activation_token_v1_set_surface(token, window->wl.surface);
xdg_activation_token_v1_add_listener(token, &activation_token_listener, (void*)r->request_id);
xdg_activation_token_v1_commit(token);
return true;
#undef fail
}
static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data)
{
struct wl_shm_pool* pool;
@@ -198,8 +251,17 @@ static bool checkScaleChange(_GLFWwindow* window)
return false;
}
static void
commit_window_surface_if_safe(_GLFWwindow *window) {
// we only commit if the buffer attached to the surface is the correct size,
// which means that at least one frame is drawn after resizeFramebuffer()
if (!window->wl.waiting_for_swap_to_commit) {
wl_surface_commit(window->wl.surface);
}
}
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window)
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
{
struct wl_region* region;
@@ -209,10 +271,22 @@ static void setOpaqueRegion(_GLFWwindow* window)
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
wl_surface_set_opaque_region(window->wl.surface, region);
wl_surface_commit(window->wl.surface);
if (commit_surface) commit_window_surface_if_safe(window);
wl_region_destroy(region);
}
static void
swap_buffers(_GLFWwindow *window) {
// this will attach the buffer to the surface,
// the client is responsible for clearing the buffer to an appropriate blank
window->swaps_disallowed = false;
GLFWwindow *current = glfwGetCurrentContext();
bool context_is_current = ((_GLFWwindow*)current)->id == window->id;
if (!context_is_current) glfwMakeContextCurrent((GLFWwindow*)window);
window->context.swapBuffers(window);
if (!context_is_current) glfwMakeContextCurrent(current);
}
static void
resizeFramebuffer(_GLFWwindow* window) {
@@ -221,10 +295,21 @@ resizeFramebuffer(_GLFWwindow* window) {
int scaledHeight = window->wl.height * scale;
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent) setOpaqueRegion(window);
if (!window->wl.transparent) setOpaqueRegion(window, false);
window->wl.waiting_for_swap_to_commit = true;
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
}
void
_glfwWaylandAfterBufferSwap(_GLFWwindow* window) {
if (window->wl.waiting_for_swap_to_commit) {
debug("Waiting for swap to commit: swap has happened\n");
window->wl.waiting_for_swap_to_commit = false;
// this is not really needed, since I think eglSwapBuffers() calls wl_surface_commit()
// but lets be safe. See https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/egl/drivers/dri2/platform_wayland.c#L1510
wl_surface_commit(window->wl.surface);
}
}
static const char*
clipboard_mime(void) {
@@ -368,9 +453,22 @@ static bool createSurface(_GLFWwindow* window,
wl_surface_set_user_data(window->wl.surface, window);
window->wl.native = wl_egl_window_create(window->wl.surface,
wndconfig->width,
wndconfig->height);
// If we already have been notified of the primary monitor scale, assume
// the window will be created on it and so avoid a rescale roundtrip in the common
// case of the window being shown on the primary monitor or all monitors having the same scale.
// If you change this also change get_window_content_scale() in the kitty code.
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
float xscale = 1.0, yscale = 1.0;
int scale = 1;
if (monitor) {
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
// see wl_monitor.c xscale is always == yscale
if (xscale <= 0.0001 || xscale != xscale || xscale >= 24) xscale = 1.0;
if (xscale > 1) scale = (int)xscale;
}
debug("Creating window at size: %dx%d and scale %d\n", wndconfig->width, wndconfig->height, scale);
window->wl.native = wl_egl_window_create(window->wl.surface, wndconfig->width * scale, wndconfig->height * scale);
if (!window->wl.native)
return false;
@@ -378,11 +476,13 @@ static bool createSurface(_GLFWwindow* window,
window->wl.height = wndconfig->height;
window->wl.user_requested_content_size.width = wndconfig->width;
window->wl.user_requested_content_size.height = wndconfig->height;
window->wl.scale = 1;
window->wl.scale = scale;
if (!window->wl.transparent)
setOpaqueRegion(window);
setOpaqueRegion(window, false);
wl_surface_set_buffer_scale(window->wl.surface, scale);
return true;
}
@@ -488,6 +588,15 @@ static void xdgSurfaceHandleConfigure(void* data,
struct xdg_surface* surface,
uint32_t serial)
{
// The poorly documented pattern Wayland requires is:
// 1) ack the configure,
// 2) set the window geometry
// 3) attach a new buffer of the correct size to the surface
// 4) only then commit the surface.
// buffer is attached only by eglSwapBuffers,
// so we set a flag to not commit the surface till the next swapbuffers. Note that
// wl_egl_window_resize() does not actually resize the buffer until the next draw call
// or buffer state query.
_GLFWwindow* window = data;
xdg_surface_ack_configure(surface, serial);
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
@@ -496,11 +605,7 @@ static void xdgSurfaceHandleConfigure(void* data,
int height = window->wl.pending.height;
if (!window->wl.surface_configured_once) {
window->wl.surface_configured_once = true;
if (!width && !height && !new_states && !window->wl.decorations.serverSide && getenv("XAUTHORITY") && strstr(getenv("XAUTHORITY"), "mutter")) {
// https://github.com/kovidgoyal/kitty/issues/4802
debug("Ignoring first empty surface configure event on mutter.\n");
return;
}
swap_buffers(window);
}
if (new_states != window->wl.current.toplevel_states ||
@@ -537,10 +642,7 @@ static void xdgSurfaceHandleConfigure(void* data,
}
inform_compositor_of_window_geometry(window, "configure");
// if a resize happened there will be a commit at the next render frame so
// dont commit here, GNOME doesnt like it and its not really needed anyway
if (!resized) wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
window->wl.pending_state = 0;
}
@@ -784,6 +886,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
initialize_csd_metrics(window);
window->wl.transparent = fbconfig->transparent;
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
window->swaps_disallowed = true;
if (!createSurface(window, wndconfig))
return false;
@@ -939,7 +1042,7 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
window->wl.width = w; window->wl.height = h;
resizeFramebuffer(window);
ensure_csd_resources(window);
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
inform_compositor_of_window_geometry(window, "SetWindowSize");
}
}
@@ -956,7 +1059,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
maxwidth = maxheight = 0;
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
}
}
@@ -993,7 +1096,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
{
if (top)
*top = window->wl.decorations.metrics.top;
*top = window->wl.decorations.metrics.top - window->wl.decorations.metrics.visible_titlebar_height;
if (left)
*left = window->wl.decorations.metrics.width;
if (right)
@@ -1062,38 +1165,51 @@ void _glfwPlatformHideWindow(_GLFWwindow* window)
xdg_surface_destroy(window->wl.xdg.surface);
window->wl.xdg.toplevel = NULL;
window->wl.xdg.surface = NULL;
window->wl.surface_configured_once = false;
window->swaps_disallowed = true;
}
window->wl.visible = false;
}
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
{
// TODO
static bool notified = false;
if (!notified) {
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
"Wayland: Window attention request not implemented yet");
notified = true;
static void
request_attention(GLFWwindow *window, const char *token, void *data UNUSED) {
if (window && token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
}
static bool
has_activation_in_flight(_GLFWwindow* window, GLFWactivationcallback callback) {
for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) {
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
if (r->window_id == window->id && r->callback == callback) return true;
}
return false;
}
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) {
if (!has_activation_in_flight(window, request_attention)) get_activation_token(window, 0, request_attention, NULL);
}
int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
{
// TODO: Use an actual Wayland API to implement this when one becomes available
static char tty[L_ctermid + 1];
int fd = open(ctermid(tty), O_WRONLY | O_CLOEXEC);
if (fd > -1) {
int ret = write(fd, "\x07", 1) == 1 ? true : false;
close(fd);
return ret;
}
return false;
}
static void
focus_window(GLFWwindow *window, const char *token, void *data UNUSED) {
if (!window) return;
if (token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Window focus request via xdg-activation protocol was denied by the compositor. Use a better compositor.");
}
}
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
{
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
"Wayland: The platform does not support setting the input focus");
// Attempt to focus the window by using the activation protocol, whether it works
// is entirely compositor dependent and as we all know Wayland and its ecosystem is
// the product of morons.
if (_glfw.wl.input_serial && !has_activation_in_flight(window, focus_window)) get_activation_token(window, _glfw.wl.input_serial, focus_window, NULL);
}
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
@@ -1178,7 +1294,7 @@ void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
}
else
wl_surface_set_input_region(window->wl.surface, 0);
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
@@ -1242,7 +1358,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
zwp_locked_pointer_v1_set_cursor_position_hint(
window->wl.pointerLock.lockedPointer,
wl_fixed_from_double(x), wl_fixed_from_double(y));
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
}
}
@@ -1440,53 +1556,68 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
}
}
static void send_text(char *text, int fd)
{
if (text) {
size_t len = strlen(text), pos = 0;
monotonic_t start = glfwGetTime();
while (pos < len && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
ssize_t ret = write(fd, text + pos, len - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
break;
}
if (ret > 0) {
start = glfwGetTime();
pos += ret;
}
static bool
write_all(int fd, const char *data, size_t sz) {
monotonic_t start = glfwGetTime();
size_t pos = 0;
while (pos < sz && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
ssize_t ret = write(fd, data + pos, sz - pos);
if (ret < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
return false;
}
if (ret > 0) {
start = glfwGetTime();
pos += ret;
}
}
return pos >= sz;
}
static void
send_clipboard_data(const _GLFWClipboardData *cd, const char *mime, int fd) {
if (strcmp(mime, "text/plain;charset=utf-8") == 0 || strcmp(mime, "UTF8_STRING") == 0 || strcmp(mime, "TEXT") == 0 || strcmp(mime, "STRING") == 0) mime = "text/plain";
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
void *iter = chunk.iter;
if (!iter) return;
bool keep_going = true;
while (keep_going) {
chunk = cd->get_data(mime, iter, cd->ctype);
if (!chunk.sz) break;
if (!write_all(fd, chunk.data, chunk.sz)) keep_going = false;
if (chunk.free) chunk.free((void*)chunk.free_data);
}
cd->get_data(NULL, iter, cd->ctype);
}
static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type, int fd) {
send_clipboard_data(&_glfw.clipboard, mime_type, fd);
close(fd);
}
static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type UNUSED, int fd) {
send_text(_glfw.wl.clipboardString, fd);
}
static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source UNUSED,
const char *mime_type UNUSED, int fd) {
send_text(_glfw.wl.primarySelectionString, fd);
const char *mime_type, int fd) {
send_clipboard_data(&_glfw.primary, mime_type, fd);
close(fd);
}
static char* read_offer_string(int data_pipe, size_t *data_sz) {
static void
read_offer(int data_pipe, GLFWclipboardwritedatafun write_data, void *object) {
wl_display_flush(_glfw.wl.display);
size_t capacity = 0;
char *buf = NULL;
*data_sz = 0;
struct pollfd fds;
fds.fd = data_pipe;
fds.events = POLLIN;
monotonic_t start = glfwGetTime();
#define bail(...) { \
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
free(buf); buf = NULL; \
close(data_pipe); \
return NULL; \
return; \
}
char buf[8192];
while (glfwGetTime() - start < s_to_monotonic_t(2ll)) {
int ret = poll(&fds, 1, 2000);
if (ret == -1) {
@@ -1496,34 +1627,65 @@ static char* read_offer_string(int data_pipe, size_t *data_sz) {
if (!ret) {
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
}
if (capacity <= *data_sz || capacity - *data_sz <= 64) {
capacity += 4096;
buf = realloc(buf, capacity);
if (!buf) {
bail("Wayland: Failed to allocate memory to read clipboard data");
}
}
ret = read(data_pipe, buf + *data_sz, capacity - *data_sz - 1);
ret = read(data_pipe, buf, sizeof(buf));
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
}
if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; }
*data_sz += ret;
if (ret == 0) { close(data_pipe); return; }
if (!write_data(object, buf, ret)) bail("Wayland: call to write_data() failed with data from data offer");
start = glfwGetTime();
}
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
#undef bail
}
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
typedef struct chunked_writer {
char *buf; size_t sz, cap;
} chunked_writer;
static bool
write_chunk(void *object, const char *data, size_t sz) {
chunked_writer *cw = object;
if (cw->cap < cw->sz + sz) {
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
cw->buf = realloc(cw->buf, cw->cap);
}
memcpy(cw->buf + cw->sz, data, sz);
cw->sz += sz;
return true;
}
static char*
read_offer_string(int data_pipe, size_t *sz) {
chunked_writer cw = {0};
read_offer(data_pipe, write_chunk, &cw);
if (cw.buf) {
*sz = cw.sz;
return cw.buf;
}
*sz = 0;
return NULL;
}
static void
read_clipboard_data_offer(struct wl_data_offer *data_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
if (pipe2(pipefd, O_CLOEXEC) != 0) return;
wl_data_offer_receive(data_offer, mime, pipefd[1]);
close(pipefd[1]);
read_offer(pipefd[0], write_data, object);
}
static void
read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) {
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) != 0) return;
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
close(pipefd[1]);
size_t sz = 0;
return read_offer_string(pipefd[0], &sz);
read_offer(pipefd[0], write_data, object);
}
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) {
@@ -1535,24 +1697,33 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime,
}
static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) {
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
if (_glfw.wl.dataSourceForClipboard == wl_data_source) {
_glfw.wl.dataSourceForClipboard = NULL;
_glfw_free_clipboard_data(&_glfw.clipboard);
}
wl_data_source_destroy(wl_data_source);
}
static void primary_selection_source_canceled(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source) {
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source) {
_glfw.wl.dataSourceForPrimarySelection = NULL;
_glfw_free_clipboard_data(&_glfw.primary);
}
zwp_primary_selection_source_v1_destroy(primary_selection_source);
}
static void data_source_target(void *data UNUSED, struct wl_data_source *wl_data_source UNUSED, const char* mime UNUSED) {
// KWin aborts if we don't define these even though they are not used for copy/paste
static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) {
}
static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) {
}
static const struct wl_data_source_listener data_source_listener = {
.send = _glfwSendClipboardText,
.cancelled = data_source_canceled,
.target = data_source_target,
.target = dummy_data_source_target,
.action = dummy_data_source_action,
};
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
@@ -1829,30 +2000,69 @@ static bool _glfwEnsureDataDevice(void) {
return true;
}
void _glfwPlatformSetClipboardString(const char* string)
{
if (!_glfwEnsureDataDevice()) return;
free(_glfw.wl.clipboardString);
_glfw.wl.clipboardString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForClipboard)
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
if (!_glfw.wl.dataSourceForClipboard)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create data source");
return;
typedef void(*add_offer_func)(void*, const char *mime);
void
_glfwPlatformSetClipboard(GLFWClipboardType t) {
_GLFWClipboardData *cd = NULL;
void *data_source;
add_offer_func f;
if (t == GLFW_CLIPBOARD) {
if (!_glfwEnsureDataDevice()) return;
cd = &_glfw.clipboard;
f = (add_offer_func)wl_data_source_offer;
if (_glfw.wl.dataSourceForClipboard) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
if (!_glfw.wl.dataSourceForClipboard)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create data source");
return;
}
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
data_source = _glfw.wl.dataSourceForClipboard;
} else {
if (!_glfw.wl.primarySelectionDevice) {
static bool warned_about_primary_selection_device = false;
if (!warned_about_primary_selection_device) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy no primary selection device available");
warned_about_primary_selection_device = true;
}
return;
}
cd = &_glfw.primary;
f = (add_offer_func)zwp_primary_selection_source_v1_offer;
if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
if (!_glfw.wl.dataSourceForPrimarySelection)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create primary selection source");
return;
}
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
data_source = _glfw.wl.dataSourceForPrimarySelection;
}
f(data_source, clipboard_mime());
for (size_t i = 0; i < cd->num_mime_types; i++) {
if (strcmp(cd->mime_types[i], "text/plain") == 0) {
f(data_source, "TEXT");
f(data_source, "STRING");
f(data_source, "UTF8_STRING");
f(data_source, "text/plain;charset=utf-8");
}
f(data_source, cd->mime_types[i]);
}
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
if (t == GLFW_CLIPBOARD) {
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
} else {
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
}
}
static bool
@@ -1870,81 +2080,47 @@ plain_text_mime_for_offer(const _GLFWWaylandDataOffer *d) {
A("text/plain");
A("UTF8_STRING");
A("STRING");
A("TEXT");
#undef A
return NULL;
}
const char* _glfwPlatformGetClipboardString(void)
{
void
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
_GLFWWaylandOfferType offer_type = clipboard_type == GLFW_PRIMARY_SELECTION ? PRIMARY_SELECTION : CLIPBOARD;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (d->id && d->offer_type == CLIPBOARD) {
if (d->is_self_offer) return _glfw.wl.clipboardString;
const char *mime = plain_text_mime_for_offer(d);
if (mime) {
free(_glfw.wl.pasteString);
size_t sz = 0;
_glfw.wl.pasteString = read_data_offer(d->id, mime, &sz);
return _glfw.wl.pasteString;
if (d->id && d->offer_type == offer_type) {
if (d->is_self_offer) {
write_data(object, NULL, 1);
return;
}
}
}
return NULL;
}
void _glfwPlatformSetPrimarySelectionString(const char* string)
{
if (!_glfw.wl.primarySelectionDevice) {
static bool warned_about_primary_selection_device = false;
if (!warned_about_primary_selection_device) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy no primary selection device available");
warned_about_primary_selection_device = true;
}
return;
}
if (_glfw.wl.primarySelectionString == string) return;
free(_glfw.wl.primarySelectionString);
_glfw.wl.primarySelectionString = _glfw_strdup(string);
if (_glfw.wl.dataSourceForPrimarySelection)
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
if (!_glfw.wl.dataSourceForPrimarySelection)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Cannot copy failed to create primary selection source");
return;
}
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
if (d->id && d->is_primary && d->offer_type == PRIMARY_SELECTION) {
if (d->is_self_offer) return _glfw.wl.primarySelectionString;
const char *mime = plain_text_mime_for_offer(d);
if (mime) {
free(_glfw.wl.pasteString);
_glfw.wl.pasteString = read_primary_selection_offer(d->id, mime);
return _glfw.wl.pasteString;
if (mime_type == NULL) {
bool ok = true;
for (size_t o = 0; o < d->mimes_count; o++) {
const char *q = d->mimes[o];
if (strchr(d->mimes[0], '/')) {
if (strcmp(q, clipboard_mime()) == 0) continue;
if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain";
} else {
if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain";
}
if (ok) ok = write_data(object, q, strlen(q));
}
return;
}
if (strcmp(mime_type, "text/plain") == 0) {
mime_type = plain_text_mime_for_offer(d);
if (!mime_type) return;
}
if (d->is_primary) {
read_primary_selection_offer(d->id, mime_type, write_data, object);
} else {
read_clipboard_data_offer(d->id, mime_type, write_data, object);
}
break;
}
}
return NULL;
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
@@ -2066,6 +2242,18 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
return window->wl.surface;
}
GLFWAPI void glfwWaylandActivateWindow(GLFWwindow* handle, const char *activation_token) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT();
if (activation_token && activation_token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, activation_token, window->wl.surface);
}
GLFWAPI void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT();
get_activation_token(window, _glfw.wl.input_serial, cb, cb_data);
}
GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
}
@@ -2074,12 +2262,19 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long
_GLFWwindow* window = (_GLFWwindow*) handle;
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
window->wl.frameCallbackData.id = id;
window->wl.frameCallbackData.callback = callback;
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
if (window->wl.frameCallbackData.current_wl_callback) {
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
wl_surface_commit(window->wl.surface);
if (window->wl.waiting_for_swap_to_commit) {
callback(id);
window->wl.frameCallbackData.id = 0;
window->wl.frameCallbackData.callback = NULL;
window->wl.frameCallbackData.current_wl_callback = NULL;
} else {
window->wl.frameCallbackData.id = id;
window->wl.frameCallbackData.callback = callback;
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
if (window->wl.frameCallbackData.current_wl_callback) {
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
commit_window_surface_if_safe(window);
}
}
}

14
glfw/x11_init.c vendored
View File

@@ -137,6 +137,8 @@ static void detectEWMH(void)
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
_glfw.x11.NET_WM_WINDOW_TYPE_DOCK =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_DOCK");
_glfw.x11.NET_WORKAREA =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
_glfw.x11.NET_CURRENT_DESKTOP =
@@ -147,6 +149,8 @@ static void detectEWMH(void)
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
_glfw.x11.NET_WM_STRUT_PARTIAL =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STRUT_PARTIAL");
XFree(supportedAtoms);
}
@@ -680,8 +684,14 @@ void _glfwPlatformTerminate(void)
glfw_xkb_release(&_glfw.x11.xkb);
glfw_dbus_terminate(&_glfw.x11.dbus);
free(_glfw.x11.primarySelectionString);
free(_glfw.x11.clipboardString);
if (_glfw.x11.mime_atoms.array) {
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
free((void*)_glfw.x11.mime_atoms.array[i].mime);
}
free(_glfw.x11.mime_atoms.array);
}
if (_glfw.x11.clipboard_atoms.array) { free(_glfw.x11.clipboard_atoms.array); }
if (_glfw.x11.primary_atoms.array) { free(_glfw.x11.primary_atoms.array); }
if (_glfw.x11.display)
{

18
glfw/x11_platform.h vendored
View File

@@ -207,6 +207,16 @@ typedef struct _GLFWwindowX11
} _GLFWwindowX11;
typedef struct MimeAtom {
Atom atom;
const char* mime;
} MimeAtom;
typedef struct AtomArray {
MimeAtom *array;
size_t sz, capacity;
} AtomArray;
// X11-specific global data
//
typedef struct _GLFWlibraryX11
@@ -225,10 +235,6 @@ typedef struct _GLFWlibraryX11
XContext context;
// Most recent error code received by X error handler
int errorCode;
// Primary selection string (while the primary selection is owned)
char* primarySelectionString;
// Clipboard string (while the selection is owned)
char* clipboardString;
// Where to place the cursor when re-enabled
double restoreCursorPosX, restoreCursorPosY;
// The window whose disabled cursor mode is active
@@ -247,6 +253,7 @@ typedef struct _GLFWlibraryX11
Atom NET_WM_PING;
Atom NET_WM_WINDOW_TYPE;
Atom NET_WM_WINDOW_TYPE_NORMAL;
Atom NET_WM_WINDOW_TYPE_DOCK;
Atom NET_WM_STATE;
Atom NET_WM_STATE_ABOVE;
Atom NET_WM_STATE_FULLSCREEN;
@@ -262,6 +269,7 @@ typedef struct _GLFWlibraryX11
Atom NET_ACTIVE_WINDOW;
Atom NET_FRAME_EXTENTS;
Atom NET_REQUEST_FRAME_EXTENTS;
Atom NET_WM_STRUT_PARTIAL;
Atom MOTIF_WM_HINTS;
// Xdnd (drag and drop) atoms
@@ -291,6 +299,8 @@ typedef struct _GLFWlibraryX11
// XRM database atom
Atom RESOURCE_MANAGER;
// Atoms for MIME types
AtomArray mime_atoms, clipboard_atoms, primary_atoms;
struct {
bool available;

366
glfw/x11_window.c vendored
View File

@@ -716,19 +716,60 @@ static bool createNativeWindow(_GLFWwindow* window,
return true;
}
static size_t
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime, char **data) {
*data = NULL;
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
char *buf = NULL;
size_t sz = 0, cap = 0;
void *iter = chunk.iter;
if (!iter) return 0;
while (true) {
chunk = cd->get_data(mime, iter, cd->ctype);
if (!chunk.sz) break;
if (cap < sz + chunk.sz) {
cap = MAX(cap * 2, sz + 4 * chunk.sz);
buf = realloc(buf, cap);
}
memcpy(buf + sz, chunk.data, chunk.sz);
sz += chunk.sz;
if (chunk.free) chunk.free((void*)chunk.free_data);
}
*data = buf;
cd->get_data(NULL, iter, cd->ctype);
return sz;
}
static void
get_atom_names(const Atom *atoms, int count, char **atom_names) {
_glfwGrabErrorHandlerX11();
XGetAtomNames(_glfw.x11.display, (Atom*)atoms, count, atom_names);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) {
for (int i = 0; i < count; i++) {
_glfwGrabErrorHandlerX11();
atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) atom_names[i] = NULL;
}
}
}
// Set the specified property to the selection converted to the requested target
//
static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
{
int i;
char* selectionString = NULL;
const Atom formats[] = { _glfw.x11.UTF8_STRING, XA_STRING };
const int formatCount = sizeof(formats) / sizeof(formats[0]);
const AtomArray *aa;
const _GLFWClipboardData *cd;
if (request->selection == _glfw.x11.PRIMARY)
selectionString = _glfw.x11.primarySelectionString;
else
selectionString = _glfw.x11.clipboardString;
if (request->selection == _glfw.x11.PRIMARY) {
aa = &_glfw.x11.primary_atoms;
cd = &_glfw.primary;
} else {
aa = &_glfw.x11.clipboard_atoms;
cd = &_glfw.clipboard;
}
if (request->property == None)
{
@@ -741,11 +782,10 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
{
// The list of supported targets was requested
const Atom targets[] = { _glfw.x11.TARGETS,
_glfw.x11.MULTIPLE,
_glfw.x11.UTF8_STRING,
XA_STRING };
Atom *targets = calloc(aa->sz + 2, sizeof(Atom));
targets[0] = _glfw.x11.TARGETS;
targets[1] = _glfw.x11.MULTIPLE;
for (size_t i = 0; i < aa->sz; i++) targets[i+2] = aa->array[i].atom;
XChangeProperty(_glfw.x11.display,
request->requestor,
request->property,
@@ -753,8 +793,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
32,
PropModeReplace,
(unsigned char*) targets,
sizeof(targets) / sizeof(targets[0]));
aa->sz + 2);
free(targets);
return request->property;
}
@@ -763,7 +803,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
// Multiple conversions were requested
Atom* targets;
unsigned long i, count;
size_t i, j, count;
count = _glfwGetWindowPropertyX11(request->requestor,
request->property,
@@ -772,24 +812,25 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
for (i = 0; i < count; i += 2)
{
int j;
for (j = 0; j < formatCount; j++)
for (j = 0; j < aa->sz; j++)
{
if (targets[i] == formats[j])
if (targets[i] == aa->array[j].atom)
break;
}
if (j < formatCount)
if (j < aa->sz)
{
if (selectionString) XChangeProperty(_glfw.x11.display,
char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[j].mime, &data);
if (data) XChangeProperty(_glfw.x11.display,
request->requestor,
targets[i + 1],
targets[i],
8,
PropModeReplace,
(unsigned char *) selectionString,
strlen(selectionString));
(unsigned char *) data,
sz);
free(data);
}
else
targets[i + 1] = None;
@@ -828,20 +869,22 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
// Conversion to a data target was requested
for (i = 0; i < formatCount; i++)
for (size_t i = 0; i < aa->sz; i++)
{
if (request->target == formats[i])
if (request->target == aa->array[i].atom)
{
// The requested target is one we support
if (selectionString) XChangeProperty(_glfw.x11.display,
char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[i].mime, &data);
if (data) XChangeProperty(_glfw.x11.display,
request->requestor,
request->property,
request->target,
8,
PropModeReplace,
(unsigned char *) selectionString,
strlen(selectionString));
(unsigned char *) data,
sz);
free(data);
return request->property;
}
@@ -856,13 +899,11 @@ static void handleSelectionClear(XEvent* event)
{
if (event->xselectionclear.selection == _glfw.x11.PRIMARY)
{
free(_glfw.x11.primarySelectionString);
_glfw.x11.primarySelectionString = NULL;
_glfw_free_clipboard_data(&_glfw.primary);
}
else
{
free(_glfw.x11.clipboardString);
_glfw.x11.clipboardString = NULL;
_glfw_free_clipboard_data(&_glfw.clipboard);
}
}
@@ -881,34 +922,21 @@ static void handleSelectionRequest(XEvent* event)
XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply);
}
static const char* getSelectionString(Atom selection)
static void
getSelectionString(Atom selection, Atom *targets, size_t num_targets, GLFWclipboardwritedatafun write_data, void *object, bool report_not_found)
{
char** selectionString = NULL;
const Atom targets[] = { _glfw.x11.UTF8_STRING, XA_STRING };
const size_t targetCount = sizeof(targets) / sizeof(targets[0]);
if (selection == _glfw.x11.PRIMARY)
selectionString = &_glfw.x11.primarySelectionString;
else
selectionString = &_glfw.x11.clipboardString;
if (XGetSelectionOwner(_glfw.x11.display, selection) ==
_glfw.x11.helperWindowHandle)
{
// Instead of doing a large number of X round-trips just to put this
// string into a window property and then read it back, just return it
return *selectionString;
#define XFREE(x) { if (x) XFree(x); x = NULL; }
if (XGetSelectionOwner(_glfw.x11.display, selection) == _glfw.x11.helperWindowHandle) {
write_data(object, NULL, 1);
return;
}
free(*selectionString);
*selectionString = NULL;
for (size_t i = 0; i < targetCount; i++)
bool found = false;
for (size_t i = 0; !found && i < num_targets; i++)
{
char* data;
Atom actualType;
int actualFormat;
unsigned long itemCount, bytesAfter;
char* data = NULL;
Atom actualType = None;
int actualFormat = 0;
unsigned long itemCount = 0, bytesAfter = 0;
monotonic_t start = glfwGetTime();
XEvent notification, dummy;
@@ -925,8 +953,7 @@ static const char* getSelectionString(Atom selection)
&notification))
{
monotonic_t time = glfwGetTime();
if (time - start > s_to_monotonic_t(2ll))
return "";
if (time - start > s_to_monotonic_t(2ll)) return;
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
}
@@ -953,9 +980,6 @@ static const char* getSelectionString(Atom selection)
if (actualType == _glfw.x11.INCR)
{
size_t size = 1;
char* string = NULL;
for (;;)
{
start = glfwGetTime();
@@ -966,13 +990,12 @@ static const char* getSelectionString(Atom selection)
{
monotonic_t time = glfwGetTime();
if (time - start > s_to_monotonic_t(2ll)) {
free(string);
return "";
return;
}
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
}
XFree(data);
XFREE(data);
XGetWindowProperty(_glfw.x11.display,
notification.xselection.requestor,
notification.xselection.property,
@@ -988,47 +1011,41 @@ static const char* getSelectionString(Atom selection)
if (itemCount)
{
size += itemCount;
string = realloc(string, size);
string[size - itemCount - 1] = '\0';
strcat(string, data);
}
if (!itemCount)
{
if (targets[i] == XA_STRING)
{
*selectionString = convertLatin1toUTF8(string);
free(string);
const char *string = data;
if (targets[i] == XA_STRING) {
string = convertLatin1toUTF8(data);
itemCount = strlen(string);
}
else
*selectionString = string;
bool ok = write_data(object, string, itemCount);
if (string != data) free((void*)string);
if (!ok) { XFREE(data); break; }
} else { found = true; break; }
break;
}
}
}
else if (actualType == targets[i])
{
if (targets[i] == XA_STRING)
*selectionString = convertLatin1toUTF8(data);
else
*selectionString = _glfw_strdup(data);
if (targets[i] == XA_STRING) {
const char *string = convertLatin1toUTF8(data);
write_data(object, string, strlen(string)); free((void*)string);
} else write_data(object, data, itemCount);
found = true;
}
else if (actualType == XA_ATOM && targets[i] == _glfw.x11.TARGETS) {
found = true;
write_data(object, data, itemCount);
}
XFree(data);
XFREE(data);
if (*selectionString)
break;
}
if (!*selectionString)
if (!found && report_not_found)
{
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
"X11: Failed to convert selection to string");
"X11: Failed to convert selection to data from clipboard");
}
return *selectionString;
#undef XFREE
}
// Make the specified window and its video mode active on its monitor
@@ -1098,21 +1115,6 @@ static void onConfigChange(void)
}
}
static void
get_atom_names(Atom *atoms, int count, char **atom_names) {
_glfwGrabErrorHandlerX11();
XGetAtomNames(_glfw.x11.display, atoms, count, atom_names);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) {
for (int i = 0; i < count; i++) {
_glfwGrabErrorHandlerX11();
atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode != Success) atom_names[i] = NULL;
}
}
}
// Process the specified X event
//
static void processEvent(XEvent *event)
@@ -2849,51 +2851,112 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
}
}
void _glfwPlatformSetClipboardString(const char* string)
{
char* copy = _glfw_strdup(string);
free(_glfw.x11.clipboardString);
_glfw.x11.clipboardString = copy;
static MimeAtom atom_for_mime(const char *mime) {
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
MimeAtom ma = _glfw.x11.mime_atoms.array[i];
if (strcmp(ma.mime, mime) == 0) {
return ma;
}
}
MimeAtom ma = {.mime=_glfw_strdup(mime), .atom=XInternAtom(_glfw.x11.display, mime, 0)};
if (_glfw.x11.mime_atoms.capacity < _glfw.x11.mime_atoms.sz + 1) {
_glfw.x11.mime_atoms.capacity += 32;
_glfw.x11.mime_atoms.array = realloc(_glfw.x11.mime_atoms.array, _glfw.x11.mime_atoms.capacity);
}
_glfw.x11.mime_atoms.array[_glfw.x11.mime_atoms.sz++] = ma;
return ma;
}
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.CLIPBOARD,
_glfw.x11.helperWindowHandle,
CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) !=
_glfw.x11.helperWindowHandle)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to become owner of clipboard selection");
void _glfwPlatformSetClipboard(GLFWClipboardType t) {
Atom which = None;
_GLFWClipboardData *cd = NULL;
AtomArray *aa = NULL;
switch (t) {
case GLFW_CLIPBOARD: which = _glfw.x11.CLIPBOARD; cd = &_glfw.clipboard; aa = &_glfw.x11.clipboard_atoms; break;
case GLFW_PRIMARY_SELECTION: which = _glfw.x11.PRIMARY; cd = &_glfw.primary; aa = &_glfw.x11.primary_atoms; break;
}
XSetSelectionOwner(_glfw.x11.display, which, _glfw.x11.helperWindowHandle, CurrentTime);
if (XGetSelectionOwner(_glfw.x11.display, which) != _glfw.x11.helperWindowHandle) {
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of clipboard selection");
}
if (aa->capacity < cd->num_mime_types + 32) {
aa->capacity = cd->num_mime_types + 32;
aa->array = reallocarray(aa->array, aa->capacity, sizeof(aa->array[0]));
}
aa->sz = 0;
for (size_t i = 0; i < cd->num_mime_types; i++) {
MimeAtom *a = aa->array + aa->sz++;
*a = atom_for_mime(cd->mime_types[i]);
if (strcmp(cd->mime_types[i], "text/plain") == 0) {
a = aa->array + aa->sz++;
a->atom = _glfw.x11.UTF8_STRING;
a->mime = "text/plain";
}
}
}
const char* _glfwPlatformGetClipboardString(void)
{
return getSelectionString(_glfw.x11.CLIPBOARD);
typedef struct chunked_writer {
char *buf; size_t sz, cap;
} chunked_writer;
static bool
write_chunk(void *object, const char *data, size_t sz) {
chunked_writer *cw = object;
if (cw->cap < cw->sz + sz) {
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
cw->buf = realloc(cw->buf, cw->cap);
}
memcpy(cw->buf + cw->sz, data, sz);
cw->sz += sz;
return true;
}
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");
static void
get_available_mime_types(Atom which_clipboard, GLFWclipboardwritedatafun write_data, void *object) {
chunked_writer cw = {0};
getSelectionString(which_clipboard, &_glfw.x11.TARGETS, 1, write_chunk, &cw, false);
size_t count = 0;
bool ok = true;
if (cw.buf) {
Atom *atoms = (Atom*)cw.buf;
count = cw.sz / sizeof(Atom);
char **names = calloc(count, sizeof(char*));
get_atom_names(atoms, count, names);
for (size_t i = 0; i < count; i++) {
if (strchr(names[i], '/')) {
if (ok) ok = write_data(object, names[i], strlen(names[i]));
} else {
if (atoms[i] == _glfw.x11.UTF8_STRING || atoms[i] == XA_STRING) {
if (ok) ok = write_data(object, "text/plain", strlen("text/plain"));
}
}
XFree(names[i]);
}
free(cw.buf);
free(names);
}
}
const char* _glfwPlatformGetPrimarySelectionString(void)
{
return getSelectionString(_glfw.x11.PRIMARY);
void
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
Atom atoms[4], which = clipboard_type == GLFW_PRIMARY_SELECTION ? _glfw.x11.PRIMARY : _glfw.x11.CLIPBOARD;
if (mime_type == NULL) {
get_available_mime_types(which, write_data, object);
return;
}
size_t count = 0;
if (strcmp(mime_type, "text/plain") == 0) {
// we need to do this because GTK/GNOME is developed by morons
// they convert text/plain to DOS line endings
// https://gitlab.gnome.org/GNOME/gtk/-/issues/2307
atoms[count++] = atom_for_mime("text/plain;charset=utf-8").atom;
atoms[count++] = _glfw.x11.UTF8_STRING;
atoms[count++] = atom_for_mime("text/plain").atom;
atoms[count++] = XA_STRING;
} else {
atoms[count++] = atom_for_mime(mime_type).atom;
}
getSelectionString(which, atoms, count, write_data, object, true);
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
@@ -3134,3 +3197,18 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
_GLFWwindow* window = (_GLFWwindow*) handle;
return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc);
}
GLFWAPI void glfwSetX11WindowAsDock(int32_t x11_window_id) {
_GLFW_REQUIRE_INIT();
Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_DOCK;
XChangeProperty(_glfw.x11.display, x11_window_id,
_glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32,
PropModeReplace, (unsigned char*) &type, 1);
}
GLFWAPI void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12]) {
_GLFW_REQUIRE_INIT();
XChangeProperty(_glfw.x11.display, x11_window_id,
_glfw.x11.NET_WM_STRUT_PARTIAL, XA_CARDINAL, 32,
PropModeReplace, (unsigned char*) dimensions, 12);
}

View File

@@ -2,10 +2,11 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import sys
from contextlib import suppress
from typing import (
TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple
TYPE_CHECKING, Callable, Dict, Iterator, List, NamedTuple, Optional, Tuple
)
from kitty.cli import parse_args
@@ -16,11 +17,12 @@ from kitty.typing import BossType, KeyEventType, TypedDict
from kitty.utils import ScreenSize
from ..tui.handler import Handler, result_handler
from ..tui.loop import Loop, MouseEvent
from ..tui.loop import Loop, MouseEvent, debug
from ..tui.operations import MouseTracking, alternate_screen, styled
if TYPE_CHECKING:
import readline
debug
else:
readline = None
@@ -108,6 +110,15 @@ The default choice is selected when the user presses the :kbd:`Enter` key.
--prompt -p
default="> "
The prompt to use when inputting a line of text or a password.
--unhide-key
default=u
The key to be pressed to unhide hidden text
--hidden-text-placeholder
The text in the message to be replaced by hidden text. The hidden text is read via STDIN.
'''
@@ -147,14 +158,17 @@ def extra_for(width: int, screen_width: int) -> int:
class Password(Handler):
def __init__(self, cli_opts: AskCLIOptions, prompt: str) -> None:
def __init__(self, cli_opts: AskCLIOptions, prompt: str, is_password: bool = True, initial_text: str = '') -> None:
self.cli_opts = cli_opts
self.prompt = prompt
self.initial_text = initial_text
from kittens.tui.line_edit import LineEdit
self.line_edit = LineEdit(is_password=True)
self.line_edit = LineEdit(is_password=is_password)
def initialize(self) -> None:
self.cmd.set_cursor_shape('beam')
if self.initial_text:
self.line_edit.on_text(self.initial_text, True)
self.draw_screen()
@Handler.atomic_update
@@ -194,10 +208,11 @@ class Password(Handler):
return ''
class Choose(Handler):
class Choose(Handler): # {{{
mouse_tracking = MouseTracking.buttons_only
def __init__(self, cli_opts: AskCLIOptions) -> None:
self.prefix_style_pat = re.compile(r'(?:\x1b\[[^m]*?m)+')
self.cli_opts = cli_opts
self.choices: Dict[str, Choice] = {}
self.clickable_ranges: Dict[str, List[Range]] = {}
@@ -219,6 +234,18 @@ class Choose(Handler):
self.response_on_accept = cli_opts.default or ''
if cli_opts.type in ('yesno', 'choices') and self.response_on_accept not in self.allowed:
self.response_on_accept = 'y' if cli_opts.type == 'yesno' else tuple(self.choices.keys())[0]
self.message = cli_opts.message
self.hidden_text_start_pos = self.hidden_text_end_pos = -1
self.hidden_text = ''
self.replacement_text = t = f'Press {styled(self.cli_opts.unhide_key, fg="green")} or click to show'
self.replacement_range = Range(-1, -1, -1)
if self.message and self.cli_opts.hidden_text_placeholder:
self.hidden_text_start_pos = self.message.find(self.cli_opts.hidden_text_placeholder)
if self.hidden_text_start_pos > -1:
self.hidden_text = sys.stdin.read().rstrip()
self.hidden_text_end_pos = self.hidden_text_start_pos + len(t)
suffix = self.message[self.hidden_text_start_pos + len(self.cli_opts.hidden_text_placeholder):]
self.message = self.message[:self.hidden_text_start_pos] + t + suffix
def initialize(self) -> None:
self.cmd.set_cursor_visible(False)
@@ -227,24 +254,35 @@ class Choose(Handler):
def finalize(self) -> None:
self.cmd.set_cursor_visible(True)
def draw_long_text(self, text: str) -> int:
y = 0
def draw_long_text(self, text: str) -> Iterator[str]:
if not text:
yield ''
return
width = self.screen_size.cols - 2
m = self.prefix_style_pat.match(text)
prefix = m.group() if m else ''
while text:
t, text = truncate_at_space(text, width)
t = t.strip()
self.print(' ' * extra_for(wcswidth(t), width), styled(t, bold=True), sep='')
y += 1
return y
yield ' ' * extra_for(wcswidth(t), width) + styled(prefix + t, bold=True)
@Handler.atomic_update
def draw_screen(self) -> None:
self.cmd.clear_screen()
y = max(0, self.screen_size.rows // 2 - 2)
msg_lines: List[str] = []
if self.message:
for line in self.message.splitlines():
msg_lines.extend(self.draw_long_text(line))
y = self.screen_size.rows - len(msg_lines)
y = max(0, (y // 2) - 2)
self.print(end='\r\n'*y)
if self.cli_opts.message:
for line in self.cli_opts.message.splitlines():
y += self.draw_long_text(line)
for line in msg_lines:
if self.replacement_text in line:
idx = line.find(self.replacement_text)
x = wcswidth(line[:idx])
self.replacement_range = Range(x, x + wcswidth(self.replacement_text), y)
self.print(line)
y += 1
if self.screen_size.rows > 2:
self.print()
y += 1
@@ -259,7 +297,8 @@ class Choose(Handler):
current_line_length = 0
current_line: List[Tuple[str, str]] = []
lines: List[List[Tuple[str, str]]] = []
sep, sep_sz = ' ', 2
sep = ' '
sep_sz = len(sep) + 2 # for the borders
for choice in choices:
self.clickable_ranges[choice.letter] = []
@@ -272,7 +311,7 @@ class Choose(Handler):
current_line = []
current_line_length = 0
current_line.append((choice.letter, text))
current_line_length += sz
current_line_length += sz + sep_sz
if current_line:
lines.append(current_line)
@@ -311,10 +350,12 @@ class Choose(Handler):
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
y += 1
self.cmd.set_line_wrapping(False)
for boxed_line in lines:
print_line(top, *boxed_line)
print_line(middle, *boxed_line)
print_line(bottom, *boxed_line, is_last=boxed_line is lines[-1])
self.cmd.set_line_wrapping(True)
def draw_choice(self, y: int) -> None:
if y + 3 <= self.screen_size.rows:
@@ -370,6 +411,14 @@ class Choose(Handler):
self.quit_loop(0)
elif self.cli_opts.type == 'yesno':
self.on_interrupt()
elif self.hidden_text and text == self.cli_opts.unhide_key:
self.unhide()
def unhide(self) -> None:
if self.hidden_text and self.message:
self.message = self.message[:self.hidden_text_start_pos] + self.hidden_text + self.message[self.hidden_text_end_pos:]
self.hidden_text = ''
self.draw_screen()
def on_key(self, key_event: KeyEventType) -> None:
if key_event.matches('esc'):
@@ -385,6 +434,8 @@ class Choose(Handler):
self.response = letter
self.quit_loop(0)
return
if self.hidden_text and self.replacement_range.has_point(ev.cell_x, ev.cell_y):
self.unhide()
def on_resize(self, screen_size: ScreenSize) -> None:
self.screen_size = screen_size
@@ -393,6 +444,7 @@ class Choose(Handler):
def on_interrupt(self) -> None:
self.quit_loop(1)
on_eot = on_interrupt
# }}}
def main(args: List[str]) -> Response:

View File

@@ -231,11 +231,11 @@ static PyMethodDef module_methods[] = {
};
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "subseq_matcher", /* name of module */
.m_doc = NULL,
.m_size = -1,
.m_methods = module_methods
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "subseq_matcher", /* name of module */
.m_doc = NULL,
.m_size = -1,
.m_methods = module_methods
};
EXPORTED PyMODINIT_FUNC

View File

@@ -1,56 +1,20 @@
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import codecs
import io
import os
import select
import sys
from typing import List, NoReturn, Optional
from kitty.cli import parse_args
from kitty.cli_stub import ClipboardCLIOptions
from kitty.fast_data_types import parse_input_from_terminal
from ..tui.handler import Handler
from ..tui.loop import Loop
class Clipboard(Handler):
def __init__(self, data_to_send: Optional[bytes], args: ClipboardCLIOptions):
self.args = args
self.clipboard_contents: Optional[str] = None
self.data_to_send = data_to_send
self.quit_on_write = False
def initialize(self) -> None:
if self.data_to_send is not None:
self.cmd.write_to_clipboard(self.data_to_send, self.args.use_primary)
if not self.args.get_clipboard:
if self.args.wait_for_completion:
# ask kitty for the TN terminfo capability and
# only quit after a response is received
self.print('\x1bP+q544e\x1b\\', end='')
self.print('Waiting for completion...')
return
self.quit_on_write = True
return
self.cmd.request_from_clipboard(self.args.use_primary)
def on_writing_finished(self) -> None:
if self.quit_on_write:
self.quit_loop(0)
def on_clipboard_response(self, text: str, from_primary: bool = False) -> None:
self.clipboard_contents = text
self.quit_loop(0)
def on_capability_response(self, name: str, val: str) -> None:
self.quit_loop(0)
def on_interrupt(self) -> None:
self.quit_loop(1)
def on_eot(self) -> None:
self.quit_loop(1)
from ..tui.operations import (
raw_mode, request_from_clipboard, write_to_clipboard
)
OPTIONS = r'''
--get-clipboard
@@ -84,6 +48,62 @@ popup, see :opt:`clipboard_control` for details.
'''
usage = ''
got_capability_response = False
got_clipboard_response = False
clipboard_contents = ''
clipboard_from_primary = False
def ignore(x: str) -> None:
pass
def on_text(x: str) -> None:
if '\x03' in x:
raise KeyboardInterrupt()
if '\x04' in x:
raise EOFError()
def on_dcs(dcs: str) -> None:
global got_capability_response
if dcs.startswith('1+r'):
got_capability_response = True
def on_osc(osc: str) -> None:
global clipboard_contents, clipboard_from_primary, got_clipboard_response
idx = osc.find(';')
if idx <= 0:
return
q = osc[:idx]
if q == '52':
got_clipboard_response = True
widx = osc.find(';', idx + 1)
if widx < idx:
clipboard_from_primary = osc.find('p', idx + 1) > -1
clipboard_contents = ''
else:
from base64 import standard_b64decode
clipboard_from_primary = osc.find('p', idx+1, widx) > -1
data = memoryview(osc.encode('ascii'))
clipboard_contents = standard_b64decode(data[widx+1:]).decode('utf-8')
def wait_loop(tty_fd: int) -> None:
os.set_blocking(tty_fd, False)
decoder = codecs.getincrementaldecoder('utf-8')('ignore')
with raw_mode(tty_fd):
buf = ''
while not got_capability_response and not got_clipboard_response:
rd = select.select([tty_fd], [], [])[0]
if rd:
raw = os.read(tty_fd, io.DEFAULT_BUFFER_SIZE)
if not raw:
raise EOFError()
data = decoder.decode(raw)
buf = (buf + data) if buf else data
buf = parse_input_from_terminal(on_text, on_dcs, ignore, on_osc, ignore, ignore, buf, False)
def main(args: List[str]) -> NoReturn:
@@ -93,17 +113,34 @@ def main(args: List[str]) -> NoReturn:
data: Optional[bytes] = None
if not sys.stdin.isatty():
data = sys.stdin.buffer.read()
try:
sys.stdin = open(os.ctermid())
except FileNotFoundError:
raise SystemExit('Not connected to a controlling terminal device, no /dev/tty')
loop = Loop()
handler = Clipboard(data, cli_opts)
loop.loop(handler)
if loop.return_code == 0 and handler.clipboard_contents:
sys.stdout.write(handler.clipboard_contents)
sys.stdout.flush()
raise SystemExit(loop.return_code)
wait_for_capability_response = False
data_to_write = []
if data:
data_to_write.append(write_to_clipboard(data, cli_opts.use_primary).encode('ascii'))
if not cli_opts.get_clipboard and cli_opts.wait_for_completion:
data_to_write.append(b'\x1bP+q544e\x1b\\')
wait_for_capability_response = True
if cli_opts.get_clipboard:
data_to_write.append(request_from_clipboard(cli_opts.use_primary).encode('ascii'))
wait_for_capability_response = True
tty_fd = os.open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC)
retcode = 0
with open(tty_fd, 'wb', closefd=True) as ttyf:
for x in data_to_write:
ttyf.write(x)
ttyf.flush()
if wait_for_capability_response:
try:
wait_loop(tty_fd)
except KeyboardInterrupt:
sys.excepthook = lambda *a: None
raise
except EOFError:
retcode = 1
if clipboard_contents:
print(end=clipboard_contents)
raise SystemExit(retcode)
if __name__ == '__main__':

View File

@@ -2,12 +2,16 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from contextlib import suppress
from fnmatch import fnmatch
from functools import lru_cache
from hashlib import md5
from typing import (
TYPE_CHECKING, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
)
from kitty.guess_mime_type import guess_type
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
from kitty.utils import control_codes_pat
if TYPE_CHECKING:
from .highlight import DiffHighlight # noqa
@@ -37,6 +41,8 @@ class Segment:
class Collection:
ignore_names: Tuple[str, ...] = ()
def __init__(self) -> None:
self.changes: Dict[str, str] = {}
self.renames: Dict[str, str] = {}
@@ -105,22 +111,34 @@ def resolve_remote_name(path: str, default: str) -> str:
return default
def allowed_items(items: Sequence[str], ignore_patterns: Sequence[str]) -> Iterator[str]:
for name in items:
for pat in ignore_patterns:
if fnmatch(name, pat):
break
else:
yield name
def walk(base: str, names: Set[str], pmap: Dict[str, str], ignore_names: Tuple[str, ...]) -> None:
for dirpath, dirnames, filenames in os.walk(base):
dirnames[:] = allowed_items(dirnames, ignore_names)
for filename in allowed_items(filenames, ignore_names):
path = os.path.abspath(os.path.join(dirpath, filename))
path_name_map[path] = name = os.path.relpath(path, base)
names.add(name)
pmap[name] = path
def collect_files(collection: Collection, left: str, right: str) -> None:
left_names: Set[str] = set()
right_names: Set[str] = set()
left_path_map: Dict[str, str] = {}
right_path_map: Dict[str, str] = {}
def walk(base: str, names: Set[str], pmap: Dict[str, str]) -> None:
for dirpath, dirnames, filenames in os.walk(base):
for filename in filenames:
path = os.path.abspath(os.path.join(dirpath, filename))
path_name_map[path] = name = os.path.relpath(path, base)
names.add(name)
pmap[name] = path
walk(left, left_names, left_path_map, collection.ignore_names)
walk(right, right_names, right_path_map, collection.ignore_names)
walk(left, left_names, left_path_map)
walk(right, right_names, right_path_map)
common_names = left_names & right_names
changed_names = {n for n in common_names if data_for_path(left_path_map[n]) != data_for_path(right_path_map[n])}
for n in changed_names:
@@ -143,12 +161,9 @@ def collect_files(collection: Collection, left: str, right: str) -> None:
collection.add_add(right_path_map[name])
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
def sanitize(text: str) -> str:
ntext = text.replace('\r\n', '\n')
return sanitize_pat.sub('', ntext)
return control_codes_pat().sub('', ntext)
@lru_cache(maxsize=1024)

View File

@@ -655,6 +655,7 @@ def main(args: List[str]) -> None:
opts = init_config(cli_opts)
set_diff_command(opts.diff_cmd)
lines_for_path.replace_tab_by = opts.replace_tab_by
Collection.ignore_names = tuple(opts.ignore_name)
left, right = map(get_remote_file, (left, right))
if os.path.isdir(left) != os.path.isdir(right):
raise SystemExit('The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.')

View File

@@ -47,6 +47,21 @@ opt('replace_tab_by', '\\x20\\x20\\x20\\x20',
option_type='python_string',
long_text='The string to replace tabs with. Default is to use four spaces.'
)
opt('+ignore_name', '',
option_type='store_multiple',
add_to_default=False,
long_text='''
A glob pattern that is matched against only the filename of files and directories. Matching
files and directories are ignored when scanning the filesystem to look for files to diff.
Can be specified multiple times to use multiple patterns. For example::
ignore_name .git
ignore_name *~
ignore_name *.pyc
''',
)
egr() # }}}
# colors {{{

View File

@@ -1,7 +1,7 @@
# generated by gen-config.py DO NOT edit
import typing
from kittens.diff.options.utils import parse_map, syntax_aliases
from kittens.diff.options.utils import parse_map, store_multiple, syntax_aliases
from kitty.conf.utils import merge_dicts, positive_int, python_string, to_color, to_color_or_none
@@ -37,6 +37,10 @@ class Parser:
def hunk_margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['hunk_margin_bg'] = to_color(val)
def ignore_name(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
for k, v in store_multiple(val, ans["ignore_name"]):
ans["ignore_name"][k] = v
def margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['margin_bg'] = to_color(val)
@@ -89,6 +93,7 @@ class Parser:
def create_result_dict() -> typing.Dict[str, typing.Any]:
return {
'ignore_name': {},
'map': [],
}

View File

@@ -20,6 +20,7 @@ option_names = ( # {{{
'highlight_removed_bg',
'hunk_bg',
'hunk_margin_bg',
'ignore_name',
'map',
'margin_bg',
'margin_fg',
@@ -64,6 +65,7 @@ class Options:
syntax_aliases: typing.Dict[str, str] = {'pyj': 'py', 'pyi': 'py', 'recipe': 'py'}
title_bg: Color = Color(255, 255, 255)
title_fg: Color = Color(0, 0, 0)
ignore_name: typing.Dict[str, str] = {}
map: typing.List[typing.Tuple[kitty.types.ParsedShortcut, kitty.conf.utils.KeyAction]] = []
key_definitions: KittensKeyMap = {}
config_paths: typing.Tuple[str, ...] = ()
@@ -116,6 +118,7 @@ class Options:
defaults = Options()
defaults.ignore_name = {}
defaults.map = [
# quit
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')), # noqa

View File

@@ -3,7 +3,7 @@
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Any, Dict, Iterable, Tuple, Union
from typing import Any, Container, Dict, Iterable, Tuple, Union
from kitty.conf.utils import (
KeyFuncWrapper, KittensKeyDefinition, parse_kittens_key
@@ -58,6 +58,12 @@ def syntax_aliases(raw: str) -> Dict[str, str]:
return ans
def store_multiple(val: str, current_val: Container[str]) -> Iterable[Tuple[str, str]]:
val = val.strip()
if val not in current_val:
yield val, val
def parse_map(val: str) -> Iterable[KittensKeyDefinition]:
x = parse_kittens_key(val, func_with_args.args_funcs)
if x is not None:

View File

@@ -187,11 +187,11 @@ static PyMethodDef module_methods[] = {
};
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "diff_speedup", /* name of module */
.m_doc = NULL,
.m_size = -1,
.m_methods = module_methods
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "diff_speedup", /* name of module */
.m_doc = NULL,
.m_size = -1,
.m_methods = module_methods
};
EXPORTED PyMODINIT_FUNC

View File

@@ -9,19 +9,20 @@ from functools import lru_cache
from gettext import gettext as _
from itertools import repeat
from typing import (
Any, Callable, Dict, Generator, Iterable, List, Optional, Pattern,
Sequence, Set, Tuple, Type, cast
Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional,
Pattern, Sequence, Set, Tuple, Type, cast
)
from kitty.cli import parse_args
from kitty.cli_stub import HintsCLIOptions
from kitty.clipboard import set_clipboard_string, set_primary_selection
from kitty.constants import website_url
from kitty.fast_data_types import get_options, set_clipboard_string
from kitty.fast_data_types import get_options, wcswidth
from kitty.key_encoding import KeyEvent
from kitty.typing import BossType, KittyCommonOpts
from kitty.utils import (
ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file,
screen_size_function, set_primary_selection
screen_size_function
)
from ..tui.handler import Handler, result_handler
@@ -44,6 +45,7 @@ DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
FILE_EXTENSION = r'\.(?:[a-zA-Z0-9]{2,7}|[ahcmo])(?!\.)'
PATH_REGEX = fr'(?:\S*?/[\r\S]+)|(?:\S[\r\S]*{FILE_EXTENSION})\b'
DEFAULT_LINENUM_REGEX = fr'(?P<path>{PATH_REGEX}):(?P<line>\d+)'
class Mark:
@@ -64,6 +66,10 @@ class Mark:
self.is_hyperlink = is_hyperlink
self.group_id = group_id
def __repr__(self) -> str:
return (f'Mark(index={self.index!r}, start={self.start!r}, end={self.end!r},'
f' text={self.text!r}, groupdict={self.groupdict!r}, is_hyperlink={self.is_hyperlink!r}, group_id={self.group_id!r})')
@lru_cache(maxsize=2048)
def encode_hint(num: int, alphabet: str) -> str:
@@ -225,14 +231,14 @@ class Hints(Handler):
self.write(self.current_text)
def regex_finditer(pat: 'Pattern[str]', minimum_match_length: int, text: str) -> Generator[Tuple[int, int, Dict[str, str]], None, None]:
def regex_finditer(pat: 'Pattern[str]', minimum_match_length: int, text: str) -> Iterator[Tuple[int, int, 're.Match[str]']]:
has_named_groups = bool(pat.groupindex)
for m in pat.finditer(text):
s, e = m.span(0 if has_named_groups else pat.groups)
while e > s + 1 and text[e-1] == '\0':
e -= 1
if e - s >= minimum_match_length:
yield s, e, m.groupdict()
yield s, e, m
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'", "": "", "": ""}
@@ -318,16 +324,22 @@ def ip(text: str, s: int, e: int) -> Tuple[int, int]:
return s, e
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Iterator[Mark]:
pat = re.compile(pattern)
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
sanitize_pat = re.compile('[\r\n\0]')
for idx, (s, e, match_object) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
try:
for func in post_processors:
s, e = func(text, s, e)
except InvalidMatch:
continue
mark_text = re.sub('[\r\n\0]', '', text[s:e])
groupdict = match_object.groupdict()
for group_name in groupdict:
group_idx = pat.groupindex[group_name]
gs, ge = match_object.span(group_idx)
gs, ge = max(gs, s), min(ge, e)
groupdict[group_name] = sanitize_pat.sub('', text[gs:ge])
mark_text = sanitize_pat.sub('', text[s:e])
yield Mark(idx, s, e, mark_text, groupdict)
@@ -400,7 +412,10 @@ def convert_text(text: str, cols: int) -> str:
appended = False
for line in full_line.split('\r'):
if line:
lines.append(line.ljust(cols, '\0'))
line_sz = wcswidth(line)
if line_sz < cols:
line += '\0' * (cols - line_sz)
lines.append(line)
lines.append('\r')
appended = True
if appended:
@@ -420,7 +435,7 @@ def parse_input(text: str) -> str:
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
regex = args.regex
if regex == DEFAULT_REGEX:
regex = fr'(?P<path>{PATH_REGEX}):(?P<line>\d+)'
regex = DEFAULT_LINENUM_REGEX
yield from mark(regex, [brackets, quotes], text, args)
@@ -525,12 +540,27 @@ OPTIONS = r'''
--program
type=list
What program to use to open matched text. Defaults to the default open program
for the operating system. Use a value of :code:`-` to paste the match into the
terminal window instead. A value of :code:`@` will copy the match to the
clipboard. A value of :code:`*` will copy the match to the primary selection
(on systems that support primary selections). A value of :code:`default` will
run the default open program. Can be specified multiple times to run multiple
programs.
for the operating system. Various special values are supported:
:code:`-`
paste the match into the terminal window.
:code:`@`
copy the match to the clipboard
:code:`*`
copy the match to the primary selection (on systems that support primary selections)
:code:`default`
run the default open program.
:code:`launch`
run :doc:`/launch` to open the program in a new kitty tab, window, overlay, etc.
For example::
--program "launch --type=tab vim"
Can be specified multiple times to run multiple programs.
--type
@@ -699,18 +729,14 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
def linenum_process_result(data: Dict[str, Any]) -> Tuple[str, int]:
pat = re.compile(r':(\d+)$')
for m, g in zip(data['match'], data['groupdicts']):
if m:
path, line = g['path'], g['line']
# look for trailers on path of the for :number
while True:
m = pat.search(path)
if m is None:
break
line = m.group(1)
path = path[:-len(m.group())]
lnum_pat = re.compile(r'(:\d+)$')
for match, g in zip(data['match'], data['groupdicts']):
path, line = g['path'], g['line']
if path and line:
m = lnum_pat.search(path)
if m is not None:
line = m.group(1)[1:]
path = path.rpartition(':')[0]
return os.path.expanduser(path), int(line)
return '', -1
@@ -796,6 +822,7 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
elif program == '*':
set_primary_selection(joined_text())
else:
from kitty.conf.utils import to_cmdline
cwd = data['cwd']
program = get_options().open_url_with if program == 'default' else program
if text_type == 'hyperlink':
@@ -804,12 +831,20 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
if w is not None:
w.open_url(m, hyperlink_id=1, cwd=cwd)
else:
launch_args = []
if isinstance(program, str) and program.startswith('launch '):
launch_args = to_cmdline(program)
launch_args.insert(1, '--cwd=' + cwd)
for m, groupdict in zip(matches, groupdicts):
if groupdict:
m = []
for k, v in groupdict.items():
m.append('{}={}'.format(k, v or ''))
boss.open_url(m, program, cwd=cwd)
if launch_args:
w = boss.window_id_map.get(target_window_id)
boss.call_remote_control(active_window=w, args=tuple(launch_args + ([m] if isinstance(m, str) else m)))
else:
boss.open_url(m, program, cwd=cwd)
if __name__ == '__main__':

View File

@@ -1,3 +1,3 @@
# generated by gen-wcwidth.py, do not edit
url_delimiters = '\x00-\x09\x0b-\x0c\x0e-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u0890-\u0891\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U00013430-\U00013438\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
url_delimiters = '\x00-\x09\x0b-\x0c\x0e-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u0890-\u0891\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U00013430-\U0001343f\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa

64
kittens/hyperlinked_grep/main.py Normal file → Executable file
View File

@@ -20,7 +20,49 @@ def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, fra
def main() -> None:
if not sys.stdout.isatty() and '--pretty' not in sys.argv and '-p' not in sys.argv:
i = 1
all_link_options = {'matching_lines', 'context_lines', 'file_headers'}
link_options = set()
delegate_to_rg = False
def parse_link_options(raw: str) -> None:
nonlocal delegate_to_rg
if not raw:
raise SystemExit('Must specify an argument for --kitten option')
p, _, s = raw.partition('=')
if p != 'hyperlink':
raise SystemExit(f'Unknown argument for --kitten: {raw}')
for option in s.split(','):
if option == 'all':
link_options.update(all_link_options)
delegate_to_rg = False
elif option == 'none':
delegate_to_rg = True
link_options.clear()
elif option not in all_link_options:
a = ', '.join(sorted(all_link_options))
raise SystemExit(f"hyperlink option must be one of all, none, {a}, not '{option}'")
else:
link_options.add(option)
delegate_to_rg = False
while i < len(sys.argv):
if sys.argv[i] == '--kitten':
next_item = '' if i + 1 >= len(sys.argv) else sys.argv[i + 1]
parse_link_options(next_item)
del sys.argv[i:i+2]
elif sys.argv[i].startswith('--kitten='):
parse_link_options(sys.argv[i][len('--kitten='):])
del sys.argv[i]
else:
i += 1
if not link_options: # Default to linking everything if no options given
link_options.update(all_link_options)
link_file_headers = 'file_headers' in link_options
link_context_lines = 'context_lines' in link_options
link_matching_lines = 'matching_lines' in link_options
if delegate_to_rg or (not sys.stdout.isatty() and '--pretty' not in sys.argv and '-p' not in sys.argv):
os.execlp('rg', 'rg', *sys.argv[1:])
cmdline = ['rg', '--pretty', '--with-filename'] + sys.argv[1:]
try:
@@ -31,7 +73,7 @@ def main() -> None:
write: Callable[[bytes], None] = cast(Callable[[bytes], None], sys.stdout.buffer.write)
sgr_pat = re.compile(br'\x1b\[.*?m')
osc_pat = re.compile(b'\x1b\\].*?\x1b\\\\')
num_pat = re.compile(br'^(\d+)[:-]')
num_pat = re.compile(br'^(\d+)([:-])')
in_result: bytes = b''
hostname = socket.gethostname().encode('utf-8')
@@ -43,20 +85,22 @@ def main() -> None:
if not clean_line:
in_result = b''
write(b'\n')
continue
if in_result:
elif in_result:
m = num_pat.match(clean_line)
if m is not None:
write_hyperlink(write, in_result, line, frag=m.group(1))
else:
write(line)
is_match_line = m.group(2) == b':'
if (is_match_line and link_matching_lines) or (not is_match_line and link_context_lines):
write_hyperlink(write, in_result, line, frag=m.group(1))
continue
write(line)
else:
if line.strip():
path = quote_from_bytes(os.path.abspath(clean_line)).encode('utf-8')
in_result = b'file://' + hostname + path
write_hyperlink(write, in_result, line)
else:
write(line)
if link_file_headers:
write_hyperlink(write, in_result, line)
continue
write(line)
except KeyboardInterrupt:
p.send_signal(signal.SIGINT)
except (EOFError, BrokenPipeError):

8
kittens/icat/main.py Executable file → Normal file
View File

@@ -353,7 +353,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
else:
import struct
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
with NamedTemporaryFile() as f:
with NamedTemporaryFile(prefix='tty-graphics-protocol-') as f:
prefix = f.name
frame_data = render_image(
path, prefix, m, available_width, available_height, args.scale_up,
@@ -405,7 +405,7 @@ def detect_support(wait_for: float = 10, silent: bool = False) -> bool:
parse_responses()
return 1 not in responses or 2 not in responses
with NamedTemporaryFile() as f:
with NamedTemporaryFile(prefix='tty-graphics-protocol') as f:
f.write(b'abcd')
f.flush()
gc = GraphicsCommand()
@@ -472,13 +472,13 @@ def process_single_item(
file_removed = False
try:
if isinstance(item, bytes):
with NamedTemporaryFile(prefix='stdin-image-data-', delete=False) as tf:
with NamedTemporaryFile(prefix='tty-graphics-protocol-', delete=False) as tf:
tf.write(item)
item = tf.name
is_tempfile = True
if url_pat is not None and url_pat.match(item) is not None:
from urllib.request import urlretrieve
with NamedTemporaryFile(prefix='url-image-data-', delete=False) as tf:
with NamedTemporaryFile(prefix='tty-graphics-protocol-', delete=False) as tf:
try:
with socket_timeout(30):
urlretrieve(item, filename=tf.name)

View File

@@ -2,8 +2,6 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import shutil
import subprocess
import sys
from typing import Any, Callable, Dict, List, Tuple
@@ -11,6 +9,7 @@ from kitty.cli import parse_args
from kitty.cli_stub import PanelCLIOptions
from kitty.constants import appname, is_macos
from kitty.os_window_size import WindowSizeData
from kitty.fast_data_types import make_x11_window_a_dock_window
OPTIONS = r'''
--lines
@@ -65,14 +64,7 @@ def parse_panel_args(args: List[str]) -> Tuple[PanelCLIOptions, List[str]]:
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
def call_xprop(*cmd: str, silent: bool = False) -> None:
cmd_ = ['xprop'] + list(cmd)
try:
cp = subprocess.run(cmd_, stdout=subprocess.DEVNULL if silent else None)
except FileNotFoundError:
raise SystemExit('You must have the xprop program installed')
if cp.returncode != 0:
raise SystemExit(cp.returncode)
Strut = Tuple[int, int, int, int, int, int, int, int, int, int, int, int]
def create_strut(
@@ -80,43 +72,33 @@ def create_strut(
left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, left_start_y: int = 0, left_end_y: int = 0,
right_start_y: int = 0, right_end_y: int = 0, top_start_x: int = 0, top_end_x: int = 0,
bottom_start_x: int = 0, bottom_end_x: int = 0
) -> None:
call_xprop(
'-id',
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
'-set', '_NET_WM_STRUT_PARTIAL',
f'{left},{right},{top},{bottom},'
f'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
f'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'
)
) -> Strut:
return left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
def create_top_strut(win_id: int, width: int, height: int) -> None:
create_strut(win_id, top=height, top_end_x=width)
def create_top_strut(win_id: int, width: int, height: int) -> Strut:
return create_strut(win_id, top=height, top_end_x=width)
def create_bottom_strut(win_id: int, width: int, height: int) -> None:
create_strut(win_id, bottom=height, bottom_end_x=width)
def create_bottom_strut(win_id: int, width: int, height: int) -> Strut:
return create_strut(win_id, bottom=height, bottom_end_x=width)
def create_left_strut(win_id: int, width: int, height: int) -> None:
create_strut(win_id, left=width, left_end_y=height)
def create_left_strut(win_id: int, width: int, height: int) -> Strut:
return create_strut(win_id, left=width, left_end_y=height)
def create_right_strut(win_id: int, width: int, height: int) -> None:
create_strut(win_id, right=width, right_end_y=height)
def create_right_strut(win_id: int, width: int, height: int) -> Strut:
return create_strut(win_id, right=width, right_end_y=height)
window_width = window_height = 0
def setup_x11_window(win_id: int) -> None:
call_xprop(
'-id', str(win_id), '-format', '_NET_WM_WINDOW_TYPE', '32a',
'-set', '_NET_WM_WINDOW_TYPE', '_NET_WM_WINDOW_TYPE_DOCK'
)
func = globals()[f'create_{args.edge}_strut']
func(win_id, window_width, window_height)
strut = func(win_id, window_width, window_height)
make_x11_window_a_dock_window(win_id, strut)
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
@@ -140,8 +122,6 @@ def main(sys_args: List[str]) -> None:
global args
if is_macos or not os.environ.get('DISPLAY'):
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
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

@@ -206,7 +206,7 @@ class ControlMaster:
show_error(msg)
def download(self) -> bool:
cmdline = self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', self.remote_path]
cmdline = self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', shlex.quote(self.remote_path)]
with open(self.dest, 'wb') as f:
cp = subprocess.run(cmdline, stdout=f, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
if cp.returncode != 0:
@@ -216,7 +216,7 @@ class ControlMaster:
def upload(self, suppress_output: bool = True) -> bool:
cmd_prefix = self.cmd_prefix if suppress_output else self.batch_cmd_prefix
cmd = cmd_prefix + [self.conn_data.hostname, 'cat', '>', self.remote_path]
cmd = cmd_prefix + [self.conn_data.hostname, 'cat', '>', shlex.quote(self.remote_path)]
if not suppress_output:
print(shlex.join(cmd))
with open(self.dest, 'rb') as f:
@@ -273,9 +273,9 @@ def save_as(conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFile
)
print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True))
print()
from ..tui.path_completer import PathCompleter
from ..tui.path_completer import get_path
try:
dest = PathCompleter().input()
dest = get_path()
except (KeyboardInterrupt, EOFError):
return
if dest:

View File

@@ -122,7 +122,11 @@ def run_kitten(kitten: str, run_name: str = '__main__') -> None:
print(kitten, file=sys.stderr)
raise SystemExit(f'No kitten named {original_kitten_name}')
m = runpy.run_path(path, init_globals={'sys': sys, 'os': os}, run_name='__run_kitten__')
m['main'](sys.argv)
from kitty.fast_data_types import set_options
try:
m['main'](sys.argv)
finally:
set_options(None)
@run_once

View File

@@ -34,7 +34,7 @@ def format_mods(mods: int) -> str:
class KeysHandler(Handler):
def initialize(self) -> None:
self.cmd.set_window_title('Kitty extended keyboard protocol demo')
self.cmd.set_window_title('kitty extended keyboard protocol demo')
self.cmd.set_cursor_visible(False)
self.print('Press any keys - Ctrl+C or Ctrl+D will terminate')
@@ -47,7 +47,10 @@ class KeysHandler(Handler):
mods = format_mods(key_event.mods)
if mods:
mods += '+'
key = f'{mods}{key_event.key} '
kk = key_event.key
if kk == ' ':
kk = 'SPACE'
key = f'{mods}{kk} '
self.cmd.colored(key, 'green')
self.cmd.colored(etype + ' ', 'yellow')
self.cmd.styled(key_event.text, italic=True)

View File

@@ -45,7 +45,7 @@ def read_keys() -> None:
def legacy_main() -> None:
print('Press any keys - Ctrl-D will terminate this program', end='\r\n', flush=True)
print('Press any keys - Ctrl+D will terminate this program', end='\r\n', flush=True)
print(styled('UNIX', italic=True, fg='green'), styled('send_text', italic=True, fg='green'), sep='\t\t', end='\r\n')
with raw_mode():
@@ -62,6 +62,7 @@ reset and :code:`application` mode is with DECCKM set. :code:`kitty` is the full
kitty extended keyboard protocol.
'''.format
help_text = 'Show the codes generated by the terminal for key presses in various keyboard modes'
usage = ''
def main(args: List[str]) -> None:
@@ -80,3 +81,8 @@ def main(args: List[str]) -> None:
if __name__ == '__main__':
main(sys.argv)
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text

View File

@@ -8,6 +8,7 @@ from typing import Callable, Dict, Iterable, Iterator, Sequence, Tuple
from kitty.complete import Completions, complete_files_and_dirs, debug
from kitty.types import run_once
from .utils import ssh_options
debug
@@ -71,47 +72,6 @@ def known_hosts() -> Tuple[str, ...]:
return tuple(sorted(filter(lambda x: '*' not in x and '[' not in x, set(iter_known_hosts()))))
@run_once
def ssh_options() -> Dict[str, str]:
try:
p = subprocess.run(['ssh'], stderr=subprocess.PIPE, encoding='utf-8')
raw = p.stderr or ''
except FileNotFoundError:
return {
'4': '', '6': '', 'A': '', 'a': '', 'C': '', 'f': '', 'G': '', 'g': '', 'K': '', 'k': '',
'M': '', 'N': '', 'n': '', 'q': '', 's': '', 'T': '', 't': '', 'V': '', 'v': '', 'X': '',
'x': '', 'Y': '', 'y': '', 'B': 'bind_interface', 'b': 'bind_address', 'c': 'cipher_spec',
'D': '[bind_address:]port', 'E': 'log_file', 'e': 'escape_char', 'F': 'configfile', 'I': 'pkcs11',
'i': 'identity_file', 'J': '[user@]host[:port]', 'L': 'address', 'l': 'login_name', 'm': 'mac_spec',
'O': 'ctl_cmd', 'o': 'option', 'p': 'port', 'Q': 'query_option', 'R': 'address',
'S': 'ctl_path', 'W': 'host:port', 'w': 'local_tun[:remote_tun]'
}
ans: Dict[str, str] = {}
pos = 0
while True:
pos = raw.find('[', pos)
if pos < 0:
break
num = 1
epos = pos
while num > 0:
epos += 1
if raw[epos] not in '[]':
continue
num += 1 if raw[epos] == '[' else -1
q = raw[pos+1:epos]
pos = epos
if len(q) < 2 or q[0] != '-':
continue
if ' ' in q:
opt, desc = q.split(' ', 1)
ans[opt[1:]] = desc
else:
ans.update(dict.fromkeys(q[1:], ''))
return ans
# option help {{{
@run_once
def option_help_map() -> Dict[str, str]:
@@ -331,6 +291,7 @@ def complete_option(ans: Completions, prefix: str = '-') -> None:
def complete(ans: Completions, words: Sequence[str], new_word: bool) -> None:
options = ssh_options()
expecting_arg = False
seen_destination = False
types = ['' for i in range(len(words))]
for i, word in enumerate(words):
if expecting_arg:
@@ -342,8 +303,10 @@ def complete(ans: Completions, words: Sequence[str], new_word: bool) -> None:
if len(word) == 2 and options.get(word[1]):
expecting_arg = True
continue
if seen_destination:
break
types[i] = 'destination'
break
seen_destination = True
if new_word:
if words:
if expecting_arg:

View File

@@ -38,7 +38,18 @@ type=list
A glob pattern. Files with names matching this pattern are excluded from being
transferred. Useful when adding directories. Can
be specified multiple times, if any of the patterns match the file will be
excluded.
excluded. To exclude a directory use a pattern like */directory_name/*.
--symlink-strategy
default=preserve
choices=preserve,resolve,keep-path
Control what happens if the specified path is a symlink. The default is to preserve
the symlink, re-creating it on the remote machine. Setting this to :code:`resolve`
will cause the symlink to be followed and its target used as the file/directory to copy.
The value of :code:`keep-path` is the same as :code:`resolve` except that the remote
file path is derived from the symlink's path instead of the path of the symlink's target.
Note that this option does not apply to symlinks encountered while recursively copying directories.
'''
@@ -100,5 +111,9 @@ def parse_copy_instructions(val: str, current_val: Dict[str, str]) -> Iterable[T
raise CopyCLIError('Specifying a remote location with more than one file is not supported')
home = home_path()
for loc in locations:
arcname = get_arcname(loc, opts.dest, home)
yield str(uuid.uuid4()), CopyInstruction(loc, arcname, tuple(opts.exclude))
if opts.symlink_strategy != 'preserve':
rp = os.path.realpath(loc)
else:
rp = loc
arcname = get_arcname(rp if opts.symlink_strategy == 'resolve' else loc, opts.dest, home)
yield str(uuid.uuid4()), CopyInstruction(rp, arcname, tuple(opts.exclude))

View File

@@ -27,26 +27,28 @@ from typing import (
Tuple, Union, cast
)
from kittens.tui.operations import restore_colors, save_colors
from kitty.constants import (
cache_dir, runtime_dir, shell_integration_dir, ssh_control_master_template,
str_version, terminfo_dir
)
from kitty.options.types import Options
from kitty.shell_integration import as_str_literal
from kitty.shm import SharedMemory
from kitty.types import run_once
from kitty.utils import (
SSHConnectionData, expandvars, resolve_abs_or_config_path,
set_echo as turn_off_echo, suppress_error_logging
set_echo as turn_off_echo
)
from .completion import complete, ssh_options
from ..tui.operations import (
RESTORE_PRIVATE_MODE_VALUES, SAVE_PRIVATE_MODE_VALUES, Mode,
restore_colors, save_colors, set_mode
)
from ..tui.utils import kitty_opts, running_in_tmux
from .config import init_config
from .copy import CopyInstruction
from .options.types import Options as SSHOptions
from .options.utils import DELETE_ENV_VAR
from .utils import create_shared_memory
from .utils import create_shared_memory, ssh_options
@run_once
@@ -111,18 +113,14 @@ def serialize_env(literal_env: Dict[str, str], env: Dict[str, str], base_env: Di
return '\n'.join(lines).encode('utf-8')
@run_once
def kitty_opts() -> Options:
from kitty.cli import create_default_opts
with suppress_error_logging():
return create_default_opts()
def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: str = 'gz', literal_env: Dict[str, str] = {}) -> bytes:
def normalize_tarinfo(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo:
tarinfo.uname = tarinfo.gname = ''
tarinfo.uid = tarinfo.gid = 0
# some distro's like nix mess with installed file permissions so ensure
# files are at least readable and writable by owning user
tarinfo.mode |= stat.S_IWUSR | stat.S_IRUSR
return tarinfo
def add_data_as_file(tf: tarfile.TarFile, arcname: str, data: Union[str, bytes]) -> tarfile.TarInfo:
@@ -152,6 +150,7 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: st
if ssh_opts.shell_integration == 'inherited':
ksi = get_effective_ksi_env_var(kitty_opts())
else:
from kitty.options.types import Options
from kitty.options.utils import shell_integration
ksi = get_effective_ksi_env_var(Options({'shell_integration': shell_integration(ssh_opts.shell_integration)}))
@@ -172,6 +171,9 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: st
env['KITTY_LOGIN_CWD'] = ssh_opts.cwd
if ssh_opts.remote_kitty != 'no':
env['KITTY_REMOTE'] = ssh_opts.remote_kitty
if os.environ.get('KITTY_PUBLIC_KEY'):
env.pop('KITTY_PUBLIC_KEY', None)
literal_env['KITTY_PUBLIC_KEY'] = os.environ['KITTY_PUBLIC_KEY']
env_script = serialize_env(literal_env, env, base_env, for_python=compression != 'gz')
buf = io.BytesIO()
with tarfile.open(mode=f'w:{compression}', fileobj=buf, encoding='utf-8') as tf:
@@ -213,7 +215,7 @@ def get_ssh_data(msg: str, request_id: str) -> Iterator[bytes]:
if pw != env_data['pw']:
raise ValueError('Incorrect password')
if rq_id != request_id:
raise ValueError('Incorrect request id')
raise ValueError(f'Incorrect request id: {rq_id!r} expecting the KITTY_PID-KITTY_WINDOW_ID for the current kitty window')
except Exception as e:
traceback.print_exc()
yield f'{e}\n'.encode('utf-8')
@@ -413,9 +415,11 @@ class InvalidSSHArgs(ValueError):
os.execlp(ssh_exe(), 'ssh')
passthrough_args = {f'-{x}' for x in 'NnfGT'}
def parse_ssh_args(args: List[str], extra_args: Tuple[str, ...] = ()) -> Tuple[List[str], List[str], bool, Tuple[str, ...]]:
boolean_ssh_args, other_ssh_args = get_ssh_cli()
passthrough_args = {f'-{x}' for x in 'NnfG'}
ssh_args = []
server_args: List[str] = []
expecting_option_val = False
@@ -512,7 +516,7 @@ def get_remote_command(
return wrap_bootstrap_script(sh_script, interpreter), replacements, shm_name
def connection_sharing_args(opts: SSHOptions, kitty_pid: int) -> List[str]:
def connection_sharing_args(kitty_pid: int) -> List[str]:
rd = runtime_dir()
# Bloody OpenSSH generates a 40 char hash and in creating the socket
# appends a 27 char temp suffix to it. Socket max path length is approx
@@ -551,17 +555,29 @@ def connection_sharing_args(opts: SSHOptions, kitty_pid: int) -> List[str]:
def restore_terminal_state() -> Iterator[bool]:
with open(os.ctermid()) as f:
val = termios.tcgetattr(f.fileno())
print(end=SAVE_PRIVATE_MODE_VALUES)
print(end=set_mode(Mode.HANDLE_TERMIOS_SIGNALS), flush=True)
try:
yield bool(val[3] & termios.ECHO)
finally:
termios.tcsetattr(f.fileno(), termios.TCSAFLUSH, val)
print(end=RESTORE_PRIVATE_MODE_VALUES, flush=True)
def dcs_to_kitty(payload: Union[bytes, str], type: str = 'ssh') -> bytes:
if isinstance(payload, str):
payload = payload.encode('utf-8')
payload = standard_b64encode(payload)
return b'\033P@kitty-' + type.encode('ascii') + b'|' + payload + b'\033\\'
ans = b'\033P@kitty-' + type.encode('ascii') + b'|' + payload
tmux = running_in_tmux()
if tmux:
cp = subprocess.run([tmux, 'set', '-p', 'allow-passthrough', 'on'])
if cp.returncode != 0:
raise SystemExit(cp.returncode)
ans = b'\033Ptmux;\033' + ans + b'\033\033\\\033\\'
else:
ans += b'\033\\'
return ans
@run_once
@@ -575,7 +591,6 @@ def ssh_version() -> Tuple[int, int]:
@contextmanager
def drain_potential_tty_garbage(p: 'subprocess.Popen[bytes]', data_request: str) -> Iterator[None]:
ssh_started_at = time.monotonic()
with open(os.open(os.ctermid(), os.O_CLOEXEC | os.O_RDWR | os.O_NOCTTY), 'wb') as tty:
if data_request:
turn_off_echo(tty.fileno())
@@ -584,14 +599,18 @@ def drain_potential_tty_garbage(p: 'subprocess.Popen[bytes]', data_request: str)
try:
yield
finally:
if p.returncode and time.monotonic() - ssh_started_at < 30:
# discard queued input data on tty in case data transmission was
# interrupted due to SSH failure, avoids spewing garbage to
# screen
data = b''
give_up_at = time.monotonic() + 1
tty_fd = tty.fileno()
while time.monotonic() < give_up_at and b'KITTY_DATA_END' not in data:
# discard queued input data on tty in case data transmission was
# interrupted due to SSH failure, avoids spewing garbage to screen
from uuid import uuid4
canary = uuid4().hex.encode('ascii')
turn_off_echo(tty.fileno())
tty.write(dcs_to_kitty(canary + b'\n\r', type='echo'))
tty.flush()
data = b''
give_up_at = time.monotonic() + 2
tty_fd = tty.fileno()
while time.monotonic() < give_up_at and canary not in data:
with suppress(KeyboardInterrupt):
rd, wr, err = select([tty_fd], [], [tty_fd], max(0, give_up_at - time.monotonic()))
if err or not rd:
break
@@ -673,7 +692,7 @@ def run_ssh(ssh_args: List[str], server_args: List[str], found_extra_args: Tuple
overrides.insert(0, f'hostname {uname}@{hostname_for_match}')
host_opts = init_config(hostname_for_match, uname, overrides)
if host_opts.share_connections:
cmd[insertion_point:insertion_point] = connection_sharing_args(host_opts, int(os.environ['KITTY_PID']))
cmd[insertion_point:insertion_point] = connection_sharing_args(int(os.environ['KITTY_PID']))
use_kitty_askpass = host_opts.askpass == 'native' or (host_opts.askpass == 'unless-set' and 'SSH_ASKPASS' not in os.environ)
need_to_request_data = True
if use_kitty_askpass:
@@ -703,16 +722,13 @@ def run_ssh(ssh_args: List[str], server_args: List[str], found_extra_args: Tuple
else:
rq = '' if need_to_request_data else 'id={REQUEST_ID}:pwfile={PASSWORD_FILENAME}:pw={DATA_PASSWORD}'.format(**replacements)
with drain_potential_tty_garbage(p, rq):
try:
raise SystemExit(p.wait())
except KeyboardInterrupt:
raise SystemExit(1)
raise SystemExit(p.wait())
finally:
if colors_changed:
print(end=restore_colors(), flush=True)
def main(args: List[str]) -> NoReturn:
def main(args: List[str]) -> None:
args = args[1:]
if args and args[0] == 'use-python':
args = args[1:] # backwards compat from when we had a python implementation
@@ -720,18 +736,26 @@ def main(args: List[str]) -> NoReturn:
ssh_args, server_args, passthrough, found_extra_args = parse_ssh_args(args, extra_args=('--kitten',))
except InvalidSSHArgs as e:
e.system_exit()
if passthrough:
if found_extra_args:
raise SystemExit(f'The SSH kitten cannot work with the options: {", ".join(passthrough_args)}')
os.execlp(ssh_exe(), 'ssh', *args)
if not os.environ.get('KITTY_WINDOW_ID') or not os.environ.get('KITTY_PID'):
raise SystemExit('The SSH kitten is meant to run inside a kitty window')
if passthrough:
raise SystemExit('The SSH kitten is meant for interactive use via SSH only')
if not sys.stdin.isatty():
raise SystemExit('The SSH kitten is meant for interactive use only, STDIN must be a terminal')
run_ssh(ssh_args, server_args, found_extra_args)
try:
run_ssh(ssh_args, server_args, found_extra_args)
except KeyboardInterrupt:
sys.excepthook = lambda *a: None
raise
if __name__ == '__main__':
main(sys.argv)
elif __name__ == '__completer__':
from .completion import complete
setattr(sys, 'kitten_completer', complete)
elif __name__ == '__conf__':
from .options.definition import definition

View File

@@ -40,10 +40,9 @@ shell or a :program:`python` executable. If the default :program:`sh` is not
available or broken, using an alternate interpreter can be useful.
''')
opt('remote_dir', '.local/share/kitty-ssh-kitten', option_type='relative_dir', long_text='''
opt('remote_dir', '.local/share/kitty-ssh-kitten', long_text='''
The location on the remote host where the files needed for this kitten are
installed. The location is relative to the HOME directory. Absolute paths or
paths that resolve to a location outside the HOME are not allowed.
installed. Relative paths are resolved with respect to :code:`$HOME`.
''')
opt('+copy', '', option_type='copy', add_to_default=False, long_text=f'''

View File

@@ -1,7 +1,7 @@
# generated by gen-config.py DO NOT edit
import typing
from kittens.ssh.options.utils import copy, env, hostname, relative_dir
from kittens.ssh.options.utils import copy, env, hostname
from kitty.conf.utils import merge_dicts, to_bool
@@ -39,7 +39,7 @@ class Parser:
ans['login_shell'] = str(val)
def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['remote_dir'] = relative_dir(val)
ans['remote_dir'] = str(val)
def remote_kitty(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
val = val.lower()

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import posixpath
from typing import Any, Dict, Iterable, Optional, Tuple
from ..copy import CopyInstruction, parse_copy_instructions
@@ -9,16 +8,6 @@ from ..copy import CopyInstruction, parse_copy_instructions
DELETE_ENV_VAR = '_delete_this_env_var_'
def relative_dir(val: str) -> str:
if posixpath.isabs(val):
raise ValueError(f'Absolute paths not allowed. {val} is invalid.')
base = '/ffjdg'
q = posixpath.normpath(posixpath.join(base, val))
if q == base or not q.startswith(base):
raise ValueError(f'Paths that escape their parent dir are not allowed. {val} is not valid')
return posixpath.normpath(val)
def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
val = val.strip()
if val:

View File

@@ -3,15 +3,61 @@
import os
from typing import Any, Dict, List
import subprocess
from typing import Any, Dict, List, Sequence
from kitty.types import run_once
def is_kitten_cmdline(q: List[str]) -> bool:
@run_once
def ssh_options() -> Dict[str, str]:
try:
p = subprocess.run(['ssh'], stderr=subprocess.PIPE, encoding='utf-8')
raw = p.stderr or ''
except FileNotFoundError:
return {
'4': '', '6': '', 'A': '', 'a': '', 'C': '', 'f': '', 'G': '', 'g': '', 'K': '', 'k': '',
'M': '', 'N': '', 'n': '', 'q': '', 's': '', 'T': '', 't': '', 'V': '', 'v': '', 'X': '',
'x': '', 'Y': '', 'y': '', 'B': 'bind_interface', 'b': 'bind_address', 'c': 'cipher_spec',
'D': '[bind_address:]port', 'E': 'log_file', 'e': 'escape_char', 'F': 'configfile', 'I': 'pkcs11',
'i': 'identity_file', 'J': '[user@]host[:port]', 'L': 'address', 'l': 'login_name', 'm': 'mac_spec',
'O': 'ctl_cmd', 'o': 'option', 'p': 'port', 'Q': 'query_option', 'R': 'address',
'S': 'ctl_path', 'W': 'host:port', 'w': 'local_tun[:remote_tun]'
}
ans: Dict[str, str] = {}
pos = 0
while True:
pos = raw.find('[', pos)
if pos < 0:
break
num = 1
epos = pos
while num > 0:
epos += 1
if raw[epos] not in '[]':
continue
num += 1 if raw[epos] == '[' else -1
q = raw[pos+1:epos]
pos = epos
if len(q) < 2 or q[0] != '-':
continue
if ' ' in q:
opt, desc = q.split(' ', 1)
ans[opt[1:]] = desc
else:
ans.update(dict.fromkeys(q[1:], ''))
return ans
def is_kitten_cmdline(q: Sequence[str]) -> bool:
if len(q) < 4:
return False
if os.path.basename(q[0]).lower() != 'kitty':
return False
return q[1:3] == ['+kitten', 'ssh'] or q[1:4] == ['+', 'kitten', 'ssh']
if q[1:3] == ['+kitten', 'ssh'] or q[1:4] == ['+', 'kitten', 'ssh']:
return True
return q[1:3] == ['+runpy', 'from kittens.runner import main; main()'] and len(q) >= 6 and q[5] == 'ssh'
def patch_cmdline(key: str, val: str, argv: List[str]) -> None:

View File

@@ -494,6 +494,7 @@ class Theme:
is_dark: bool = False
blurb: str = ''
num_settings: int = 0
is_user_defined: bool = False
def apply_dict(self, d: Dict[str, Any]) -> None:
self.name = str(d['name'])
@@ -501,7 +502,7 @@ class Theme:
a = d.get(x)
if isinstance(a, str):
setattr(self, x, a)
for x in ('is_dark', 'num_settings'):
for x in ('is_dark', 'num_settings', 'is_user_defined'):
a = d.get(x)
if isinstance(a, int):
setattr(self, x, a)
@@ -594,6 +595,7 @@ class Themes:
d = parse_theme(name, raw)
except (Exception, SystemExit):
continue
d['is_user_defined'] = True
t = Theme(raw.__str__)
t.apply_dict(d)
if t.name:

View File

@@ -69,6 +69,10 @@ def create_recent_filter(names: Iterable[str]) -> Callable[[Theme], bool]:
return recent_filter
def user_filter(q: Theme) -> bool:
return q.is_user_defined
def mark_shortcut(text: str, acc: str) -> str:
acc_idx = text.lower().index(acc.lower())
return text[:acc_idx] + styled(text[acc_idx], underline='straight', bold=True, fg_intense=True) + text[acc_idx+1:]
@@ -153,12 +157,13 @@ class ThemesHandler(Handler):
self.report_traceback_on_exit: Optional[str] = None
self.filter_map: Dict[str, Callable[[Theme], bool]] = {
'dark': dark_filter, 'light': light_filter, 'all': all_filter,
'recent': create_recent_filter(self.cached_values.get('recent', ()))
'recent': create_recent_filter(self.cached_values.get('recent', ())),
'user': user_filter
}
self.themes_list = ThemesList()
self.colors_set_once = False
self.line_edit = LineEdit()
self.tabs = tuple('all dark light recent'.split())
self.tabs = tuple('all dark light recent user'.split())
self.quit_on_next_key_release = -1
def update_recent(self) -> None:
@@ -389,7 +394,7 @@ class ThemesHandler(Handler):
if key_event.matches('esc') or key_event.matches_text('q'):
self.quit_on_next_key_release = 0
return
for cat in 'all dark light recent'.split():
for cat in self.tabs:
if key_event.matches_text(cat[0]) or key_event.matches(f'alt+{cat[0]}'):
if cat != self.current_category:
self.current_category = cat

View File

@@ -243,14 +243,14 @@ static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL}
END_IGNORE_PEDANTIC_WARNINGS
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "rsync", /* name of module */
.m_doc = NULL,
.m_slots = slots,
.m_methods = module_methods
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "rsync", /* name of module */
.m_doc = NULL,
.m_slots = slots,
.m_methods = module_methods
};
EXPORTED PyMODINIT_FUNC
PyInit_rsync(void) {
return PyModuleDef_Init(&module);
return PyModuleDef_Init(&module);
}

View File

@@ -121,11 +121,22 @@ class Handler:
self._tui_loop.quit(1)
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
' Override this method and perform_default_key_action() to handle all key events '
if key_event.text:
self.on_text(key_event.text, in_bracketed_paste)
else:
self.on_key(key_event)
def perform_default_key_action(self, key_event: KeyEventType) -> bool:
' Override in sub-class if you want to handle these key events yourself '
if key_event.matches('ctrl+c'):
self.on_interrupt()
return True
if key_event.matches('ctrl+d'):
self.on_eot()
return True
return False
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
pass

View File

@@ -301,7 +301,7 @@ def render_as_single_image(
remove_alpha: str = '', flip: bool = False, flop: bool = False,
) -> Tuple[str, int, int]:
import tempfile
fd, output = tempfile.mkstemp(prefix='icat-', suffix=f'.{m.mode}', dir=tdir)
fd, output = tempfile.mkstemp(prefix='tty-graphics-protocol-', suffix=f'.{m.mode}', dir=tdir)
os.close(fd)
result = render_image(
path, output, m, available_width, available_height, scale_up,

View File

@@ -109,17 +109,17 @@ class TermManager:
class MouseButton(IntFlag):
NONE, LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 0, 1, 2, 4, 8, 16
WHEEL_UP, WHEEL_DOWN = -1, -2
NONE, LEFT, MIDDLE, RIGHT, FOURTH, FIFTH, SIXTH, SEVENTH = 0, 1, 2, 4, 8, 16, 32, 64
WHEEL_UP, WHEEL_DOWN, WHEEL_LEFT, WHEEL_RIGHT = -1, -2, -4, -8
bmap = {0: MouseButton.LEFT, 1: MouseButton.MIDDLE, 2: MouseButton.RIGHT}
bmap = MouseButton.LEFT, MouseButton.MIDDLE, MouseButton.RIGHT
ebmap = MouseButton.FOURTH, MouseButton.FIFTH, MouseButton.SIXTH, MouseButton.SEVENTH
wbmap = MouseButton.WHEEL_UP, MouseButton.WHEEL_DOWN, MouseButton.WHEEL_LEFT, MouseButton.WHEEL_RIGHT
SHIFT_INDICATOR = 1 << 2
ALT_INDICATOR = 1 << 3
CTRL_INDICATOR = 1 << 4
MOTION_INDICATOR = 1 << 5
SCROLL_BUTTON_INDICATOR = 1 << 6
EXTRA_BUTTON_INDICATOR = 1 << 7
class EventType(Enum):
@@ -150,13 +150,12 @@ def decode_sgr_mouse(text: str, screen_size: ScreenSize) -> MouseEvent:
typ = EventType.RELEASE if m == 'm' else (EventType.MOVE if cb & MOTION_INDICATOR else EventType.PRESS)
buttons: MouseButton = MouseButton.NONE
cb3 = cb & 3
if cb3 != 3:
if cb & SCROLL_BUTTON_INDICATOR:
buttons = MouseButton.WHEEL_DOWN if cb3 & 1 else MouseButton.WHEEL_UP
elif cb & EXTRA_BUTTON_INDICATOR:
buttons |= MouseButton.FIFTH if cb3 & 1 else MouseButton.FOURTH
else:
buttons |= bmap[cb3]
if cb >= 128:
buttons |= ebmap[cb3]
elif cb >= 64:
buttons |= wbmap[cb3]
elif cb3 < 3:
buttons |= bmap[cb3]
mods = 0
if cb & SHIFT_INDICATOR:
mods |= SHIFT
@@ -331,13 +330,8 @@ class Loop:
except Exception:
pass
else:
if k.matches('ctrl+c'):
self.handler.on_interrupt()
return
if k.matches('ctrl+d'):
self.handler.on_eot()
return
self.handler.on_key_event(k)
if not self.handler.perform_default_key_action(k):
self.handler.on_key_event(k)
def _on_pm(self, pm: str) -> None:
pass

View File

@@ -47,6 +47,7 @@ class Mode(Enum):
ALTERNATE_SCREEN = 1049, '?'
BRACKETED_PASTE = 2004, '?'
PENDING_UPDATE = 2026, '?'
HANDLE_TERMIOS_SIGNALS = 19997, '?'
def cmd(f: F) -> F:

View File

@@ -147,5 +147,9 @@ class PathCompleter:
return ''
def get_path(prompt: str = '> ') -> str:
return PathCompleter(prompt).input()
def develop() -> None:
PathCompleter().input()

View File

@@ -1,12 +1,18 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from contextlib import suppress
from typing import Tuple
from typing import TYPE_CHECKING, Optional, Sequence, Tuple, cast
from kitty.types import run_once
from .operations import raw_mode, set_cursor_visible
if TYPE_CHECKING:
from kitty.options.types import Options
def get_key_press(allowed: str, default: str) -> str:
response = default
@@ -50,6 +56,31 @@ def human_size(
return format_number(size / 1024**exponent, max_num_of_decimals) + sep + unit_list[exponent]
def kitty_opts() -> 'Options':
from kitty.fast_data_types import get_options, set_options
try:
ans = cast(Optional['Options'], get_options())
except RuntimeError:
ans = None
if ans is None:
from kitty.cli import create_default_opts
from kitty.utils import suppress_error_logging
with suppress_error_logging():
ans = create_default_opts()
set_options(ans)
return ans
def set_kitty_opts(paths: Sequence[str], overrides: Sequence[str] = ()) -> 'Options':
from kitty.config import load_config
from kitty.fast_data_types import set_options
from kitty.utils import suppress_error_logging
with suppress_error_logging():
opts = load_config(*paths, overrides=overrides or None)
set_options(opts)
return opts
def report_error(msg: str = '', return_code: int = 1, print_exc: bool = False) -> None:
' Report an error also sending the overlay ready message to ensure kitten is visible '
from .operations import overlay_ready
@@ -69,3 +100,26 @@ def report_error(msg: str = '', return_code: int = 1, print_exc: bool = False) -
def report_unhandled_error(msg: str = '') -> None:
' Report an unhandled exception with the overlay ready message '
return report_error(msg, print_exc=True)
@run_once
def running_in_tmux() -> str:
socket = os.environ.get('TMUX')
if not socket:
return ''
parts = socket.split(',')
if len(parts) < 2:
return ''
try:
if not os.access(parts[0], os.R_OK | os.W_OK):
return ''
except OSError:
return ''
from kitty.child import cmdline_of_pid
c = cmdline_of_pid(int(parts[1]))
if not c:
return ''
exe = os.path.basename(c[0])
if exe.lower() == 'tmux':
return exe
return ''

File diff suppressed because one or more lines are too long

View File

@@ -101,14 +101,14 @@ static PyModuleDef_Slot slots[] = { {Py_mod_exec, (void*)exec_module}, {0, NULL}
END_IGNORE_PEDANTIC_WARNINGS
static struct PyModuleDef module = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "unicode_names", /* name of module */
.m_doc = NULL,
.m_slots = slots,
.m_methods = module_methods
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "unicode_names", /* name of module */
.m_doc = NULL,
.m_slots = slots,
.m_methods = module_methods
};
EXPORTED PyMODINIT_FUNC
PyInit_unicode_names(void) {
return PyModuleDef_Init(&module);
return PyModuleDef_Init(&module);
}

View File

@@ -1 +0,0 @@
# empty

View File

@@ -1,8 +1,8 @@
#version GLSL_VERSION
#define left -1.0f
#define top 1.0f
#define right 1.0f
#define bottom -1.0f
#define left 0
#define top 1
#define right 2
#define bottom 3
#define tex_left 0
#define tex_top 0
#define tex_right 1
@@ -14,15 +14,10 @@
uniform float tiled;
uniform vec4 sizes; // [ window_width, window_height, image_width, image_height ]
uniform vec4 positions; // [ left, top, right, bottom ]
out vec2 texcoord;
const vec2 pos_map[] = vec2[4](
vec2(left, top),
vec2(left, bottom),
vec2(right, bottom),
vec2(right, top)
);
const vec2 tex_map[] = vec2[4](
vec2(tex_left, tex_top),
vec2(tex_left, tex_bottom),
@@ -39,6 +34,12 @@ float tiling_factor(int i) {
}
void main() {
vec2 pos_map[] = vec2[4](
vec2(positions[left], positions[top]),
vec2(positions[left], positions[bottom]),
vec2(positions[right], positions[bottom]),
vec2(positions[right], positions[top])
);
vec2 tex_coords = tex_map[gl_VertexID];
texcoord = vec2(
tex_coords[x_axis] * tiling_factor(x_axis),

View File

@@ -1,8 +1,7 @@
#version GLSL_VERSION
uniform float background_opacity;
in vec3 color;
in vec4 color;
out vec4 final_color;
void main() {
final_color = vec4(color * background_opacity, background_opacity);
final_color = color;
}

View File

@@ -6,9 +6,11 @@ uniform vec3 inactive_border_color;
uniform vec3 bell_border_color;
uniform vec3 tab_bar_bg;
uniform vec3 tab_bar_margin_color;
uniform float background_opacity;
uniform float tint_opacity, tint_premult;
in vec4 rect; // left, top, right, bottom
in uint rect_color;
out vec3 color;
out vec4 color;
// indices into the rect vector
const int LEFT = 0;
@@ -35,5 +37,9 @@ void main() {
gl_Position = vec4(rect[pos.x], rect[pos.y], 0, 1);
int rc = int(rect_color);
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
color = W(0, default_bg) + W(1, active_border_color) + W(2, inactive_border_color) + W(3, window_bg) + W(4, bell_border_color) + W(5, tab_bar_bg) + W(6, tab_bar_margin_color);
vec3 color3 = W(0, default_bg) + W(1, active_border_color) + W(2, inactive_border_color) + W(3, window_bg) + W(4, bell_border_color) + W(5, tab_bar_bg) + W(6, tab_bar_margin_color);
float is_default_bg = float(rc & 1);
float final_opacity = is_default_bg * tint_opacity + (1 - is_default_bg) * background_opacity;
float final_premult_opacity = is_default_bg * tint_premult + (1 - is_default_bg) * background_opacity;
color = vec4(color3 * final_premult_opacity, final_opacity);
}

View File

@@ -86,7 +86,7 @@ class Borders:
draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
has_background_image = os_window_has_background_image(self.os_window_id)
if not has_background_image:
if not has_background_image or opts.background_tint > 0.0:
for br in current_layout.blank_rects:
add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg)
for tbr in tab_bar_rects:

View File

@@ -2,6 +2,7 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
import base64
import json
import os
import re
@@ -11,35 +12,40 @@ from functools import partial
from gettext import gettext as _
from time import monotonic
from typing import (
Any, Callable, Container, Dict, Iterable, Iterator, List, Optional, Set,
Tuple, Union
TYPE_CHECKING, Any, Callable, Container, Dict, Iterable, Iterator, List, Optional,
Set, Tuple, Union,
)
from weakref import WeakValueDictionary
from .child import cached_process_data, default_env, set_default_env
from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .clipboard import (
Clipboard, get_clipboard_string, get_primary_selection, set_clipboard_string,
set_primary_selection,
)
from .conf.utils import BadLine, KeyAction, to_cmdline
from .config import common_opts_as_dict, prepare_config_file_for_editing
from .constants import (
appname, cache_dir, config_dir, is_macos, is_wayland, kitty_exe,
logo_png_file, supports_primary_selection, website_url
RC_ENCRYPTION_PROTOCOL_VERSION, appname, cache_dir, clear_handled_signals,
config_dir, handled_signals, is_macos, is_wayland, kitty_exe, logo_png_file,
supports_primary_selection, website_url,
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT,
GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS,
GLFW_MOD_SUPER, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, GLFW_PRIMARY_SELECTION,
IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color,
KeyEvent, add_timer, apply_options_update, background_opacity_of,
change_background_opacity, change_os_window_state, cocoa_set_menubar_title,
create_os_window, current_application_quit_request, current_os_window,
destroy_global_data, focus_os_window, get_boss, get_clipboard_string,
get_options, get_os_window_size, global_font_size,
mark_os_window_for_close, os_window_font_size, patch_global_colors,
redirect_mouse_handling, ring_bell, safe_pipe,
set_application_quit_request, set_background_image, set_boss,
set_clipboard_string, set_in_sequence_mode, set_options,
set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen,
toggle_maximized, toggle_secure_input
EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update,
background_opacity_of, change_background_opacity, change_os_window_state,
cocoa_set_menubar_title, create_os_window, current_application_quit_request,
current_focused_os_window_id, current_os_window, destroy_global_data,
focus_os_window, get_boss, get_options, get_os_window_size, global_font_size,
last_focused_os_window_id, mark_os_window_for_close, os_window_font_size,
patch_global_colors, redirect_mouse_handling, ring_bell, run_with_activation_token,
safe_pipe, send_data_to_peer, set_application_quit_request, set_background_image,
set_boss, set_in_sequence_mode, set_options, set_os_window_size,
set_os_window_title, thread_write, toggle_fullscreen, toggle_maximized,
toggle_secure_input,
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches
@@ -48,27 +54,32 @@ from .notify import notification_activated
from .options.types import Options
from .options.utils import MINIMUM_FONT_SIZE, KeyMap, SubSequenceMap
from .os_window_size import initial_window_size_func
from .prewarm import PrewarmProcess
from .rgb import color_from_int
from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
)
from .types import _T, AsyncResponse, SingleKey, WindowSystemMouseEvent, ac
from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
from .types import _T, AsyncResponse, WindowSystemMouseEvent, ac
from .typing import PopenType, TypedDict
from .utils import (
cleanup_ssh_control_masters, func_name, get_editor, get_new_os_window_size,
get_primary_selection, is_path_in_temp_dir, less_version, log_error,
macos_version, open_url, parse_address_spec, parse_uri_list,
platform_window_id, remove_socket_file, safe_print, set_primary_selection,
single_instance, startup_notification_handler, which
is_path_in_temp_dir, less_version, log_error, macos_version, open_url,
parse_address_spec, parse_uri_list, platform_window_id, remove_socket_file,
safe_print, single_instance, startup_notification_handler, which,
)
from .window import CommandOutput, CwdRequest, Window
if TYPE_CHECKING:
from .rc.base import ResponseType
RCResponse = Union[Dict[str, Any], None, AsyncResponse]
class OSWindowDict(TypedDict):
id: int
platform_window_id: Optional[int]
is_focused: bool
is_active: bool
last_focused: bool
tabs: List[TabDict]
wm_class: str
wm_name: str
@@ -216,7 +227,7 @@ class VisualSelect:
t = tm.tab_for_id(self.prev_tab_id)
if t is not tm.active_tab and t is not None:
tm.set_active_tab(t)
if current_os_window() != self.prev_os_window_id and self.prev_os_window_id is not None:
if current_focused_os_window_id() != self.prev_os_window_id and self.prev_os_window_id is not None:
focus_os_window(self.prev_os_window_id, True)
@@ -227,10 +238,15 @@ class Boss:
opts: Options,
args: CLIOptions,
cached_values: Dict[str, Any],
global_shortcuts: Dict[str, SingleKey]
global_shortcuts: Dict[str, SingleKey],
prewarm: PrewarmProcess,
):
set_layout_options(opts)
self.clipboard = Clipboard()
self.primary_selection = Clipboard(GLFW_PRIMARY_SELECTION)
self.update_check_started = False
self.encryption_key = EllipticCurveKey()
self.encryption_public_key = f'{RC_ENCRYPTION_PROTOCOL_VERSION}:{base64.b85encode(self.encryption_key.public).decode("ascii")}'
self.clipboard_buffers: Dict[str, str] = {}
self.update_check_process: Optional['PopenType[bytes]'] = None
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
@@ -250,12 +266,19 @@ class Boss:
# we dont allow reloading the config file to change
# allow_remote_control
self.allow_remote_control = opts.allow_remote_control
if args.listen_on and (self.allow_remote_control in ('y', 'socket-only')):
if self.allow_remote_control in ('y', 'yes', 'true'):
self.allow_remote_control = 'y'
elif self.allow_remote_control in ('n', 'no', 'false'):
self.allow_remote_control = 'n'
self.listening_on = ''
if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'):
listen_fd = listen_on(args.listen_on)
self.listening_on = args.listen_on
self.prewarm = prewarm
self.child_monitor = ChildMonitor(
self.on_child_death,
DumpCommands(args) if args.dump_commands or args.dump_bytes else None,
talk_fd, listen_fd
talk_fd, listen_fd, self.prewarm.take_from_worker_fd()
)
set_boss(self)
self.args = args
@@ -264,9 +287,7 @@ class Boss:
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
self.update_keymap()
if is_macos:
from .fast_data_types import (
cocoa_set_notification_activated_callback
)
from .fast_data_types import cocoa_set_notification_activated_callback
cocoa_set_notification_activated_callback(notification_activated)
def update_keymap(self) -> None:
@@ -276,14 +297,22 @@ class Boss:
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
focused_os_window = wid = 0
token = os.environ.pop('XDG_ACTIVATION_TOKEN', '')
for startup_session in si:
self.add_os_window(startup_session, os_window_id=os_window_id)
wid = self.add_os_window(startup_session, os_window_id=os_window_id)
if startup_session.focus_os_window:
focused_os_window = wid
os_window_id = None
if self.args.start_as != 'normal':
if self.args.start_as == 'fullscreen':
self.toggle_fullscreen()
else:
change_os_window_state(self.args.start_as)
if focused_os_window > 0:
focus_os_window(focused_os_window, True, token)
elif token and is_wayland() and wid:
focus_os_window(wid, True, token)
def add_os_window(
self,
@@ -314,14 +343,15 @@ class Boss:
def list_os_windows(self, self_window: Optional[Window] = None) -> Iterator[OSWindowDict]:
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,
'platform_window_id': platform_window_id(os_window_id),
'is_focused': tm is active_tab_manager,
'tabs': list(tm.list_tabs(active_tab, active_window, self_window)),
'is_active': tm is active_tab_manager,
'is_focused': current_focused_os_window_id() == os_window_id,
'last_focused': os_window_id == last_focused_os_window_id(),
'tabs': list(tm.list_tabs(self_window)),
'wm_class': tm.wm_class,
'wm_name': tm.wm_name
}
@@ -346,6 +376,10 @@ class Boss:
return
from .search_query_parser import search
tab = self.active_tab
if current_focused_os_window_id() <= 0:
tm = self.os_window_map.get(last_focused_os_window_id())
if tm is not None:
tab = tm.active_tab
def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]:
return {wid for wid in candidates if self.window_id_map[wid].matches_query(location, query, tab)}
@@ -367,6 +401,8 @@ class Boss:
return self.all_tabs
from .search_query_parser import search
tm = self.active_tab_manager
if current_focused_os_window_id() <= 0:
tm = self.os_window_map.get(last_focused_os_window_id()) or tm
tim = {t.id: t for t in self.all_tabs}
def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]:
@@ -393,7 +429,7 @@ class Boss:
if tab is not self.active_tab:
tm.set_active_tab(tab, for_keep_focus=window.tabref() if for_keep_focus else None)
tab.set_active_window(w, for_keep_focus=window if for_keep_focus else None)
if switch_os_window_if_needed and current_os_window() != os_window_id:
if switch_os_window_if_needed and current_focused_os_window_id() != os_window_id:
focus_os_window(os_window_id, True)
return os_window_id
return None
@@ -434,26 +470,106 @@ class Boss:
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
self.window_id_map[window.id] = window
def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> Union[Dict[str, Any], None, AsyncResponse]:
from .remote_control import handle_cmd
def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> RCResponse:
from .remote_control import is_cmd_allowed, parse_cmd
response = None
window = window or None
if self.allow_remote_control == 'y' or peer_id > 0 or getattr(window, 'allow_remote_control', False):
try:
response = handle_cmd(self, window, cmd, peer_id)
except Exception as err:
import traceback
response = {'ok': False, 'error': str(err)}
if not getattr(err, 'hide_traceback', False):
response['tb'] = traceback.format_exc()
else:
no_response = False
try:
no_response = json.loads(cmd).get('no_response')
except Exception:
pass
window_has_remote_control = bool(window and window.allow_remote_control)
if not window_has_remote_control:
if self.allow_remote_control == 'n':
return {'ok': False, 'error': 'Remote control is disabled'}
if self.allow_remote_control == 'socket-only' and peer_id == 0:
return {'ok': False, 'error': 'Remote control is allowed over a socket only'}
try:
pcmd = parse_cmd(cmd, self.encryption_key)
except Exception as e:
log_error(f'Failed to parse remote command with error: {e}')
return response
if not pcmd:
return response
extra_data: Dict[str, Any] = {}
try:
allowed_unconditionally = (
self.allow_remote_control == 'y' or (peer_id > 0 and self.allow_remote_control in ('socket-only', 'socket')) or
(window and window.remote_control_allowed(pcmd, extra_data)))
except PermissionError:
return {'ok': False, 'error': 'Remote control disallowed by window specific password'}
if allowed_unconditionally:
return self._execute_remote_command(pcmd, window, peer_id)
q = is_cmd_allowed(pcmd, window, peer_id > 0, extra_data)
if q is True:
return self._execute_remote_command(pcmd, window, peer_id)
if q is None:
if self.ask_if_remote_cmd_is_allowed(pcmd, window, peer_id):
return AsyncResponse()
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
if q is False and pcmd.get('password'):
response['error'] = 'The user rejected this password or it is disallowed by remote_control_password in kitty.conf'
no_response = pcmd.get('no_response') or False
if no_response:
return None
return response
def ask_if_remote_cmd_is_allowed(self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0) -> bool:
from kittens.tui.operations import styled
in_flight = 0
for w in self.window_id_map.values():
if w.window_custom_type == 'remote_command_permission_dialog':
in_flight += 1
if in_flight > 4:
log_error('Denying remote command permission as there are too many existing permission requests')
return False
wid = 0 if window is None else window.id
hidden_text = styled(pcmd['password'], fg='yellow')
overlay_window = self.choose(
_('A program wishes to control kitty.\n'
'Action: {1}\n' 'Password: {0}\n\n' '{2}'
).format(
hidden_text, styled(pcmd['cmd'], fg='magenta'),
'\x1b[m' + styled(_(
'Note that allowing the password will allow all future actions using the same password, in this kitty instance.'
), dim=True, italic=True)),
partial(self.remote_cmd_permission_received, pcmd, wid, peer_id),
'a;green:Allow request', 'p;yellow:Allow password', 'r;magenta:Deny request', 'd;red:Deny password',
window=window, default='a', hidden_text=hidden_text
)
if overlay_window is None:
return False
overlay_window.window_custom_type = 'remote_command_permission_dialog'
return True
def remote_cmd_permission_received(self, pcmd: Dict[str, Any], window_id: int, peer_id: int, choice: str) -> None:
from .remote_control import encode_response_for_peer, set_user_password_allowed
response: RCResponse = None
window = self.window_id_map.get(window_id)
choice = choice or 'r'
if choice in ('r', 'd'):
if choice == 'd':
set_user_password_allowed(pcmd['password'], False)
no_response = pcmd.get('no_response') or False
if not no_response:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
response = {'ok': False, 'error': 'The user rejected this ' + ('request' if choice == 'r' else 'password')}
elif choice in ('a', 'p'):
if choice == 'p':
set_user_password_allowed(pcmd['password'], True)
response = self._execute_remote_command(pcmd, window, peer_id)
if window is not None and response is not None and not isinstance(response, AsyncResponse):
window.send_cmd_response(response)
if peer_id > 0:
if response is None:
send_data_to_peer(peer_id, b'')
elif not isinstance(response, AsyncResponse):
send_data_to_peer(peer_id, encode_response_for_peer(response))
def _execute_remote_command(self, pcmd: Dict[str, Any], window: Optional[Window] = None, peer_id: int = 0) -> RCResponse:
from .remote_control import handle_cmd
try:
response = handle_cmd(self, window, pcmd, peer_id)
except Exception as err:
import traceback
response = {'ok': False, 'error': str(err)}
if not getattr(err, 'hide_traceback', False):
response['tb'] = traceback.format_exc()
return response
@ac('misc', '''
@@ -461,34 +577,49 @@ class Boss:
For example::
map F1 remote_control set-spacing margin=30
map f1 remote_control set-spacing margin=30
See :ref:`rc_mapping` for details.
''')
def remote_control(self, *args: str) -> None:
from .rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli
)
from .remote_control import parse_rc_args
try:
global_opts, items = parse_rc_args(['@'] + list(args))
if not items:
return
cmd = items[0]
c = command_for_name(cmd)
opts, items = parse_subcommand_cli(c, items)
payload = c.message_to_kitty(global_opts, opts, items)
import types
if isinstance(payload, types.GeneratorType):
for x in payload:
c.response_from_kitty(self, self.active_window, PayloadGetter(c, x if isinstance(x, dict) else {}))
else:
c.response_from_kitty(self, self.active_window, PayloadGetter(c, payload if isinstance(payload, dict) else {}))
self.call_remote_control(self.active_window, args)
except (Exception, SystemExit):
import traceback
tb = traceback.format_exc()
self.show_error(_('remote_control mapping failed'), tb)
def call_remote_control(self, active_window: Optional[Window], args: Tuple[str, ...]) -> 'ResponseType':
from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
from .remote_control import parse_rc_args
aa = list(args)
silent = False
if aa and aa[0].startswith('!'):
aa[0] = aa[0][1:]
silent = True
try:
global_opts, items = parse_rc_args(['@'] + aa)
if not items:
return None
cmd = items[0]
c = command_for_name(cmd)
opts, items = parse_subcommand_cli(c, items)
payload = c.message_to_kitty(global_opts, opts, items)
except SystemExit as e:
raise Exception(str(e)) from e
import types
try:
if isinstance(payload, types.GeneratorType):
for x in payload:
c.response_from_kitty(self, active_window, PayloadGetter(c, x if isinstance(x, dict) else {}))
return None
return c.response_from_kitty(self, active_window, PayloadGetter(c, payload if isinstance(payload, dict) else {}))
except Exception as e:
if silent:
log_error(f'Failed to run remote_control mapping: {aa} with error: {e}')
return None
raise
def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Union[bytes, bool, None]:
cmd_prefix = b'\x1bP@kitty-cmd'
terminator = b'\x1b\\'
@@ -506,6 +637,7 @@ class Boss:
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
from .cli_stub import CLIOptions
startup_id = data.get('startup_id')
activation_token = data.get('activation_token', '')
args, rest = parse_args(data['args'][1:], result_class=CLIOptions)
cmdline_args_for_open = data.get('cmdline_args_for_open')
if cmdline_args_for_open:
@@ -518,14 +650,21 @@ class Boss:
args.session = PreReadSession(data['stdin'])
if not os.path.isabs(args.directory):
args.directory = os.path.join(data['cwd'], args.directory)
focused_os_window = os_window_id = 0
for session in create_sessions(opts, args, respect_cwd=True):
os_window_id = self.add_os_window(
session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id,
override_title=args.title or None)
if session.focus_os_window:
focused_os_window = os_window_id
if opts.background_opacity != get_options().background_opacity:
self._set_os_window_background_opacity(os_window_id, opts.background_opacity)
if data.get('notify_on_os_window_death'):
self.os_window_death_actions[os_window_id] = partial(self.notify_on_os_window_death, data['notify_on_os_window_death'])
if focused_os_window > 0:
focus_os_window(focused_os_window, True, activation_token or '')
elif activation_token and is_wayland() and os_window_id:
focus_os_window(os_window_id, True, activation_token or '')
else:
log_error('Unknown message received from peer, ignoring')
return None
@@ -649,10 +788,19 @@ class Boss:
confirm_on_cancel: bool = False, # on closing window
confirm_on_accept: bool = True, # on pressing enter
) -> None:
result: bool = False
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') == 'y', *args)
self._run_kitten('ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
window=window, custom_callback=callback_, default_data={'response': 'y' if confirm_on_cancel else 'n'})
nonlocal result
result = res.get('response') == 'y'
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result, *args)
self.run_kitten_with_metadata(
'ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal,
default_data={'response': 'y' if confirm_on_cancel else 'n'})
def choose(
self, msg: str, # can contain newlines and ANSI formatting
@@ -660,15 +808,39 @@ class Boss:
*choices: str, # The choices, see the help for the ask kitten for format of a choice
window: Optional[Window] = None, # the window associated with the confirmation
default: str = '', # the default choice when the user presses Enter
) -> None:
hidden_text: str = '', # text to hide in the message
hidden_text_placeholder: str = 'HIDDEN_TEXT_PLACEHOLDER', # placeholder text to insert in to message
unhide_key: str = 'u', # key to press to unhide hidden text
) -> Optional[Window]:
result: str = ''
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') or '')
nonlocal result
result = res.get('response') or ''
if hidden_text:
msg = msg.replace(hidden_text, hidden_text_placeholder)
cmd = ['--type=choices', '--message', msg]
if default:
cmd += ['-d', default]
for c in choices:
cmd += ['-c', c]
self._run_kitten('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''})
if hidden_text:
cmd += ['--hidden-text-placeholder', hidden_text_placeholder, '--unhide-key', unhide_key]
input_data = hidden_text
else:
input_data = None
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result)
ans = self.run_kitten_with_metadata(
'ask', cmd, window=window, custom_callback=callback_, input_data=input_data, default_data={'response': ''},
action_on_removal=on_popup_overlay_removal
)
if isinstance(ans, Window):
return ans
return None
def get_line(
self, msg: str, # can contain newlines and ANSI formatting
@@ -677,10 +849,19 @@ class Boss:
prompt: str = '> ',
is_password: bool = False
) -> None:
result: str = ''
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
callback(res.get('response') or '')
nonlocal result
result = res.get('response') or ''
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result)
cmd = ['--type', 'password' if is_password else 'line', '--message', msg, '--prompt', prompt]
self._run_kitten('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''})
self.run_kitten_with_metadata(
'ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal
)
def confirm_tab_close(self, tab: Tab) -> None:
x = get_options().confirm_os_window_close
@@ -732,6 +913,8 @@ class Boss:
if not getattr(self, 'io_thread_started', False):
self.child_monitor.start()
self.io_thread_started = True
for signum in self.child_monitor.handled_signals():
handled_signals.add(signum)
urls: List[str] = getattr(sys, 'cmdline_args_for_open', [])
if urls:
delattr(sys, 'cmdline_args_for_open')
@@ -906,8 +1089,14 @@ class Boss:
@property
def active_tab_manager(self) -> Optional[TabManager]:
os_window_id = current_os_window()
return None if os_window_id is None else self.os_window_map.get(os_window_id)
os_window_id = current_focused_os_window_id()
if os_window_id <= 0:
os_window_id = last_focused_os_window_id()
if os_window_id <= 0:
q = current_os_window()
if q is not None:
os_window_id = q
return self.os_window_map.get(os_window_id)
@property
def active_tab(self) -> Optional[Tab]:
@@ -1073,7 +1262,7 @@ class Boss:
w = self.active_window
if w is None:
return
overlay_window = self._run_kitten('resize_window', args=[
overlay_window = self.run_kitten_with_metadata('resize_window', args=[
f'--horizontal-increment={get_options().window_resize_step_cells}',
f'--vertical-increment={get_options().window_resize_step_lines}'
])
@@ -1099,6 +1288,13 @@ class Boss:
w, h = get_new_os_window_size(metrics, width, height, unit, incremental, has_window_scaling)
set_os_window_size(os_window_id, w, h)
def tab_for_id(self, tab_id: int) -> Optional[Tab]:
for tm in self.os_window_map.values():
tab = tm.tab_for_id(tab_id)
if tab is not None:
return tab
return None
def default_bg_changed_for(self, window_id: int) -> None:
w = self.window_id_map.get(window_id)
if w is not None:
@@ -1333,9 +1529,7 @@ class Boss:
@ac('misc', 'Edit the kitty.conf config file in your favorite text editor')
def edit_config_file(self, *a: Any) -> None:
confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay.
cmd = [kitty_exe(), '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + get_editor(get_options()) + [confpath]
cmd = [kitty_exe(), '+edit'] + get_editor(get_options()) + [confpath]
self.new_os_window(*cmd)
def run_kitten_with_metadata(
@@ -1419,10 +1613,10 @@ class Boss:
@ac('misc', 'Run the specified kitten. See :doc:`/kittens/custom` for details')
def kitten(self, kitten: str, *kargs: str) -> None:
self._run_kitten(kitten, kargs)
self.run_kitten_with_metadata(kitten, kargs)
def run_kitten(self, kitten: str, *args: str) -> None:
self._run_kitten(kitten, args)
self.run_kitten_with_metadata(kitten, args)
def on_kitten_finish(
self, target_window_id: int, end_kitten: Callable[[Dict[str, Any], int, 'Boss'], None],
@@ -1437,16 +1631,34 @@ class Boss:
@ac('misc', 'Input an arbitrary unicode character. See :doc:`/kittens/unicode_input` for details.')
def input_unicode_character(self) -> None:
self._run_kitten('unicode_input')
self.run_kitten_with_metadata('unicode_input')
@ac('tab', 'Change the title of the active tab')
def set_tab_title(self) -> None:
@ac(
'tab', '''
Change the title of the active tab interactively, by typing in the new title.
If you specify an argument to this action then that is used as the title instead of asking for it.
Use the empty string ("") to reset the title to default. For example::
# interactive usage
map f1 set_tab_title
# set a specific title
map f2 set_tab_title some title
# reset to default
map f3 set_tab_title ""
'''
)
def set_tab_title(self, title: Optional[str] = None) -> None:
tab = self.active_tab
if tab:
if title is not None:
if title in ('""', "''"):
title = ''
tab.set_title(title)
return
args = [
'--name=tab-title', '--message', _('Enter the new title for this tab below.'),
'--default', tab.name or tab.title, 'do_set_tab_title', str(tab.id)]
self._run_kitten('ask', args)
self.run_kitten_with_metadata('ask', args)
def do_set_tab_title(self, title: str, tab_id: int) -> None:
tm = self.active_tab_manager
@@ -1463,7 +1675,7 @@ class Boss:
if ec != (None, None, None):
import traceback
tb = traceback.format_exc()
self._run_kitten('show_error', args=['--title', title], input_data=json.dumps({'msg': msg, 'tb': tb}))
self.run_kitten_with_metadata('show_error', args=['--title', title], input_data=json.dumps({'msg': msg, 'tb': tb}))
@ac('mk', 'Create a new marker')
def create_marker(self) -> None:
@@ -1483,7 +1695,7 @@ class Boss:
except Exception as err:
self.show_error(_('Invalid marker specification'), str(err))
self._run_kitten('ask', [
self.run_kitten_with_metadata('ask', [
'--name=create-marker', '--message',
_('Create marker, for example:\ntext 1 ERROR\nSee {}\n').format(website_url('marks'))
],
@@ -1495,31 +1707,34 @@ class Boss:
cmd = [kitty_exe(), '@']
aw = self.active_window
if aw is not None:
kw['env'] = {'KITTY_SHELL_ACTIVE_WINDOW_ID': str(aw.id)}
env = {'KITTY_SHELL_ACTIVE_WINDOW_ID': str(aw.id)}
at = self.active_tab
if at is not None:
env['KITTY_SHELL_ACTIVE_TAB_ID'] = str(at.id)
kw['env'] = env
if window_type == 'tab':
tab = self._new_tab(SpecialWindow(cmd, **kw))
if tab is not None:
for w in tab:
w.allow_remote_control = True
window = w
elif window_type == 'os_window':
os_window_id = self._new_os_window(SpecialWindow(cmd, **kw))
for tab in self.os_window_map[os_window_id]:
for w in tab:
w.allow_remote_control = True
window = w
elif window_type == 'overlay':
tab = self.active_tab
if aw is not None and tab is not None:
kw['overlay_for'] = aw.id
window = tab.new_special_window(SpecialWindow(cmd, **kw), allow_remote_control=True)
window = tab.new_special_window(SpecialWindow(cmd, **kw))
else:
tab = self.active_tab
if tab is not None:
window = tab.new_special_window(SpecialWindow(cmd, **kw), allow_remote_control=True)
window = tab.new_special_window(SpecialWindow(cmd, **kw))
path, ext = os.path.splitext(logo_png_file)
window.set_logo(f'{path}-128{ext}', position='bottom-right', alpha=0.25)
window.allow_remote_control = True
def switch_focus_to(self, window_id: int) -> None:
tab = self.active_tab
@@ -1545,11 +1760,23 @@ class Boss:
if actions:
self.drain_actions(actions)
if not found_action:
open_url(url, program or get_options().open_url_with, cwd=cwd)
extra_env = {}
if self.listening_on:
extra_env['KITTY_LISTEN_ON'] = self.listening_on
def doit(activation_token: str = '') -> None:
if activation_token:
extra_env['XDG_ACTIVATION_TOKEN'] = activation_token
open_url(url, program or get_options().open_url_with, cwd=cwd, extra_env=extra_env)
if is_wayland():
run_with_activation_token(doit)
else:
doit()
@ac('misc', 'Click a URL using the keyboard')
def open_url_with_hints(self) -> None:
self._run_kitten('hints')
self.run_kitten_with_metadata('hints')
def drain_actions(self, actions: List[KeyAction], window_for_dispatch: Optional[Window] = None, dispatch_type: str = 'KeyPress') -> None:
@@ -1601,6 +1828,12 @@ class Boss:
if get_options().copy_on_select:
self.copy_to_buffer(get_options().copy_on_select)
def get_active_selection(self) -> Optional[str]:
w = self.active_window
if w is not None and not w.destroyed:
return w.text_for_selection()
return None
@ac('cp', '''
Copy the selection from the active window to the specified buffer
@@ -1733,18 +1966,28 @@ class Boss:
with suppress(Exception):
cwd = cwd_from.cwd_of_child
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd)
except Exception:
os.close(w)
def doit(activation_token: str = '') -> None:
nonlocal env
if activation_token:
if env is None:
env = default_env().copy()
env['XDG_ACTIVATION_TOKEN'] = activation_token
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals)
except Exception:
os.close(w)
else:
thread_write(w, stdin)
finally:
os.close(r)
else:
thread_write(w, stdin)
finally:
os.close(r)
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals)
if is_wayland():
run_with_activation_token(doit)
else:
subprocess.Popen(cmd, env=env, cwd=cwd)
doit()
def pipe(self, source: str, dest: str, exe: str, *args: str) -> Optional[Window]:
cmd = [exe] + list(args)
@@ -1805,6 +2048,8 @@ class Boss:
special_window = args
else:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
if not self.os_window_map:
self.add_os_window()
tm = self.active_tab_manager
if tm is not None:
return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor)
@@ -1826,8 +2071,6 @@ class Boss:
self._create_tab(list(args), cwd_from=CwdRequest(self.active_window_for_cwd))
def new_tab_with_wd(self, wd: Union[str, List[str]], str_is_multiple_paths: bool = False) -> None:
if not self.os_window_map:
self.add_os_window()
if isinstance(wd, str):
wd = wd.split(os.pathsep) if str_is_multiple_paths else [wd]
for path in wd:
@@ -1835,6 +2078,11 @@ class Boss:
self._new_tab(special_window)
def _new_window(self, args: List[str], cwd_from: Optional[CwdRequest] = None) -> Optional[Window]:
if not self.os_window_map:
os_window_id = self.add_os_window()
tm = self.os_window_map.get(os_window_id)
if tm is not None and not tm.active_tab:
tm.new_tab(empty_tab=True)
tab = self.active_tab
if tab is None:
return None
@@ -1953,6 +2201,7 @@ class Boss:
for w in self.all_windows:
self.default_bg_changed_for(w.id)
w.refresh()
self.prewarm.reload_kitty_config()
@ac('misc', '''
Reload the config file
@@ -1964,8 +2213,8 @@ class Boss:
map f5 load_config_file /path/to/some/kitty.conf
''')
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None:
from .config import load_config
from .cli import default_config_paths
from .config import load_config
old_opts = get_options()
prev_paths = old_opts.config_paths or default_config_paths(self.args.config)
paths = paths or prev_paths
@@ -1974,8 +2223,8 @@ class Boss:
if bad_lines:
self.show_bad_config_lines(bad_lines)
self.apply_new_options(opts)
from .open_actions import load_open_actions
load_open_actions.clear_cached()
from .open_actions import clear_caches
clear_caches()
def safe_delete_temp_file(self, path: str) -> None:
if is_path_in_temp_dir(path):
@@ -2006,9 +2255,7 @@ class Boss:
log_error(f'Failed to process update check data {raw!r}, with error: {e}')
def dbus_notification_callback(self, activated: bool, a: int, b: Union[int, str]) -> None:
from .notify import (
dbus_notification_activated, dbus_notification_created
)
from .notify import dbus_notification_activated, dbus_notification_created
if activated:
assert isinstance(b, str)
dbus_notification_activated(a, b)
@@ -2037,14 +2284,12 @@ class Boss:
@ac('misc', '''
Change colors in the specified windows
For details, see :ref:`at_set-colors`. For example::
For details, see :ref:`at-set-colors`. For example::
map f5 set_colors --configured /path/to/some/config/file/colors.conf
''')
def set_colors(self, *args: str) -> None:
from kitty.rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli
)
from kitty.rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
from kitty.remote_control import parse_rc_args
c = command_for_name('set_colors')
try:
@@ -2139,7 +2384,7 @@ class Boss:
def done2(target_window_id: int, self: Boss) -> None:
callback(ans)
q = self._run_kitten(
q = self.run_kitten_with_metadata(
'hints', args=(
'--ascending', '--customize-processing=::import::kitty.choose_entry',
'--window-title', title,
@@ -2245,8 +2490,11 @@ class Boss:
@ac('debug', 'Show the environment variables that the kitty process sees')
def show_kitty_env_vars(self) -> None:
w = self.active_window
env = os.environ.copy()
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not getattr(sys, 'kitty_run_data').get('lc_ctype_before_python'):
del env['LC_CTYPE']
if w:
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items())
output = '\n'.join(f'{k}={v}' for k, v in env.items())
self.display_scrollback(w, output, title=_('Current kitty env vars'), report_cursor=False)
@ac('debug', '''
@@ -2285,8 +2533,8 @@ class Boss:
if failures:
from kittens.tui.operations import styled
spec = '\n '.join(styled(u, fg='red') for u in failures)
bdata = f"Unknown URL type, cannot open:\n\n {spec}".encode('utf-8')
spec = '\n '.join(styled(u, fg='yellow') for u in failures)
bdata = json.dumps({'msg': f"Unknown URL type, cannot open:\n {spec}"}).encode('utf-8')
special_window = SpecialWindow([kitty_exe(), '+kitten', 'show_error', '--title', 'Open URL Error'], bdata, 'Open URL Error')
if needs_window_replaced and tab is not None:
tab.new_special_window(special_window)

View File

@@ -20,16 +20,21 @@
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
extern PyTypeObject Screen_Type;
#if defined(__APPLE__) || defined(__OpenBSD__)
#define NO_SIGQUEUE 1
#endif
#ifdef DEBUG_EVENT_LOOP
#define EVDBG(...) log_event(__VA_ARGS__)
#else
#define EVDBG(...)
#endif
#define EXTRA_FDS 2
#define EXTRA_FDS 3
#ifndef MSG_NOSIGNAL
// Apple does not implement MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
@@ -52,7 +57,7 @@ typedef struct {
bool shutting_down;
pthread_t io_thread, talk_thread;
int talk_fd, listen_fd;
int talk_fd, listen_fd, prewarm_fd;
Message *messages;
size_t messages_capacity, messages_count;
LoopData io_loop_data;
@@ -119,11 +124,11 @@ static PyObject *
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
ChildMonitor *self;
PyObject *dump_callback, *death_notify;
int talk_fd = -1, listen_fd = -1;
int talk_fd = -1, listen_fd = -1, prewarm_fd = -1;
int ret;
if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; }
if (!PyArg_ParseTuple(args, "OO|ii", &death_notify, &dump_callback, &talk_fd, &listen_fd)) return NULL;
if (!PyArg_ParseTuple(args, "OO|iii", &death_notify, &dump_callback, &talk_fd, &listen_fd, &prewarm_fd)) return NULL;
if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) {
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
return NULL;
@@ -133,10 +138,10 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
return NULL;
}
self = (ChildMonitor *)type->tp_alloc(type, 0);
if (!init_loop_data(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
if (!install_signal_handlers(&self->io_loop_data)) return PyErr_SetFromErrno(PyExc_OSError);
if (!init_loop_data(&self->io_loop_data, SIGINT, SIGHUP, SIGTERM, SIGCHLD, SIGUSR1, SIGUSR2, 0)) return PyErr_SetFromErrno(PyExc_OSError);
self->talk_fd = talk_fd;
self->listen_fd = listen_fd;
self->prewarm_fd = prewarm_fd;
if (self == NULL) return PyErr_NoMemory();
self->death_notify = death_notify; Py_INCREF(death_notify);
if (dump_callback != Py_None) {
@@ -145,7 +150,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
} else parse_func = parse_worker;
self->count = 0;
children_fds[0].fd = self->io_loop_data.wakeup_read_fd; children_fds[1].fd = self->io_loop_data.signal_read_fd;
children_fds[0].events = POLLIN; children_fds[1].events = POLLIN;
children_fds[2].fd = self->prewarm_fd;
children_fds[0].events = POLLIN; children_fds[1].events = POLLIN; children_fds[2].events = POLLIN;
the_monitor = self;
return (PyObject*) self;
@@ -171,9 +177,21 @@ dealloc(ChildMonitor* self) {
FREE_CHILD(add_queue[add_queue_count]);
}
free_loop_data(&self->io_loop_data);
safe_close(self->prewarm_fd, __FILE__, __LINE__); self->prewarm_fd = -1;
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject*
handled_signals(ChildMonitor *self, PyObject *args UNUSED) {
PyObject *ans = PyTuple_New(self->io_loop_data.num_handled_signals);
if (ans) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(ans); i++) {
PyTuple_SET_ITEM(ans, i, PyLong_FromLong((long)self->io_loop_data.handled_signals[i]));
}
}
return ans;
}
static void
wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) {
wakeup_loop(&self->io_loop_data, in_signal_handler, "io_loop");
@@ -669,16 +687,14 @@ render_os_window(OSWindow *os_window, unsigned int active_window_id, color_type
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;
bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC;
float x_ratio = 1, y_ratio = 1;
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window);
br->is_dirty = false;
bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC;
if (static_live_resize_in_progress) {
x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width;
y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height;
}
if (!static_live_resize_in_progress) {
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window);
br->is_dirty = false;
}
if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, 0, &TD, x_ratio, y_ratio, os_window, true, false, NULL);
for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i;
@@ -740,7 +756,6 @@ render(monotonic_t now, bool input_read) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i;
w->render_calls++;
if (!w->num_tabs) continue;
if (!should_os_window_be_rendered(w)) {
update_os_window_title(w);
@@ -749,8 +764,11 @@ render(monotonic_t now, bool input_read) {
}
if (USE_RENDER_FRAMES && w->render_state != RENDER_FRAME_READY) {
if (w->render_state == RENDER_FRAME_NOT_REQUESTED || no_render_frame_received_recently(w, now, ms_to_monotonic_t(250ll))) request_frame_render(w);
continue;
// dont respect render frames soon after a resize on Wayland as they cause flicker because
// we want to fill the newly resized buffer ASAP, not at compositors convenience
if (!global_state.is_wayland || (monotonic() - w->viewport_resized_at) > s_double_to_monotonic_t(1)) continue;
}
w->render_calls++;
make_os_window_context_current(w);
if (w->live_resize.in_progress && OPT(resize_draw_strategy) >= RESIZE_DRAW_BLANK) {
blank_os_window(w);
@@ -801,11 +819,25 @@ free_twd(ThreadWriteData *x) {
free(x);
}
static PyObject*
sig_queue(PyObject *self UNUSED, PyObject *args) {
int pid, signal, value;
if (!PyArg_ParseTuple(args, "iii", &pid, &signal, &value)) return NULL;
#ifdef NO_SIGQUEUE
if (kill(pid, signal) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
#else
union sigval v;
v.sival_int = value;
if (sigqueue(pid, signal, v) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
#endif
Py_RETURN_NONE;
}
static PyObject*
monitor_pid(PyObject *self UNUSED, PyObject *args) {
long pid;
int pid;
bool ok = true;
if (!PyArg_ParseTuple(args, "l", &pid)) return NULL;
if (!PyArg_ParseTuple(args, "i", &pid)) return NULL;
children_mutex(lock);
if (monitored_pids_count >= arraysz(monitored_pids)) {
PyErr_SetString(PyExc_RuntimeError, "Too many monitored pids");
@@ -940,7 +972,7 @@ process_pending_resizes(monotonic_t now) {
static void
close_os_window(ChildMonitor *self, OSWindow *os_window) {
int w = os_window->window_width, h = os_window->window_height;
int w = os_window->content_area_width, h = os_window->content_area_height;
if (os_window->before_fullscreen.is_set && is_os_window_fullscreen(os_window)) {
w = os_window->before_fullscreen.w; h = os_window->before_fullscreen.h;
}
@@ -1223,12 +1255,13 @@ read_bytes(int fd, Screen *screen) {
typedef struct { bool kill_signal, child_died, reload_config; } SignalSet;
static void
handle_signal(int signum, void *data) {
static bool
handle_signal(const siginfo_t *siginfo, void *data) {
SignalSet *ss = data;
switch(signum) {
switch(siginfo->si_signo) {
case SIGINT:
case SIGTERM:
case SIGHUP:
ss->kill_signal = true;
break;
case SIGCHLD:
@@ -1237,9 +1270,13 @@ handle_signal(int signum, void *data) {
case SIGUSR1:
ss->reload_config = true;
break;
case SIGUSR2:
log_error("Received SIGUSR2: %d\n", siginfo->si_value.sival_int);
break;
default:
break;
}
return true;
}
static void
@@ -1269,6 +1306,34 @@ mark_monitored_pids(pid_t pid, int status) {
children_mutex(unlock);
}
static void
reap_prewarmed_children(ChildMonitor *self, int fd, bool enable_close_on_child_death) {
static char buf[256];
static size_t buf_pos = 0;
while(true) {
ssize_t len = read(fd, buf + buf_pos, sizeof(buf) - buf_pos);
if (len < 0) {
if (errno == EINTR) continue;
if (errno != EIO && errno != EAGAIN) log_error("Call to read() from reap_prewarmed_children() failed with error: %s", strerror(errno));
break;
}
buf_pos += len;
char *nl;
while (buf_pos > 1 && (nl = memchr(buf, '\n', buf_pos)) != NULL) {
size_t sz = nl - buf + 1;
if (enable_close_on_child_death) {
*nl = 0;
int pid = atoi(buf);
if (pid) mark_child_for_removal(self, pid);
}
memmove(buf, buf + sz, sz);
buf_pos -= sz;
}
if (len == 0) break;
}
}
static void
reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
int status;
@@ -1359,6 +1424,9 @@ io_loop(void *data) {
}
if (ss.child_died) reap_children(self, OPT(close_on_child_death));
}
if (children_fds[2].revents && POLLIN) {
reap_prewarmed_children(self, children_fds[2].fd, OPT(close_on_child_death));
}
for (i = 0; i < self->count; i++) {
if (children_fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) {
data_received = true;
@@ -1416,7 +1484,7 @@ io_loop(void *data) {
typedef struct {
id_type id;
size_t num_of_unresponded_messages_sent_to_main_thread;
size_t num_of_unresponded_messages_sent_to_main_thread, fd_array_idx;
bool finished_reading;
int fd;
struct {
@@ -1570,8 +1638,7 @@ wakeup_talk_loop(bool in_signal_handler) {
static void
prune_peers(void) {
for (size_t i = 0; i < talk_data.num_peers; i++) {
size_t idx = talk_data.num_peers - 1 - i;
for (size_t idx = talk_data.num_peers; idx-- > 0;) {
Peer *p = talk_data.peers + idx;
if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) {
free_peer(p);
@@ -1585,7 +1652,7 @@ talk_loop(void *data) {
// The talk thread loop
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyPeerMon");
if (!init_loop_data(&talk_data.loop_data)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
if (!init_loop_data(&talk_data.loop_data, 0)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
PollFD fds[PEER_LIMIT + 8] = {{0}};
size_t num_listen_fds = 0, num_peer_fds = 0;
#define add_listener(which) \
@@ -1604,13 +1671,14 @@ talk_loop(void *data) {
for (size_t i = 0; i < talk_data.num_peers; i++) {
Peer *p = talk_data.peers + i;
if (!p->read.finished || p->write.used) {
fds[num_listen_fds + num_peer_fds].fd = p->fd;
fds[num_listen_fds + num_peer_fds].revents = 0;
p->fd_array_idx = num_listen_fds + num_peer_fds++;
fds[p->fd_array_idx].fd = p->fd;
fds[p->fd_array_idx].revents = 0;
int flags = 0;
if (!p->read.finished) flags |= POLLIN;
if (p->write.used) flags |= POLLOUT;
fds[num_listen_fds + num_peer_fds++].events = flags;
}
fds[p->fd_array_idx].events = flags;
} else p->fd_array_idx = 0;
}
talk_mutex(unlock);
}
@@ -1625,13 +1693,16 @@ talk_loop(void *data) {
if (fds[num_listen_fds - 1].revents & POLLIN) {
drain_fd(fds[num_listen_fds - 1].fd); // wakeup
}
for (size_t i = num_listen_fds, k = 0; i < num_peer_fds + num_listen_fds; i++, k++) {
for (size_t k = 0; k < talk_data.num_peers; k++) {
Peer *p = talk_data.peers + k;
if (fds[i].revents & (POLLIN | POLLHUP)) read_from_peer(self, p);
if (fds[i].revents & POLLOUT) write_to_peer(p);
if (fds[i].revents & POLLNVAL) {
p->read.finished = true;
p->write.failed = true; p->write.used = 0;
if (p->fd_array_idx) {
if (fds[p->fd_array_idx].revents & (POLLIN | POLLHUP)) read_from_peer(self, p);
if (fds[p->fd_array_idx].revents & POLLOUT) write_to_peer(p);
if (fds[p->fd_array_idx].revents & POLLNVAL) {
p->read.finished = true;
p->write.failed = true; p->write.used = 0;
}
break;
}
}
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
@@ -1659,8 +1730,10 @@ send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz) {
peer->write.capacity += msg_sz;
} else fatal("Out of memory");
}
if (msg) memcpy(peer->write.data + peer->write.used, msg, msg_sz);
peer->write.used += msg_sz;
if (msg_sz && msg) {
memcpy(peer->write.data + peer->write.used, msg, msg_sz);
peer->write.used += msg_sz;
}
}
wakeup = true;
break;
@@ -1682,6 +1755,7 @@ static PyMethodDef methods[] = {
METHOD(main_loop, METH_NOARGS)
METHOD(mark_for_close, METH_VARARGS)
METHOD(resize_pty, METH_VARARGS)
METHODB(handled_signals, METH_NOARGS),
{"set_iutf8_winid", (PyCFunction)pyset_iutf8, METH_VARARGS, ""},
{NULL} /* Sentinel */
};
@@ -1720,7 +1794,6 @@ cocoa_set_menubar_title(PyObject *self UNUSED, PyObject *args UNUSED) {
}
static PyObject*
send_data_to_peer(PyObject *self UNUSED, PyObject *args) {
char * msg; Py_ssize_t sz;
unsigned long long peer_id;
@@ -1729,13 +1802,34 @@ send_data_to_peer(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE;
}
static PyObject *
random_unix_socket(PyObject *self UNUSED, PyObject *args UNUSED) {
#ifndef SO_PASSCRED
errno = ENOTSUP;
return PyErr_SetFromErrno(PyExc_OSError);
#else
int fd, optval = 1;
struct sockaddr_un bind_addr = {.sun_family=AF_UNIX};
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return PyErr_SetFromErrno(PyExc_OSError);
if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &optval, sizeof optval) != 0) goto fail;
if (bind(fd, (struct sockaddr *)&bind_addr, sizeof(sa_family_t)) != 0) goto fail;
return PyLong_FromLong((long)fd);
fail:
safe_close(fd, __FILE__, __LINE__);
return PyErr_SetFromErrno(PyExc_OSError);
#endif
}
static PyMethodDef module_methods[] = {
METHODB(safe_pipe, METH_VARARGS),
METHODB(random_unix_socket, METH_NOARGS),
{"add_timer", (PyCFunction)add_python_timer, METH_VARARGS, ""},
{"remove_timer", (PyCFunction)remove_python_timer, METH_VARARGS, ""},
METHODB(monitor_pid, METH_VARARGS),
METHODB(send_data_to_peer, METH_VARARGS),
METHODB(cocoa_set_menubar_title, METH_VARARGS),
{"sigqueue", (PyCFunction)sig_queue, METH_VARARGS, ""},
{NULL} /* Sentinel */
};
@@ -1745,6 +1839,11 @@ init_child_monitor(PyObject *module) {
if (PyModule_AddObject(module, "ChildMonitor", (PyObject *)&ChildMonitor_Type) != 0) return false;
Py_INCREF(&ChildMonitor_Type);
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
#ifdef NO_SIGQUEUE
PyModule_AddIntConstant(module, "has_sigqueue", 0);
#else
PyModule_AddIntConstant(module, "has_sigqueue", 1);
#endif
return true;
}

View File

@@ -79,14 +79,16 @@ wait_for_terminal_ready(int fd) {
static PyObject*
spawn(PyObject *self UNUSED, PyObject *args) {
PyObject *argv_p, *env_p;
PyObject *argv_p, *env_p, *handled_signals_p;
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd;
char *cwd, *exe;
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;
if (!PyArg_ParseTuple(args, "ssO!O!iiiiiiO!", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd, &PyTuple_Type, &handled_signals_p)) 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);
int handled_signals[16] = {0}, num_handled_signals = MIN((int)arraysz(handled_signals), PyTuple_GET_SIZE(handled_signals_p));
for (Py_ssize_t i = 0; i < num_handled_signals; i++) handled_signals[i] = PyLong_AsLong(PyTuple_GET_ITEM(handled_signals_p, i));
#if PY_VERSION_HEX >= 0x03070000
PyOS_BeforeFork();
@@ -98,34 +100,43 @@ spawn(PyObject *self UNUSED, PyObject *args) {
#if PY_VERSION_HEX >= 0x03070000
PyOS_AfterFork_Child();
#endif
sigset_t signals = {0};
struct sigaction act = {.sa_handler=SIG_DFL};
#define SA(which) { if (sigaction(which, &act, NULL) != 0) exit_on_err("sigaction() in child process failed"); }
SA(SIGINT); SA(SIGTERM); SA(SIGCHLD);
const struct sigaction act = {.sa_handler=SIG_DFL};
#define SA(which) if (sigaction(which, &act, NULL) != 0) exit_on_err("sigaction() in child process failed");
for (int si = 0; si < num_handled_signals; si++) { SA(handled_signals[si]); }
// See _Py_RestoreSignals in signalmodule.c for a list of signals python nukes
#ifdef SIGPIPE
SA(SIGPIPE)
#endif
#ifdef SIGXFSZ
SA(SIGXFSZ);
#endif
#ifdef SIGXFZ
SA(SIGXFZ);
#endif
#undef SA
sigset_t signals; sigemptyset(&signals);
if (sigprocmask(SIG_SETMASK, &signals, NULL) != 0) exit_on_err("sigprocmask() in child process failed");
// 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 = safe_open(name, O_RDWR, 0);
int tfd = safe_open(name, O_RDWR | O_CLOEXEC, 0);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
#ifdef TIOCSCTTY
// 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
safe_close(tfd, __FILE__, __LINE__);
// 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 (safe_dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
if (safe_dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
if (stdin_read_fd > -1) {
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
if (safe_dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
safe_close(stdin_read_fd, __FILE__, __LINE__);
safe_close(stdin_write_fd, __FILE__, __LINE__);
} else {
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
if (safe_dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
}
safe_close(slave, __FILE__, __LINE__);
safe_close(master, __FILE__, __LINE__);
@@ -139,9 +150,6 @@ spawn(PyObject *self UNUSED, PyObject *args) {
for (int c = 3; c < 201; c++) safe_close(c, __FILE__, __LINE__);
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
@@ -176,14 +184,36 @@ spawn(PyObject *self UNUSED, PyObject *args) {
return PyLong_FromLong(pid);
}
#ifdef __APPLE__
#include <crt_externs.h>
#else
extern char **environ;
#endif
static PyObject*
clearenv_py(PyObject *self UNUSED, PyObject *args UNUSED) {
#ifdef __APPLE__
char **e = *_NSGetEnviron();
if (e) *e = NULL;
#else
if (environ) *environ = NULL;
#endif
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
METHODB(spawn, METH_VARARGS),
{"clearenv", clearenv_py, METH_NOARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_child(PyObject *module) {
PyModule_AddIntMacro(module, CLD_KILLED);
PyModule_AddIntMacro(module, CLD_STOPPED);
PyModule_AddIntMacro(module, CLD_EXITED);
PyModule_AddIntMacro(module, CLD_CONTINUED);
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

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