Compare commits

..

436 Commits

Author SHA1 Message Date
Kovid Goyal
e06ef732d8 version 0.19.2 2020-11-13 09:32:53 +05:30
Kovid Goyal
792a3e7db8 Dont mix the raw/non-raw malloc/free for pagerhist 2020-11-13 08:12:26 +05:30
Kovid Goyal
9b32842f66 Fix #3096 2020-11-13 07:47:40 +05:30
Kovid Goyal
9816979169 Don't restrict the DCH control code to only the current scroll region
Matches behavior of other terminal emulators.  Fixes #3090
2020-11-12 11:44:09 +05:30
Kovid Goyal
a74679dd95 Fix last character of URL not being detected if it is the only character on a new line
Fixes #3088
2020-11-11 14:00:33 +05:30
Kovid Goyal
a40059729e Fix the send_text action not working in URL handlers
send_text keeps its args as byte string, so variable expansion was not
working. Fixes #3081
2020-11-11 07:51:43 +05:30
Kovid Goyal
6bab967586 Merge branch 'memory-leak' of https://github.com/Luflosi/kitty 2020-11-05 18:15:44 +05:30
Luflosi
bb88b33469 Fix memory leak
Found with the Clang Static Analyzer.
2020-11-05 13:15:19 +01:00
Kovid Goyal
c440470276 Entry point to edit config file 2020-11-04 16:30:29 +05:30
Kovid Goyal
ef6164eecb Merge branch 'incorrect-reference-count-decrement' of https://github.com/Luflosi/kitty 2020-11-04 07:44:00 +05:30
Luflosi
7ad360d872 Fix incorrect decrement of the reference counts of objects
Found with the Clang Static Analyzer.
The function `schedule_notification()` for the new notification API also does not `release` similar objects.
2020-11-03 18:17:58 +01:00
Kovid Goyal
0870a33956 Merge branch 'memory-leak' of https://github.com/Luflosi/kitty 2020-11-01 18:47:31 +05:30
Luflosi
3ccc2f538d Fix memory leak in unlikely failure condition
Found with the Clang Static Analyzer.
2020-11-01 13:41:52 +01:00
Kovid Goyal
17375f0be2 Add a link to nix-shell 2020-10-31 09:10:50 +05:30
Kovid Goyal
5a987a162e Merge branch 'add-shell.nix' of https://github.com/Luflosi/kitty 2020-10-31 07:31:19 +05:30
Luflosi
5058fb9919 Add shell.nix
This makes it possible to execute `nix-shell` to create the correct environment to build kitty. Use `nix-shell --pure` to eliminate most of the influence of the outside system, e.g. globally installed packages.
This works on NixOS and any Linux or macOS system with the Nix package manager installed.
The build inputs are split into `buildInputs`, `nativeBuildInputs`, `propagatedBuildInputs` and `checkInputs` so it closely resembles 2bb3a9da24/pkgs/applications/terminal-emulators/kitty/default.nix. This makes it easy to port changes between the two files.
2020-10-31 01:44:33 +01:00
Luflosi
3335f8602b Allow reading some build system flags as environment variables 2020-10-29 12:20:11 +01:00
Kovid Goyal
87ab1bbe54 Add a changelog entry for previous merge 2020-10-29 07:20:45 +05:30
Kovid Goyal
f8d52b33f4 Merge branch 'wl_fix_destroy_csds' of https://github.com/LeonardKoenig/kitty 2020-10-29 07:19:57 +05:30
Leonard König
bc2dcdf9db wl: Fix destroying CSDs in correct order (#3051)
On Wayland, some compositors refuse to implement SSDs in which case we draw
primitive CSDs.  However, the destructors were called in the wrong order,
leading to a dereference of an already freed object.
2020-10-28 18:57:37 +01:00
Kovid Goyal
93d1aacfb6 macOS: Fix kitty not being added to PATH automatically
Fixes #3063. Apparently in newer Python, Py_Initialize() nukes
sys._xoptions when the isolate flag is set.
2020-10-27 10:38:33 +05:30
Kovid Goyal
3ddaa4aff9 Make it clear that remote control mappings dont need remote control enabled 2020-10-26 16:51:53 +05:30
Kovid Goyal
75488d568a Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-10-26 16:34:30 +05:30
Luflosi
3df80545bd Reduce the difference of GLFW to upstream 2020-10-26 11:02:55 +01:00
Kovid Goyal
c605fd9783 Merge branch 'remove-extra-space' of https://github.com/Luflosi/kitty 2020-10-26 15:03:25 +05:30
Luflosi
60719e7bff Remove extra space 2020-10-26 10:22:14 +01:00
Kovid Goyal
e9faa3277a Merge branch 'fix-memory-leak' of https://github.com/Luflosi/kitty 2020-10-26 07:33:06 +05:30
Luflosi
35f7388725 Fix memory leaks in coretext_all_fonts()
Found with the Clang Static Analyzer.
2020-10-25 17:40:51 +01:00
Kovid Goyal
ef1486a779 Add .js and .json to known extensions 2020-10-25 20:25:31 +05:30
Kovid Goyal
95c44165e9 Merge branch 'fix-clang-analyze-error' of https://github.com/Luflosi/kitty 2020-10-25 20:24:28 +05:30
Kovid Goyal
7b816bb96f Font independent rendering for braille characters, which ensures they are properly aligned at all font sizes. 2020-10-25 20:20:22 +05:30
Luflosi
d2ac51aa4a Fix Clang Static Analyzer complaining about Apple coding conventions
The error message was: "Method accepting NSError** should have a non-void return value to indicate whether or not an error occurred".
2020-10-25 15:47:43 +01:00
Kovid Goyal
26974b1f3a Dont ignore --title when using an empty session
Fixes #3055
2020-10-25 18:53:57 +05:30
Kovid Goyal
3960c6678d Override MIME for some types that are actually text 2020-10-25 15:11:44 +05:30
Kovid Goyal
30b8991a73 DRYer 2020-10-25 13:47:12 +05:30
Kovid Goyal
75a94bcd96 Improving MIME type detection for some common file types when they are missing from the system MIME database
Also allow the user to specify their own database via mime.types in the
kitty config directory. See #3056
2020-10-25 13:42:11 +05:30
Kovid Goyal
e160cbf32b ssh kitten: Fix parsing of combined single letter options to more closely match ssh. Fixes #3053 2020-10-23 16:55:53 +05:30
Kovid Goyal
2da2e63245 Add another integration 2020-10-23 09:36:35 +05:30
Kovid Goyal
f160ea6012 Fix #3048 2020-10-21 21:57:35 +05:30
Kovid Goyal
8057bc639d Add diff context parser defs 2020-10-20 11:37:48 +05:30
Kovid Goyal
6f5ec5aa03 Merge branch 'fix-mistake-in-previous-pr' of https://github.com/Luflosi/kitty into master 2020-10-19 16:41:01 +05:30
Luflosi
e343c9445c Creating a temporary directory is not actually required
While developing the previous PR (https://github.com/kovidgoyal/kitty/pull/3043), I was using more compiler command line arguments, one of which was `-MMD`. This caused the .d file to be created. Without this argument, there is no need to create a temporary directory.
2020-10-19 13:08:32 +02:00
Kovid Goyal
c73e374f1a Remove unneeded space 2020-10-19 16:02:49 +05:30
Kovid Goyal
d9b292f808 Merge branch 'fix-macos-notification-api-detection' of https://github.com/Luflosi/kitty into master 2020-10-19 16:00:38 +05:30
Luflosi
46ef2f313b macOS: Fix detection for notification API
For some reason it can happen that the `UserNotifications` framework exists but the `UserNotifications/UserNotifications.h` header doesn't.
Closes https://github.com/kovidgoyal/kitty/issues/3042.
2020-10-19 12:19:51 +02:00
Luflosi
92a705c79e Add language parameter to first_successful_compile() and test_compile()
This allows compiling Objective-C files.
The temporary directory is needed because clang would try to write to `/dev/null.d`, which would obviously fail.
2020-10-18 23:08:19 +02:00
Kovid Goyal
12ebdd2574 Add a changelog entry for the last PR 2020-10-18 08:48:07 +05:30
Kovid Goyal
ed2e951032 Merge branch 'pointer_shape_when_dragging' of https://github.com/jaeheum/kitty into master 2020-10-18 08:46:18 +05:30
Kovid Goyal
01a34a8846 Add links to viu that supports the graphics protocol 2020-10-18 08:42:59 +05:30
Jay Han
b13174cb9c pointer_shape_when_dragging option + set_mouse_cursor_when_dragging() 2020-10-17 09:52:44 -07:00
Jay Han
03ac6cab99 default_pointer_shape option 2020-10-17 09:28:04 -07:00
Kovid Goyal
532eebaefb Merge branch 'fix-garbage-pointer-dereference' of https://github.com/Luflosi/kitty into master 2020-10-16 07:35:31 +05:30
Luflosi
f825345b14 Fix potential garbage pointer dereference
`create_os_window()` calls `get_window_content_scale()` without initializing the `xscale` and `yscale` variables. Instead of initializing them everywhere, where this function is called, just do it once in the function itself.
Found with the Clang Static Analyzer.
2020-10-15 20:41:16 +02:00
Kovid Goyal
1409477ed7 Merge branch 'double-free' of https://github.com/Luflosi/kitty into master 2020-10-15 19:11:31 +05:30
Luflosi
d8e4294432 Fix double free and memory leak
Found with the Clang Static Analyzer.
This is a copy & paste error. This bug was introduced in 72e2307c16 in Jul 2018.
2020-10-15 15:29:44 +02:00
Kovid Goyal
5d13908776 github actions CI updated to python 3.9
Pillow wheel currently not available for python 3.9. So tries to install
from source and fails on missing zlib. So install zlib in brew and make
it available via PKG_CONFIG_PATH. Apparently, this wont be necessary
after Pillow 8, but who knows.
2020-10-14 19:13:46 +05:30
Luflosi
2832a73285 GLFW: Wayland: Assume 96 DPI if physical size is <= 0
From upstream: 0ef149c8f2.
2020-10-14 14:43:40 +02:00
Kovid Goyal
4152ed5809 Update changelog 2020-10-14 08:51:07 +05:30
Kovid Goyal
8a90ed70ee Merge branch 'fix-upwards-selection-clearing' of https://github.com/Luflosi/kitty into master 2020-10-14 05:31:08 +05:30
Luflosi
e2646c08a5 Fix upwards selection not being cleared when lines change
When selecting text by dragging upwards, the "end" of the selection is above the "start". This causes the `selection_has_screen_line()` to never return `true` in this case.
In practice this bug causes the selection to not be cleared when the screen contents under the selection change if it was made by dragging upwards.
2020-10-13 21:40:03 +02:00
Kovid Goyal
7122ba820f Fix eight bar unicode chars not being rendered at very small font sizes
Fixes #3025
2020-10-13 20:00:25 +05:30
Kovid Goyal
efca2658ea Fix typing issues pointed out by updated mypy 2020-10-12 23:03:07 +05:30
Kovid Goyal
91a714947c Fix encoding error when reporting capabilities for number of colors 2020-10-12 22:39:05 +05:30
Kovid Goyal
5d3ac649ba Fix #3017 2020-10-08 17:39:44 +05:30
Kovid Goyal
6443e726a6 Add a note about how the query_terminal kitten works 2020-10-08 07:42:47 +05:30
Kovid Goyal
6a755bda45 A new kitten to easily query kitty for config values 2020-10-07 13:35:16 +05:30
Kovid Goyal
dab555ea3b kittens: Dont discard pending input on startup
Fixes #3012
2020-10-06 16:39:00 +05:30
Kovid Goyal
a3bbad0060 Allow specifying the optional actions for tcsetattr 2020-10-06 16:30:23 +05:30
Kovid Goyal
73db717a0c version 0.19.1 2020-10-06 14:03:25 +05:30
Kovid Goyal
f1552c8354 Avoid alias related complications with completion delegation for hyperlinked_grep 2020-10-06 13:53:41 +05:30
Kovid Goyal
fb87fc32f0 Fix a regression that caused a segfault when using scrollback_pager_history_size
Fixes #3011
2020-10-06 13:28:48 +05:30
Kovid Goyal
b04b0c670d Fix #3010
Probably ancient version of Python
2020-10-06 12:09:47 +05:30
Kovid Goyal
b33845df8a Update Changelog 2020-10-06 06:50:29 +05:30
Kovid Goyal
12c9d8d8f8 Delay load the ipaddress module 2020-10-06 06:50:17 +05:30
Kovid Goyal
fe97bbcbbf Merge branch 'master' of https://github.com/Sighery/kitty into master 2020-10-06 06:45:05 +05:30
Sighery
10533c3eba Hints kitten: validate IPs with ipaddress
On the initial commit of this feature, IPs were just matched with a
very simple regex that prioritised simplicity/readability over
accuracy.

This commit adds a postprocessor for ip matches that makes use of
Python's `ipaddress` in the standard library to validate all the IP
matches.

This way we don't need huge and complex regex patterns to match _and_
validate the IPs, and we can just use `ipaddress` to abstract us from
implementing all the validation logic into the regex pattern.
2020-10-05 20:11:25 +02:00
Sighery
b4415c90f9 Hints kitten: add support for IPs (v4 and v6)
It selects v4 and v6 IPs with a simple regex that doesn't actually
check for the validity of the IPs.
2020-10-05 20:11:22 +02:00
Kovid Goyal
2970bbdf6f Update GA tags
Also auto gen docs if running publish only for website
2020-10-05 20:04:32 +05:30
Kovid Goyal
95a420534d Remove the old asciidoc files
By now hopefully the kitty website is well established
2020-10-05 19:34:02 +05:30
Kovid Goyal
be56a21574 ... 2020-10-05 19:33:09 +05:30
Kovid Goyal
3741a235fe Merge branch 'patch-1' of https://github.com/Nudin/kitty into master 2020-10-05 19:28:22 +05:30
Michael F. Schönitzer
523b6c4c2d Fix wrong kittenname in documentation
It's called `hyperlinked_grep` not `hyperlink_grep`.
2020-10-05 15:54:11 +02:00
Kovid Goyal
c3b2300310 Merge branch 'remove-outdated-check' of https://github.com/Luflosi/kitty into master 2020-10-05 18:16:06 +05:30
Luflosi
1c4a20d86f Remove outdated Python version check
The Python version is already checked at the top of the file. That check was added in 81a58186c6.
2020-10-05 14:41:07 +02:00
Kovid Goyal
f5af475012 Merge branch 'notification_fix' of https://github.com/nertpinx/kitty into master 2020-10-05 18:09:35 +05:30
Martin Kletzander
f149b74332 Fix repeated version notifications
Without this fix the file with the list of notifications looks like this:
```
0.19.0,1601899026.4373078,<built-in method count of Notification object at 0x7f8c582193b0>
```
which ends up notifying about a new version all the time.
2020-10-05 14:27:47 +02:00
Kovid Goyal
8cd51386cb Fix some typos in the OSC 99 spec 2020-10-05 17:27:41 +05:30
Kovid Goyal
fb2cca88df Merge branch 'pip3-in-ci' of https://github.com/Luflosi/kitty into master 2020-10-04 21:23:29 +05:30
Luflosi
031d4dc85a Use pip3 instead of pip for CI
There was a warning when the CI executed the "macOS Brew" job:
```
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. More details about Python 2 support in pip, can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support
```
To fix this, use pip3 instead of pip.
2020-10-04 17:47:41 +02:00
Kovid Goyal
bf66d1c0f5 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty into master 2020-10-04 21:13:48 +05:30
Luflosi
0edfa88755 Reduce the difference of GLFW to upstream 2020-10-04 17:34:51 +02:00
Kovid Goyal
dba1f3bcbc Merge branch 'add-comment' of https://github.com/Luflosi/kitty into master 2020-10-04 21:01:12 +05:30
Luflosi
e204cc8f12 Add comment on how to call send_test_notification() 2020-10-04 17:23:50 +02:00
Kovid Goyal
9e3c60f43f Make ancient gcc happy 2020-10-04 19:56:10 +05:30
Kovid Goyal
b03dd5b6b2 ... 2020-10-04 18:43:59 +05:30
Kovid Goyal
2aa9886ebb version 0.19.0 2020-10-04 18:42:21 +05:30
Kovid Goyal
225e52b8b9 Merge branch 're-add-deprecated-macos-notifications' of https://github.com/Luflosi/kitty into master 2020-10-04 18:16:09 +05:30
Luflosi
0ae1f9906f macOS: re-add deprecated notification API
The new User Notifications Framework is only available on macOS 10.14 and above, while the old NSUserNotification API is deprecated in macOS 11 (Big Sur) and will probably be removed in the future.
This commit compiles a simple test program to see if the Framework is available and then uses either the new or the old API.
2020-10-04 13:32:28 +02:00
Kovid Goyal
a5918b50f8 Merge branch 'better-build-output-for-pipes' of https://github.com/Luflosi/kitty into master 2020-10-04 15:50:39 +05:30
Luflosi
fa0374ee61 Better build system output when stdout is not a tty
When piping the output of `setup.py` to another program, that program cannot usually deal with escape sequences well. To fix this, output the compilation progress on new lines instead of overwriting the current line.
2020-10-04 12:02:49 +02:00
Kovid Goyal
5b1e1aee3d Merge branch 'flag-to-disable-lto' of https://github.com/Luflosi/kitty into master 2020-10-04 15:00:10 +05:30
Luflosi
3ba11d08fb Add flag to disable LTO
Disabling Link Time Optimization is useful for Nix on Darwin because LTO is broken there and has been for a long time, see https://github.com/NixOS/nixpkgs/pull/19312.
This is currently worked around in the Nix package with a patch that removes the lines that add -flto to the compiler flags.
2020-10-04 10:50:42 +02:00
Kovid Goyal
35d0f42d07 Add a note about creating self-signed app bundles 2020-10-04 08:25:58 +05:30
Kovid Goyal
f08f222337 Merge branch 'macos-notifications-add-subtitle' of https://github.com/Luflosi/kitty into master 2020-10-03 21:31:10 +05:30
Luflosi
7c4ad278d5 macOS: add ability to show subtitles in notifications
This used to be implemented before 4e3c6e52aa, when the now deprecated notifications framework was still being used.
Implement it again for feature parity.
2020-10-03 16:04:32 +02:00
Kovid Goyal
d3d2930bd2 typo 2020-10-01 20:19:14 +05:30
Kovid Goyal
b571c20acc Clean up docs for --copy-env 2020-10-01 12:43:26 +05:30
Kovid Goyal
f6cc0302c4 Remove trailing whitespace 2020-10-01 08:34:14 +05:30
Kovid Goyal
d9abcf0c83 Merge branch 'launch-doc' of https://github.com/poliorcetics/kitty into master 2020-10-01 08:22:30 +05:30
Alexis Bourget
5346849439 Add example of remote_control use with launch --copy-env 2020-10-01 01:01:56 +02:00
Alexis Bourget
9742b0c5b1 Add docs precisions for launch and --copy-env 2020-09-30 20:40:14 +02:00
Alexis Bourget
41cfe26898 use :code: block for @... in --stdin-source doc 2020-09-30 20:31:04 +02:00
Kovid Goyal
05a254eee9 Merge branch 'simplify' of https://github.com/Luflosi/kitty into master 2020-09-28 16:20:01 +05:30
Luflosi
8ef53aa363 Remove unnecessary condition
According to the python documentation, `Py_CLEAR()` has no effect when the argument is `NULL`, see https://docs.python.org/3/c-api/refcounting.html#c.Py_CLEAR.
2020-09-28 12:21:23 +02:00
Kovid Goyal
470abc25e4 Changelog entry for hyperlinks with hints kitten 2020-09-25 21:02:27 +05:30
Kovid Goyal
7fe6c7e78b Also handle EOFError 2020-09-25 18:58:03 +05:30
Kovid Goyal
6c8a4f8d9f Handle SIGINT more gracefully 2020-09-25 18:55:43 +05:30
Kovid Goyal
2a8c8c0cbb Fix #2980 2020-09-25 16:06:07 +05:30
Kovid Goyal
12bbe5459d Allow selecting hyperlinks via the hints kitten 2020-09-24 13:21:15 +05:30
Kovid Goyal
e0d9e05fd4 Preserve rg exit code 2020-09-23 20:33:03 +05:30
Kovid Goyal
8bea61cb99 hyperlinked_grep: Ensure filename is always printed
Fixes hyperlinks not working when a single file is searched
2020-09-23 19:11:09 +05:30
Kovid Goyal
d09666aba9 Unicode input kitten: Add symbols from NERD font
These are mostly Private Use symbols not in any standard,
however they are common enough to be useful.

Fixes #2972
2020-09-22 19:47:39 +05:30
Kovid Goyal
48e0ba1d3d Add a note about completion for the hyperlinked_grep kitten 2020-09-22 19:07:36 +05:30
Kovid Goyal
fb72be41f4 Add a test for serializing id less hyperlinks 2020-09-22 17:52:40 +05:30
Kovid Goyal
ef1db6d089 macOS: Add some keys to Info.plist to allow programs running inside kitty to access protected Cocoa APIs. 2020-09-22 16:32:40 +05:30
Kovid Goyal
9d4246a285 Output hyperlink markup when serializing as ANSI 2020-09-22 16:18:22 +05:30
Kovid Goyal
e0f5c39297 Get rid of unneeded malloc in as_text_generic 2020-09-22 09:57:45 +05:30
Kovid Goyal
e4d353b105 Ignore undecodeable bytes in pagerhist
Also add API to get pagerhist as bytes
2020-09-22 09:33:48 +05:30
Kovid Goyal
0d665495b8 Make getting hyperlinks from ids useable throughtout the codebase 2020-09-22 09:25:16 +05:30
Kovid Goyal
fdaf857885 Add more tests for pagerhist 2020-09-22 09:05:40 +05:30
Kovid Goyal
987b41d2cb Implement rewrp for pagerhist using the streaming wcswidth 2020-09-21 19:05:23 +05:30
Kovid Goyal
444080f320 Make wcswidth useable in a streaming fashion 2020-09-21 15:55:22 +05:30
Kovid Goyal
b5007ba276 Store pager history in UTF-8
Reduces storage requirement by a fator of 4 at the cost of some CPU
usage, to encode to UTF-8
2020-09-21 12:29:10 +05:30
Kovid Goyal
bf19d2b156 Add some tests for the pagerhist code 2020-09-21 09:08:46 +05:30
Kovid Goyal
a78515e5bf Remove the max size limit for line_as_ansi
Needed for output of hyperlinks, also more efficient, since avoids
malloc per line. Also fix pagerhist not having SGR reset at the start of
every line.
2020-09-20 11:26:59 +05:30
Kovid Goyal
581126c748 More powerful match criteria for fragments 2020-09-19 07:31:17 +05:30
Kovid Goyal
7b3e345a2a Document the framework for customizing URL open actions 2020-09-18 21:33:30 +05:30
Kovid Goyal
cc7cefd3ed Use unquoted path for filename and path matching 2020-09-18 21:11:39 +05:30
Kovid Goyal
de6528b7d8 Dispatch url actions once per event loop tick
This allows subsequent actions to act on the results of the previous
actions
2020-09-18 20:48:40 +05:30
Kovid Goyal
9efdfe0de4 Fix processing of non-string key actions 2020-09-18 20:41:31 +05:30
Kovid Goyal
f936918278 ... 2020-09-18 19:46:49 +05:30
Kovid Goyal
00125577a9 Add a link to rg hyperlink issue 2020-09-18 19:36:43 +05:30
Kovid Goyal
083c47b0f0 A new kitten to easily search files and open the results at the matched line by clicking on them. 2020-09-18 19:33:51 +05:30
Kovid Goyal
8021686154 Merge branch 'fix-typo' of https://github.com/Luflosi/kitty into master 2020-09-18 17:24:00 +05:30
Luflosi
f55ca296da Fix typo 2020-09-18 13:50:56 +02:00
Kovid Goyal
0d6bca3e5d Allow specifying rules to perform arbitrary actions in kitty when opening URLs 2020-09-18 15:01:25 +05:30
Kovid Goyal
be1ff61e4a Improve some utility functions
Make expandvars accept both $VARNAME and ${VARNAME} now matches
the behavior of posixpath.expandvars

Allow overriding the environment variables to_cmdline expands against
2020-09-18 13:25:59 +05:30
Kovid Goyal
e36d41b46f Add a --hold option to icat
Keeps it alive after display images
2020-09-18 11:48:31 +05:30
Kovid Goyal
b4693dc1b3 Fix some lines in the scrollback buffer not being properly rendered after a window resize/font size change
Fixes #2619
2020-09-18 08:26:50 +05:30
Kovid Goyal
ca6010789e Fix a memory leak when changing font sizes
had forgotten to free unused font groups
2020-09-18 07:40:30 +05:30
Kovid Goyal
f8a80ccf5f Move the wcswidth functions out of screen.c
They have nothing to do with screens
2020-09-17 21:24:52 +05:30
Kovid Goyal
6461dccbdc Make wcswidth ignore more escape codes
Needed to ignore OSC 8 hyperlinks since we will
eventually have to send those with SGR formatting to the hints
kitten.
2020-09-17 20:16:17 +05:30
Kovid Goyal
b06f4f2574 Use the correct type for png_row_bytes() result 2020-09-17 16:54:21 +05:30
Kovid Goyal
7695e3a9c8 Cleanup exit flow control for remote hostname not matching 2020-09-17 16:45:02 +05:30
Kovid Goyal
ae475b408f oops 2020-09-17 16:45:02 +05:30
Kovid Goyal
6d9d048287 Verify hostname before acting on remote file URL
Fixes #2971
2020-09-17 16:45:02 +05:30
Kovid Goyal
09e4d101c5 DRYer 2020-09-17 16:45:02 +05:30
Kovid Goyal
36340caf4c Fix URL marking + scrolling
The ranges have to use on screen line numbers not line->ynum
Fixes #2969
2020-09-17 16:44:10 +05:30
Kovid Goyal
9a80ab0700 More robust 2020-09-17 10:42:30 +05:30
Kovid Goyal
339d972d8d Use INT_MAX rather than self->lines + 10
More likely to never co-incide
2020-09-17 10:41:13 +05:30
Kovid Goyal
6d1e1030bd ... 2020-09-17 10:37:14 +05:30
Kovid Goyal
8a3665ce55 Add links to some modern terminal feature specs 2020-09-17 10:25:37 +05:30
Kovid Goyal
8351f0beab Fix #2970 2020-09-17 09:34:22 +05:30
Kovid Goyal
c26808c59e Fix #2967 2020-09-17 09:30:30 +05:30
Kovid Goyal
50b1dfc746 Fix #2968 2020-09-16 22:30:13 +05:30
Kovid Goyal
2ec8ec2327 Add a note about nested SSH sessions not being supported
Fixes #2971
2020-09-16 08:36:08 +05:30
Kovid Goyal
168480b0ee Docs for the remote file kitten 2020-09-15 18:16:45 +05:30
Kovid Goyal
58f78afc2e Assume localhost is always local
Required by the spec
2020-09-15 16:19:27 +05:30
Kovid Goyal
d60020f5ac Allow copying hyperlink URL to clipboard 2020-09-15 13:23:31 +05:30
Kovid Goyal
437efe5473 Allow using the ask kitten to pick a choice 2020-09-15 12:39:07 +05:30
Kovid Goyal
5737904cec Next release will be 0.19.0 2020-09-15 11:53:34 +05:30
Kovid Goyal
431a8af08b Add changelog entry for hyperlinks support
Fixes #68
2020-09-15 10:35:46 +05:30
Kovid Goyal
be29c4a243 Move class definition to utils so can be used from multiple kittens easily 2020-09-15 10:34:37 +05:30
Kovid Goyal
0a027fad9a Allow asking for confirmation before opening hyperlinks 2020-09-15 10:26:05 +05:30
Kovid Goyal
e21a8e3cc2 Implement save as action for remote file kitten 2020-09-15 09:28:59 +05:30
Kovid Goyal
4279d6514d Fix path completion for ~ paths 2020-09-15 09:22:24 +05:30
Kovid Goyal
457db5994b Fix coloring of links using target 2020-09-15 09:14:44 +05:30
Kovid Goyal
71c942d290 Add colors to the path completer 2020-09-14 22:19:56 +05:30
Kovid Goyal
ee5bc8523b Fix input redisplay when cursor not at end 2020-09-14 20:15:36 +05:30
Kovid Goyal
70f4112643 ... 2020-09-14 20:15:30 +05:30
Kovid Goyal
174f6b7ea8 Start work on a path completion implementation 2020-09-14 19:58:36 +05:30
Kovid Goyal
b14fb6f3c4 Implement editing of remote files 2020-09-12 18:43:04 +05:30
Kovid Goyal
d6e27e776b Start work on the remote_file kitten
Easy access to files over SSH
2020-09-12 06:36:41 +05:30
Kovid Goyal
e6839b45e3 typo 2020-09-10 11:57:41 +05:30
Kovid Goyal
229134cf31 Misc. fixes for issues reported by clang's static analyzer
Most of them are false positives. A couple of mem leaks after unlikely
failure conditions.
2020-09-10 10:47:28 +05:30
Kovid Goyal
b552173185 Start work on special handling for remote file hyperlinks 2020-09-10 10:47:28 +05:30
Kovid Goyal
399a1f8fee Add an option to disable OSC 8 2020-09-10 10:47:28 +05:30
Kovid Goyal
9293d9b0ed Add some tests for hyperlink marking 2020-09-10 10:47:28 +05:30
Kovid Goyal
4a75952fc7 Implement marking of hyperlinks 2020-09-10 10:47:27 +05:30
Kovid Goyal
c19906eb87 Fix read overflow reported by ASAN 2020-09-10 10:47:27 +05:30
Kovid Goyal
00ea189074 Infrastructure to support multiple selection ranges
Needed for hyperlinks, that can be discontinuous
2020-09-10 10:47:27 +05:30
Kovid Goyal
c8e2061e2a Move hyperlink to end of hash table when re-referenced 2020-09-10 10:47:27 +05:30
Kovid Goyal
78dc93721d Add tests for hyperlink storage 2020-09-10 10:47:27 +05:30
Kovid Goyal
33beecddda Implement storage of URLs in a hash map 2020-09-10 10:47:27 +05:30
Kovid Goyal
e99d93ca30 Implement parsing of OSC 8
Also start work on storing hyperlinks with cells
2020-09-10 10:47:24 +05:30
Kovid Goyal
0cc54484a4 Add a note about using the scrollback pager history for large scrollbacks 2020-09-09 12:17:19 +05:30
Kovid Goyal
4df7d59de0 ... 2020-09-09 10:18:56 +05:30
Kovid Goyal
9a54da84dc Add a setting tab_activity_symbol
Fixes #2515
2020-09-08 21:54:56 +05:30
Kovid Goyal
83315f7999 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty into master 2020-09-08 17:45:04 +05:30
Luflosi
eb5ebfd304 GLFW: Fix typo in GLFWwindowmaximizefun documentation
From upstream: 1ed1489831.
2020-09-08 12:57:00 +02:00
Kovid Goyal
77c9124a3d Merge branch 'gwl/fix-move_window_to_top-183' of https://github.com/gregorlarson/kitty into master 2020-09-08 08:38:18 +05:30
Kovid Goyal
317da76ced Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty into master 2020-09-08 07:46:13 +05:30
Luflosi
e8d8ced006 GLFW: Null: Fix out parameters not being set
From upstream: cd0dc76c7c.
2020-09-07 21:26:02 +02:00
Gregor Larson
0321f9bddf tabs.py::move_window_to_top fix 2020-09-07 14:01:56 -04:00
Kovid Goyal
5937892ebc Move focus tracking into the Screen object 2020-09-07 09:31:44 +05:30
Kovid Goyal
e429bea2f4 Move new features to the top of the changelog 2020-09-06 12:44:26 +05:30
Kovid Goyal
d33ad5c616 typo 2020-09-06 12:43:17 +05:30
Kovid Goyal
3e0614a293 Fix a regression that broke kitten_alias
Fixes #2952
2020-09-06 12:15:46 +05:30
Kovid Goyal
80e39212b8 Linux: Fix automatic detection of bold/italic faces for fonts such as IBM Plex Mono and have the regular face with a full name that is the same as the family name
Fixes #2951
2020-09-03 17:14:55 +05:30
Kovid Goyal
d7a6ceb3a6 Add character cell diagonals to box_chars 2020-09-03 13:51:54 +05:30
Kovid Goyal
9d3a9e9d1e Add the half triangle box chars 2020-09-03 11:22:36 +05:30
Kovid Goyal
e95c789d4b Add the block elements to box_chars
Also use a single eight_bar function for all blocks
2020-09-03 10:56:06 +05:30
Kovid Goyal
a508161265 Add smooth mosaic chars to box drawing 2020-09-02 22:00:58 +05:30
Kovid Goyal
a935c80adc Use box drawing for sextants
See the Symbols for Legacy Computing section in the Unicode standard
https://www.unicode.org/charts/PDF/U1FB00.pdf
2020-09-01 19:40:39 +05:30
Kovid Goyal
1d607bf13e Fix #2943 2020-08-30 20:42:01 +05:30
Kovid Goyal
ad57467c93 Avoid exception when generating minimal borders for empty tab in tall/vertical layouts 2020-08-30 20:41:57 +05:30
Kovid Goyal
430eaefc3e If a border width in pts is non-zero have it result in a minimum 1px border
Change the default to 0.5pt instead of 1px
2020-08-30 15:26:37 +05:30
Kovid Goyal
3d60724651 Allow specifying border sizes in either pt or px
Change the default to 1px borders as this works best with the new minimal border style.
2020-08-30 14:17:47 +05:30
Kovid Goyal
31d9f663fc Simplify border drawing code 2020-08-30 14:05:43 +05:30
Kovid Goyal
f324697c7c Implement new minimal border style for tall layout 2020-08-30 13:49:34 +05:30
Kovid Goyal
befae7e677 Make borders code for vertical layout re-usable 2020-08-30 11:32:45 +05:30
Kovid Goyal
7a2c2587ba Use new minimal borders style for vertical layouts 2020-08-30 09:17:14 +05:30
Kovid Goyal
cf3f9d68b3 Use similar border drawing techniques for splits 2020-08-30 08:37:16 +05:30
Kovid Goyal
c99f7c554f Fix unfocused windows in which a bell occurs not changing their border color to red until a relayout 2020-08-29 18:12:17 +05:30
Kovid Goyal
f01c0945da Fix mypy failing 2020-08-29 18:08:32 +05:30
Kovid Goyal
1bcd0c4471 Merge branch 'docs-kitten-example-mouse-buttons' of https://github.com/trygveaa/kitty into master 2020-08-29 18:06:55 +05:30
Trygve Aaberge
07f28e6ec7 Don't use GLFW buttons in custom kitten example
Since send_mouse_event now uses the same numbers as X11 for buttons
instead of the same numbers as GLFW, it is misleading to use the GLFW
constants. I forgot to change this example in commit 8efe08c4.
2020-08-29 13:33:34 +02:00
Kovid Goyal
7074f9f776 Grid layout: Improve rendering of borders when using minimal borders 2020-08-29 15:53:14 +05:30
Kovid Goyal
5029a9ad5a Dont use wheel_scroll_multiplier when sending scroll events in the main screen 2020-08-28 08:14:49 +05:30
Kovid Goyal
0bff30e954 Send wheel events to program when it asks to grab mouse, even in main screen
This matches behavior of other terminals and I dont have a strong
preference either way. Fixes #2939
2020-08-27 21:48:41 +05:30
Kovid Goyal
601d37aa3d Avoid malloc+copy for unknown OSC codes 2020-08-24 20:14:16 +05:30
Kovid Goyal
e2b1ca10c9 Save a line :P 2020-08-24 12:08:09 +05:30
Kovid Goyal
11942ed6dc Fix compilation on OpenBSD
Fixes #2935
2020-08-24 12:05:39 +05:30
Kovid Goyal
f65914599b Remove unreachable code 2020-08-23 10:53:47 +05:30
Kovid Goyal
392c31f5fe Allow specifying watchers in session files and via a command line argument 2020-08-23 10:41:36 +05:30
Kovid Goyal
f4ddaacb3c Allow tracking focus change events in watchers
Fixes #2918
2020-08-23 08:51:04 +05:30
Kovid Goyal
db64aef666 Prevent an infinite recursion if a watcher causes a resize in the on_resize handler 2020-08-23 08:38:25 +05:30
Kovid Goyal
95c8bbd7fb Forgot to increment token 2020-08-22 14:37:51 +05:30
Kovid Goyal
67b4f37d53 ... 2020-08-22 11:38:05 +05:30
Kovid Goyal
427288c16b Add a tmux FAQ
So I can just point to it for all the spurious tmux caused bug reports
2020-08-22 11:36:01 +05:30
Kovid Goyal
4202eee084 Fix launcher on OpenBSD
OpenBSD has apparently refused to move into the 21st century and lacks a
way of determining path to the executable. So we use a kludge, looking
for kitty in PATH and using that. To make it work, simply put a symlink
to kitty somewhere in PATH. Fixes #2675
2020-08-22 09:12:50 +05:30
Kovid Goyal
eca53bfab0 Add a new escape code to allow terminal programs to trigger desktop notifications
Fixes #1474
2020-08-21 20:29:54 +05:30
Kovid Goyal
cd76d109f5 Fix a couple of documentation build warnings 2020-08-21 07:50:35 +05:30
Kovid Goyal
2787f9f19b Drain the notifications queue in FIFO 2020-08-20 14:25:07 +05:30
Kovid Goyal
4e3c6e52aa macOS: Switch to the User Notifications framework
The current notifications framework has been deprecated in Big Sur. The new
framework only allows notifications from signed and notarized applications,
so people using kitty from HomeBrew/source are out of luck. And
notifications can only be displayed once the user grants permission. A
completely brain-dead design. Complain to Apple.
2020-08-20 13:03:00 +05:30
Kovid Goyal
ae5ceedfe9 Allow kitty to build on Big Sur 2020-08-19 20:33:14 +05:30
Kovid Goyal
022e2eb1db Allow updaing on ox with signed/notarized app easily 2020-08-19 20:08:34 +05:30
Kovid Goyal
2fe1ef4774 Mappable function to easily send test notification 2020-08-19 16:45:51 +05:30
Kovid Goyal
70afb2dd4d Fix lib names for OpenSSL 1.1 2020-08-19 12:33:54 +05:30
Kovid Goyal
efa02e3f94 Bump versions of openssl and glib 2020-08-18 21:25:00 +05:30
Kovid Goyal
5eefd41059 Add support for displaying correct colors with PNG files that contain embedded ICC color profiles 2020-08-12 10:34:16 +05:30
Kovid Goyal
6ca1b7c240 Convenience command to upload CI bundles 2020-08-12 10:30:15 +05:30
Kovid Goyal
31191fd48c Log warnings from libpng when debug rendering is on 2020-08-12 06:21:14 +05:30
Kovid Goyal
a6ed54ed48 Image display: Render PNG images with correct colors that have a gamma value different from the sRGB gamma value set in their metadata 2020-08-11 22:52:50 +05:30
Kovid Goyal
95132b4fe1 formatting 2020-08-11 20:19:29 +05:30
Kovid Goyal
df0c5d99e6 Fix #2913 2020-08-11 20:13:50 +05:30
Kovid Goyal
4b0724530e Fix #2912 2020-08-11 14:19:32 +05:30
Kovid Goyal
e5681a93f9 version 0.18.3 2020-08-11 13:38:38 +05:30
Kovid Goyal
fb838a694e ... 2020-08-11 13:08:16 +05:30
Kovid Goyal
8c6f188767 Forgot to use " for key names 2020-08-11 13:01:33 +05:30
Kovid Goyal
55e15dc79e Properly type check os window initial size function
Also use the sizes specified int he session file in preference to those
specified on the command line. Fixes #2909
2020-08-11 09:28:53 +05:30
Kovid Goyal
514073cb75 Fix #2911 2020-08-11 07:52:32 +05:30
Kovid Goyal
f6ece6522e Fix a regression that broke specifying OS window size in the session file
Fixes #2908
2020-08-10 21:55:54 +05:30
Kovid Goyal
817f0997cf Fix one pixel mis-alignment of rounded corners when either the cell dimensions or the thickness of the line is an odd number of pixels
The control points of the Bezier curve and the spread due to thickness
must match up with the values used to draw straight lines. In order to
ensure that, one calculates them before upscaling by the super sampling
factor, rather than after. Fixes #2907
2020-08-10 17:28:08 +05:30
Kovid Goyal
1d651270d3 Merge branch 'weechat-hints-kitten' of https://github.com/GermainZ/kitty into master 2020-08-07 21:55:51 +05:30
GermainZ
6ea58fac1a Add link to weechat-hints kitten 2020-08-07 17:38:38 +02:00
Kovid Goyal
c7eb63d4ff Avoid extra layer update if clearing images has no effect 2020-08-07 09:36:28 +05:30
Kovid Goyal
4062365b19 Revert "Adjust cell height automatically for buggy fonts that draw brackets outside the bounding box"
This reverts commit 5555a66638.
Since it does not fix the issue.
2020-08-06 21:20:54 +05:30
Kovid Goyal
7f48a927de Fix image leaving behind a black rectangle when switching away and back to alternate screen
layer_dirty was being reset if remove_images was called more than once
without an intervening call to update_layers.

Fixes #2901
2020-08-06 19:33:28 +05:30
Kovid Goyal
4a740cc556 Cleanup 2020-08-06 18:14:56 +05:30
Kovid Goyal
628b92f20b Speed up is_ignored_char in the common case 2020-08-06 18:05:33 +05:30
Kovid Goyal
8f9616c230 Speed up wcwidth_std in the common case 2020-08-06 17:57:57 +05:30
Kovid Goyal
df475dfde5 Fix indentation 2020-08-06 17:55:02 +05:30
Kovid Goyal
a835b56a51 Speed up is_combining_char() in the common case 2020-08-06 17:45:40 +05:30
Kovid Goyal
5555a66638 Adjust cell height automatically for buggy fonts that draw brackets outside the bounding box
See https://github.com/cormullion/juliamono/issues/14
2020-08-06 14:06:06 +05:30
Kovid Goyal
dad355b932 oops 2020-08-05 11:06:24 +05:30
Kovid Goyal
90a097d45c Ensure beep works even very soon after startup 2020-08-05 11:04:07 +05:30
Kovid Goyal
dba01bc014 Make ringing bell not dependent on having a OSWindow 2020-08-05 10:58:00 +05:30
Kovid Goyal
1d0a3ce998 Merge branch 'master' of https://github.com/ayang64/kitty into master 2020-08-05 07:46:27 +05:30
Ayan George
076ad850bb Declare _glfwPlatformInitJoysticks() to return bool
This patch declares _glfwPlatformInitJoysticks() to return bool which
matches its prototype in glfw/internal.h.
2020-08-04 20:24:35 -04:00
Kovid Goyal
868546abe4 Fix #2896 2020-08-03 22:14:13 +05:30
Kovid Goyal
6b421fabd1 Wayland: Fix a typo in the previous release that broke reading mouse cursor size
Fixes #2895
2020-08-03 17:08:08 +05:30
Kovid Goyal
ab3c9658bd Add hunter to integrations 2020-08-03 09:05:04 +05:30
Kovid Goyal
8c7886d429 Add a changelog entry for the last PR 2020-08-03 09:00:19 +05:30
Kovid Goyal
110a3e7e0f Merge branch 'master' of https://github.com/jaeheum/kitty into master 2020-08-03 08:58:24 +05:30
Jay Han
ee0b70f51b allow users to choose hint colors 2020-08-02 15:33:17 -07:00
Kovid Goyal
df48061a8f Merge branch 'master' of https://github.com/wengxt/kitty into master 2020-08-01 07:30:09 +05:30
Weng Xuetian
b1387d50f0 Register to bus when connect to ibus.
This is required when the actual dbus is not from ibus but session bus.
This may happen when other run a shim layer for ibus, or ibus is
running with flatpak mode.
2020-07-31 17:54:10 -07:00
Kovid Goyal
73dd218a95 Merge branch 'with-suppress' of https://github.com/Luflosi/kitty into master 2020-07-31 21:36:02 +05:30
Luflosi
ee4cedcc81 Use "with suppress()" to suppress a python exception 2020-07-31 18:00:01 +02:00
Kovid Goyal
d1b2f9c6a9 Dont use SystemExit if kitty exe not found 2020-07-31 20:03:04 +05:30
Kovid Goyal
40dee44978 Merge branch 'kitty-exe-error-message' of https://github.com/Luflosi/kitty into master 2020-07-31 20:02:15 +05:30
Luflosi
db04c70d4a Let kitty_exe() raise an error message if kitty could not be found 2020-07-31 15:46:47 +02:00
Kovid Goyal
7401c6ac97 Merge branch 'dryer' of https://github.com/Luflosi/kitty into master 2020-07-31 16:38:23 +05:30
Luflosi
f7a1222cc3 DRYer 2020-07-30 22:11:41 +02:00
Kovid Goyal
069f09c43c Move wlc destructor to just before the functions are unloaded 2020-07-29 17:18:03 +05:30
Kovid Goyal
35f821f6a1 Ensure wl_cursor_theme_destroy is called before it is released
Fix #2884
2020-07-29 17:13:00 +05:30
Kovid Goyal
67f60847ee Fix the spawn test in the bypy environment
Required the launcher to be built since the pre-built
one was being used. Fixes #2881
2020-07-29 15:11:35 +05:30
Kovid Goyal
65f6b142a5 typo 2020-07-28 15:22:14 +05:30
Kovid Goyal
81bda736d8 Skip multiprocessing spawn monkeypatch test on 32bit 2020-07-28 14:15:31 +05:30
Kovid Goyal
ec3998a9b8 version 0.18.2 2020-07-28 14:01:16 +05:30
Kovid Goyal
01be77e0bc Fix change in window size that does not change number of cells not being reported to the kernel
Fixes #2880
2020-07-25 22:49:39 +05:30
Kovid Goyal
6f40b8d0a1 Simplify Wayland cursor theme handling
Now themes are loaded once per scale and stored centrally not per
window. They are not unloaded till application shutdown. Since there
is unlikely to be more than two or three scales this is not a big waste
of resources. Since cursor lifetime is tied to theme lifetime and cursors
are stored per window, destroying a theme requires destroying all
cursors win all windows referring to that theme, which is too much work.

Should hopefully fix #2810
2020-07-25 15:36:46 +05:30
Kovid Goyal
a083aa04b2 vim also produces CSI > 4;m ignore that as well 2020-07-24 19:19:18 +05:30
Kovid Goyal
1f33348cf6 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-24 15:10:35 +05:30
Luflosi
a45651933c GLFW: Cocoa: Support some sim page joystick elements
From upstream: 0d355379e0.
2020-07-24 11:28:53 +02:00
Kovid Goyal
ab57fbff73 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-24 14:56:54 +05:30
Luflosi
6b3f774f05 GLFW: Cocoa: Fix potential leak of CFNumber object
From upstream: a2674a9034.
2020-07-24 10:59:04 +02:00
Kovid Goyal
1ef2880c6c Also make the platform window id available via kitty @ ls 2020-07-24 08:47:13 +05:30
Kovid Goyal
ace152d8a3 DRYer 2020-07-24 08:41:55 +05:30
Kovid Goyal
8d08036a9d Update changelog for previous PR 2020-07-24 08:35:01 +05:30
Kovid Goyal
b0876fae64 Merge branch 'cocoa-window-number' of https://github.com/eraserhd/kitty 2020-07-24 08:33:16 +05:30
Kovid Goyal
01633776d3 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-24 08:26:18 +05:30
Jason Felice
e4202228eb Update fast_data_types.pyi 2020-07-23 19:09:16 -04:00
Jason Felice
79d5049273 Provide cocoa_window_id() on non-Mac, also
So that its import in tabs.py doesn't have to be conditional.
2020-07-23 18:55:04 -04:00
Jason Felice
9120e3b790 Reorganize window id functions slightly 2020-07-23 18:47:36 -04:00
Jason Felice
974e6140b3 Extract find_os_window() 2020-07-23 18:36:32 -04:00
Jason Felice
1fd338cc6c Set Cocoa window number 2020-07-23 18:36:32 -04:00
Luflosi
bc61a81d7a GLFW: X11: Fix joystick detection before joystick init
From upstream: c01acc267a.
2020-07-23 23:57:03 +02:00
Luflosi
06b30d1cc3 GLFW: Cleanup
From upstream: f771d41292.
2020-07-23 23:56:32 +02:00
Luflosi
785973bb5e GLFW: Make joystick platform code init on demand
From upstream: 782e6b6cef.
2020-07-23 23:56:30 +02:00
Luflosi
78efefcea1 Revert "Linux: Fix slow startup on some systems caused by GLFW searching for joysticks. Since kitty does not use joysticks, disable joystick support."
This reverts commit ab960ea12d.
2020-07-23 23:56:21 +02:00
Kovid Goyal
1bb6242020 macOS: no longer render emoji 20% below the baseline. This causes some emoji to be cut-off and also look misaligned with very high cells
Fixes #2873
2020-07-23 20:38:02 +05:30
Kovid Goyal
1af426bf84 Add support for legacy DECSET codes 47, 1047 and 1048
Fixes #2871
2020-07-22 09:00:51 +05:30
Kovid Goyal
dc4762a69a Dont crash if Wayland returns a cursor image array with some null images 2020-07-21 19:33:05 +05:30
Kovid Goyal
a785b77da9 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-19 09:27:44 +05:30
Luflosi
1342767805 GLFW: Wayland: Fix scroll offsets being inverted
From upstream: f760b124ca.

This commit does not actually change any behaviour, it merely makes the code style of the two lines more similar to the new upstream code.
2020-07-18 19:32:03 +02:00
Kovid Goyal
a3fc3233cd Fix pre-edit text not always being cleared with iBus input
Fixes #2862
2020-07-18 19:31:38 +05:30
Kovid Goyal
8093d70643 Allow setting the class for new OS windows in a session file 2020-07-18 12:11:41 +05:30
Kovid Goyal
9d9ec84b54 fix talk mutex typo unlock -> lock 2020-07-18 11:47:22 +05:30
Kovid Goyal
6b26088c27 move wakeup out of locked region 2020-07-18 11:42:57 +05:30
Kovid Goyal
43af715caa free peer message queue at shutdown 2020-07-18 10:50:09 +05:30
Kovid Goyal
c6eea488b1 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-18 09:24:01 +05:30
Luflosi
b1c3999a81 GLFW: Wayland: Fix partial framebuffer size retrieval
From upstream: 5f60c22cfa.
2020-07-17 21:10:32 +02:00
Kovid Goyal
4f19403518 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-17 19:48:06 +05:30
Luflosi
4d1b537f78 GLFW: Documentation work for GLFW_MOUSE_PASSTHROUGH
From upstream: a122d91303.
2020-07-17 00:08:22 +02:00
Luflosi
dd54db47a9 GLFW: Move mouse passthrough before window showing
From upstream: dfeacee000.
2020-07-17 00:07:36 +02:00
Luflosi
44775e4644 GLFW: Remove no-op call at window creation
From upstream: 1095a43708.
2020-07-16 23:55:24 +02:00
Luflosi
805921d6a3 GLFW: Move management of shared state to shared code
From upstream: 6d2003d07a.
2020-07-16 23:54:16 +02:00
Luflosi
d7cd6edaa5 GLFW: X11: Include X Shape extension headers
From upstream: 68e4261d73.
2020-07-16 23:50:26 +02:00
Luflosi
6a472bd6da GLFW: X11: Fix disabling of GLFW_MOUSE_PASSTHROUGH
From upstream: e81d381256.
2020-07-16 23:24:48 +02:00
Luflosi
d0c6ce9fd7 GLFW: Add support for mouse input transparency
From upstream: d285a9fdeb.
2020-07-16 22:25:12 +02:00
Kovid Goyal
df48e2f9e2 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-16 23:43:07 +05:30
Luflosi
2b4b3bbf18 GLFW: Null: Fix typos in workarea retrieval
From upstream: 6c031af245.
2020-07-16 20:01:57 +02:00
Kovid Goyal
6ea5027bf6 Remote control close_tab should not ask for confirmation 2020-07-16 12:55:25 +05:30
Kovid Goyal
384ccb4fc7 Have the :opt:confirm_os_window_close option also apply when closing tabs with multiple windows
Fixes #2857
2020-07-16 12:51:19 +05:30
Kovid Goyal
ff763b099e Merge branch 'patch-1' of https://github.com/tammersaleh/kitty 2020-07-16 08:06:47 +05:30
Kovid Goyal
741ae4c04b Merge branch 'typo' of https://github.com/Luflosi/kitty 2020-07-16 08:06:20 +05:30
Tammer Saleh
d84ec941c0 Fix typo 2020-07-15 17:44:02 -07:00
Luflosi
f0b5dd9799 Fix typo 2020-07-15 19:09:35 +02:00
Kovid Goyal
cec4371b51 Rewrite the talk loop for interaction with peers
The new code is simpler and hopefully more robust. Supports
multiple requests/responses. Fixes #2852
2020-07-15 14:31:45 +05:30
Kovid Goyal
c0ff39c59d Fix extra close for loops with no signal_read_fd 2020-07-14 20:23:45 +05:30
Kovid Goyal
022634f2d9 Log an extra error message 2020-07-14 20:18:38 +05:30
Kovid Goyal
392f576d07 Instrument safe_close() to make it easy to see where an fd is closed from 2020-07-14 20:18:04 +05:30
Kovid Goyal
6d59e2125e Prevent invalid fds from spinning the talk thread forever 2020-07-14 19:09:04 +05:30
Kovid Goyal
6476a1d2e0 When a character from the Unicode Dingbat block is followed by a space, use the extra space to render a larger version of the character
Fixes #2850
2020-07-12 21:12:34 +05:30
Kovid Goyal
ca998fb4b0 Fix incorrect centering when a PUA or symbol glyph is followed by more than one space 2020-07-12 20:16:16 +05:30
Kovid Goyal
48631c4ea6 Add changelog entry for previous merge
Also clean up use of typing.cast
2020-07-12 14:47:14 +05:30
Kovid Goyal
294be2a772 Merge branch 'neighboring_window_changes' of https://github.com/juho-p/kitty 2020-07-12 14:33:21 +05:30
Kovid Goyal
533688a67a Implement support for box drawing rounded-corners characters
Fixes #2240
2020-07-12 12:35:52 +05:30
Kovid Goyal
4309e2371d Simplify code by using supersampling for diagonal lines as well
Wu antialiasing obviously performs better but since characters are
rendered only once and are very small in pixel size, performance is
irrelevant here.
2020-07-12 10:07:55 +05:30
Juho Peltonen
ea30c84240 Fix grid layout neighbors
Grid layout can have multiple neighbors in one side when either current
column or neighboring column is special.
2020-07-11 23:52:37 +03:00
Juho Peltonen
d4c2b13dda Prefer most recently activated window when selecting neighbors
This affects both activating and moving neighbor windows.
2020-07-11 23:52:33 +03:00
Juho Peltonen
1decae2938 Change splits layout neighbors to only include real neighbors 2020-07-11 21:35:29 +03:00
Kovid Goyal
145ec86d3b Forgot to add supersampling to rendering of filled D 2020-07-11 23:10:43 +05:30
Kovid Goyal
e17c96b20f Improve anti-aliasing of triangular box drawing characters, noticeable on low-resolution screens
Fixes #2844
2020-07-11 23:08:28 +05:30
Kovid Goyal
2cfcefd9eb Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-11 22:57:53 +05:30
Luflosi
356e950122 X11: Fix cursor hover query retry behavior
From upstream: 49dbcfcb8c.
2020-07-11 18:59:28 +02:00
Kovid Goyal
64dee63b73 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-07-11 22:26:32 +05:30
Luflosi
431d013d32 X11: Fix race in window hovered query
From upstream: ceb20c7f97.
2020-07-11 18:18:33 +02:00
Kovid Goyal
40eb8455d0 Fix `launch --location=last` not working
Fixes #2841
2020-07-09 22:29:24 +05:30
Kovid Goyal
c46b36509b macOS: Fix cmd+plus not increasing font size with keyboards where the + key is a shifted key. Fixes #2839 2020-07-09 22:14:07 +05:30
Kovid Goyal
f7ff63132d Merge branch 'remove-trailing-whitespace' of https://github.com/Luflosi/kitty 2020-07-09 07:42:02 +05:30
Luflosi
e17036a7a5 Remove trailing whitespace 2020-07-09 01:57:39 +02:00
Kovid Goyal
e678c41f56 More code scanning warning fixes 2020-07-07 10:24:57 +05:30
Kovid Goyal
ee3462592a Silence more code scanning warnings 2020-07-07 10:01:38 +05:30
Kovid Goyal
521f921424 Fix a bunch of code scanning warnings 2020-07-07 09:52:59 +05:30
Kovid Goyal
ee47a10b37 Simplify code a bit 2020-07-07 09:44:51 +05:30
Kovid Goyal
b81700144d Silence another code scanning warning 2020-07-07 09:38:33 +05:30
Kovid Goyal
9f28dd61d3 Silence a spurious code scanning warning
I hope it silences it anyway
2020-07-07 09:31:00 +05:30
Kovid Goyal
dad7d366b3 Fix out of bounds write on platforms where sizeof(bool) != 1
Found by code scanning
2020-07-07 09:25:14 +05:30
Kovid Goyal
209b78a3ec Silence a code scanning warning 2020-07-07 09:20:59 +05:30
Kovid Goyal
17f34d1f95 Use the more secure localtime_r variant
Pointed out by code scanning
2020-07-07 09:20:27 +05:30
Kovid Goyal
26699cd9ef Fix incorrect format specifier to printf
Detected by code scanning
2020-07-07 09:14:33 +05:30
Kovid Goyal
907db09a58 Only use pythonLocation if it is available 2020-07-07 08:57:50 +05:30
Kovid Goyal
d506fa0565 Use python3 in code analysis build step 2020-07-07 08:41:20 +05:30
Kovid Goyal
de1a0d48a6 Use ci script to build kitty for code scanning 2020-07-07 08:35:04 +05:30
Kovid Goyal
a20e5fefd3 ... 2020-07-07 08:27:10 +05:30
Kovid Goyal
44558560f9 Dont use autobuild for code scanning 2020-07-07 08:24:37 +05:30
Kovid Goyal
677f154f03 Create codeql-analysis.yml
Enable GitHub code scanning for vulnerabilities
2020-07-07 08:21:14 +05:30
Kovid Goyal
0adaf064e7 Fix #2832 2020-07-05 07:34:11 +05:30
Kovid Goyal
3c828feef2 Merge branch 'environmenterror-to-oserror' of https://github.com/Luflosi/kitty 2020-07-02 08:29:27 +05:30
Kovid Goyal
4d3c5e61fd Update changelog 2020-07-02 08:26:37 +05:30
Kovid Goyal
67baefb52a Merge branch 'hints-linenum-expand-home' of https://github.com/trygveaa/kitty 2020-07-02 08:23:59 +05:30
Trygve Aaberge
d509a4fa8c Expand ~ in the path argument for linenum in hints
Fixes #2822
2020-07-02 01:01:14 +02:00
Luflosi
cad1c12b7b Replace EnvironmentError with OSError
See 527ff0238a.
2020-07-01 22:39:01 +02:00
Kovid Goyal
9f751d8715 Add kitti3 to integrations.rst 2020-06-30 08:15:16 +05:30
Kovid Goyal
cf8a1dff33 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-30 08:13:27 +05:30
Luflosi
973d98457f EGL: Add support for EGL_ANGLE_platform_angle
From upstream: 0dea8a4441.
2020-06-30 00:34:32 +02:00
Kovid Goyal
ce262f810d Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-29 19:13:34 +05:30
Luflosi
bd31c2b484 Add GLFW_CONTEXT_DEBUG window hint alias
From upstream: b420ca7404.
2020-06-29 15:30:38 +02:00
Kovid Goyal
ef0e610802 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-29 18:45:19 +05:30
Luflosi
3040d4c83f Re-word docs for GLFW_OPENGL_DEBUG_CONTEXT attrib
From upstream: 4e3b43383a.
2020-06-29 11:43:24 +02:00
Kovid Goyal
c9252e8639 When erasing in display upto cursor, the line containing the cursor should be marked as not continued.
Also add some tests for the action of ED on continued flags
2020-06-29 14:29:03 +05:30
Kovid Goyal
e2e701460b Fix the CSI J (Erase in display ED) escape code not removing line continued markers
Fixes #2809

Remains to write tests for this.
2020-06-29 14:17:40 +05:30
Kovid Goyal
f0c3641ebd Some more commands for replay commands 2020-06-29 13:04:10 +05:30
Kovid Goyal
025f6d7701 Add keypad mode support to replay commands
Also name the constants correctly
2020-06-29 12:46:34 +05:30
Kovid Goyal
c68516ca8d Wayland: Fix a crash when using animated mouse cursors
Fixes #2810
2020-06-29 08:03:25 +05:30
Kovid Goyal
a9e739e08d Apply single instance BO override when different from current BO not when less than 1 2020-06-27 17:32:03 +05:30
Kovid Goyal
cc15ff73b7 Respect --override of background_opacity for --single-instance
Fixes #2806
2020-06-27 09:18:41 +05:30
Kovid Goyal
fd3a4536e7 Fix imperative close request being ignored in absence of any other activity 2020-06-26 16:37:17 +05:30
Kovid Goyal
75caafb5ac Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-26 16:34:33 +05:30
Kovid Goyal
cf2f8165eb Only ask application to abort once 2020-06-26 16:33:25 +05:30
Luflosi
a2f80740d7 Remove EGL and OSMesa struct member macros
From upstream: bcb8558189.
2020-06-26 12:51:11 +02:00
Kovid Goyal
c7703791d1 Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-26 16:19:04 +05:30
Luflosi
c39e9e9c61 EGL: Add support for EGL_EXT_platform_base
From upstream: aec9fae8f3.
2020-06-26 12:39:17 +02:00
Kovid Goyal
713059319c Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-26 15:59:34 +05:30
Luflosi
d7a7509da6 EGL: Replace native handle macros with functions
From upstream: d7f7b0e1b5.
2020-06-26 12:17:13 +02:00
Kovid Goyal
57e6e7b2c5 Use ctrl+[] for changing tabs in unicode input as ctrl+tab is taken on macOS
ctrl+tab remains in addition for backwards compat
2020-06-26 12:29:42 +05:30
Kovid Goyal
d675977f10 Better error message for xterm specific key resource CSI codes that vim has started emitting 2020-06-25 23:07:34 +05:30
Kovid Goyal
7ef911e17d Fix #2798 2020-06-25 21:43:54 +05:30
Kovid Goyal
9a384c5045 Allow passing the current selection to kittens
Fixes #2796
2020-06-25 08:46:05 +05:30
Kovid Goyal
2b3753d99c Prevent super large tab margin widths from causing a crash
Fixes #2793
2020-06-24 22:56:54 +05:30
Kovid Goyal
d173ab9f45 macOS: Fix the LC_TYPE env var being set to UTF-8 on systems in which the language and country code do not form a valid locale
Fixes #1233
2020-06-24 22:09:25 +05:30
Kovid Goyal
fbd0e8e26a X11: Improve handling of multiple keyboards
Now pressing a modifier key in one keyboard and a normal key in another works. Fixes #2362

Don't rebuild keymaps on new keyboard events that only change geometry. Fixes #2787

Better handling of multiple keyboards with incompatible layouts (this is for free from the above fixes). Fixes #2726
2020-06-24 12:05:30 +05:30
Kovid Goyal
50414b333a Re-enable the spawn tesRe-enable the spawn test 2020-06-23 20:43:40 +05:30
Kovid Goyal
ee2fdb3d25 Merge branch 'typos' of https://github.com/Luflosi/kitty 2020-06-23 20:16:42 +05:30
Luflosi
2b884377d4 Fix typos 2020-06-23 16:35:13 +02:00
Kovid Goyal
36a20f7b00 Temporarily disable spawn test as it is failing in the Linux build container
Will investigate later
2020-06-23 17:37:04 +05:30
Kovid Goyal
35cbf49b08 version 0.18.1 2020-06-23 17:29:22 +05:30
Kovid Goyal
21836cedda Merge branch 'glfw_upstream' of https://github.com/Luflosi/kitty 2020-06-21 22:07:44 +05:30
Luflosi
0a892b72e6 Fix GLU header inclusion being disabled
From upstream: 81f475bccb.
2020-06-21 18:07:31 +02:00
Kovid Goyal
2c6e5a6e73 Get multiprocessing working in kitty
Monkeypatch the stdlib multiprocessing module to
use kitty as its python interpreter for spawning worker
processes.
2020-06-21 14:47:24 +05:30
Kovid Goyal
2cb25cf5a8 Build the launcher when freezing on macOS as well
Can be used by the test suite
2020-06-21 14:37:36 +05:30
Kovid Goyal
e07916425e macOS: Fix for diff kitten not working with python 3.8
Fixes #2780
2020-06-20 19:03:45 +05:30
Kovid Goyal
f62e2374e4 Ensure mimetype is sent to github 2020-06-20 13:02:34 +05:30
184 changed files with 46056 additions and 30464 deletions

3
.gitattributes vendored
View File

@@ -11,3 +11,6 @@ kitty/parse-graphics-command.h linguist-generated=true
glfw/*.c linguist-vendored=true
glfw/*.h linguist-vendored=true
kittens/unicode_input/names.h linguist-generated=true
*.py text diff=python
*.m text diff=objc

View File

@@ -23,7 +23,7 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Enviroment details**
**Environment details**
OS: Name and version of operating system(s)
```

View File

@@ -5,6 +5,7 @@
import io
import os
import shlex
import shutil
import subprocess
import sys
import tarfile
@@ -35,15 +36,18 @@ def install_deps():
run('sudo apt-get update')
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev'
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev'
' libpng-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev')
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev')
if is_bundle:
install_bundle()
else:
run('pip install Pillow pygments')
if is_macos:
# needed for zlib for pillow, should not be needed after pillow 8.0
os.environ['PKG_CONFIG_PATH'] = '/usr/local/opt/zlib/lib/pkgconfig'
run('pip3 install Pillow pygments')
def build_kitty():
python = 'python3' if is_bundle else sys.executable
python = shutil.which('python3') if is_bundle else sys.executable
cmd = '{} setup.py build --verbose'.format(python)
if os.environ.get('KITTY_SANITIZE') == '1':
cmd += ' --debug --sanitize'
@@ -71,7 +75,7 @@ def replace_in_file(path, src, dest):
def setup_bundle_env():
global SW
os.environ['SW'] = SW = '/Users/Shared/buildbot/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig'
if is_macos:
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
@@ -85,8 +89,8 @@ def install_bundle():
cwd = os.getcwd()
os.makedirs(SW)
os.chdir(SW)
with urlopen('https://download.calibre-ebook.com/travis/kitty/{}.tar.xz'.format(
'osx' if is_macos else 'linux-64')) as f:
with urlopen('https://download.calibre-ebook.com/ci/kitty/{}-64.tar.xz'.format(
'macos' if is_macos else 'linux')) as f:
data = f.read()
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
tf.extractall()
@@ -106,7 +110,7 @@ def main():
if is_bundle:
setup_bundle_env()
else:
if not is_macos:
if not is_macos and 'pythonLocation' in os.environ:
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
action = sys.argv[-1]
if action in ('build', 'package'):

41
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: "Code scanning - action"
on:
push:
branches: [master, ]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: '0 22 * * 5'
jobs:
CodeQL-Build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
- name: Build kitty
run: python3 .github/workflows/ci.py build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@@ -1,5 +1,6 @@
pkg-config
python
imagemagick
harfbuzz
zlib
python
imagemagick
harfbuzz
sphinx-doc

View File

@@ -1,7 +1,7 @@
### Reporting bugs
Please first search existing bug reports (especially closed ones) for a report
that matches your issue.
that matches your issue.
When reporting a bug, provide full details of your environment, that means, at
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal

View File

@@ -65,6 +65,11 @@ def run_kitten(args: List[str]) -> None:
rk(kitten)
def edit_config_file(args: List[str]) -> None:
from kitty.utils import edit_config_file as f
f()
def namespaced(args: List[str]) -> None:
func = namespaced_entry_points[args[1]]
func(args[1:])
@@ -77,6 +82,7 @@ entry_points = {
'runpy': runpy,
'launch': launch,
'kitten': run_kitten,
'edit-config': edit_config_file,
'@': remote_control,
'+': namespaced,

View File

@@ -7,6 +7,7 @@ import re
import shutil
import subprocess
import sys
from contextlib import suppress
from bypy.constants import (
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
@@ -47,6 +48,16 @@ def build_c_extensions(ext_dir, args):
shutil.copytree(
KITTY_DIR, writeable_src_dir, symlinks=True,
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o'))
# Build the launcher as it is needed for the spawn test
with suppress(FileNotFoundError):
os.remove(os.path.join(writeable_src_dir, 'kitty', 'launcher', 'kitty'))
if run(PYTHON, 'setup.py', 'build-launcher', cwd=writeable_src_dir) != 0:
print('Building of kitty launcher failed', file=sys.stderr)
os.chdir(KITTY_DIR)
run_shell()
raise SystemExit('Building of kitty launcher failed')
cmd = [PYTHON, 'setup.py']
bundle = 'macos-freeze' if ismacos else 'linux-freeze'
cmd.append(bundle)
@@ -62,7 +73,12 @@ def build_c_extensions(ext_dir, args):
def run_tests(path_to_kitty, cwd_on_failure):
ret = run(PYTHON, 'test.py', cwd=cwd_on_failure)
kw = {'cwd': cwd_on_failure}
if not ismacos:
# this is needed for the spawn test which starts an interpreter
# using the kitty launcher.
kw['PYTHONHOME'] = PREFIX
ret = run(PYTHON, 'test.py', **kw)
if ret != 0:
os.chdir(cwd_on_failure)
print(

View File

@@ -26,7 +26,7 @@ kitty_constants = iv['kitty_constants']
def binary_includes():
return tuple(map(get_dll_path, (
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16',
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2',
# dont include freetype because fontconfig is closely coupled to it
# and also distros often patch freetype
@@ -35,7 +35,7 @@ def binary_includes():
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
'ncursesw', 'readline',
))) + (
get_dll_path('bz2', 2), get_dll_path('ssl', 3), get_dll_path('crypto', 3),
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
get_dll_path('python' + py_ver, 2),
)
@@ -165,7 +165,7 @@ def create_tarfile(env, compression_level='9'):
base = OUTPUT_DIR
try:
shutil.rmtree(base)
except EnvironmentError as err:
except OSError as err:
if err.errno != errno.ENOENT:
raise
os.mkdir(base)

View File

@@ -262,8 +262,9 @@ class Freeze(object):
'z.1',
'harfbuzz.0',
'png16.16',
'crypto.1.0.0',
'ssl.1.0.0',
'lcms2.2',
'crypto.1.1',
'ssl.1.1',
):
print('\nAdding', x)
x = 'lib%s.dylib' % x

View File

@@ -1 +1 @@
to_vm_excludes '/build /dist /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc'
to_vm_excludes '/build /dist /kitty/launcher/kitty /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc'

View File

@@ -71,8 +71,8 @@
{
"name": "openssl",
"unix": {
"filename": "openssl-1.0.2u.tar.gz",
"hash": "sha256:ecd0c6ffb493dd06707d38b14bb4d8c2288bb7033735606569d8f90f89669d16",
"filename": "openssl-1.1.1g.tar.gz",
"hash": "sha256:ddb04774f1e32f0c49751e21b67216ac87852ceb056b75209af2443400636d46",
"urls": ["https://www.openssl.org/source/{filename}"]
}
},
@@ -153,6 +153,15 @@
}
},
{
"name": "lcms2",
"unix": {
"filename": "lcms2-2.11.tar.gz",
"hash": "sha256:478c9c3938d7a91b1171de4616f8b04308a8676d73eadc19505b7ace41327f28",
"urls": ["https://github.com/mm2/Little-CMS/archive/2.11/{filename}"]
}
},
{
"name": "graphite",
"os": "linux",
@@ -179,7 +188,7 @@
"unix": {
"filename": "libiconv-1.16.tar.gz",
"hash": "sha256:e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04",
"urls": ["http://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
"urls": ["https://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
}
},
@@ -187,13 +196,12 @@
"name": "glib",
"os": "linux",
"unix": {
"filename": "glib-2.57.1.tar.xz",
"hash": "sha256:d029e7c4536835f1f103472f7510332c28d58b9b7d6cd0e9f45c2653e670d9b4",
"urls": ["http://ftp.gnome.org/pub/GNOME/sources/glib/2.57/{filename}"]
"filename": "glib-2.65.1.tar.xz",
"hash": "sha256:bc63bf6c32713e0ee1dddc28e03f23b4a20c78bcb9a2c5b0f4eea41e46fb9cee",
"urls": ["https://ftp.gnome.org/pub/GNOME/sources/glib/2.65/{filename}"]
}
},
{
"name": "freetype",
"os": "linux",
@@ -241,7 +249,7 @@
"hash": "sha256:4675a79f091020817a98fd0484e7208c8762242266967f55a67776936c2e294d",
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
}
},
},
{
"name": "wayland-protocols",

View File

@@ -18,6 +18,7 @@ Run-time dependencies:
* harfbuzz >= 1.5.0
* zlib
* libpng
* liblcms2
* freetype (not needed on macOS)
* fontconfig (not needed on macOS)
* libcanberra (not needed on macOS)
@@ -63,10 +64,32 @@ This :file:`kitty.app` unlike the released one does not include its own copy of
python and the other dependencies. So if you ever un-install/upgrade those dependencies
you might have to rebuild the app.
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
is designed to run on Linux and is not for the faint of heart.
.. note::
The released :file:`kitty.dmg` includes all dependencies, unlike the
:file:`kitty.app` built above and is built automatically by using the
:file:`kitty` branch of `build-calibre
<https://github.com/kovidgoyal/build-calibre>`_ however, that is designed to
run on Linux and is not for the faint of heart.
.. note::
Apple disallows certain functionality, such as notifications for unsigned applications.
If you need this functionality, you can try signing the built kitty.app with
a self signed certificate, see for example, `here
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
Build and run from source with Nix
-------------------------------------------
On NixOS or any other Linux or macOS system with the Nix package manager
installed, execute `nix-shell
<https://nixos.org/guides/nix-pills/developing-with-nix-shell.html>`_ to create
the correct environment to build kitty or use ``nix-shell --pure`` instead to
eliminate most of the influence of the outside system, e.g. globally installed
packages. ``nix-shell`` will automatically fetch all required dependencies and
make them available in the newly spawned shell.
Then proceed with ``make`` or ``make app`` according to the platform specific instructions above.
Note for Linux/macOS packagers

View File

@@ -4,6 +4,225 @@ Changelog
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
To update |kitty|, :doc:`follow the instructions <binary>`.
0.19.2 [2020-11-13]
-------------------
- A new :doc:`kittens/query_terminal` kitten to easily query the running kitty
via escape codes to detect its version, and the values of
configuration options that enable or disable terminal features.
- Options to control mouse pointer shape, :opt:`default_pointer_shape`, and
:opt:`pointer_shape_when_dragging` (:pull:`3041`)
- Font independent rendering for braille characters, which ensures they are properly
aligned at all font sizes.
- Fix a regression in 0.19.0 that caused borders not to be drawn when setting
:opt:`window_margin_width` and keeping :opt:`draw_minimal_borders` on
(:iss:`3017`)
- Fix a regression in 0.19.0 that broke rendering of one-eight bar unicode
characters at very small font sizes (:iss:`3025`)
- Wayland: Fix a crash under GNOME when using multiple OS windows
(:pull:`3066`)
- Fix selections created by dragging upwards not being auto-cleared when
screen contents change (:pull:`3028`)
- macOS: Fix kitty not being added to PATH automatically when using pre-built
binaries (:iss:`3063`)
- Allow adding MIME definitions to kitty by placing a ``mime.types`` file in
the kitty config directory (:iss:`3056`)
- Dont ignore :option:`--title` when using a session file that defines no
windows (:iss:`3055`)
- Fix the send_text action not working in URL handlers (:iss:`3081`)
- Fix last character of URL not being detected if it is the only character on a
new line (:iss:`3088`)
- Don't restrict the ICH,DCH,REP control codes to only the current scroll region (:iss:`3090`, :iss:`3096`)
0.19.1 [2020-10-06]
-------------------
- hints kitten: Add an ``ip`` type for easy selection of IP addresses
(:pull:`3009`)
- Fix a regression that caused a segfault when using
:opt:`scrollback_pager_history_size` and it needs to be expanded (:iss:`3011`)
- Fix update available notifications repeating (:pull:`3006`)
0.19.0 [2020-10-04]
-------------------
- Add support for `hyperlinks from terminal programs
<https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`_.
Controlled via :opt:`allow_hyperlinks` (:iss:`68`)
- Add support for easily editing or downloading files over SSH sessions
without the need for any special software, see :doc:`kittens/remote_file`
- A new :doc:`kittens/hyperlinked_grep` kitten to easily search files and open
the results at the matched line by clicking on them.
- Allow customizing the :doc:`actions kitty takes <open_actions>` when clicking on URLs
- Improve rendering of borders when using minimal borders. Use less space and
do not display a box around active windows
- Add a new extensible escape code to allow terminal programs to trigger
desktop notifications. See :ref:`desktop_notifications` (:iss:`1474`)
- Implement special rendering for various characters from the set of "Symbols
for Legacy Computing" from the Unicode 13 standard
- Unicode input kitten: Allow choosing symbols from the NERD font as well.
These are mostly Private Use symbols not in any standard, however are common. (:iss:`2972`)
- Allow specifying border sizes in either pts or pixels. Change the default to
0.5pt borders as this works best with the new minimal border style
- Add support for displaying correct colors with non-sRGB PNG files (Adds a
dependency on liblcms2)
- hints kitten: Add a new :option:`kitty +kitten hints --type` of ``hyperlink`` useful
for activating hyperlinks using just the keyboard
- Allow tracking focus change events in watchers (:iss:`2918`)
- Allow specifying watchers in session files and via a command line argument
(:iss:`2933`)
- Add a setting :opt:`tab_activity_symbol` to show a symbol in the tab title
if one of the windows has some activity after it was last focused
(:iss:`2515`)
- macOS: Switch to using the User Notifications framework for notifications.
The current notifications framework has been deprecated in Big Sur. The new
framework only allows notifications from signed and notarized applications,
so people using kitty from homebrew/source are out of luck. Complain to
Apple.
- When in the main screen and a program grabs the mouse, do not use the scroll
wheel events to scroll the scrollback buffer, instead send them to the
program (:iss:`2939`)
- Fix unfocused windows in which a bell occurs not changing their border color
to red until a relayout
- Linux: Fix automatic detection of bold/italic faces for fonts such as IBM
Plex Mono that have the regular face with a full name that is the same as the
family name (:iss:`2951`)
- Fix a regression that broke :opt:`kitten_alias` (:iss:`2952`)
- Fix a regression that broke the ``move_window_to_top`` action (:pull:`2953`)
- Fix a memory leak when changing font sizes
- Fix some lines in the scrollback buffer not being properly rendered after a
window resize/font size change (:iss:`2619`)
0.18.3 [2020-08-11]
-------------------
- hints kitten: Allow customizing hint colors (:pull:`2894`)
- Wayland: Fix a typo in the previous release that broke reading mouse cursor size (:iss:`2895`)
- Fix a regression in the previous release that could cause an exception during
startup in rare circumstances (:iss:`2896`)
- Fix image leaving behind a black rectangle when switch away and back to
alternate screen (:iss:`2901`)
- Fix one pixel mis-alignment of rounded corners when either the cell
dimensions or the thickness of the line is an odd number of pixels
(:iss:`2907`)
- Fix a regression that broke specifying OS window size in the session file
(:iss:`2908`)
0.18.2 [2020-07-28]
--------------------
- X11: Improve handling of multiple keyboards. Now pressing a modifier key in
one keyboard and a normal key in another works (:iss:`2362`). Don't rebuild
keymaps on new keyboard events that only change geometry (:iss:`2787`).
Better handling of multiple keyboards with incompatible layouts (:iss:`2726`)
- Improve anti-aliasing of triangular box drawing characters, noticeable on
low-resolution screens (:iss:`2844`)
- Fix ``kitty @ send-text`` not working reliably when using a socket for remote
control (:iss:`2852`)
- Implement support for box drawing rounded-corners characters (:iss:`2240`)
- Allow setting the class for new OS windows in a session file
- When a character from the Unicode Dingbat block is followed by a space, use
the extra space to render a larger version of the character (:iss:`2850`)
- macOS: Fix the LC_CTYPE env var being set to UTF-8 on systems in which the
language and country code do not form a valid locale (:iss:`1233`)
- macOS: Fix :kbd:`cmd+plus` not changing font size (:iss:`2839`)
- Make neighboring window selection in grid and splits layouts more intelligent
(:pull:`2840`)
- Allow passing the current selection to kittens (:iss:`2796`)
- Fix pre-edit text not always being cleared with ibus input (:iss:`2862`)
- Allow setting the :opt:`background_opacity` of new OS windows created via
:option:`kitty --single-instance` using the :option:`kitty --override` command line
argument (:iss:`2806`)
- Fix the CSI J (Erase in display ED) escape code not removing line continued
markers (:iss:`2809`)
- hints kitten: In linenumber mode expand paths that starts with ~
(:iss:`2822`)
- Fix ``launch --location=last`` not working (:iss:`2841`)
- Fix incorrect centering when a PUA or symbol glyph is followed by more than one space
- Have the :opt:`confirm_os_window_close` option also apply when closing tabs
with multiple windows (:iss:`2857`)
- Add support for legacy DECSET codes 47, 1047 and 1048 (:pull:`2871`)
- macOS: no longer render emoji 20% below the baseline. This caused some emoji
to be cut-off and also look misaligned with very high cells (:iss:`2873`)
- macOS: Make the window id of OS windows available in the ``WINDOWID``
environment variable (:pull:`2877`)
- Wayland: Fix a regression in 0.18.0 that could cause crashes related to mouse
cursors in some rare circumstances (:iss:`2810`)
- Fix change in window size that does not change number of cells not being
reported to the kernel (:iss:`2880`)
0.18.1 [2020-06-23]
--------------------
- macOS: Fix for diff kitten not working with python 3.8 (:iss:`2780`)
0.18.0 [2020-06-20]
--------------------

View File

@@ -555,7 +555,7 @@ def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, titl
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
app.add_lexer('conf', ConfLexer())
app.add_lexer('conf', ConfLexer)
app.add_object_type(
'opt', 'opt',
indextemplate="pair: %s; Config Setting",
@@ -604,7 +604,7 @@ def setup(app: Any) -> None:
write_cli_docs(kn)
write_remote_control_protocol_docs()
write_conf_docs(app, kn)
app.add_lexer('session', SessionLexer())
app.add_lexer('session', SessionLexer)
app.add_role('link', link_role)
app.add_role('iss', partial(num_role, 'issues'))
app.add_role('pull', partial(num_role, 'pull'))

View File

@@ -165,9 +165,12 @@ kitty is not able to use my favorite font?
|kitty| achieves its stellar performance by caching alpha masks of each rendered
character on the GPU, so that every character needs to be rendered only once.
This means it is a strictly character cell based display. As such it can use
only monospace fonts, since every cell in the grid has to be the same size. If
your font is not listed in ``kitty list-fonts`` it means that it is not
monospace. On Linux you can list all monospace fonts with::
only monospace fonts, since every cell in the grid has to be the same size.
Furthermore, it needs fonts to be freely resizable, so it does not support
bitmapped fonts.
If your font is not listed in ``kitty list-fonts`` it means that it is not
monospace or is a bitmapped font. On Linux you can list all monospace fonts with::
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
@@ -232,3 +235,30 @@ available. The manual way to figure it out is:
This shows the hex value, ``13`` in this case.
3. Use ``\x(hexval)`` in your ``send_text`` command in kitty. So in this example, ``\x13``
I am using tmux and have a problem
--------------------------------------
First, terminal multiplexers are `a bad idea
<https://github.com/kovidgoyal/kitty/issues/391#issuecomment-638320745>`_, do
not use them, if at all possible. kitty contains features that do all of what
tmux does, but better, with the exception of remote persistence (:iss:`391`).
If you still want to use tmux, read on.
Image display will not work, see `tmux issue
<https://github.com/tmux/tmux/issues/1391>`_.
If you are using tmux with multiple terminals or you start it under one
terminal and then switch to another and these terminals have different TERM
variables, tmux will break. You will need to restart it as tmux does not
support multiple terminfo definitions.
Copying to clipboard via OSC 52 will not work, because tmux does not support
the extended version of that protocol, you will need to add ``no-append`` to
:opt:`clipboard_control` in kitty.conf.
If you use any of the advanced features that kitty has innovated, such as
styled underlines, desktop notifications, extended keyboard support, etc.
they may or may not work, depending on the whims of tmux's maintainer, your
version of tmux, etc.

View File

@@ -37,6 +37,7 @@ Some programs that use the kitty graphics protocol:
* `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
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
.. contents::
@@ -54,27 +55,34 @@ In C:
.. code-block:: c
struct ttysize ts;
ioctl(0, TIOCGWINSZ, &ts);
printf("number of columns: %i, number of rows: %i, screen width: %i, screen height: %i\n", sz.ws_col, sz.ws_row, sz.ws_xpixel, sz.ws_ypixel);
#include <stdio.h>
#include <sys/ioctl.h>
int main(int argc, char **argv) {
struct winsize sz;
ioctl(0, TIOCGWINSZ, &sz);
printf("number of rows: %i, number of columns: %i, screen width: %i, screen height: %i\n", sz.ws_row, sz.ws_col, sz.ws_xpixel, sz.ws_ypixel);
return 0;
}
In Python:
.. code-block:: python
import array, fcntl, termios
import array, fcntl, sys, termios
buf = array.array('H', [0, 0, 0, 0])
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
print('number of columns: {}, number of rows: {}, screen width: {}, screen height: {}'.format(*buf))
print('number of rows: {}, number of columns: {}, screen width: {}, screen height: {}'.format(*buf))
Note that some terminals return ``0`` for the width and height values. Such
terminals should be modified to return the correct values. Examples of
terminals that return correct values: ``kitty, xterm``
You can also use the *CSI t* escape code to get the screen size. Send
``<ESC>[14t`` to *stdout* and kitty will reply on *stdin* with
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
pixels. This escape code is supported in many terminals, not just kitty.
``<ESC>[14t`` to ``STDOUT`` and kitty will reply on ``STDIN`` with
``<ESC>[4;<height>;<width>t`` where ``height`` and ``width`` are the window
size in pixels. This escape code is supported in many terminals, not just
kitty.
A minimal example
------------------
@@ -154,9 +162,10 @@ of transmitting paletted images.
RGB and RGBA data
~~~~~~~~~~~~~~~~~~~
In these formats the pixel data is stored directly as 3 or 4 bytes per pixel, respectively.
When specifying images in this format, the image dimensions **must** be sent in the control data.
For example::
In these formats the pixel data is stored directly as 3 or 4 bytes per pixel,
respectively. The colors in the data **must** be in the *sRGB color space*. When
specifying images in this format, the image dimensions **must** be sent in the
control data. For example::
<ESC>_Gf=24,s=10,v=20;<payload><ESC>\

View File

@@ -10,10 +10,12 @@ kitty - the fast, featureful, GPU based terminal emulator
buttery smooth scrolling. Uses threaded rendering to minimize input latency.
* Supports all modern terminal features: :doc:`graphics (images)
<graphics-protocol>`, unicode, true-color, OpenType ligatures, mouse
protocol, focus tracking, `bracketed paste
<https://cirw.in/blog/bracketed-paste>`_ and several :doc:`new terminal
protocol extensions <protocol-extensions>`.
<graphics-protocol>`, unicode, `true-color
<https://gist.github.com/XVilka/8346728>`_,
OpenType ligatures, mouse protocol, :doc:`hyperlinks <open_actions>`,
focus tracking, `bracketed paste <https://cirw.in/blog/bracketed-paste>`_
and several :doc:`new terminal protocol extensions
<protocol-extensions>`.
* Supports tiling multiple terminal windows side by side in different
:ref:`layouts <layouts>` without needing to use an extra program like tmux
@@ -346,11 +348,22 @@ For example:
new_os_window
# set new window size to 80x25 cells
os_window_size 80c 25c
# set the --class for the new OS window
os_window_class mywindow
launch sh
# Make the current window the active (focused) window
focus
launch emacs
# Add a watcher that will be called with various events that occur
# on all subsequent windows. See the documentation of the launch command
# for details on watchers.
watcher /some/python/file.py
launch mpd
launch irssi
# Remove the watcher for further windows
watcher clear
Mouse features
-------------------

View File

@@ -24,9 +24,16 @@ images and other types of documents directly in your terminal, even over SSH.
`nnn <https://github.com/jarun/nnn/>`_
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
`hunter <https://github.com/rabite0/hunter>`_
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
`koneko <https://github.com/twenty5151/koneko>`_
browse images from the pixiv artist community directly in kitty
`viu <https://github.com/atanunq/viu>`_
view images in the terminal, similar to kitty's icat, however also supports
displaying animated GIFs
System and data visualisation tools
---------------------------------------
@@ -60,6 +67,9 @@ System and data visualisation tools
iplot 'sin(x*3)*exp(x*.2)'
`onefetch <https://github.com/o2sh/onefetch>`_
a tool to fetch information about your git repositories
Editor integration
-----------------------
@@ -106,3 +116,10 @@ Miscellaneous
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
reload key bindings from :file:`kitty.conf` without needing to restart
kitty
`kitti3 <https://github.com/LandingEllipse/kitti3>`_
allow using kitty as a drop-down terminal under the i3 window manager
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
URL hints kitten for WeeChat that works without having to use WeeChat's
raw-mode.

View File

@@ -58,7 +58,8 @@ You can pass arguments to kittens by defining them in the map directive in
These will be available as the ``args`` parameter in the ``main()`` and
``handle_result()`` functions. Note also that the current working directory
of the kitten is set to the working directory of whatever program is
running in the active kitty window.
running in the active kitty window. The special argument ``@selection``
is replaced by the currently selected text in the active kitty window.
Passing the contents of the screen to the kitten
@@ -87,7 +88,8 @@ This will send the plain text of the active window to the kitten's
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
instead. If you want line wrap markers as well, use ``screen-ansi``
or just ``screen``. For the scrollback buffer as well, use
``history``, ``ansi-history`` or ``screen-history``.
``history``, ``ansi-history`` or ``screen-history``. To get
the currently selected text, use ``selection``.
Using kittens to script kitty, without any terminal UI
@@ -154,8 +156,8 @@ from ``kitty.fast_data_types``.
For example, to send a left click at position x: 2, y: 3 to the active window::
from kitty.fast_data_types import send_mouse_event, GLFW_MOUSE_BUTTON_LEFT, PRESS
send_mouse_event(boss.active_window.screen, 2, 3, GLFW_MOUSE_BUTTON_LEFT, PRESS, 0)
from kitty.fast_data_types import send_mouse_event, PRESS
send_mouse_event(boss.active_window.screen, 2, 3, 1, PRESS, 0)
The function will only send the event if the program is receiving events of
that type, and will return ``True`` if it sent the event, and ``False`` if not.
@@ -194,3 +196,7 @@ Kittens created by kitty users
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
Insert a password from a CLI password manager, taking care to only do it at
a password prompt.
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
URL hints kitten for WeeChat that works without having to use WeeChat's
raw-mode.

View File

@@ -0,0 +1,68 @@
Hyperlinked grep
=================
This kitten allows you to search your files using `ripgrep
<https://www.google.com/search?q=ripgrep>`_ and open the results
directly in your favorite editor in the terminal, at the line containing
the search result, simply by clicking on the result you want.
To set it up, first create :file:`~/.config/kitty/open-actions.conf` with the
following contents:
.. code:: conf
# Open any file with a fragment in vim, fragments are generated
# by the hyperlink_grep kitten and nothing else so far.
protocol file
fragment_matches [0-9]+
action launch --type=overlay vim +${FRAGMENT} ${FILE_PATH}
# Open text files without fragments in the editor
protocol file
mime text/*
action launch --type=overlay ${EDITOR} ${FILE_PATH}
Now, run a search with::
kitty +kitten hyperlinked_grep something
Hold down the :kbd:`ctrl+shift` keys and click on any of the
result lines, to open the file in vim at the matching line. If
you use some editor other than vim, you should adjust the
:file:`open-actions.conf` file accordingly.
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
alias hg='kitty +kitten hyperlinked_grep'
You can now run searches with::
hg some-search-term
If you want to enable completion, for the kitten, you can delegate completion
to rg. For that, instead of using an alias create a simple wrapper script named
:file:`hg` somewhere in your ``PATH``:
.. code-block:: sh
#!/bin/sh
exec kitty +kitten hyperlinked_grep "$@"
Then, for example, for ZSH, add the following to :file:`.zshrc`::
compdef _rg hg
To learn more about kitty's powerful framework for customizing URL click
actions, :doc:`see here <../open_actions>`.
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.
.. note::
While you can pass any of ripgrep's comand line options to the kitten and
they will be forwarded to rg, do not use options that change the output
formatting as the kitten works by parsing the output from ripgrep.

View File

@@ -0,0 +1,21 @@
Query terminal
=================
Used to query kitty from terminal programs about version, values of various
runtime options controlling its features, etc.
The querying is done using the (*semi*) standard XTGETTCAP escape sequence
pioneered by XTerm, so it works over SSH as well. The downside is that it
is slow, since it requires a roundtrip to the terminal emulator and back.
If you want to do some of the same querying in your terminal program without
depending on the kitten, you can do so, by processing the same escape codes.
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`_
for *XTGETTCAP* to see the syntax for the escape code and read the source
of this kitten to find the values of the keys for the various queries.
Command Line Interface
-------------------------
.. include:: ../generated/cli-kitten-query_terminal.rst

View File

@@ -0,0 +1,35 @@
Remote files
==============
|kitty| has the ability to easily *Edit*, *Open* or *Download* files
from a computer into which you are SSHed. In your SSH session run::
ls --hyperlink=auto
Then hold down :kbd:`ctrl+shift` and click the name of the file.
.. figure:: ../screenshots/remote_file.png
:alt: Remote file actions
:align: center
:scale: 100%
Remote file actions
|kitty| will ask you what you want to do with the remote file. You can choose
to *Edit* it in which case kitty will download it and open it locally in your
``EDITOR``. As you make changes to the file, they are automatically transferred
to the remote computer. Note that this happens without needing to install *any*
special software on the server, beyond ``ls`` that supports hyperlinks.
.. note::
Nested SSH sessions are not supported. The kitten will always try to copy
remote files from the first SSH host. This is because there is no way for
|kitty| to detect and follow a nested SSH session robustly.
.. note::
If you have not setup automatic password-less SSH access, then, when
editing starts you will be asked to enter your password just once,
thereafter the SSH connection will be re-used.
Similarly, you can choose to save the file to the local computer or download
and open it in its default file handler.

View File

@@ -24,7 +24,7 @@ a leading period and the index for the match if you don't like to use arrow
keys.
You can switch between modes using either the function keys or by pressing
:kbd:`Ctrl+Tab` and :kbd:`Ctrl+Shift+Tab`.
:kbd:`Ctrl+[` and :kbd:`Ctrl+]`.
Command Line Interface

View File

@@ -5,7 +5,7 @@ Launching programs in new windows/tabs
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
in news windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
in new windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
It is very powerful and allows sending the contents of
the current window to the launched program, as well as many other options.
@@ -74,6 +74,9 @@ functions for the events you are interested in, for example:
def on_resize(boss, window, data):
# Here data will contain old_geometry and new_geometry
def on_focus_change(boss, window, data):
# Here data kill contain focused
def on_close(boss, window, data):
# called when window is closed, typically when the program running in
# it exits.

95
docs/open_actions.rst Normal file
View File

@@ -0,0 +1,95 @@
Customizing the actions taken when clicking on links
======================================================
|kitty| has support for `terminal hyperlinks <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`_. These
are generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``,
``mdcat``, etc. You can customize exactly what happens when clicking on these hyperlinks
in |kitty|.
You can tell kitty to take arbitrarily many, complex actions
when a link is clicked. Let us illustrate with some examples, first. Create
the file :file:`~/.config/kitty/open-actions.conf` with the following:
.. code:: conf
# Open any image in the full kitty window by clicking on it
protocol file
mime image/*
action launch --type=overlay kitty +kitten icat --hold ${FILE_PATH}
Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an
image, holding down :kbd:`ctrl+shift`. It will be opened over the current
window. Press any key to close it.
Each entry in :file:`open-actions.conf` consists of one or more
:ref:`matching_criteria`, such as ``protocol`` and ``mime`` and one or more
``action`` entries. In the example above kitty uses the :doc:`launch <launch>`
action which can be used to run external programs. Entries are separated by
blank lines.
Actions are very powerful, anything that you can map to a key combination in
`kitty.conf` can be used as an action. You can specify more than one action per
entry if you like, for example:
.. code:: conf
# Tail a log file (*.log) in a new OS Window and reduce its font size
protocol file
ext log
action launch --title ${FILE} --type=os-window tail -f ${FILE_PATH}
action change_font_size current -2
In the action specification you can expand environment variables, as shown in
the examples above. In addition to regular environment variables, there are
some special variables, documented below:
``URL``
The full URL being opened
``FILE_PATH``
The path portion of the URL (unquoted)
``FILE``
The file portion of the path of the URL (unquoted)
``FRAGMENT``
The fragment (unquoted), if any of the URL or the empty string.
.. _matching_criteria:
Matching criteria
------------------
An entry in :file:`open-actions.conf` must have one or more matching criteria.
URLs that match all criteria for an entry will trigger that entries' actions.
Processing stops at the first matching entry, so put more specific matching
criteria at the start of the list. Entries in the file are separated by blank
lines. The various available criteria are:
``protocol``
A comma separated list of protocols, for example: ``http, https``. If
absent, there is no constraint on protocol
``url``
A regular expression that must match against the entire (unquoted) URL
``fragment_matches``
A regular expression that must match against the fragment (part after #) in
the URL
``mime``
A comma separated list of MIME types, for example: ``text/*, image/*,
application/pdf``. You can add MIME types to kitty by creating the
:file:`mime.types` in the kitty configuration directory. Useful if your
system MIME database does not have definitions you need. This file is
in the standard format of one definition per line, like: ``text/plain rst
md``.
``ext``
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
``file``
A shell glob pattern that must match the filename, for example:
``image-??.png``

View File

@@ -222,9 +222,133 @@ Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
Further, since it is invalid, it should be ignored by terminal emulators
that do not support this extension, thereby making it safe to use, simply
always send it before starting a new OSC 52 paste, even if you aren't chunking
up large pastes, that way kitty wont concatenate your paste, and it will have
up large pastes, that way kitty won't concatenate your paste, and it will have
no ill-effects in other terminal emulators.
In case you're using software that can't be easily adapted to this
protocol extension, it can be disabled by specifying ``no-append`` to the
:opt:`clipboard_control` setting.
.. _desktop_notifications:
Desktop notifications
---------------------------------
|kitty| implements an extensible escape code (OSC 99) to show desktop
notifications. It is easy to use from shell scripts and fully extensible to
show title and body. Clicking on the notification can optionally focus the
window it came from, and/or send an escape code back to the application running
in that window.
The design of the escape code is partially based on the discussion in
the defunct
`terminal-wg <https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/13>`_
The escape code has the form::
<OSC> 99 ; metadata ; payload <terminator>
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is
:code:`<ESC><backslash>`. The metadata is a section of colon separated
:code:`key=value` pairs. Every key must be a single character from the set
:code:`a-zA-Z` and every value must be a word consisting of characters from
the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be
interpreted based on the metadata section. The two semi-colons *must* always be
present even when no metadata is present.
Before going into details, lets see how one can display a simple, single line
notification from a shell script::
printf '\x1b]99;;Hello world\x1b\\'
To show a message with a title and a body::
printf '\x1b]99;i=1:d=0;Hello world\x1b\\'
printf '\x1b]99;i=1:d=1:p=body;This is cool\x1b\\'
The most important key in the metadata is the ``p`` key, it controls how the
payload is interpreted. A value of ``title`` means the payload is setting the
title for the notification. A value of ``body`` means it is setting the body,
and so on, see the table below for full details.
The design of the escape code is fundamentally chunked, this is because
different terminal emulators have different limits on how large a single escape
code can be. Chunking is accomplished by the ``i`` and ``d`` keys. The ``i``
key is the *notification id* which can be any string containing the characters
``[a-zA-Z0-9_-+.]``. The ``d`` key stands for *done* and
can only take the values ``0`` and ``1``. A value of ``0`` means the
notification is not yet done and the terminal emulator should hold off
displaying it. A value of ``1`` means the notification is done, and should be
displayed. You can specify the title or body multiple times and the terminal
emulator will concatenate them, thereby allowing arbitrarily long text
(terminal emulators are free to impose a sensible limit to avoid
Denial-of-Service attacks).
Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
text with no embedded escape codes, or UTF-8 text that is base64 encoded, in
which case there must be an ``e=1`` key in the metadata to indicate the payload
is base64 encoded.
When the user clicks the notification, a couple of things can happen, the
terminal emulator can focus the window from which the notification came, and/or
it can send back an escape code to the application indicating the notification
was activated. This is controlled by the ``a`` key which takes a comma
separated set of values, ``report`` and ``focus``. The value ``focus`` means
focus the window from which the notification was issued and is the default.
``report`` means send an escape code back to the application. The format of the
returned escape code is::
<OSC> 99 ; i=identifier ; <terminator>
The value of ``identifier`` comes from the ``i`` key in the escape code sent by
the application. If the application sends no identifier, then the terminal
*must* use ``i=0``. Actions can be preceded by a negative sign to turn them
off, so for example if you do not want any action, turn off the default
``focus`` action with::
a=-focus
Complete specification of all the metadata keys is in the table below. If a
terminal emulator encounters a key in the metadata it does not understand,
the key *must* be ignored, to allow for future extensibility of this escape
code. Similarly if values for known keys are unknown, the terminal emulator
*should* either ignore the entire escape code or perform a best guess effort
to display it based on what it does understand.
.. note::
It is possible to extend this escape code to allow specifying an icon for
the notification, however, given that some platforms, such as macOS, dont
allow displaying custom icons on a notification, at all, it was decided to
leave it out of the spec for the time being.
Similarly, features such as scheduled notifications could be added in future
revisions.
======= ==================== ========= =================
Key Value Default Description
======= ==================== ========= =================
``a`` Comma separated list ``focus`` What action to perform when the
of ``report``, notification is clicked
``focus``, with
optional leading
``-``
``d`` ``0`` or ``1`` ``1`` Indicates if the notification is
complete or not.
``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is base64 encoded UTF-8,
otherwise it is plain UTF-8 text with no C0 control codes in it
``i`` ``[a-zA-Z0-9-_+.]`` ``0`` Identifier for the notification
``p`` One of ``title`` or ``title`` Whether the payload is the notification title or body. If a
``body``. notification has no title, the body will be used as title.
======= ==================== ========= =================
.. note::
|kitty| also supports the legacy OSC 9 protocol developed by iTerm2 for
desktop notifications.

View File

@@ -10,18 +10,18 @@ form::
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form::
{
'cmd': "command name",
'version': "kitty version",
'no_response': Optional Boolean,
'payload': <Optional JSON object>,
"cmd": "command name",
"version": <kitty version>,
"no_response": Optional Boolean,
"payload": <Optional JSON object>,
}
The ``version`` above is a string of the form :code:`0.14.2`. If you are developing a
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are developing a
standalone client, use the kitty version that you are developing against. Using
a version greater than the version of the kitty instance you are talking to,
will cause a failure.
Set ``no_response`` to True if you don't want a response from kitty.
Set ``no_response`` to ``true`` if you don't want a response from kitty.
The optional payload is a JSON object that is specific to the actual command being sent.
The fields in the object for every command are documented below.

View File

@@ -144,7 +144,8 @@ you can map it in :file:`kitty.conf`. For example::
Then pressing the :kbd:`F1` key will set the active window margins to 30.
The syntax for what follows :code:`remote_control` is exactly the same
as the syntax for what follows :code:`kitty @` above.
as the syntax for what follows :code:`kitty @` above. You do not need
to enable remote control to use these mappings.
Documentation for the remote control protocol

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -98,6 +98,18 @@ def parse_ucd() -> None:
elif category.startswith('S'):
all_symbols.add(codepoint)
with open('nerd-fonts-glyphs.txt') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
continue
code, category, name = line.split(' ', 2)
codepoint = int(code, 16)
if name and codepoint not in name_map:
name_map[codepoint] = name.upper()
for word in name.lower().split():
add_word(word, codepoint)
# Some common synonyms
word_search_map['bee'] |= word_search_map['honeybee']
word_search_map['lambda'] |= word_search_map['lamda']
@@ -274,7 +286,9 @@ def category_test(
comment: str,
use_static: bool = False,
extra_chars: Union[FrozenSet[int], Set[int]] = frozenset(),
exclude: Union[Set[int], FrozenSet[int]] = frozenset()
exclude: Union[Set[int], FrozenSet[int]] = frozenset(),
least_check_return: Optional[str] = None,
ascii_range: Optional[str] = None
) -> None:
static = 'static inline ' if use_static else ''
chars: Set[int] = set()
@@ -284,6 +298,11 @@ def category_test(
chars -= exclude
p(f'{static}bool\n{name}(char_type code) {{')
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
if least_check_return is not None:
least = min(chars)
p(f'\tif (LIKELY(code < {least})) return {least_check_return};')
if ascii_range is not None:
p(f'\tif (LIKELY(0x20 <= code && code <= 0x7e)) return {ascii_range};')
p('\tswitch(code) {')
for spec in get_ranges(list(chars)):
write_case(spec, p)
@@ -337,11 +356,15 @@ def gen_ucd() -> None:
{c for c in class_maps if c.startswith('M')},
'M category (marks)',
# See https://github.com/harfbuzz/harfbuzz/issues/169
extra_chars=emoji_skin_tone_modifiers | {zwj}
extra_chars=emoji_skin_tone_modifiers | {zwj},
least_check_return='false'
)
category_test(
'is_ignored_char', p, 'Cc Cf Cs'.split(),
'Control characters and non-characters', extra_chars=non_characters, exclude={zwj})
'Control characters and non-characters',
extra_chars=non_characters, exclude={zwj},
ascii_range='false'
)
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
category_test('is_CZ_category', p, cz, 'C and Z categories')
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
@@ -421,7 +444,7 @@ def gen_names() -> None:
p('}; // }}}\n')
# The trie
p('typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
p('typedef struct { uint32_t children_offset; uint32_t match_offset; } word_trie;\n')
all_trie_nodes: List['TrieNode'] = [] # noqa
class TrieNode:
@@ -482,6 +505,7 @@ def gen_wcwidth() -> None:
with create_header('kitty/wcwidth-std.h') as p:
p('static int\nwcwidth_std(int32_t code) {')
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) return 1;')
p('\tswitch(code) {')
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
@@ -500,7 +524,7 @@ def gen_wcwidth() -> None:
p('\t}')
p('\treturn 1;\n}')
p('static bool\nis_emoji_presentation_base(uint32_t code) {')
p('static inline bool\nis_emoji_presentation_base(uint32_t code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(emoji_presentation_bases)):
write_case(spec, p)

View File

@@ -669,8 +669,6 @@ int _glfwPlatformInit(void)
if (!initializeTIS())
return false;
_glfwInitJoysticksNS();
_glfwPollMonitorsNS();
return true;
@@ -723,7 +721,6 @@ void _glfwPlatformTerminate(void)
free(_glfw.ns.clipboardString);
_glfwTerminateNSGL();
_glfwTerminateJoysticksNS();
} // autoreleasepool
}

View File

@@ -44,7 +44,3 @@ typedef struct _GLFWjoystickNS
CFMutableArrayRef hats;
} _GLFWjoystickNS;
void _glfwInitJoysticksNS(void);
void _glfwTerminateJoysticksNS(void);

View File

@@ -233,6 +233,19 @@ static void matchCallback(void* context UNUSED,
break;
}
}
else if (page == kHIDPage_Simulation)
{
switch (usage)
{
case kHIDUsage_Sim_Accelerator:
case kHIDUsage_Sim_Brake:
case kHIDUsage_Sim_Throttle:
case kHIDUsage_Sim_Rudder:
case kHIDUsage_Sim_Steering:
target = axes;
break;
}
}
else if (page == kHIDPage_Button || page == kHIDPage_Consumer)
target = buttons;
@@ -291,12 +304,10 @@ static void removeCallback(void* context UNUSED,
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
// Initialize joystick interface
//
void _glfwInitJoysticksNS(void)
bool _glfwPlatformInitJoysticks(void)
{
CFMutableArrayRef matching;
const long usages[] =
@@ -315,7 +326,7 @@ void _glfwInitJoysticksNS(void)
if (!matching)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to create array");
return;
return false;
}
for (size_t i = 0; i < sizeof(usages) / sizeof(long); i++)
@@ -370,26 +381,24 @@ void _glfwInitJoysticksNS(void)
// Execute the run loop once in order to register any initially-attached
// joysticks
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
return true;
}
// Close all opened joystick handles
//
void _glfwTerminateJoysticksNS(void)
void _glfwPlatformTerminateJoysticks(void)
{
int jid;
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
closeJoystick(_glfw.joysticks + jid);
CFRelease(_glfw.ns.hidManager);
_glfw.ns.hidManager = NULL;
if (_glfw.ns.hidManager)
{
CFRelease(_glfw.ns.hidManager);
_glfw.ns.hidManager = NULL;
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
{
if (mode & _GLFW_POLL_AXES)

View File

@@ -279,14 +279,20 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
CFSTR("IOFBCurrentPixelCount"),
kCFAllocatorDefault,
kNilOptions);
if (!clockRef || !countRef)
break;
uint32_t clock = 0, count = 0;
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
CFNumberGetValue(countRef, kCFNumberIntType, &count);
CFRelease(clockRef);
CFRelease(countRef);
if (clockRef)
{
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
CFRelease(clockRef);
}
if (countRef)
{
CFNumberGetValue(countRef, kCFNumberIntType, &count);
CFRelease(countRef);
}
if (clock > 0 && count > 0)
refreshRate = clock / (double) count;
@@ -523,7 +529,6 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
*width = (int)frameRect.size.width;
if (height)
*height = (int)frameRect.size.height;
}
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)

View File

@@ -96,16 +96,11 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
#include "posix_thread.h"
#include "cocoa_joystick.h"
#include "nsgl_context.h"
#include "egl_context.h"
#include "osmesa_context.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNS ns
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerNS ns

View File

@@ -1595,10 +1595,10 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
{
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE)
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
else
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
else
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
}
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
@@ -1854,6 +1854,11 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
[window->ns.object setLevel:NSNormalWindowLevel];
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
{
[window->ns.object setIgnoresMouseEvents:enabled];
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
return (float) [window->ns.object alphaValue];
@@ -1989,8 +1994,10 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
bytesPerRow:src->width * 4
bitsPerPixel:32];
if (rep == nil)
if (rep == nil) {
[native release];
return false;
}
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
[native addRepresentation:rep];
@@ -2105,6 +2112,47 @@ const char* _glfwPlatformGetClipboardString(void)
return _glfw.ns.clipboardString;
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
{
int type = 0;
if (_glfw.egl.ANGLE_platform_angle_opengl)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
}
if (_glfw.egl.ANGLE_platform_angle_metal)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_METAL)
type = EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE;
}
if (type)
{
*attribs = calloc(3, sizeof(EGLint));
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
(*attribs)[1] = type;
(*attribs)[2] = EGL_NONE;
return EGL_PLATFORM_ANGLE_ANGLE;
}
}
return 0;
}
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
{
return EGL_DEFAULT_DISPLAY;
}
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
{
return window->ns.layer;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
@@ -2400,7 +2448,7 @@ START_ALLOW_CASE_RANGE
END_ALLOW_CASE_RANGE
}
if (utf_16_key != 0) {
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
} else {
unsigned str_pos = 0;
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {

68
glfw/egl_context.c vendored
View File

@@ -273,6 +273,8 @@ static void destroyContextEGL(_GLFWwindow* window)
bool _glfwInitEGL(void)
{
int i;
EGLint* attribs = NULL;
const char* extensions;
const char* sonames[] =
{
#if defined(_GLFW_EGL_LIBRARY)
@@ -351,7 +353,51 @@ bool _glfwInitEGL(void)
return false;
}
_glfw.egl.display = eglGetDisplay(_GLFW_EGL_NATIVE_DISPLAY);
extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (extensions && eglGetError() == EGL_SUCCESS)
_glfw.egl.EXT_client_extensions = true;
if (_glfw.egl.EXT_client_extensions)
{
_glfw.egl.EXT_platform_base =
_glfwStringInExtensionString("EGL_EXT_platform_base", extensions);
_glfw.egl.EXT_platform_x11 =
_glfwStringInExtensionString("EGL_EXT_platform_x11", extensions);
_glfw.egl.EXT_platform_wayland =
_glfwStringInExtensionString("EGL_EXT_platform_wayland", extensions);
_glfw.egl.ANGLE_platform_angle =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle", extensions);
_glfw.egl.ANGLE_platform_angle_opengl =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_opengl", extensions);
_glfw.egl.ANGLE_platform_angle_d3d =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_d3d", extensions);
_glfw.egl.ANGLE_platform_angle_vulkan =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_vulkan", extensions);
_glfw.egl.ANGLE_platform_angle_metal =
_glfwStringInExtensionString("EGL_ANGLE_platform_angle_metal", extensions);
}
if (_glfw.egl.EXT_platform_base)
{
_glfw.egl.GetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)
eglGetProcAddress("eglGetPlatformDisplayEXT");
_glfw.egl.CreatePlatformWindowSurfaceEXT = (PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)
eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT");
}
_glfw.egl.platform = _glfwPlatformGetEGLPlatform(&attribs);
if (_glfw.egl.platform)
{
_glfw.egl.display =
eglGetPlatformDisplayEXT(_glfw.egl.platform,
_glfwPlatformGetEGLNativeDisplay(),
attribs);
}
else
_glfw.egl.display = eglGetDisplay(_glfwPlatformGetEGLNativeDisplay());
free(attribs);
if (_glfw.egl.display == EGL_NO_DISPLAY)
{
_glfwInputError(GLFW_API_UNAVAILABLE,
@@ -419,6 +465,7 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
EGLint attribs[40];
EGLConfig config;
EGLContext share = NULL;
EGLNativeWindowType native;
int index = 0;
if (!_glfw.egl.display)
@@ -554,11 +601,20 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
setAttrib(EGL_NONE, EGL_NONE);
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display,
config,
_GLFW_EGL_NATIVE_WINDOW,
attribs);
native = _glfwPlatformGetEGLNativeWindow(window);
// HACK: ANGLE does not implement eglCreatePlatformWindowSurfaceEXT
// despite reporting EGL_EXT_platform_base
if (_glfw.egl.platform && _glfw.egl.platform != EGL_PLATFORM_ANGLE_ANGLE)
{
window->context.egl.surface =
eglCreatePlatformWindowSurfaceEXT(_glfw.egl.display, config, native, attribs);
}
else
{
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display, config, native, attribs);
}
if (window->context.egl.surface == EGL_NO_SURFACE)
{
_glfwInputError(GLFW_PLATFORM_ERROR,

54
glfw/egl_context.h vendored
View File

@@ -25,26 +25,10 @@
//
//========================================================================
#if defined(_GLFW_USE_EGLPLATFORM_H)
#include <EGL/eglplatform.h>
#elif defined(_GLFW_WIN32)
#if defined(_GLFW_WIN32)
#define EGLAPIENTRY __stdcall
typedef HDC EGLNativeDisplayType;
typedef HWND EGLNativeWindowType;
#elif defined(_GLFW_COCOA)
#define EGLAPIENTRY
typedef void* EGLNativeDisplayType;
typedef id EGLNativeWindowType;
#elif defined(_GLFW_X11)
#define EGLAPIENTRY
typedef Display* EGLNativeDisplayType;
typedef Window EGLNativeWindowType;
#elif defined(_GLFW_WAYLAND)
#define EGLAPIENTRY
typedef struct wl_display* EGLNativeDisplayType;
typedef struct wl_egl_window* EGLNativeWindowType;
#else
#error "No supported EGL platform selected"
#define EGLAPIENTRY
#endif
#define EGL_SUCCESS 0x3000
@@ -108,6 +92,17 @@ typedef struct wl_egl_window* EGLNativeWindowType;
#define EGL_CONTEXT_RELEASE_BEHAVIOR_KHR 0x2097
#define EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR 0
#define EGL_CONTEXT_RELEASE_BEHAVIOR_FLUSH_KHR 0x2098
#define EGL_PLATFORM_X11_EXT 0x31d5
#define EGL_PLATFORM_WAYLAND_EXT 0x31d8
#define EGL_PLATFORM_ANGLE_ANGLE 0x3202
#define EGL_PLATFORM_ANGLE_TYPE_ANGLE 0x3203
#define EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE 0x320d
#define EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE 0x320e
#define EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE 0x3207
#define EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE 0x3208
#define EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE 0x3450
#define EGL_PLATFORM_ANGLE_TYPE_METAL_ANGLE 0x3489
#define EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE 0x348f
typedef int EGLint;
typedef unsigned int EGLBoolean;
@@ -117,6 +112,9 @@ typedef void* EGLContext;
typedef void* EGLDisplay;
typedef void* EGLSurface;
typedef void* EGLNativeDisplayType;
typedef void* EGLNativeWindowType;
// EGL function pointer typedefs
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
@@ -153,9 +151,10 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglQueryString _glfw.egl.QueryString
#define eglGetProcAddress _glfw.egl.GetProcAddress
#define _GLFW_EGL_CONTEXT_STATE _GLFWcontextEGL egl
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE _GLFWlibraryEGL egl
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLDisplay,EGLConfig,void*,const EGLint*);
#define eglGetPlatformDisplayEXT _glfw.egl.GetPlatformDisplayEXT
#define eglCreatePlatformWindowSurfaceEXT _glfw.egl.CreatePlatformWindowSurfaceEXT
// EGL-specific per-context data
//
@@ -173,6 +172,7 @@ typedef struct _GLFWcontextEGL
//
typedef struct _GLFWlibraryEGL
{
EGLenum platform;
EGLDisplay display;
EGLint major, minor;
bool prefix;
@@ -182,6 +182,15 @@ typedef struct _GLFWlibraryEGL
bool KHR_gl_colorspace;
bool KHR_get_all_proc_addresses;
bool KHR_context_flush_control;
bool EXT_client_extensions;
bool EXT_platform_base;
bool EXT_platform_x11;
bool EXT_platform_wayland;
bool ANGLE_platform_angle;
bool ANGLE_platform_angle_opengl;
bool ANGLE_platform_angle_d3d;
bool ANGLE_platform_angle_vulkan;
bool ANGLE_platform_angle_metal;
void* handle;
@@ -203,6 +212,9 @@ typedef struct _GLFWlibraryEGL
PFN_eglQueryString QueryString;
PFN_eglGetProcAddress GetProcAddress;
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC CreatePlatformWindowSurfaceEXT;
} _GLFWlibraryEGL;

View File

@@ -10,6 +10,7 @@ from typing import Callable, Dict, List, Optional, Tuple
_plat = sys.platform.lower()
is_linux = 'linux' in _plat
is_openbsd = 'openbsd' in _plat
base = os.path.dirname(os.path.abspath(__file__))
@@ -72,8 +73,9 @@ def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_co
if module in ('x11', 'wayland'):
ans.cflags.append('-pthread')
ans.ldpaths.append('-pthread')
ans.ldpaths.extend('-lrt -lm -ldl'.split())
ans.ldpaths.extend('-pthread -lm'.split())
if not is_openbsd:
ans.ldpaths.extend('-lrt -ldl'.split())
at_least_version('xkbcommon', 0, 5)
if module == 'x11':

66
glfw/glfw3.h vendored
View File

@@ -195,6 +195,22 @@ extern "C" {
#endif /*__APPLE__*/
#elif defined(GLFW_INCLUDE_GLU)
#if defined(__APPLE__)
#if defined(GLFW_INCLUDE_GLU)
#include <OpenGL/glu.h>
#endif
#else /*__APPLE__*/
#if defined(GLFW_INCLUDE_GLU)
#include <GL/glu.h>
#endif
#endif /*__APPLE__*/
#elif !defined(GLFW_INCLUDE_NONE) && \
!defined(__gl_h_) && \
!defined(__gles1_gl_h_) && \
@@ -218,9 +234,6 @@ extern "C" {
#define GL_GLEXT_LEGACY
#endif
#include <OpenGL/gl.h>
#if defined(GLFW_INCLUDE_GLU)
#include <OpenGL/glu.h>
#endif
#else /*__APPLE__*/
@@ -228,9 +241,6 @@ extern "C" {
#if defined(GLFW_INCLUDE_GLEXT)
#include <GL/glext.h>
#endif
#if defined(GLFW_INCLUDE_GLU)
#include <GL/glu.h>
#endif
#endif /*__APPLE__*/
@@ -821,7 +831,7 @@ extern "C" {
* @analysis Application programmer error. Fix the offending call.
*/
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
/*! @brief The reuqested feature is not provided by the platform.
/*! @brief The requested feature is not provided by the platform.
*
* The requested feature is not provided by the platform, so GLFW is unable to
* implement it. The documentation for each function notes if it could emit
@@ -922,11 +932,19 @@ extern "C" {
* [window attribute](@ref GLFW_FOCUS_ON_SHOW_attrib).
*/
#define GLFW_FOCUS_ON_SHOW 0x0002000C
/*! @brief Mouse input transparency window hint and attribute
*
* Mouse input transparency [window hint](@ref GLFW_MOUSE_PASSTHROUGH_hint) or
* [window attribute](@ref GLFW_MOUSE_PASSTHROUGH_attrib).
*/
#define GLFW_MOUSE_PASSTHROUGH 0x0002000D
/*! @brief Occlusion window attribute
*
* Occlusion [window attribute](@ref GLFW_OCCLUDED_attrib).
*/
#define GLFW_OCCLUDED 0x0002000D
#define GLFW_OCCLUDED 0x0002000E
/*! @brief Framebuffer bit depth hint.
*
* Framebuffer bit depth [hint](@ref GLFW_RED_BITS).
@@ -1044,12 +1062,17 @@ extern "C" {
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
*/
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
/*! @brief OpenGL debug context hint and attribute.
/*! @brief Debug mode context hint and attribute.
*
* OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
* Debug mode context [hint](@ref GLFW_CONTEXT_DEBUG_hint) and
* [attribute](@ref GLFW_CONTEXT_DEBUG_attrib).
*/
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
#define GLFW_CONTEXT_DEBUG 0x00022007
/*! @brief Legacy name for compatibility.
*
* This is an alias for compatibility with earlier versions.
*/
#define GLFW_OPENGL_DEBUG_CONTEXT GLFW_CONTEXT_DEBUG
/*! @brief OpenGL profile hint and attribute.
*
* OpenGL profile [hint](@ref GLFW_OPENGL_PROFILE_hint) and
@@ -1132,6 +1155,14 @@ extern "C" {
#define GLFW_EGL_CONTEXT_API 0x00036002
#define GLFW_OSMESA_CONTEXT_API 0x00036003
#define GLFW_ANGLE_PLATFORM_TYPE_NONE 0x00037001
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGL 0x00037002
#define GLFW_ANGLE_PLATFORM_TYPE_OPENGLES 0x00037003
#define GLFW_ANGLE_PLATFORM_TYPE_D3D9 0x00037004
#define GLFW_ANGLE_PLATFORM_TYPE_D3D11 0x00037005
#define GLFW_ANGLE_PLATFORM_TYPE_VULKAN 0x00037007
#define GLFW_ANGLE_PLATFORM_TYPE_METAL 0x00037008
/*! @defgroup shapes Standard cursor shapes
* @brief Standard system cursor shapes.
*
@@ -1165,8 +1196,12 @@ typedef enum {
* Joystick hat buttons [init hint](@ref GLFW_JOYSTICK_HAT_BUTTONS).
*/
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
#define GLFW_DEBUG_KEYBOARD 0x00050002
#define GLFW_ENABLE_JOYSTICKS 0x00050003
/*! @brief ANGLE rendering backend init hint.
*
* ANGLE rendering backend [init hint](@ref GLFW_ANGLE_PLATFORM_TYPE_hint).
*/
#define GLFW_ANGLE_PLATFORM_TYPE 0x00050002
#define GLFW_DEBUG_KEYBOARD 0x00050003
/*! @brief macOS specific init hint.
*
* macOS specific [init hint](@ref GLFW_COCOA_CHDIR_RESOURCES_hint).
@@ -1467,7 +1502,7 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int);
* @endcode
*
* @param[in] window The window that was maximized or restored.
* @param[in] iconified `true` if the window was maximized, or
* @param[in] maximized `true` if the window was maximized, or
* `false` if it was restored.
*
* @sa @ref window_maximize
@@ -3713,6 +3748,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib),
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib) and
* [GLFW_FOCUS_ON_SHOW](@ref GLFW_FOCUS_ON_SHOW_attrib).
* [GLFW_MOUSE_PASSTHROUGH](@ref GLFW_MOUSE_PASSTHROUGH_attrib)
*
* Some of these attributes are ignored for full screen windows. The new
* value will take effect if the window is later made windowed.

2
glfw/ibus_glfw.c vendored
View File

@@ -269,7 +269,7 @@ setup_connection(_GLFWIBUSData *ibus) {
ibus->conn = NULL;
}
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", false);
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", true);
if (!ibus->conn) return false;
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
if (!glfw_dbus_call_method_with_reply(

9
glfw/init.c vendored
View File

@@ -53,8 +53,8 @@ static GLFWerrorfun _glfwErrorCallback;
static _GLFWinitconfig _glfwInitHints =
{
true, // hat buttons
GLFW_ANGLE_PLATFORM_TYPE_NONE, // ANGLE backend
false, // debug keyboard
true, // enable joystick
{
true, // macOS menu bar
true // macOS bundle chdir
@@ -92,6 +92,7 @@ static void terminate(void)
_glfw.mappingCount = 0;
_glfwTerminateVulkan();
_glfwPlatformTerminateJoysticks();
_glfwPlatformTerminate();
_glfw.initialized = false;
@@ -275,12 +276,12 @@ GLFWAPI void glfwInitHint(int hint, int value)
{
switch (hint)
{
case GLFW_ENABLE_JOYSTICKS:
_glfwInitHints.enableJoysticks = value;
return;
case GLFW_JOYSTICK_HAT_BUTTONS:
_glfwInitHints.hatButtons = value;
return;
case GLFW_ANGLE_PLATFORM_TYPE:
_glfwInitHints.angleType = value;
return;
case GLFW_DEBUG_KEYBOARD:
_glfwInitHints.debugKeyboard = value;
return;

47
glfw/input.c vendored
View File

@@ -44,6 +44,22 @@
#define _GLFW_JOYSTICK_BUTTON 2
#define _GLFW_JOYSTICK_HATBIT 3
// Initializes the platform joystick API if it has not been already
//
static bool initJoysticks(void)
{
if (!_glfw.joysticksInitialized)
{
if (!_glfwPlatformInitJoysticks())
{
_glfwPlatformTerminateJoysticks();
return false;
}
}
return _glfw.joysticksInitialized = true;
}
// Finds a mapping based on joystick GUID
//
static _GLFWmapping* findMapping(const char* guid)
@@ -1101,6 +1117,9 @@ GLFWAPI int glfwJoystickPresent(int jid)
return false;
}
if (!initJoysticks())
return false;
js = _glfw.joysticks + jid;
if (!js->present)
return false;
@@ -1126,6 +1145,9 @@ GLFWAPI const float* glfwGetJoystickAxes(int jid, int* count)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1155,6 +1177,9 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1188,6 +1213,9 @@ GLFWAPI const unsigned char* glfwGetJoystickHats(int jid, int* count)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1214,6 +1242,9 @@ GLFWAPI const char* glfwGetJoystickName(int jid)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1239,6 +1270,9 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1284,6 +1318,10 @@ GLFWAPI void* glfwGetJoystickUserPointer(int jid)
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
if (!initJoysticks())
return NULL;
_GLFW_SWAP_POINTERS(_glfw.callbacks.joystick, cbfun);
return cbfun;
}
@@ -1363,6 +1401,9 @@ GLFWAPI int glfwJoystickIsGamepad(int jid)
return false;
}
if (!initJoysticks())
return false;
js = _glfw.joysticks + jid;
if (!js->present)
return false;
@@ -1388,6 +1429,9 @@ GLFWAPI const char* glfwGetGamepadName(int jid)
return NULL;
}
if (!initJoysticks())
return NULL;
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
@@ -1420,6 +1464,9 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
return false;
}
if (!initJoysticks())
return false;
js = _glfw.joysticks + jid;
if (!js->present)
return false;

23
glfw/internal.h vendored
View File

@@ -198,6 +198,9 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
#error "No supported window creation API selected"
#endif
#include "egl_context.h"
#include "osmesa_context.h"
#define remove_i_from_array(array, i, count) { \
(count)--; \
if ((i) < (count)) { \
@@ -271,8 +274,8 @@ struct _GLFWerror
struct _GLFWinitconfig
{
bool hatButtons;
int angleType;
bool debugKeyboard;
bool enableJoysticks;
struct {
bool menubar;
bool chdir;
@@ -299,6 +302,7 @@ struct _GLFWwndconfig
bool maximized;
bool centerCursor;
bool focusOnShow;
bool mousePassthrough;
bool scaleToMonitor;
struct {
bool retina;
@@ -392,9 +396,9 @@ struct _GLFWcontext
// This is defined in the context API's context.h
_GLFW_PLATFORM_CONTEXT_STATE
// This is defined in egl_context.h
_GLFW_EGL_CONTEXT_STATE;
_GLFWcontextEGL egl;
// This is defined in osmesa_context.h
_GLFW_OSMESA_CONTEXT_STATE;
_GLFWcontextOSMesa osmesa;
};
// Window and context structure
@@ -409,6 +413,7 @@ struct _GLFWwindow
bool autoIconify;
bool floating;
bool focusOnShow;
bool mousePassthrough;
bool shouldClose;
void* userPointer;
GLFWid id;
@@ -569,6 +574,7 @@ struct _GLFWlibrary
_GLFWmonitor** monitors;
int monitorCount;
bool joysticksInitialized;
_GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1];
_GLFWmapping* mappings;
int mappingCount;
@@ -613,9 +619,9 @@ struct _GLFWlibrary
// This is defined in the platform's joystick.h
_GLFW_PLATFORM_LIBRARY_JOYSTICK_STATE
// This is defined in egl_context.h
_GLFW_EGL_LIBRARY_CONTEXT_STATE;
_GLFWlibraryEGL egl;
// This is defined in osmesa_context.h
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
_GLFWlibraryOSMesa osmesa;
};
// Global state shared between compilation units of GLFW
@@ -662,6 +668,8 @@ void _glfwPlatformSetPrimarySelectionString(const char* string);
const char* _glfwPlatformGetPrimarySelectionString(void);
#endif
bool _glfwPlatformInitJoysticks(void);
void _glfwPlatformTerminateJoysticks(void);
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
void _glfwPlatformUpdateGamepadGUID(char* guid);
@@ -713,6 +721,7 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled);
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
@@ -721,6 +730,10 @@ void _glfwPlatformWaitEvents(void);
void _glfwPlatformWaitEventsTimeout(monotonic_t timeout);
void _glfwPlatformPostEmptyEvent(void);
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs);
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void);
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window);
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
VkPhysicalDevice device,

98
glfw/linux_joystick.c vendored
View File

@@ -264,9 +264,50 @@ static int compareJoysticks(const void* fp, const void* sp)
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize joystick interface
//
bool _glfwInitJoysticksLinux(void)
void _glfwDetectJoystickConnectionLinux(void)
{
if (_glfw.linjs.inotify <= 0)
return;
ssize_t offset = 0;
char buffer[16384];
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
while (size > offset)
{
regmatch_t match;
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
offset += sizeof(struct inotify_event) + e->len;
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
continue;
char path[PATH_MAX];
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
if (e->mask & (IN_CREATE | IN_ATTRIB))
openJoystickDevice(path);
else if (e->mask & IN_DELETE)
{
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
{
closeJoystick(_glfw.joysticks + jid);
break;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
bool _glfwPlatformInitJoysticks(void)
{
const char* dirname = "/dev/input";
@@ -320,9 +361,7 @@ bool _glfwInitJoysticksLinux(void)
return true;
}
// Close all opened joystick handles
//
void _glfwTerminateJoysticksLinux(void)
void _glfwPlatformTerminateJoysticks(void)
{
int jid;
@@ -333,61 +372,16 @@ void _glfwTerminateJoysticksLinux(void)
closeJoystick(js);
}
regfree(&_glfw.linjs.regex);
if (_glfw.linjs.inotify > 0)
{
if (_glfw.linjs.watch > 0)
inotify_rm_watch(_glfw.linjs.inotify, _glfw.linjs.watch);
close(_glfw.linjs.inotify);
regfree(&_glfw.linjs.regex);
}
}
void _glfwDetectJoystickConnectionLinux(void)
{
if (_glfw.linjs.inotify <= 0)
return;
ssize_t offset = 0;
char buffer[16384];
const ssize_t size = read(_glfw.linjs.inotify, buffer, sizeof(buffer));
while (size > offset)
{
regmatch_t match;
const struct inotify_event* e = (struct inotify_event*) (buffer + offset);
offset += sizeof(struct inotify_event) + e->len;
if (regexec(&_glfw.linjs.regex, e->name, 1, &match, 0) != 0)
continue;
char path[PATH_MAX];
snprintf(path, sizeof(path), "/dev/input/%s", e->name);
if (e->mask & (IN_CREATE | IN_ATTRIB))
openJoystickDevice(path);
else if (e->mask & IN_DELETE)
{
for (int jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
if (strcmp(_glfw.joysticks[jid].linjs.path, path) == 0)
{
closeJoystick(_glfw.joysticks + jid);
break;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode UNUSED)
{
// Read all queued events (non-blocking)

View File

@@ -55,7 +55,4 @@ typedef struct _GLFWlibraryLinux
bool dropped;
} _GLFWlibraryLinux;
bool _glfwInitJoysticksLinux(void);
void _glfwTerminateJoysticksLinux(void);
void _glfwDetectJoystickConnectionLinux(void);

3
glfw/memfd.h vendored
View File

@@ -39,8 +39,7 @@ static inline int memfd_create(const char *name, unsigned int flags) {
#include <unistd.h>
#include <fcntl.h>
static inline int
createTmpfileCloexec(char* tmpname)
static inline int createTmpfileCloexec(char* tmpname)
{
int fd;

View File

@@ -33,6 +33,15 @@
////// GLFW platform API //////
//////////////////////////////////////////////////////////////////////////
bool _glfwPlatformInitJoysticks(void)
{
return true;
}
void _glfwPlatformTerminateJoysticks(void)
{
}
int _glfwPlatformPollJoystick(_GLFWjoystick* js UNUSED, int mode UNUSED)
{
return false;

10
glfw/null_monitor.c vendored
View File

@@ -70,8 +70,12 @@ void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
_glfwFreeGammaArrays(&monitor->null.ramp);
}
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos UNUSED, int* ypos UNUSED)
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos, int* ypos)
{
if (xpos)
*xpos = 0;
if (ypos)
*ypos = 0;
}
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED,
@@ -90,9 +94,9 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED,
const GLFWvidmode mode = getVideoMode();
if (xpos)
*xpos = 10;
*xpos = 0;
if (ypos)
ypos = 0;
*ypos = 10;
if (width)
*width = mode.width;
if (height)

View File

@@ -34,10 +34,7 @@
#define _GLFW_PLATFORM_CONTEXT_STATE
#define _GLFW_PLATFORM_CURSOR_STATE
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
#define _GLFW_EGL_CONTEXT_STATE
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE
#include "osmesa_context.h"
#include "posix_time.h"
#include "posix_thread.h"
#include "null_joystick.h"

27
glfw/null_window.c vendored
View File

@@ -274,10 +274,25 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
{
if (window->null.decorated && !window->monitor)
{
*left = 1;
*top = 10;
*right = 1;
*bottom = 1;
if (left)
*left = 1;
if (top)
*top = 10;
if (right)
*right = 1;
if (bottom)
*bottom = 1;
}
else
{
if (left)
*left = 0;
if (top)
*top = 0;
if (right)
*right = 0;
if (bottom)
*bottom = 0;
}
}
@@ -372,6 +387,10 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
window->null.floating = enabled;
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window UNUSED, bool enabled UNUSED)
{
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
return window->null.opacity;

View File

@@ -54,10 +54,6 @@ typedef GLFWglproc (GLAPIENTRY * PFN_OSMesaGetProcAddress)(const char*);
#define OSMesaGetDepthBuffer _glfw.osmesa.GetDepthBuffer
#define OSMesaGetProcAddress _glfw.osmesa.GetProcAddress
#define _GLFW_OSMESA_CONTEXT_STATE _GLFWcontextOSMesa osmesa
#define _GLFW_OSMESA_LIBRARY_CONTEXT_STATE _GLFWlibraryOSMesa osmesa
// OSMesa-specific per-context data
//
typedef struct _GLFWcontextOSMesa

35
glfw/window.c vendored
View File

@@ -240,13 +240,14 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->videoMode.blueBits = fbconfig.blueBits;
window->videoMode.refreshRate = _glfw.hints.refreshRate;
window->monitor = (_GLFWmonitor*) monitor;
window->resizable = wndconfig.resizable;
window->decorated = wndconfig.decorated;
window->autoIconify = wndconfig.autoIconify;
window->floating = wndconfig.floating;
window->focusOnShow = wndconfig.focusOnShow;
window->cursorMode = GLFW_CURSOR_NORMAL;
window->monitor = (_GLFWmonitor*) monitor;
window->resizable = wndconfig.resizable;
window->decorated = wndconfig.decorated;
window->autoIconify = wndconfig.autoIconify;
window->floating = wndconfig.floating;
window->focusOnShow = wndconfig.focusOnShow;
window->mousePassthrough = wndconfig.mousePassthrough;
window->cursorMode = GLFW_CURSOR_NORMAL;
window->minwidth = GLFW_DONT_CARE;
window->minheight = GLFW_DONT_CARE;
@@ -273,6 +274,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
}
}
if (wndconfig.mousePassthrough)
_glfwPlatformSetWindowMousePassthrough(window, true);
if (window->monitor)
{
if (wndconfig.centerCursor)
@@ -422,6 +426,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_FOCUS_ON_SHOW:
_glfw.hints.window.focusOnShow = value ? true : false;
return;
case GLFW_MOUSE_PASSTHROUGH:
_glfw.hints.window.mousePassthrough = value ? true : false;
return;
case GLFW_CLIENT_API:
_glfw.hints.context.client = value;
return;
@@ -440,7 +447,7 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_OPENGL_FORWARD_COMPAT:
_glfw.hints.context.forward = value ? true : false;
return;
case GLFW_OPENGL_DEBUG_CONTEXT:
case GLFW_CONTEXT_DEBUG:
_glfw.hints.context.debug = value ? true : false;
return;
case GLFW_CONTEXT_NO_ERROR:
@@ -905,6 +912,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return _glfwPlatformWindowHovered(window);
case GLFW_FOCUS_ON_SHOW:
return window->focusOnShow;
case GLFW_MOUSE_PASSTHROUGH:
return window->mousePassthrough;
case GLFW_TRANSPARENT_FRAMEBUFFER:
return _glfwPlatformFramebufferTransparent(window);
case GLFW_OCCLUDED:
@@ -931,7 +940,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return window->context.robustness;
case GLFW_OPENGL_FORWARD_COMPAT:
return window->context.forward;
case GLFW_OPENGL_DEBUG_CONTEXT:
case GLFW_CONTEXT_DEBUG:
return window->context.debug;
case GLFW_OPENGL_PROFILE:
return window->context.profile;
@@ -985,6 +994,14 @@ GLFWAPI void glfwSetWindowAttrib(GLFWwindow* handle, int attrib, int value)
}
else if (attrib == GLFW_FOCUS_ON_SHOW)
window->focusOnShow = value;
else if (attrib == GLFW_MOUSE_PASSTHROUGH)
{
if (window->mousePassthrough == value)
return;
window->mousePassthrough = value;
_glfwPlatformSetWindowMousePassthrough(window, value);
}
else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window attribute 0x%08X", attrib);
}

181
glfw/wl_cursors.c vendored
View File

@@ -1,7 +1,5 @@
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
#include "wl_cursors.h"
#include "internal.h"
#include <assert.h>
@@ -10,155 +8,58 @@
#include <stdio.h>
#include <stdlib.h>
typedef struct {
struct wl_cursor_theme *theme;
int px;
int refcount;
} _themeData;
struct _wlCursorThemeManager {
size_t count;
/** Pointer to the head of an unsorted array of themes with no sentinel.
*
* The lack of sort (and thus forcing a linear search) is intentional;
* in most cases, users are likely to have 1-2 different cursor sizes loaded.
* For those cases, we get no benefit from sorting and added constant overheads.
*
* Don't change this to a flexible array member because that complicates growing/shrinking.
*/
_themeData *themes;
};
static void
_themeInit(_themeData *dest, const char *name, int px) {
dest->px = px;
dest->refcount = 1;
if (_glfw.wl.shm) {
dest->theme = wl_cursor_theme_load(name, px, _glfw.wl.shm);
if(!dest->theme) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Unable to load cursor theme");
static int
pixels_from_scale(int scale) {
static bool queried_env = false;
static int factor = 32;
if (!queried_env) {
const char *env = getenv("XCURSOR_SIZE");
if (env) {
const int retval = atoi(env);
if (retval > 0 && retval < 2048) factor = retval;
}
} else {
dest->theme = NULL;
queried_env = true;
}
return factor * scale;
}
static struct wl_cursor_theme*
_themeAdd(int px, _wlCursorThemeManager *manager) {
++manager->count;
_themeData *temp = realloc(manager->themes, sizeof(_themeData)*manager->count);
if (!temp) {
_glfwInputError(GLFW_OUT_OF_MEMORY,
"OOM during cursor theme management.");
return NULL;
} else {
manager->themes = temp;
_themeInit(manager->themes + manager->count-1,
getenv("XCURSOR_THEME"),
px);
return manager->themes[manager->count-1].theme;
}
}
//WARNING: No input safety checks.
static inline void _themeInc(_themeData
*theme) {
++(theme->refcount);
}
struct wl_cursor_theme*
glfw_wlc_theme_for_scale(int scale) {
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
for (size_t i = 0; i < t->count; i++) {
if (t->themes[i].scale == scale) return t->themes[i].theme;
}
// WARNING: No input safety checks.
// In particular, doesn't check if theme is actually managed by the manager.
static void
_themeDec(_themeData *theme, _wlCursorThemeManager *manager) {
if (--(theme->refcount) == 0) {
wl_cursor_theme_destroy(theme->theme);
if (--(manager->count) > 0) {
const _themeData *last_theme = (manager->themes)+(manager->count);
*theme = *last_theme;
_themeData *temp = realloc(manager->themes, (manager->count)*sizeof(_themeData));
// We're shrinking here, so it's not catastrophic if realloc fails.
if (temp) manager->themes = temp;
} else {
free(manager->themes);
manager->themes = NULL;
if (t->count >= t->capacity) {
t->themes = realloc(t->themes, sizeof(GLFWWLCursorTheme) * (t->count + 16));
if (!t->themes) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Out of memory allocating space for cursor themes");
return NULL;
}
t->capacity = t->count + 16;
}
}
static _wlCursorThemeManager _default = {0};
_wlCursorThemeManager*
_wlCursorThemeManagerDefault() {
return &_default;
struct wl_cursor_theme *ans = wl_cursor_theme_load(getenv("XCURSOR_THEME"), pixels_from_scale(scale), _glfw.wl.shm);
if (!ans) {
_glfwInputError(
GLFW_PLATFORM_ERROR, "Wayland: wl_cursor_theme_load failed at scale: %d pixels: %d",
scale, pixels_from_scale(scale)
);
return NULL;
}
GLFWWLCursorTheme *theme = t->themes + t->count++;
theme->scale = scale;
theme->theme = ans;
return ans;
}
void
_wlCursorThemeManagerDestroy(_wlCursorThemeManager *manager) {
if (manager) {
for (size_t i = 0; i < manager->count; ++i) {
wl_cursor_theme_destroy(manager->themes[i].theme);
}
free(manager->themes);
}
}
glfw_wlc_destroy(void) {
GLFWWLCursorThemes *t = &_glfw.wl.cursor_themes;
static struct wl_cursor_theme*
_wlCursorThemeManagerGet(_wlCursorThemeManager *manager, int px) {
_themeData *themedata = NULL;
for (size_t i = 0; i < manager->count; ++i) {
_themeData *temp = manager->themes+i;
if (temp->px == px) {
themedata = temp;
break;
}
for (size_t i = 0; i < t->count; i++) {
wl_cursor_theme_destroy(t->themes[i].theme);
}
if (themedata != NULL) {
_themeInc(themedata);
return themedata->theme;
}
return _themeAdd(px, manager);
}
struct wl_cursor_theme*
_wlCursorThemeManage(_wlCursorThemeManager *manager, struct wl_cursor_theme *theme, int px) {
//WARNING: Multiple returns.
if (manager == NULL) {
return NULL;
}
if (theme != NULL) {
// Search for the provided theme in the manager.
_themeData *themedata = NULL;
for (size_t i = 0; i < manager->count; ++i) {
_themeData *temp = manager->themes+i;
if (temp->theme == theme) {
themedata = temp;
break;
}
}
if (themedata != NULL) {
// Search succeeded. Check if we can avoid unnecessary operations.
if (themedata->px == px) return theme;
_themeDec(themedata, manager);
} else {
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland internal: managed theme isn't in the provided manager");
return theme;
//^ This is probably the sanest behavior for this situation: do nothing.
}
}
return px > 0 ? _wlCursorThemeManagerGet(manager, px) : NULL;
}
int
_wlCursorPxFromScale(int scale) {
const char *envStr = getenv("XCURSOR_SIZE");
if(envStr != NULL) {
const int retval = atoi(envStr);
//^ atoi here is fine since 0 is an invalid value.
if(retval > 0 && retval <= INT_MAX/scale) {
return retval*scale;
}
}
return 32*scale;
free(t->themes);
t->themes = NULL; t->capacity = 0; t->count = 0;
}

32
glfw/wl_cursors.h vendored
View File

@@ -2,29 +2,17 @@
#include <wayland-cursor.h>
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
typedef struct {
struct wl_cursor_theme *theme;
int scale;
} GLFWWLCursorTheme;
/** Returns a pointer to a wlCursorThemeManagerInstance.
* Repeatedly calling this function will return the same instance.
*
* The retrieved instance must be destroyed with _wlCursorThemeManagerDestroy.
*/
_wlCursorThemeManager* _wlCursorThemeManagerDefault(void);
/** Set a wl_cursor_theme pointer variable to a pointer to a managed cursor theme.
* Pass the desired px as the third argument.
* Returns a pointer to a managed theme, or NULL if the desired px is 0 or an error occurs.
*
* The passed theme pointer must either be NULL or a pointer to a theme managed by the passed manager.
* The provided pointer may be invalidated if it's non-NULL.
*/
struct wl_cursor_theme*
_wlCursorThemeManage(_wlCursorThemeManager*, struct wl_cursor_theme*, int);
typedef struct {
GLFWWLCursorTheme *themes;
size_t count, capacity;
} GLFWWLCursorThemes;
void _wlCursorThemeManagerDestroy(_wlCursorThemeManager*);
/** Helper method to determine the appropriate size in pixels for a given scale.
*
* Reads XCURSOR_SIZE if it's set and is valid, else defaults to 32*scale.
*/
int _wlCursorPxFromScale(int);
struct wl_cursor_theme* glfw_wlc_theme_for_scale(int scale);
void glfw_wlc_destroy(void);

44
glfw/wl_init.c vendored
View File

@@ -148,12 +148,9 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
struct wl_surface* surface = _glfw.wl.cursorSurface;
const int scale = window->wl.scale;
window->wl.cursorTheme = _wlCursorThemeManage(
_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
_wlCursorPxFromScale(scale)
);
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (!theme) return;
cursor = _glfwLoadCursor(shape, theme);
if (!cursor) return;
// TODO: handle animated cursors too.
image = cursor->images[0];
@@ -337,9 +334,9 @@ static void pointerHandleAxis(void* data UNUSED,
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
x = wl_fixed_to_double(value) * -1;
x = -wl_fixed_to_double(value);
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
y = wl_fixed_to_double(value) * -1;
y = -wl_fixed_to_double(value);
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
}
@@ -720,6 +717,9 @@ glfwWaylandCheckForServerSideDecorations(void) {
int _glfwPlatformInit(void)
{
int i;
_GLFWmonitor* monitor;
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
if (!_glfw.wl.cursor.handle)
{
@@ -771,12 +771,16 @@ int _glfwPlatformInit(void)
// Sync so we got all initial output events
wl_display_roundtrip(_glfw.wl.display);
#ifdef __linux__
if (_glfw.hints.init.enableJoysticks) {
if (!_glfwInitJoysticksLinux())
return false;
for (i = 0; i < _glfw.monitorCount; ++i)
{
monitor = _glfw.monitors[i];
if (monitor->widthMM <= 0 || monitor->heightMM <= 0)
{
// If Wayland does not provide a physical size, assume the default 96 DPI
monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f);
monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f);
}
}
#endif
if (!_glfw.wl.wmBase)
{
@@ -787,13 +791,14 @@ int _glfwPlatformInit(void)
if (_glfw.wl.shm)
{
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
_glfw.wl.cursorSurface =
_glfw.wl.cursorSurface =
wl_compositor_create_surface(_glfw.wl.compositor);
}
else
{
_glfw.wl.cursorThemeManager = NULL;
_glfwInputError(GLFW_PLATFORM_ERROR,
"Wayland: Failed to find Wayland SHM");
return false;
}
return true;
@@ -801,9 +806,6 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void)
{
#ifdef __linux__
_glfwTerminateJoysticksLinux();
#endif
_glfwTerminateEGL();
if (_glfw.wl.egl.handle)
{
@@ -814,6 +816,7 @@ void _glfwPlatformTerminate(void)
glfw_xkb_release(&_glfw.wl.xkb);
glfw_dbus_terminate(&_glfw.wl.dbus);
glfw_wlc_destroy();
if (_glfw.wl.cursor.handle)
{
_glfw_dlclose(_glfw.wl.cursor.handle);
@@ -822,9 +825,6 @@ void _glfwPlatformTerminate(void)
if (_glfw.wl.cursorSurface)
wl_surface_destroy(_glfw.wl.cursorSurface);
if (_glfw.wl.cursorThemeManager) {
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
}
if (_glfw.wl.subcompositor)
wl_subcompositor_destroy(_glfw.wl.subcompositor);
if (_glfw.wl.compositor)

19
glfw/wl_platform.h vendored
View File

@@ -50,8 +50,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#endif
#include "backend_utils.h"
#include "xkb_glfw.h"
#include "egl_context.h"
#include "osmesa_context.h"
#include "wl_cursors.h"
#include "wayland-xdg-shell-client-protocol.h"
@@ -66,9 +64,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->wl.native)
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.wl.display)
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowWayland wl
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWayland wl
@@ -137,7 +132,6 @@ typedef struct _GLFWwindowWayland
_GLFWcursor* currentCursor;
double cursorPosX, cursorPosY;
struct wl_cursor_theme* cursorTheme;
char* title;
char appId[256];
@@ -210,14 +204,14 @@ typedef struct _GLFWlibraryWayland
struct wl_seat* seat;
struct wl_pointer* pointer;
struct wl_keyboard* keyboard;
struct wl_data_device_manager* dataDeviceManager;
struct wl_data_device* dataDevice;
struct xdg_wm_base* wmBase;
struct zxdg_decoration_manager_v1* decorationManager;
struct wp_viewporter* viewporter;
struct zwp_relative_pointer_manager_v1* relativePointerManager;
struct zwp_pointer_constraints_v1* pointerConstraints;
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
struct wl_data_device_manager* dataDeviceManager;
struct wl_data_device* dataDevice;
struct wl_data_source* dataSourceForClipboard;
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
struct zwp_primary_selection_device_v1* primarySelectionDevice;
@@ -232,6 +226,7 @@ typedef struct _GLFWlibraryWayland
int32_t keyboardRepeatRate;
monotonic_t keyboardRepeatDelay;
struct {
uint32_t key;
id_type keyRepeatTimer;
@@ -267,8 +262,7 @@ typedef struct _GLFWlibraryWayland
size_t dataOffersCounter;
_GLFWWaylandDataOffer dataOffers[8];
char* primarySelectionString;
_wlCursorThemeManager* cursorThemeManager;
GLFWWLCursorThemes cursor_themes;
} _GLFWlibraryWayland;
// Wayland-specific per-monitor data
@@ -282,7 +276,8 @@ typedef struct _GLFWmonitorWayland
int x;
int y;
int scale;
} _GLFWmonitorWayland;
} _GLFWmonitorWayland;
// Wayland-specific per-cursor data
//
@@ -292,7 +287,7 @@ typedef struct _GLFWcursorWayland
struct wl_buffer* buffer;
int width, height;
int xhot, yhot;
int currentImage;
unsigned int currentImage;
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
int scale;
/** Cursor shape stored to allow late cursor loading in setCursorImage. */

103
glfw/wl_window.c vendored
View File

@@ -41,14 +41,8 @@
#include <fcntl.h>
#include <sys/mman.h>
static void
load_cursor_theme(_GLFWwindow* window) {
window->wl.cursorTheme = _wlCursorThemeManage(
_glfw.wl.cursorThemeManager, window->wl.cursorTheme, _wlCursorPxFromScale(window->wl.scale));
}
static void
setCursorImage(_GLFWwindow* window)
static void setCursorImage(_GLFWwindow* window)
{
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
@@ -63,18 +57,23 @@ setCursorImage(_GLFWwindow* window)
} else
{
if (cursorWayland->scale != scale) {
if (!window->wl.cursorTheme) load_cursor_theme(window);
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
struct wl_cursor *newCursor = NULL;
struct wl_cursor_theme *theme = glfw_wlc_theme_for_scale(scale);
if (theme) newCursor = _glfwLoadCursor(cursorWayland->shape, theme);
if (newCursor != NULL) {
cursorWayland->cursor = newCursor;
cursorWayland->scale = scale;
cursorWayland->currentImage = 0;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
}
}
if (!cursorWayland->cursor)
if (!cursorWayland->cursor || !cursorWayland->cursor->image_count)
return;
if (cursorWayland->currentImage >= cursorWayland->cursor->image_count) cursorWayland->currentImage = 0;
image = cursorWayland->cursor->images[cursorWayland->currentImage];
if (!image) image = cursorWayland->cursor->images[0];
if (!image) return;
buffer = wl_cursor_image_get_buffer(image);
if (image->delay && window->cursor) {
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
@@ -132,7 +131,6 @@ static bool checkScaleChange(_GLFWwindow* window)
{
window->wl.scale = scale;
wl_surface_set_buffer_scale(window->wl.surface, scale);
load_cursor_theme(window);
setCursorImage(window);
return true;
}
@@ -248,8 +246,7 @@ static void dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, in
* is set to ENOSPC. If posix_fallocate() is not supported, program may
* receive SIGBUS on accessing mmap()'ed file contents instead.
*/
static int
createAnonymousFile(off_t size)
static int createAnonymousFile(off_t size)
{
int ret, fd = -1, shm_anon = 0;
#ifdef HAS_MEMFD_CREATE
@@ -416,10 +413,10 @@ static void createDecorations(_GLFWwindow* window)
static void destroyDecoration(_GLFWdecorationWayland* decoration)
{
if (decoration->surface)
wl_surface_destroy(decoration->surface);
if (decoration->subsurface)
wl_subsurface_destroy(decoration->subsurface);
if (decoration->surface)
wl_surface_destroy(decoration->surface);
if (decoration->viewport)
wp_viewport_destroy(decoration->viewport);
decoration->surface = NULL;
@@ -440,7 +437,9 @@ static void xdgDecorationHandleConfigure(void* data,
uint32_t mode)
{
_GLFWwindow* window = data;
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
if (!window->wl.decorations.serverSide)
createDecorations(window);
}
@@ -548,8 +547,7 @@ static bool createSurface(_GLFWwindow* window,
return true;
}
static void
setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
{
if (window->wl.xdg.toplevel)
{
@@ -740,8 +738,7 @@ static bool createXdgSurface(_GLFWwindow* window)
return true;
}
static void
incrementCursorImage(_GLFWwindow* window)
static void incrementCursorImage(_GLFWwindow* window)
{
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
_GLFWcursor* cursor = window->wl.currentCursor;
@@ -764,14 +761,18 @@ animateCursorImage(id_type timer_id UNUSED, void *data UNUSED) {
static void
abortOnFatalError(int last_error) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
else {
_GLFWwindow* window = _glfw.windowListHead;
while (window)
{
_glfwInputWindowCloseRequest(window);
window = window->next;
static bool abort_called = false;
if (!abort_called) {
abort_called = true;
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
else {
_GLFWwindow* window = _glfw.windowListHead;
while (window)
{
_glfwInputWindowCloseRequest(window);
window = window->next;
}
}
}
// ensure the tick callback is called
@@ -785,8 +786,7 @@ wayland_read_events(int poll_result, int events, void *data UNUSED) {
else wl_display_cancel_read(_glfw.wl.display);
}
static void
handleEvents(monotonic_t timeout)
static void handleEvents(monotonic_t timeout)
{
struct wl_display* display = _glfw.wl.display;
errno = 0;
@@ -937,12 +937,6 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
{
if (window->wl.cursorTheme) {
_wlCursorThemeManage(_glfw.wl.cursorThemeManager,
window->wl.cursorTheme,
0);
window->wl.cursorTheme = NULL;
}
if (window == _glfw.wl.pointerFocus)
{
_glfw.wl.pointerFocus = NULL;
@@ -961,7 +955,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
window->context.destroy(window);
destroyDecorations(window);
if (window->wl.xdg.decoration)
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
@@ -1075,8 +1068,10 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
int* width, int* height)
{
_glfwPlatformGetWindowSize(window, width, height);
*width *= window->wl.scale;
*height *= window->wl.scale;
if (width)
*width *= window->wl.scale;
if (height)
*height *= window->wl.scale;
}
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
@@ -1263,6 +1258,19 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNU
"Wayland: Window attribute setting not implemented yet");
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
{
if (enabled)
{
struct wl_region* region = wl_compositor_create_region(_glfw.wl.compositor);
wl_surface_set_input_region(window->wl.surface, region);
wl_region_destroy(region);
}
else
wl_surface_set_input_region(window->wl.surface, 0);
wl_surface_commit(window->wl.surface);
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
{
return 1.f;
@@ -1350,6 +1358,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
cursor->wl.buffer = createShmBuffer(image);
if (!cursor->wl.buffer)
return false;
cursor->wl.width = image->width;
cursor->wl.height = image->height;
cursor->wl.xhot = xhot;
@@ -2008,6 +2017,24 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
return NULL;
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
{
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_wayland)
return EGL_PLATFORM_WAYLAND_EXT;
else
return 0;
}
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
{
return _glfw.wl.display;
}
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
{
return window->wl.native;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface || !_glfw.vk.KHR_wayland_surface)

36
glfw/x11_init.c vendored
View File

@@ -327,6 +327,30 @@ static bool initExtensions(void)
}
}
#if defined(__CYGWIN__)
_glfw.x11.xshape.handle = _glfw_dlopen("libXext-6.so");
#else
_glfw.x11.xshape.handle = _glfw_dlopen("libXext.so.6");
#endif
if (_glfw.x11.xshape.handle)
{
glfw_dlsym(_glfw.x11.xshape.QueryExtension, _glfw.x11.xshape.handle, "XShapeQueryExtension");
glfw_dlsym(_glfw.x11.xshape.ShapeCombineRegion, _glfw.x11.xshape.handle, "XShapeCombineRegion");
glfw_dlsym(_glfw.x11.xshape.QueryVersion, _glfw.x11.xshape.handle, "XShapeQueryVersion");
if (XShapeQueryExtension(_glfw.x11.display,
&_glfw.x11.xshape.errorBase,
&_glfw.x11.xshape.eventBase))
{
if (XShapeQueryVersion(_glfw.x11.display,
&_glfw.x11.xshape.major,
&_glfw.x11.xshape.minor))
{
_glfw.x11.xshape.available = true;
}
}
}
_glfw.x11.xkb.major = 1;
_glfw.x11.xkb.minor = 0;
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
@@ -629,15 +653,6 @@ int _glfwPlatformInit(void)
_glfw.x11.helperWindowHandle = createHelperWindow();
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
#if defined(__linux__)
if (_glfw.hints.init.enableJoysticks) {
if (!_glfwInitJoysticksLinux())
return false;
if (_glfw.linjs.inotify > 0)
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
}
#endif
_glfwPollMonitorsX11();
return true;
}
@@ -716,9 +731,6 @@ void _glfwPlatformTerminate(void)
_glfwTerminateEGL();
_glfwTerminateGLX();
#if defined(__linux__)
_glfwTerminateJoysticksLinux();
#endif
finalizePollData(&_glfw.x11.eventLoopData);
}

2
glfw/x11_monitor.c vendored
View File

@@ -169,7 +169,7 @@ void _glfwPollMonitorsX11(void)
if (widthMM <= 0 || heightMM <= 0)
{
// HACK: If RandR does not provide a physical size, assume the
// X11 default 96 DPI and calcuate from the CRTC viewport
// X11 default 96 DPI and calculate from the CRTC viewport
// NOTE: These members are affected by rotation, unlike the mode
// info and output info members
widthMM = (int) (ci->width * 25.4f / 96.f);

31
glfw/x11_platform.h vendored
View File

@@ -51,6 +51,9 @@
// The XInput extension provides raw mouse motion input
#include <X11/extensions/XInput2.h>
// The Shape extension provides custom window shapes
#include <X11/extensions/shape.h>
// The libxkb library is used for improved keyboard support
#include "xkb_glfw.h"
#include "backend_utils.h"
@@ -125,6 +128,16 @@ typedef XRenderPictFormat* (* PFN_XRenderFindVisualFormat)(Display*,Visual const
#define XRenderQueryVersion _glfw.x11.xrender.QueryVersion
#define XRenderFindVisualFormat _glfw.x11.xrender.FindVisualFormat
typedef Bool (* PFN_XShapeQueryExtension)(Display*,int*,int*);
typedef Status (* PFN_XShapeQueryVersion)(Display*dpy,int*,int*);
typedef void (* PFN_XShapeCombineRegion)(Display*,Window,int,int,int,Region,int);
typedef void (* PFN_XShapeCombineMask)(Display*,Window,int,int,int,Pixmap,int);
#define XShapeQueryExtension _glfw.x11.xshape.QueryExtension
#define XShapeQueryVersion _glfw.x11.xshape.QueryVersion
#define XShapeCombineRegion _glfw.x11.xshape.ShapeCombineRegion
#define XShapeCombineMask _glfw.x11.xshape.ShapeCombineMask
typedef VkFlags VkXlibSurfaceCreateFlagsKHR;
typedef VkFlags VkXcbSurfaceCreateFlagsKHR;
@@ -153,8 +166,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
#include "posix_thread.h"
#include "glx_context.h"
#include "egl_context.h"
#include "osmesa_context.h"
#if defined(__linux__)
#include "linux_joystick.h"
#else
@@ -165,9 +176,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->x11.handle)
#define _GLFW_EGL_NATIVE_DISPLAY ((EGLNativeDisplayType) _glfw.x11.display)
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowX11 x11
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryX11 x11
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorX11 x11
@@ -382,6 +390,19 @@ typedef struct _GLFWlibraryX11
PFN_XRenderFindVisualFormat FindVisualFormat;
} xrender;
struct {
bool available;
void* handle;
int major;
int minor;
int eventBase;
int errorBase;
PFN_XShapeQueryExtension QueryExtension;
PFN_XShapeCombineRegion ShapeCombineRegion;
PFN_XShapeQueryVersion QueryVersion;
PFN_XShapeCombineMask ShapeCombineMask;
} xshape;
EventLoopData eventLoopData;
} _GLFWlibraryX11;

130
glfw/x11_window.c vendored
View File

@@ -422,14 +422,18 @@ static char* convertLatin1toUTF8(const char* source)
size_t size = 1;
const char* sp;
for (sp = source; *sp; sp++)
size += (*sp & 0x80) ? 2 : 1;
if (source) {
for (sp = source; *sp; sp++)
size += (*sp & 0x80) ? 2 : 1;
}
char* target = calloc(size, 1);
char* tp = target;
for (sp = source; *sp; sp++)
tp += encodeUTF8(tp, *sp);
if (source) {
for (sp = source; *sp; sp++)
tp += encodeUTF8(tp, *sp);
}
return target;
}
@@ -771,7 +775,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
if (j < formatCount)
{
XChangeProperty(_glfw.x11.display,
if (selectionString) XChangeProperty(_glfw.x11.display,
request->requestor,
targets[i + 1],
targets[i],
@@ -823,7 +827,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
{
// The requested target is one we support
XChangeProperty(_glfw.x11.display,
if (selectionString) XChangeProperty(_glfw.x11.display,
request->requestor,
request->property,
request->target,
@@ -954,8 +958,10 @@ static const char* getSelectionString(Atom selection)
(XPointer) &notification))
{
monotonic_t time = glfwGetTime();
if (time - start > s_to_monotonic_t(2ll))
if (time - start > s_to_monotonic_t(2ll)) {
free(string);
return "";
}
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
}
@@ -1176,15 +1182,26 @@ static void processEvent(XEvent *event)
else if (event->type == _glfw.x11.xkb.eventBase)
{
XkbEvent *kb_event = (XkbEvent*)event;
if (kb_event->any.device != (unsigned int)_glfw.x11.xkb.keyboard_device_id) return;
switch(kb_event->any.xkb_type) {
case XkbNewKeyboardNotify: {
int32_t old_id = _glfw.x11.xkb.keyboard_device_id;
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
if (old_id != _glfw.x11.xkb.keyboard_device_id) keymap_dirty = true;
XkbNewKeyboardNotifyEvent *newkb_event = (XkbNewKeyboardNotifyEvent*)kb_event;
if (_glfw.hints.init.debugKeyboard) printf(
"Got XkbNewKeyboardNotify event with changes: key codes: %d geometry: %d device id: %d\n",
!!(newkb_event->changed & XkbNKN_KeycodesMask), !!(newkb_event->changed & XkbNKN_GeometryMask),
!!(newkb_event->changed & XkbNKN_DeviceIDMask));
if (newkb_event->changed & XkbNKN_DeviceIDMask) {
keymap_dirty = true;
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
}
if (newkb_event->changed & XkbNKN_KeycodesMask) {
keymap_dirty = true;
}
return;
}
/* fallthrough */
case XkbMapNotify:
{
if (_glfw.hints.init.debugKeyboard) printf("Got XkbMapNotify event, keymaps will be reloaded\n");
keymap_dirty = true;
return;
}
@@ -1507,7 +1524,7 @@ static void processEvent(XEvent *event)
count = 3;
formats = (Atom*) event->xclient.data.l + 2;
}
char **atom_names = calloc(count, sizeof(char**));
char **atom_names = calloc(count, sizeof(char*));
if (atom_names) {
get_atom_names(formats, count, atom_names);
@@ -2449,13 +2466,19 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
int rootX, rootY, childX, childY;
unsigned int mask;
if (!XQueryPointer(_glfw.x11.display, w,
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
{
return false;
}
_glfwGrabErrorHandlerX11();
if (w == window->x11.handle)
const Bool result = XQueryPointer(_glfw.x11.display, w,
&root, &w, &rootX, &rootY,
&childX, &childY, &mask);
_glfwReleaseErrorHandlerX11();
if (_glfw.x11.errorCode == BadWindow)
w = _glfw.x11.root;
else if (!result)
return false;
else if (w == window->x11.handle)
return true;
}
@@ -2569,6 +2592,25 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
XFlush(_glfw.x11.display);
}
void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
{
if (!_glfw.x11.xshape.available)
return;
if (enabled)
{
Region region = XCreateRegion();
XShapeCombineRegion(_glfw.x11.display, window->x11.handle,
ShapeInput, 0, 0, region, ShapeSet);
XDestroyRegion(region);
}
else
{
XShapeCombineMask(_glfw.x11.display, window->x11.handle,
ShapeInput, 0, 0, None, ShapeSet);
}
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
{
float opacity = 1.f;
@@ -2617,7 +2659,8 @@ _glfwDispatchX11Events(void) {
unsigned dispatched = 0;
#if defined(__linux__)
if (_glfw.hints.init.enableJoysticks) _glfwDetectJoystickConnectionLinux();
if (_glfw.joysticksInitialized)
_glfwDetectJoystickConnectionLinux();
#endif
dispatched += dispatch_x11_queued_events(XEventsQueued(_glfw.x11.display, QueuedAfterFlush));
@@ -2844,6 +2887,55 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
return getSelectionString(_glfw.x11.PRIMARY);
}
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
{
if (_glfw.egl.ANGLE_platform_angle)
{
int type = 0;
if (_glfw.egl.ANGLE_platform_angle_opengl)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_OPENGL)
type = EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE;
}
if (_glfw.egl.ANGLE_platform_angle_vulkan)
{
if (_glfw.hints.init.angleType == GLFW_ANGLE_PLATFORM_TYPE_VULKAN)
type = EGL_PLATFORM_ANGLE_TYPE_VULKAN_ANGLE;
}
if (type)
{
*attribs = calloc(5, sizeof(EGLint));
(*attribs)[0] = EGL_PLATFORM_ANGLE_TYPE_ANGLE;
(*attribs)[1] = type;
(*attribs)[2] = EGL_PLATFORM_ANGLE_NATIVE_PLATFORM_TYPE_ANGLE;
(*attribs)[3] = EGL_PLATFORM_X11_EXT;
(*attribs)[4] = EGL_NONE;
return EGL_PLATFORM_ANGLE_ANGLE;
}
}
if (_glfw.egl.EXT_platform_base && _glfw.egl.EXT_platform_x11)
return EGL_PLATFORM_X11_EXT;
return 0;
}
EGLNativeDisplayType _glfwPlatformGetEGLNativeDisplay(void)
{
return _glfw.x11.display;
}
EGLNativeWindowType _glfwPlatformGetEGLNativeWindow(_GLFWwindow* window)
{
if (_glfw.egl.platform)
return &window->x11.handle;
else
return (EGLNativeWindowType) window->x11.handle;
}
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
{
if (!_glfw.vk.KHR_surface)

56
glfw/xkb_glfw.c vendored
View File

@@ -371,9 +371,31 @@ load_compose_tables(_GLFWXKBData *xkb) {
xkb_compose_table_unref(compose_table);
}
static inline xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
i++;
}
return ans;
}
static void
update_modifiers(_GLFWXKBData *xkb) {
XKBStateGroup *group = &xkb->states;
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
}
bool
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
const char *err;
debug("Loading new XKB keymaps\n");
release_keyboard_data(xkb);
err = load_keymaps(xkb, map_str);
if (err) {
@@ -396,36 +418,17 @@ glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
S(capsLock, XKB_MOD_NAME_CAPS);
S(numLock, XKB_MOD_NAME_NUM);
#undef S
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
size_t capacity = arraysz(xkb->unknownModifiers), j = 0;
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
}
xkb->states.modifiers = 0;
xkb->states.activeUnknownModifiers = 0;
update_modifiers(xkb);
return true;
}
static inline xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0;
xkb_mod_mask_t ans = 0;
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
i++;
}
return ans;
}
static void
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
#undef S
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
}
void
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
if (!xkb->keymap) return;
@@ -434,7 +437,7 @@ glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_m
// We have to update the groups in clean_state, as they change for
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
update_modifiers(xkb, &xkb->states);
update_modifiers(xkb);
}
bool
@@ -503,14 +506,19 @@ static inline const char*
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
static char buf[512];
char *p = buf, *s;
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
#define pr(x) { \
int num_needed = -1; \
ssize_t space_left = sizeof(buf) - (p - buf) - 1; \
if (space_left > 0) num_needed = snprintf(p, space_left, "%s", x); \
if (num_needed > 0) p += num_needed; \
}
pr(name); pr(": ");
s = p;
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
xkb_mod_mask_t m = 1 << i;
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
}
if (p == s) pr("none");
if (p == s) { pr("none"); }
else p--;
pr(" ");
#undef pr

View File

@@ -1 +0,0 @@
See https://sw.kovidgoyal.net/kitty/graphics-protocol.html

View File

@@ -1 +0,0 @@
See https://sw.kovidgoyal.net/kitty/key-encoding.html

View File

@@ -13,7 +13,8 @@ from kitty.constants import cache_dir
from kitty.typing import BossType
from ..tui.handler import result_handler
from ..tui.operations import alternate_screen, set_cursor_visible, styled
from ..tui.operations import alternate_screen, styled
from ..tui.utils import get_key_press
if TYPE_CHECKING:
import readline
@@ -70,7 +71,7 @@ class HistoryCompleter:
def option_text() -> str:
return '''\
--type -t
choices=line,yesno
choices=line,yesno,choices
default=line
Type of input. Defaults to asking for a line of text.
@@ -83,6 +84,16 @@ message is shown.
--name -n
The name for this question. Used to store history of previous answers which can
be used for completions and via the browse history readline bindings.
--choice -c
type=list
dest=choices
A choice for the choices type. Every choice has the syntax: letter:text Where
letter is the accelerator key and text is the corresponding text. There can be
an optional color specification after the letter to indicate what color it should
be.
For example: y:Yes and n;red:No
'''
@@ -97,23 +108,34 @@ class Response(TypedDict):
response: Optional[str]
def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response:
import tty
def choice(cli_opts: AskCLIOptions, items: List[str]) -> Response:
with alternate_screen():
if cli_opts.message:
print(styled(cli_opts.message, bold=True))
print()
print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o', set_cursor_visible(False))
sys.stdout.flush()
tty.setraw(sys.stdin.fileno())
try:
response = sys.stdin.buffer.read(1)
yes = response in (b'y', b'Y', b'\r', b'\n' b' ')
return {'items': items, 'response': 'y' if yes else 'n'}
finally:
sys.stdout.write(set_cursor_visible(True))
tty.setcbreak(sys.stdin.fileno())
sys.stdout.flush()
allowed = ''
for choice in cli_opts.choices:
color = 'green'
letter, text = choice.split(':', maxsplit=1)
if ';' in letter:
letter, color = letter.split(';', maxsplit=1)
letter = letter.lower()
idx = text.lower().index(letter)
allowed += letter
print(text[:idx], styled(text[idx], fg=color), text[idx + 1:], sep='', end=' ')
print()
response = get_key_press(allowed, '')
return {'items': items, 'response': response}
def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response:
with alternate_screen():
if cli_opts.message:
print(styled(cli_opts.message, bold=True))
print()
print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o')
response = get_key_press('yn', 'n')
return {'items': items, 'response': response}
def main(args: List[str]) -> Response:
@@ -132,6 +154,8 @@ def main(args: List[str]) -> Response:
if cli_opts.type == 'yesno':
return yesno(cli_opts, items)
if cli_opts.type == 'choices':
return choice(cli_opts, items)
import readline as rl
readline = rl

View File

@@ -111,7 +111,7 @@ end:
if (job_data[i] && job_data[i]->started) wait_for_thread(threads, i);
}
}
for (i = 0; i < num_threads; i++) job_data[i] = free_job(job_data[i]);
if (job_data) { for (i = 0; i < num_threads; i++) job_data[i] = free_job(job_data[i]); }
free(job_data);
free_threads(threads);
return ret;
@@ -130,7 +130,7 @@ run_search(Options *opts, GlobalData *global, const char * const *lines, const s
ALLOC_VEC(text_t, chars, 8192 * 20);
if (chars.data == NULL) return 1;
ALLOC_VEC(Candidate, candidates, 8192);
if (candidates.data == NULL) { FREE_VEC(chars) return 1; }
if (candidates.data == NULL) { FREE_VEC(chars); return 1; }
for (size_t i = 0; i < num_lines; i++) {
sz = sizes[i];

View File

@@ -28,8 +28,9 @@
#define ENSURE_SPACE(TYPE, vec, amt) \
if (vec.size + amt >= vec.capacity) { \
vec.capacity = MAX(vec.capacity * 2, vec.size + amt); \
vec.data = (TYPE*)realloc(vec.data, sizeof(TYPE) * vec.capacity); \
if (vec.data == NULL) { REPORT_OOM; ret = 1; break; } \
void *temp = realloc(vec.data, sizeof(TYPE) * vec.capacity); \
if (temp == NULL) { REPORT_OOM; ret = 1; free(vec.data); vec.data = NULL; vec.size = 0; vec.capacity = 0; break; } \
else vec.data = temp; \
}
#define NEXT(vec) (vec.data[vec.size])

View File

@@ -7,7 +7,7 @@ import re
from contextlib import suppress
from functools import lru_cache
from hashlib import md5
from mimetypes import guess_type
from kitty.guess_mime_type import guess_type
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
if TYPE_CHECKING:
@@ -68,7 +68,10 @@ class Collection:
self.removed_count += len(lines_for_path(left_path))
def finalize(self) -> None:
self.all_paths.sort(key=path_name_map.get)
def key(x: str) -> str:
return path_name_map.get(x, '')
self.all_paths.sort(key=key)
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
for path in self.all_paths:
@@ -133,7 +136,7 @@ def sanitize(text: str) -> str:
@lru_cache(maxsize=1024)
def mime_type_for_path(path: str) -> str:
return guess_type(path)[0] or 'application/octet-stream'
return guess_type(path) or 'application/octet-stream'
@lru_cache(maxsize=1024)

View File

@@ -12,6 +12,7 @@ from pygments.formatter import Formatter # type: ignore
from pygments.lexers import get_lexer_for_filename # type: ignore
from pygments.util import ClassNotFound # type: ignore
from kitty.multiprocessing import get_process_pool_executor
from kitty.rgb import color_as_sgr, parse_sharp
from .collect import Collection, Segment, data_for_path, lines_for_path
@@ -139,7 +140,7 @@ def highlight_for_diff(path: str, aliases: Dict[str, str]) -> DiffHighlight:
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
jobs = {}
ans: Dict[str, DiffHighlight] = {}
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
with get_process_pool_executor(prefer_fork=True) as executor:
for path, item_type, other_path in collection:
if item_type != 'rename':
for p in (path, other_path):

View File

@@ -13,7 +13,9 @@ from collections import defaultdict
from contextlib import suppress
from functools import partial
from gettext import gettext as _
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
from typing import (
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
)
from kitty.cli import CONFIG_HELP, parse_args
from kitty.cli_stub import DiffCLIOptions
@@ -24,6 +26,11 @@ from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
from kitty.options_stub import DiffOptions
from kitty.utils import ScreenSize
from ..tui.handler import Handler
from ..tui.images import ImageManager, Placement
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import styled
from . import global_data
from .collect import (
Collection, create_collection, data_for_path, lines_for_path, sanitize,
@@ -35,14 +42,11 @@ from .render import (
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
)
from .search import BadRegex, Search
from ..tui.handler import Handler
from ..tui.images import ImageManager, Placement
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import styled
try:
from .highlight import initialize_highlighter, highlight_collection, DiffHighlight
from .highlight import (
DiffHighlight, highlight_collection, initialize_highlighter
)
has_highlighter = True
DiffHighlight
except ImportError:
@@ -566,8 +570,8 @@ class ShowWarning:
def __init__(self) -> None:
self.warnings: List[str] = []
def __call__(self, message: str, category: object, filename: str, lineno: int, file: object = None, line: object = None) -> None:
if category is ImageSupportWarning:
def __call__(self, message: Any, category: Any, filename: str, lineno: int, file: object = None, line: object = None) -> None:
if category is ImageSupportWarning and isinstance(message, str):
showwarning.warnings.append(message)

View File

@@ -45,12 +45,21 @@ ESCAPE = K['ESCAPE']
class Mark:
__slots__ = ('index', 'start', 'end', 'text', 'groupdict')
__slots__ = ('index', 'start', 'end', 'text', 'is_hyperlink', 'group_id', 'groupdict')
def __init__(self, index: int, start: int, end: int, text: str, groupdict: Any):
def __init__(
self,
index: int, start: int, end: int,
text: str,
groupdict: Any,
is_hyperlink: bool = False,
group_id: Optional[str] = None
):
self.index, self.start, self.end = index, start, end
self.text = text
self.groupdict = groupdict
self.is_hyperlink = is_hyperlink
self.group_id = group_id
@lru_cache(maxsize=2048)
@@ -72,7 +81,7 @@ def decode_hint(x: str, alphabet: str = DEFAULT_HINT_ALPHABET) -> int:
return i
def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str) -> str:
def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str, colors: Dict[str, str]) -> str:
hint = encode_hint(m.index, alphabet)
if current_input and not hint.startswith(current_input):
return faint(text)
@@ -80,19 +89,19 @@ def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str) -> str
text = text[len(hint):]
return styled(
hint,
fg='black',
bg='green',
fg=colors['foreground'],
bg=colors['background'],
bold=True
) + styled(
text, fg='gray', fg_intense=True, bold=True
text, fg=colors['text'], fg_intense=True, bold=True
)
def render(text: str, current_input: str, all_marks: Sequence[Mark], ignore_mark_indices: Set[int], alphabet: str) -> str:
def render(text: str, current_input: str, all_marks: Sequence[Mark], ignore_mark_indices: Set[int], alphabet: str, colors: Dict[str, str]) -> str:
for mark in reversed(all_marks):
if mark.index in ignore_mark_indices:
continue
mtext = highlight_mark(mark, text[mark.start:mark.end], current_input, alphabet)
mtext = highlight_mark(mark, text[mark.start:mark.end], current_input, alphabet, colors)
text = text[:mark.start] + mtext + text[mark.end:]
text = text.replace('\0', '')
@@ -105,6 +114,9 @@ class Hints(Handler):
def __init__(self, text: str, all_marks: Sequence[Mark], index_map: Dict[int, Mark], args: HintsCLIOptions):
self.text, self.index_map = text, index_map
self.alphabet = args.alphabet or DEFAULT_HINT_ALPHABET
self.colors = {'foreground': args.hints_foreground_color,
'background': args.hints_background_color,
'text': args.hints_text_color}
self.all_marks = all_marks
self.ignore_mark_indices: Set[int] = set()
self.args = args
@@ -198,7 +210,7 @@ class Hints(Handler):
def draw_screen(self) -> None:
if self.current_text is None:
self.current_text = render(self.text, self.current_input, self.all_marks, self.ignore_mark_indices, self.alphabet)
self.current_text = render(self.text, self.current_input, self.all_marks, self.ignore_mark_indices, self.alphabet, self.colors)
self.cmd.clear_screen()
self.write(self.current_text)
@@ -224,6 +236,11 @@ def postprocessor(func: PostprocessorFunc) -> PostprocessorFunc:
return func
class InvalidMatch(Exception):
"""Raised when a match turns out to be invalid."""
pass
@postprocessor
def url(text: str, s: int, e: int) -> Tuple[int, int]:
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
@@ -268,11 +285,29 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
return s, e
@postprocessor
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
from ipaddress import ip_address
# Check validity of IPs (or raise InvalidMatch)
ip = text[s:e]
try:
ip_address(ip)
except Exception:
raise InvalidMatch("Invalid IP")
return s, e
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
pat = re.compile(pattern)
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
for func in post_processors:
s, e = func(text, s, e)
try:
for func in post_processors:
s, e = func(text, s, e)
except InvalidMatch:
continue
mark_text = text[s:e].replace('\n', '').replace('\0', '')
yield Mark(idx, s, e, mark_text, groupdict)
@@ -313,6 +348,15 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
elif args.type == 'hash':
pattern = '[0-9a-f]{7,128}'
elif args.type == 'ip':
pattern = (
# # IPv4 with no validation
r"((?:\d{1,3}\.){3}\d{1,3}"
r"|"
# # IPv6 with no validation
r"(?:[a-fA-F0-9]{0,4}:){2,7}[a-fA-F0-9]{1,4})"
)
post_processors.append(ip)
elif args.type == 'word':
chars = args.word_characters
if chars is None:
@@ -366,13 +410,66 @@ def load_custom_processor(customize_processing: str) -> Any:
return runpy.run_path(custom_path, run_name='__main__')
def remove_sgr(text: str) -> str:
return re.sub(r'\x1b\[.*?m', '', text)
def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
hyperlinks: List[Mark] = []
removed_size = idx = 0
active_hyperlink_url: Optional[str] = None
active_hyperlink_id: Optional[str] = None
active_hyperlink_start_offset = 0
def add_hyperlink(end: int) -> None:
nonlocal idx, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
assert active_hyperlink_url is not None
hyperlinks.append(Mark(
idx, active_hyperlink_start_offset, end,
active_hyperlink_url,
groupdict={},
is_hyperlink=True, group_id=active_hyperlink_id
))
active_hyperlink_url = active_hyperlink_id = None
active_hyperlink_start_offset = 0
idx += 1
def process_hyperlink(m: 're.Match') -> str:
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
raw = m.group()
start = m.start() - removed_size
removed_size += len(raw)
if active_hyperlink_url is not None:
add_hyperlink(start)
raw = raw[4:-2]
parts = raw.split(';', 1)
if len(parts) == 2 and parts[1]:
active_hyperlink_url = parts[1]
active_hyperlink_start_offset = start
if parts[0]:
for entry in parts[0].split(':'):
if entry.startswith('id=') and len(entry) > 3:
active_hyperlink_id = entry[3:]
break
return ''
text = re.sub(r'\x1b\]8.+?\x1b\\', process_hyperlink, text)
if active_hyperlink_url is not None:
add_hyperlink(len(text))
return text, tuple(hyperlinks)
def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) -> Optional[Dict[str, Any]]:
try:
text = parse_input(text)
text = parse_input(remove_sgr(text))
text, hyperlinks = process_hyperlinks(text)
pattern, post_processors = functions_for(args)
if args.type == 'linenum':
args.customize_processing = '::linenum::'
if args.customize_processing:
if args.type == 'hyperlink':
all_marks = hyperlinks
elif args.customize_processing:
m = load_custom_processor(args.customize_processing)
if 'mark' in m:
all_marks = tuple(m['mark'](text, args, Mark, extra_cli_args))
@@ -381,9 +478,8 @@ def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) ->
else:
all_marks = tuple(mark(pattern, post_processors, text, args))
if not all_marks:
input(_('No {} found, press Enter to quit.').format(
'URLs' if args.type == 'url' else 'matches'
))
none_of = {'url': 'URLs', 'hyperlink': 'hyperlinks'}.get(args.type, 'matches')
input(_('No {} found, press Enter to quit.').format(none_of))
return None
largest_index = all_marks[-1].index
@@ -418,7 +514,7 @@ programs.
--type
default=url
choices=url,regex,path,line,hash,word,linenum
choices=url,regex,path,line,hash,word,linenum,hyperlink,ip
The type of text to search for. A value of :code:`linenum` is special, it looks
for error messages using the pattern specified with :option:`--regex`, which
must have the named groups, :code:`path` and :code:`line`. If not specified,
@@ -513,6 +609,24 @@ type=bool-set
Have the hints increase from top to bottom instead of decreasing from top to bottom.
--hints-foreground-color
default=black
type=str
The foreground color for hints
--hints-background-color
default=green
type=str
The background color for hints
--hints-text-color
default=gray
type=str
The foreground color for text pointed to by the hints
--customize-processing
Name of a python file in the kitty config directory which will be imported to provide
custom implementations for pattern finding and performing actions
@@ -552,14 +666,19 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
if items and not (opts.customize_processing or opts.type == 'linenum'):
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
input(_('Press Enter to quit'))
return run(opts, text, items)
try:
return run(opts, text, items)
except Exception:
import traceback
traceback.print_exc()
input(_('Press Enter to quit'))
def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None:
for m, g in zip(data['match'], data['groupdicts']):
if m:
path, line = g['path'], g['line']
path = path.split(':')[-1]
path = os.path.expanduser(path.split(':')[-1])
line = int(line)
break
else:
@@ -583,7 +702,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
}[action])(*cmd)
@result_handler(type_of_input='screen')
@result_handler(type_of_input='screen-ansi')
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
if data['customize_processing']:
m = load_custom_processor(data['customize_processing'])
@@ -635,13 +754,20 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
w = boss.window_id_map.get(target_window_id)
if w is not None:
cwd = w.cwd_of_child
if w is None:
w = boss.active_window
program = None if program == 'default' else program
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 text_type == 'hyperlink':
for m in matches:
if w is not None:
w.open_url(m, hyperlink_id=1, cwd=cwd)
else:
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 __name__ == '__main__':

View File

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import signal
import socket
import subprocess
import sys
from typing import Callable, cast
from urllib.parse import quote_from_bytes
def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, frag: bytes = b'') -> None:
text = b'\033]8;;' + url
if frag:
text += b'#' + frag
text += b'\033\\' + line + b'\033]8;;\033\\'
write(text)
def main() -> None:
if not sys.stdout.isatty() and '--pretty' not in sys.argv:
os.execlp('rg', 'rg', *sys.argv[1:])
cmdline = ['rg', '--pretty', '--with-filename'] + sys.argv[1:]
p = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
assert p.stdout is not 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(b'^(\\d+):')
in_result: bytes = b''
hostname = socket.gethostname().encode('utf-8')
try:
for line in p.stdout:
line = osc_pat.sub(b'', line) # remove any existing hyperlinks
clean_line = sgr_pat.sub(b'', line).rstrip() # remove SGR formatting
if not clean_line:
in_result = b''
write(b'\n')
continue
if in_result:
m = num_pat.match(clean_line)
if m is not None:
write_hyperlink(write, in_result, line, frag=m.group(1))
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)
except KeyboardInterrupt:
p.send_signal(signal.SIGINT)
p.stdout.close()
except EOFError:
p.stdout.close()
raise SystemExit(p.wait())
if __name__ == '__main__':
main()

View File

@@ -3,7 +3,6 @@
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import contextlib
import mimetypes
import os
import re
import socket
@@ -18,6 +17,7 @@ from typing import (
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
)
from kitty.guess_mime_type import guess_type
from kitty.cli import parse_args
from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname
@@ -30,7 +30,7 @@ from ..tui.images import (
ConvertFailed, GraphicsCommand, NoImageMagick, OpenFailed, convert, fsenc,
identify
)
from ..tui.operations import clear_images_on_screen
from ..tui.operations import clear_images_on_screen, raw_mode
OPTIONS = '''\
--align
@@ -110,6 +110,11 @@ default=0
Z-index of the image. When negative, text will be displayed on top of the image. Use
a double minus for values under the threshold for drawing images under cell background
colors. For example, --1 evaluates as -1,073,741,825.
--hold
type=bool-set
Wait for keypress before exiting after displaying the images.
'''
@@ -268,7 +273,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
for dirpath, dirnames, filenames in os.walk(d):
for f in filenames:
mt = mimetypes.guess_type(f)[0]
mt = guess_type(f)
if mt and mt.startswith('image/'):
yield os.path.join(dirpath, f), mt
@@ -473,11 +478,14 @@ def main(args: List[str] = sys.argv) -> None:
errors.append(e)
if parsed_opts.place:
sys.stdout.buffer.write(b'\0338') # restore cursor
if not errors:
return
for err in errors:
print(err, file=sys.stderr)
raise SystemExit(1)
if errors:
for err in errors:
print(err, file=sys.stderr)
if cli_opts.hold:
with open(os.ctermid()) as tty:
with raw_mode(tty.fileno()):
tty.buffer.read(1)
raise SystemExit(1 if errors else 0)
if __name__ == '__main__':

View File

@@ -10,8 +10,8 @@ from typing import Callable, Dict, List, Tuple
from kitty.cli import parse_args
from kitty.cli_stub import PanelCLIOptions
from kitty.options_stub import Options
from kitty.constants import is_macos
from kitty.os_window_size import WindowSizeData
OPTIONS = r'''
--lines
@@ -108,7 +108,7 @@ def setup_x11_window(win_id: int) -> None:
func(win_id, window_width, window_height)
def initial_window_size_func(opts: Options, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
from kitty.fast_data_types import glfw_primary_monitor_size, set_smallest_allowed_resize
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:

View File

View File

@@ -0,0 +1,162 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import re
import sys
from binascii import unhexlify, hexlify
from contextlib import suppress
from typing import Dict, Iterable, List, Type
from kitty.cli import parse_args
from kitty.cli_stub import QueryTerminalCLIOptions
from kitty.constants import appname
from kitty.utils import TTYIO
from kitty.terminfo import names
class Query:
name: str = ''
ans: str = ''
query_name: str = ''
help_text: str = ''
def __init__(self) -> None:
self.encoded_query_name = hexlify(self.query_name.encode('utf-8')).decode('ascii')
self.pat = re.compile('\x1bP([01])\\+r{}(.*?)\x1b\\\\'.format(self.encoded_query_name).encode('ascii'))
def query_code(self) -> str:
return "\x1bP+q{}\x1b\\".format(self.encoded_query_name)
def decode_response(self, res: bytes) -> str:
return unhexlify(res).decode('utf-8')
def more_needed(self, buffer: bytes) -> bool:
m = self.pat.search(buffer)
if m is None:
return True
if m.group(1) == b'1':
q = m.group(2)
if q.startswith(b'='):
with suppress(Exception):
self.ans = self.decode_response(memoryview(q)[1:])
return False
def output_line(self) -> str:
return self.ans
all_queries: Dict[str, Type[Query]] = {}
def query(cls: Type[Query]) -> Type[Query]:
all_queries[cls.name] = cls
return cls
@query
class TerminalName(Query):
name: str = 'name'
query_name: str = 'TN'
help_text: str = f'Terminal name ({names[0]})'
@query
class TerminalVersion(Query):
name: str = 'version'
query_name: str = 'kitty-query-version'
help_text: str = 'Terminal version, for e.g.: 0.19.2'
@query
class AllowHyperlinks(Query):
name: str = 'allow_hyperlinks'
query_name: str = 'kitty-query-allow_hyperlinks'
help_text: str = 'yes, no or ask'
def do_queries(queries: Iterable, cli_opts: QueryTerminalCLIOptions) -> Dict[str, str]:
actions = tuple(all_queries[x]() for x in queries)
qstring = ''.join(a.query_code() for a in actions)
received = b''
def more_needed(data: bytes) -> bool:
nonlocal received
received += data
for a in actions:
if a.more_needed(received):
return True
return False
with TTYIO() as ttyio:
ttyio.send(qstring)
ttyio.recv(more_needed, timeout=cli_opts.wait_for)
return {a.name: a.output_line() for a in actions}
def options_spec() -> str:
return '''\
--wait-for
type=float
default=10
The amount of time (in seconds) to wait for a response from the terminal, after
querying it.
'''
help_text = '''\
Query the terminal this kitten is run in for various
capabilities. This sends escape codes to the terminal
and based on its response prints out data about supported
capabilities. Note that this is a blocking operation, since
it has to wait for a response from the terminal. You can control
the maximum wait time via the ``--wait-for`` option.
The output is lines of the form::
query: data
If a particular query is unsupported by the running kitty version,
the data will be blank.
Note that when calling this from another program, be very
careful not to perform any I/O on the terminal device
until the kitten exits.
Available queries are::
{}
'''.format(' ' + '\n '.join(
f'{name}: {c.help_text}' for name, c in all_queries.items()))
usage = '[query1 query2 ...]'
def main(args: List[str] = sys.argv) -> None:
cli_opts, items_ = parse_args(
args[1:],
options_spec,
usage,
help_text,
'{} +kitten query_terminal'.format(appname),
result_class=QueryTerminalCLIOptions
)
queries: List[str] = list(items_)
if 'all' in queries or not queries:
queries = sorted(all_queries)
else:
extra = frozenset(queries) - frozenset(all_queries)
if extra:
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
for key, val in do_queries(queries, cli_opts).items():
print(key + ':', val)
if __name__ == '__main__':
main()
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = options_spec
cd['help_text'] = help_text

View File

325
kittens/remote_file/main.py Normal file
View File

@@ -0,0 +1,325 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import json
import os
import shlex
import shutil
import subprocess
import sys
import tempfile
import time
from typing import Any, List, Optional
from kitty.cli import parse_args
from kitty.cli_stub import RemoteFileCLIOptions
from kitty.constants import cache_dir
from kitty.typing import BossType
from kitty.utils import (
SSHConnectionData, command_for_open, get_editor, open_cmd
)
from ..tui.handler import result_handler
from ..tui.operations import (
faint, raw_mode, reset_terminal, styled
)
from ..tui.utils import get_key_press
def key(x: str) -> str:
return styled(x, bold=True, fg='green')
def option_text() -> str:
return '''\
--mode -m
choices=ask,edit
default=ask
Which mode to operate in.
--path -p
Path to the remote file.
--hostname -h
Hostname of the remote host.
--ssh-connection-data
The data used to connect over ssh.
'''
def show_error(msg: str) -> None:
print(styled(msg, fg='red'))
print()
print('Press any key to exit...')
sys.stdout.flush()
with raw_mode():
while True:
try:
q = sys.stdin.buffer.read(1)
if q:
break
except (KeyboardInterrupt, EOFError):
break
def ask_action(opts: RemoteFileCLIOptions) -> str:
print('What would you like to do with the remote file on {}:'.format(styled(opts.hostname or 'unknown', bold=True, fg='magenta')))
print(styled(opts.path or '', fg='yellow', fg_intense=True))
print()
def help_text(x: str) -> str:
return faint(x)
print('{}dit the file'.format(key('E')))
print(help_text('The file will be downloaded and opened in an editor. Any changes you save will'
' be automatically sent back to the remote machine'))
print()
print('{}pen the file'.format(key('O')))
print(help_text('The file will be downloaded and opened by the default open program'))
print()
print('{}ave the file'.format(key('S')))
print(help_text('The file will be downloaded to a destination you select'))
print()
print('{}ancel'.format(key('C')))
print()
sys.stdout.flush()
response = get_key_press('ceos', 'c')
return {'e': 'edit', 'o': 'open', 's': 'save'}.get(response, 'cancel')
def hostname_matches(from_hyperlink: str, actual: str) -> bool:
if from_hyperlink == actual:
return True
if from_hyperlink.partition('.')[0] == actual.partition('.')[0]:
return True
return False
class ControlMaster:
def __init__(self, conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions, dest: str = ''):
self.conn_data = conn_data
self.cli_opts = cli_opts
self.remote_path = remote_path
self.dest = dest
self.tdir = ''
self.cmd_prefix = cmd = [
conn_data.binary, '-o', f'ControlPath=~/.ssh/kitty-master-{os.getpid()}-%r@%h:%p',
'-o', 'TCPKeepAlive=yes', '-o', 'ControlPersist=yes'
]
if conn_data.port:
cmd += ['-p', str(conn_data.port)]
self.batch_cmd_prefix = cmd + ['-o', 'BatchMode=yes']
def __enter__(self) -> 'ControlMaster':
subprocess.check_call(
self.cmd_prefix + ['-o', 'ControlMaster=auto', '-fN', self.conn_data.hostname])
subprocess.check_call(
self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname])
if not self.dest:
self.tdir = tempfile.mkdtemp()
self.dest = os.path.join(self.tdir, os.path.basename(self.remote_path))
return self
def __exit__(self, *a: Any) -> bool:
subprocess.Popen(
self.batch_cmd_prefix + ['-O', 'exit', self.conn_data.hostname],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
).wait()
if self.tdir:
shutil.rmtree(self.tdir)
return True
@property
def is_alive(self) -> bool:
return subprocess.Popen(
self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
).wait() == 0
def check_hostname_matches(self) -> bool:
cp = subprocess.run(self.batch_cmd_prefix + [self.conn_data.hostname, 'hostname', '-f'], stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
if cp.returncode == 0:
q = cp.stdout.decode('utf-8').strip()
if not hostname_matches(self.cli_opts.hostname or '', q):
print(reset_terminal(), end='')
print(f'The remote hostname {styled(q, fg="green")} does not match the')
print(f'hostname in the hyperlink {styled(self.cli_opts.hostname or "", fg="red")}')
print('This indicates that kitty has not connected to the correct remote machine.')
print('This can happen, for example, when using nested SSH sessions.')
print(f'The hostname kitty used to connect was: {styled(self.conn_data.hostname, fg="yellow")}', end='')
if self.conn_data.port is not None:
print(f' with port: {self.conn_data.port}')
print()
print()
print('Do you want to continue anyway?')
print(
f'{styled("Y", fg="green")}es',
f'{styled("N", fg="red")}o', sep='\t'
)
sys.stdout.flush()
response = get_key_press('yn', 'n')
print(reset_terminal(), end='')
return response == 'y'
return True
def download(self) -> bool:
with open(self.dest, 'wb') as f:
return subprocess.run(
self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', self.remote_path],
stdout=f, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL
).returncode == 0
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]
if not suppress_output:
print(' '.join(map(shlex.quote, cmd)))
redirect = subprocess.DEVNULL if suppress_output else None
with open(self.dest, 'rb') as f:
return subprocess.run(cmd, stdout=redirect, stderr=redirect, stdin=f).returncode == 0
Result = Optional[str]
def main(args: List[str]) -> Result:
msg = 'Ask the user what to do with the remote file'
try:
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty remote_file', result_class=RemoteFileCLIOptions)
except SystemExit as e:
if e.code != 0:
print(e.args[0])
input('Press enter to quit...')
raise SystemExit(e.code)
try:
action = ask_action(cli_opts)
finally:
print(reset_terminal(), end='', flush=True)
try:
return handle_action(action, cli_opts)
except Exception:
print(reset_terminal(), end='', flush=True)
import traceback
traceback.print_exc()
show_error('Failed with unhandled exception')
def save_as(conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFileCLIOptions) -> None:
ddir = cache_dir()
os.makedirs(ddir, exist_ok=True)
last_used_store_path = os.path.join(ddir, 'remote-file-last-used.txt')
try:
with open(last_used_store_path) as f:
last_used_path = f.read()
except FileNotFoundError:
last_used_path = tempfile.gettempdir()
last_used_file = os.path.join(last_used_path, os.path.basename(remote_path))
print(
'Where do you wish to save the file? Leaving it blank will save it as:',
styled(last_used_file, fg='yellow')
)
print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True))
print()
from ..tui.path_completer import PathCompleter
try:
dest = PathCompleter().input()
except (KeyboardInterrupt, EOFError):
return
if dest:
dest = os.path.expandvars(os.path.expanduser(dest))
if os.path.isdir(dest):
dest = os.path.join(dest, os.path.basename(remote_path))
with open(last_used_store_path, 'w') as f:
f.write(os.path.dirname(os.path.abspath(dest)))
else:
dest = last_used_file
if os.path.exists(dest):
print(reset_terminal(), end='')
print(f'The file {styled(dest, fg="yellow")} already exists. What would you like to do?')
print(f'{key("O")}verwrite {key("A")}bort Auto {key("R")}ename {key("N")}ew name')
response = get_key_press('anor', 'a')
if response == 'a':
return
if response == 'n':
print(reset_terminal(), end='')
return save_as(conn_data, remote_path, cli_opts)
if response == 'r':
q = dest
c = 0
while os.path.exists(q):
c += 1
b, ext = os.path.splitext(dest)
q = f'{b}-{c}{ext}'
dest = q
if os.path.dirname(dest):
os.makedirs(os.path.dirname(dest), exist_ok=True)
with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master:
if master.check_hostname_matches():
if not master.download():
show_error('Failed to copy file from remote machine')
def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result:
conn_data = SSHConnectionData(*json.loads(cli_opts.ssh_connection_data or ''))
remote_path = cli_opts.path or ''
if action == 'open':
print('Opening', cli_opts.path, 'from', cli_opts.hostname)
dest = os.path.join(tempfile.mkdtemp(), os.path.basename(remote_path))
with ControlMaster(conn_data, remote_path, cli_opts, dest=dest) as master:
if master.check_hostname_matches():
if master.download():
return dest
show_error('Failed to copy file from remote machine')
elif action == 'edit':
print('Editing', cli_opts.path, 'from', cli_opts.hostname)
with ControlMaster(conn_data, remote_path, cli_opts) as master:
if not master.check_hostname_matches():
return None
if not master.download():
show_error(f'Failed to download {remote_path}')
return None
mtime = os.path.getmtime(master.dest)
print(reset_terminal(), end='', flush=True)
editor = get_editor()
editor_process = subprocess.Popen(editor + [master.dest])
while editor_process.poll() is None:
time.sleep(0.1)
newmtime = os.path.getmtime(master.dest)
if newmtime > mtime:
mtime = newmtime
if master.is_alive:
master.upload()
print(reset_terminal(), end='', flush=True)
if master.is_alive:
if not master.upload(suppress_output=False):
show_error(f'Failed to upload {remote_path}')
else:
show_error(f'Failed to upload {remote_path}, SSH master process died')
elif action == 'save':
print('Saving', cli_opts.path, 'from', cli_opts.hostname)
save_as(conn_data, remote_path, cli_opts)
@result_handler()
def handle_result(args: List[str], data: Result, target_window_id: int, boss: BossType) -> None:
if data:
cmd = command_for_open(boss.opts.open_url_with)
open_cmd(cmd, data)
if __name__ == '__main__':
main(sys.argv)

View File

@@ -7,7 +7,10 @@ import re
import shlex
import subprocess
import sys
from typing import List, NoReturn, Set, Tuple
from contextlib import suppress
from typing import List, NoReturn, Optional, Set, Tuple
from kitty.utils import SSHConnectionData
SHELL_SCRIPT = '''\
#!/bin/sh
@@ -60,6 +63,41 @@ def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
return set('-' + x for x in boolean_ssh_args), set('-' + x for x in other_ssh_args)
def get_connection_data(args: List[str]) -> Optional[SSHConnectionData]:
boolean_ssh_args, other_ssh_args = get_ssh_cli()
found_ssh = ''
port: Optional[int] = None
expecting_port = False
expecting_option_val = False
for i, arg in enumerate(args):
if not found_ssh:
if os.path.basename(arg).lower() in ('ssh', 'ssh.exe'):
found_ssh = arg
continue
if arg.startswith('-') and not expecting_option_val:
if arg in boolean_ssh_args:
continue
if arg.startswith('-p'):
if arg[2:].isdigit():
with suppress(Exception):
port = int(arg[2:])
elif arg == '-p':
expecting_port = True
expecting_option_val = True
continue
if expecting_option_val:
if expecting_port:
with suppress(Exception):
port = int(arg)
expecting_port = False
expecting_option_val = False
continue
return SSHConnectionData(found_ssh, arg, port)
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
boolean_ssh_args, other_ssh_args = get_ssh_cli()
passthrough_args = {'-' + x for x in 'Nnf'}
@@ -80,15 +118,14 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
if arg in boolean_ssh_args:
ssh_args.append(arg)
continue
if arg.startswith('-p') and arg[2:].isdigit():
ssh_args.append(arg)
continue
if arg in other_ssh_args:
if i != len(all_args) - 1:
raise SystemExit('Option {} cannot occur in the middle'.format(arg))
ssh_args.append(arg)
expecting_option_val = True
continue
rest = all_args[i+1:]
if rest:
ssh_args.append(rest)
else:
expecting_option_val = True
break
raise SystemExit('Unknown option: {}'.format(arg))
continue
if expecting_option_val:

385
kittens/tui/dircolors.py Normal file
View File

@@ -0,0 +1,385 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
import stat
from contextlib import suppress
from typing import Dict, Generator, Optional, Tuple, Union
DEFAULT_DIRCOLORS = r"""# {{{
# Configuration file for dircolors, a utility to help you set the
# LS_COLORS environment variable used by GNU ls with the --color option.
# Copyright (C) 1996-2019 Free Software Foundation, Inc.
# Copying and distribution of this file, with or without modification,
# are permitted provided the copyright notice and this notice are preserved.
# The keywords COLOR, OPTIONS, and EIGHTBIT (honored by the
# slackware version of dircolors) are recognized but ignored.
# Below are TERM entries, which can be a glob patterns, to match
# against the TERM environment variable to determine if it is colorizable.
TERM Eterm
TERM ansi
TERM *color*
TERM con[0-9]*x[0-9]*
TERM cons25
TERM console
TERM cygwin
TERM dtterm
TERM gnome
TERM hurd
TERM jfbterm
TERM konsole
TERM kterm
TERM linux
TERM linux-c
TERM mlterm
TERM putty
TERM rxvt*
TERM screen*
TERM st
TERM terminator
TERM tmux*
TERM vt100
TERM xterm*
# Below are the color init strings for the basic file types.
# One can use codes for 256 or more colors supported by modern terminals.
# The default color codes use the capabilities of an 8 color terminal
# with some additional attributes as per the following codes:
# Attribute codes:
# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed
# Text color codes:
# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
# Background color codes:
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
#NORMAL 00 # no color code at all
#FILE 00 # regular file: use no color at all
RESET 0 # reset to "normal" color
DIR 01;34 # directory
LINK 01;36 # symbolic link. (If you set this to 'target' instead of a
# numerical value, the color is as for the file pointed to.)
MULTIHARDLINK 00 # regular file with more than one link
FIFO 40;33 # pipe
SOCK 01;35 # socket
DOOR 01;35 # door
BLK 40;33;01 # block device driver
CHR 40;33;01 # character device driver
ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ...
MISSING 00 # ... and the files they point to
SETUID 37;41 # file that is setuid (u+s)
SETGID 30;43 # file that is setgid (g+s)
CAPABILITY 30;41 # file with capability
STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable
# This is for files with execute permission:
EXEC 01;32
# List any file extensions like '.gz' or '.tar' that you would like ls
# to colorize below. Put the extension, a space, and the color init string.
# (and any comments you want to add after a '#')
# If you use DOS-style suffixes, you may want to uncomment the following:
#.cmd 01;32 # executables (bright green)
#.exe 01;32
#.com 01;32
#.btm 01;32
#.bat 01;32
# Or if you want to colorize scripts even if they do not have the
# executable bit actually set.
#.sh 01;32
#.csh 01;32
# archives or compressed (bright red)
.tar 01;31
.tgz 01;31
.arc 01;31
.arj 01;31
.taz 01;31
.lha 01;31
.lz4 01;31
.lzh 01;31
.lzma 01;31
.tlz 01;31
.txz 01;31
.tzo 01;31
.t7z 01;31
.zip 01;31
.z 01;31
.dz 01;31
.gz 01;31
.lrz 01;31
.lz 01;31
.lzo 01;31
.xz 01;31
.zst 01;31
.tzst 01;31
.bz2 01;31
.bz 01;31
.tbz 01;31
.tbz2 01;31
.tz 01;31
.deb 01;31
.rpm 01;31
.jar 01;31
.war 01;31
.ear 01;31
.sar 01;31
.rar 01;31
.alz 01;31
.ace 01;31
.zoo 01;31
.cpio 01;31
.7z 01;31
.rz 01;31
.cab 01;31
.wim 01;31
.swm 01;31
.dwm 01;31
.esd 01;31
# image formats
.jpg 01;35
.jpeg 01;35
.mjpg 01;35
.mjpeg 01;35
.gif 01;35
.bmp 01;35
.pbm 01;35
.pgm 01;35
.ppm 01;35
.tga 01;35
.xbm 01;35
.xpm 01;35
.tif 01;35
.tiff 01;35
.png 01;35
.svg 01;35
.svgz 01;35
.mng 01;35
.pcx 01;35
.mov 01;35
.mpg 01;35
.mpeg 01;35
.m2v 01;35
.mkv 01;35
.webm 01;35
.ogm 01;35
.mp4 01;35
.m4v 01;35
.mp4v 01;35
.vob 01;35
.qt 01;35
.nuv 01;35
.wmv 01;35
.asf 01;35
.rm 01;35
.rmvb 01;35
.flc 01;35
.avi 01;35
.fli 01;35
.flv 01;35
.gl 01;35
.dl 01;35
.xcf 01;35
.xwd 01;35
.yuv 01;35
.cgm 01;35
.emf 01;35
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
.ogv 01;35
.ogx 01;35
# audio formats
.aac 00;36
.au 00;36
.flac 00;36
.m4a 00;36
.mid 00;36
.midi 00;36
.mka 00;36
.mp3 00;36
.mpc 00;36
.ogg 00;36
.ra 00;36
.wav 00;36
# https://wiki.xiph.org/MIME_Types_and_File_Extensions
.oga 00;36
.opus 00;36
.spx 00;36
.xspf 00;36
""" # }}}
# special file?
special_types = (
(stat.S_IFLNK, 'ln'), # symlink
(stat.S_IFIFO, 'pi'), # pipe (FIFO)
(stat.S_IFSOCK, 'so'), # socket
(stat.S_IFBLK, 'bd'), # block device
(stat.S_IFCHR, 'cd'), # character device
(stat.S_ISUID, 'su'), # setuid
(stat.S_ISGID, 'sg'), # setgid
)
CODE_MAP = {
'RESET': 'rs',
'DIR': 'di',
'LINK': 'ln',
'MULTIHARDLINK': 'mh',
'FIFO': 'pi',
'SOCK': 'so',
'DOOR': 'do',
'BLK': 'bd',
'CHR': 'cd',
'ORPHAN': 'or',
'MISSING': 'mi',
'SETUID': 'su',
'SETGID': 'sg',
'CAPABILITY': 'ca',
'STICKY_OTHER_WRITABLE': 'tw',
'OTHER_WRITABLE': 'ow',
'STICKY': 'st',
'EXEC': 'ex',
}
def stat_at(file: str, cwd: Optional[Union[int, str]] = None, follow_symlinks: bool = False) -> os.stat_result:
dirfd: Optional[int] = None
need_to_close = False
if isinstance(cwd, str):
dirfd = os.open(cwd, os.O_RDONLY)
need_to_close = True
elif isinstance(cwd, int):
dirfd = cwd
try:
return os.stat(file, dir_fd=dirfd, follow_symlinks=follow_symlinks)
finally:
if need_to_close and dirfd is not None:
os.close(dirfd)
class Dircolors:
def __init__(self) -> None:
self.codes: Dict[str, str] = {}
self.extensions: Dict[str, str] = {}
if not self.load_from_environ() and not self.load_from_file():
self.load_defaults()
def clear(self) -> None:
self.codes.clear()
self.extensions.clear()
def load_from_file(self) -> bool:
for candidate in (os.path.expanduser('~/.dir_colors'), '/etc/DIR_COLORS'):
with suppress(Exception):
with open(candidate) as f:
return self.load_from_dircolors(f.read())
return False
def load_from_lscolors(self, lscolors: str) -> bool:
self.clear()
if not lscolors:
return False
for item in lscolors.split(':'):
try:
code, color = item.split('=', 1)
except ValueError:
continue
if code.startswith('*.'):
self.extensions[code[1:]] = color
else:
self.codes[code] = color
return bool(self.codes or self.extensions)
def load_from_environ(self, envvar: str = 'LS_COLORS') -> bool:
return self.load_from_lscolors(os.environ.get(envvar) or '')
def load_from_dircolors(self, database: str, strict: bool = False) -> bool:
self.clear()
for line in database.splitlines():
line = line.split('#')[0].strip()
if not line:
continue
split = line.split()
if len(split) != 2:
if strict:
raise ValueError(f'Warning: unable to parse dircolors line "{line}"')
continue
key, val = split
if key == 'TERM':
continue
if key in CODE_MAP:
self.codes[CODE_MAP[key]] = val
elif key.startswith('.'):
self.extensions[key] = val
elif strict:
raise ValueError(f'Warning: unable to parse dircolors line "{line}"')
return bool(self.codes or self.extensions)
def load_defaults(self) -> bool:
self.clear()
return self.load_from_dircolors(DEFAULT_DIRCOLORS, True)
def generate_lscolors(self) -> str:
""" Output the database in the format used by the LS_COLORS environment variable. """
def gen_pairs() -> Generator[Tuple[str, str], None, None]:
for pair in self.codes.items():
yield pair
for pair in self.extensions.items():
# change .xyz to *.xyz
yield '*' + pair[0], pair[1]
return ':'.join('%s=%s' % pair for pair in gen_pairs())
def _format_code(self, text: str, code: str) -> str:
val = self.codes.get(code)
return '\033[%sm%s\033[%sm' % (val, text, self.codes.get('rs', '0')) if val else text
def _format_ext(self, text: str, ext: str) -> str:
val = self.extensions.get(ext, '0')
return '\033[%sm%s\033[%sm' % (val, text, self.codes.get('rs', '0')) if val else text
def format_mode(self, text: str, sr: os.stat_result) -> str:
mode = sr.st_mode
if stat.S_ISDIR(mode):
if (mode & (stat.S_ISVTX | stat.S_IWOTH)) == (stat.S_ISVTX | stat.S_IWOTH):
# sticky and world-writable
return self._format_code(text, 'tw')
if mode & stat.S_ISVTX:
# sticky but not world-writable
return self._format_code(text, 'st')
if mode & stat.S_IWOTH:
# world-writable but not sticky
return self._format_code(text, 'ow')
# normal directory
return self._format_code(text, 'di')
for mask, code in special_types:
if (mode & mask) == mask:
return self._format_code(text, code)
# executable file?
if mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
return self._format_code(text, 'ex')
# regular file, format according to its extension
ext = os.path.splitext(text)[1]
if ext:
return self._format_ext(text, ext)
return text
def __call__(self, path: str, text: str, cwd: Optional[Union[int, str]] = None) -> str:
follow_symlinks = self.codes.get('ln') == 'target'
try:
sr = stat_at(path, cwd, follow_symlinks)
except OSError:
return text
return self.format_mode(text, sr)
def develop() -> None:
import sys
print(Dircolors()(sys.argv[-1], sys.argv[-1]))

View File

@@ -10,6 +10,7 @@ import re
import selectors
import signal
import sys
import termios
from contextlib import contextmanager
from functools import partial
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
@@ -61,8 +62,9 @@ debug = Debug()
class TermManager:
def __init__(self) -> None:
def __init__(self, optional_actions: int = termios.TCSANOW) -> None:
self.extra_finalize: Optional[str] = None
self.optional_actions = optional_actions
def set_state_for_loop(self, set_raw: bool = True) -> None:
if set_raw:
@@ -82,7 +84,7 @@ class TermManager:
self.set_state_for_loop()
def __enter__(self) -> 'TermManager':
self.tty_fd, self.original_termios = open_tty()
self.tty_fd, self.original_termios = open_tty(False, self.optional_actions)
self.set_state_for_loop(set_raw=False)
return self
@@ -183,7 +185,8 @@ class Loop:
def __init__(
self,
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]',
optional_actions: int = termios.TCSADRAIN
):
if is_macos:
# On macOS PTY devices are not supported by the KqueueSelector and
@@ -193,6 +196,7 @@ class Loop:
else:
self.asycio_loop = asyncio.get_event_loop()
self.return_code = 0
self.optional_actions = optional_actions
self.read_buf = ''
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
try:
@@ -388,7 +392,7 @@ class Loop:
handler.on_resize(handler.screen_size)
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
with TermManager() as term_manager, signal_manager:
with TermManager(self.optional_actions) as term_manager, signal_manager:
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
image_manager = None
if handler.image_manager_class is not None:

View File

@@ -73,6 +73,11 @@ def clear_to_eol() -> str:
return '\033[K'
@cmd
def reset_terminal() -> str:
return '\033]\033\\\033c'
@cmd
def bell() -> str:
return '\a'
@@ -288,6 +293,20 @@ def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
@contextmanager
def raw_mode(fd: Optional[int] = None) -> Generator[None, None, None]:
import tty
import termios
if fd is None:
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
try:
tty.setraw(fd)
yield
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
@cmd
def set_default_colors(
fg: Optional[Union[Color, str]] = None,
@@ -341,7 +360,7 @@ def request_from_clipboard(use_primary: bool = False) -> str:
return '\x1b]52;{};?\x07'.format('p' if use_primary else 'c')
# Boilerplate to make operations availble via Handler.cmd {{{
# Boilerplate to make operations available via Handler.cmd {{{
def writer(handler: HandlerType, func: Callable) -> Callable:

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
from typing import Any, Callable, Dict, Generator, Optional, Sequence, Tuple
from kitty.fast_data_types import wcswidth
from kitty.utils import ScreenSize, screen_size_function
from .operations import styled
def directory_completions(path: str, qpath: str, prefix: str = '') -> Generator[str, None, None]:
try:
entries = os.scandir(qpath)
except OSError:
return
for x in entries:
try:
is_dir = x.is_dir()
except OSError:
is_dir = False
name = x.name + (os.sep if is_dir else '')
if not prefix or name.startswith(prefix):
if path:
yield os.path.join(path, name)
else:
yield name
def expand_path(path: str) -> str:
return os.path.abspath(os.path.expandvars(os.path.expanduser(path)))
def find_completions(path: str) -> Generator[str, None, None]:
if path and path[0] == '~':
if path == '~':
yield '~' + os.sep
return
if os.sep not in path:
qpath = os.path.expanduser(path)
if qpath != path:
yield path + os.sep
return
qpath = expand_path(path)
if not path or path.endswith(os.sep):
yield from directory_completions(path, qpath)
else:
yield from directory_completions(os.path.dirname(path), os.path.dirname(qpath), os.path.basename(qpath))
def print_table(items: Sequence[str], screen_size: ScreenSize, dir_colors: Callable[[str, str], str]) -> None:
max_width = 0
item_widths = {}
for item in items:
item_widths[item] = w = wcswidth(item)
max_width = max(w, max_width)
col_width = max_width + 2
num_of_cols = max(1, screen_size.cols // col_width)
cr = 0
at_start = False
for item in items:
w = item_widths[item]
left = col_width - w
print(dir_colors(expand_path(item), item), ' ' * left, sep='', end='')
at_start = False
cr = (cr + 1) % num_of_cols
if not cr:
print()
at_start = True
if not at_start:
print()
class PathCompleter:
def __init__(self, prompt: str = '> '):
self.prompt = prompt
self.prompt_len = wcswidth(self.prompt)
def __enter__(self) -> 'PathCompleter':
import readline
from .dircolors import Dircolors
if 'libedit' in readline.__doc__:
readline.parse_and_bind("bind -e")
readline.parse_and_bind("bind '\t' rl_complete")
else:
readline.parse_and_bind('tab: complete')
readline.parse_and_bind('set colored-stats on')
readline.set_completer_delims(' \t\n`!@#$%^&*()-=+[{]}\\|;:\'",<>?')
readline.set_completion_display_matches_hook(self.format_completions)
self.original_completer = readline.get_completer()
readline.set_completer(self)
self.cache: Dict[str, Tuple[str, ...]] = {}
self.dircolors = Dircolors()
return self
def format_completions(self, substitution: str, matches: Sequence[str], longest_match_length: int) -> None:
import readline
print()
files, dirs = [], []
for m in matches:
if m.endswith('/'):
if len(m) > 1:
m = m[:-1]
dirs.append(m)
else:
files.append(m)
ss = screen_size_function()()
if dirs:
print(styled('Directories', bold=True, fg_intense=True))
print_table(dirs, ss, self.dircolors)
if files:
print(styled('Files', bold=True, fg_intense=True))
print_table(files, ss, self.dircolors)
buf = readline.get_line_buffer()
x = readline.get_endidx()
buflen = wcswidth(buf)
print(self.prompt, buf, sep='', end='')
if x < buflen:
pos = x + self.prompt_len
print(f"\r\033[{pos}C", end='')
print(sep='', end='', flush=True)
def __call__(self, text: str, state: int) -> Optional[str]:
options = self.cache.get(text)
if options is None:
options = self.cache[text] = tuple(find_completions(text))
if options and state < len(options):
return options[state]
def __exit__(self, *a: Any) -> bool:
import readline
del self.cache
readline.set_completer(self.original_completer)
readline.set_completion_display_matches_hook()
return True
def input(self) -> str:
with self:
return input(self.prompt)
def develop() -> None:
PathCompleter().input()

29
kittens/tui/utils.py Normal file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import sys
from contextlib import suppress
from .operations import raw_mode, set_cursor_visible
def get_key_press(allowed: str, default: str) -> str:
response = default
with raw_mode():
print(set_cursor_visible(False), end='', flush=True)
try:
while True:
q = sys.stdin.buffer.read(1)
if q:
if q in b'\x1b\x03':
break
with suppress(Exception):
response = q.decode('utf-8').lower()
if response in allowed:
break
except (KeyboardInterrupt, EOFError):
pass
finally:
print(set_cursor_visible(True), end='', flush=True)
return response

View File

@@ -20,7 +20,7 @@ from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
from kitty.key_encoding import (
CTRL, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
CTRL, PRESS, RELEASE, SHIFT, KeyEvent, enter_key, key_defs as K
)
from kitty.typing import BossType
from kitty.utils import ScreenSize, get_editor
@@ -38,6 +38,8 @@ UP = K['UP']
DOWN = K['DOWN']
LEFT = K['LEFT']
RIGHT = K['RIGHT']
RIGHT_BRACKET = K['RIGHT_BRACKET']
LEFT_BRACKET = K['LEFT_BRACKET']
TAB = K['TAB']
ESCAPE = K['ESCAPE']
F1 = K['F1']
@@ -478,7 +480,7 @@ class UnicodeInput(Handler):
return
if key_event is enter_key:
self.quit_loop(0)
elif key_event.type is RELEASE:
elif key_event.type is PRESS:
if not key_event.mods:
if key_event.key is ESCAPE:
self.quit_loop(1)
@@ -492,8 +494,8 @@ class UnicodeInput(Handler):
self.switch_mode(FAVORITES)
elif key_event.key is F12 and self.mode is FAVORITES:
self.edit_favorites()
elif key_event.mods == CTRL and key_event.key is TAB:
self.next_mode()
elif key_event.mods == CTRL and key_event.key in (TAB, RIGHT_BRACKET, LEFT_BRACKET):
self.next_mode(-1 if key_event.key is LEFT_BRACKET else 1)
elif key_event.mods == CTRL | SHIFT and key_event.key is TAB:
self.next_mode(-1)

File diff suppressed because one or more lines are too long

View File

@@ -69,6 +69,7 @@ class Borders:
self.os_window_id = os_window_id
self.tab_id = tab_id
self.draw_active_borders = opts.active_border_color is not None
self.draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1
def __call__(
self,
@@ -88,29 +89,24 @@ class Borders:
if groups:
bw = groups[0].effective_border()
draw_borders = bw > 0 and draw_window_borders
if draw_borders:
border_data = current_layout.resolve_borders(all_windows)
active_group = all_windows.active_group
for i, wg in enumerate(groups):
window_bg = wg.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders:
if draw_borders and not self.draw_minimal_borders:
# Draw the border rectangles
if wg is active_group and self.draw_active_borders:
color = BorderColor.active
else:
color = BorderColor.bell if wg.needs_attention else BorderColor.inactive
try:
colors = tuple(color if needed else window_bg for needed in next(border_data))
draw_edges(self.os_window_id, self.tab_id, colors, wg, borders=True)
except StopIteration:
pass
draw_edges(self.os_window_id, self.tab_id, (color, color, color, color), wg, borders=True)
if not has_background_image:
# Draw the background rectangles over the padding region
colors = window_bg, window_bg, window_bg, window_bg
draw_edges(self.os_window_id, self.tab_id, colors, wg)
color = BorderColor.inactive
for (left, top, right, bottom) in current_layout.window_independent_borders(all_windows):
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, color)
if self.draw_minimal_borders:
for border_line in current_layout.minimal_borders(all_windows):
left, top, right, bottom = border_line.edges
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, border_line.color)

View File

@@ -19,7 +19,7 @@ from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .conf.utils import BadLine, to_cmdline
from .config import (
KeyAction, SubSequenceMap, common_opts_as_dict, initial_window_size_func,
KeyAction, SubSequenceMap, common_opts_as_dict,
prepare_config_file_for_editing
)
from .config_data import MINIMUM_FONT_SIZE
@@ -28,7 +28,7 @@ from .constants import (
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
ChildMonitor, background_opacity_of, change_background_opacity,
ChildMonitor, add_timer, 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_clipboard_string, global_font_size,
@@ -39,9 +39,11 @@ from .fast_data_types import (
)
from .keys import get_shortcut, shortcut_matches
from .layout.base import set_layout_options
from .notify import notification_activated
from .options_stub import Options
from .os_window_size import initial_window_size_func
from .rgb import Color, color_from_int
from .session import Session, create_sessions
from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
)
@@ -49,24 +51,19 @@ from .typing import PopenType, TypedDict
from .utils import (
func_name, get_editor, get_primary_selection, is_path_in_temp_dir,
log_error, open_url, parse_address_spec, parse_uri_list,
remove_socket_file, safe_print, set_primary_selection, single_instance,
startup_notification_handler
platform_window_id, remove_socket_file, safe_print, set_primary_selection,
single_instance, startup_notification_handler
)
from .window import MatchPatternType, Window
class OSWindowDict(TypedDict):
id: int
platform_window_id: Optional[int]
is_focused: bool
tabs: List[TabDict]
def notification_activated(identifier: str) -> None:
if identifier == 'new-version':
from .update_check import notification_activated as do
do()
def listen_on(spec: str) -> int:
import socket
family, address, socket_path = parse_address_spec(spec)
@@ -170,12 +167,18 @@ class Boss:
if new_os_window_trigger is not None:
self.keymap.pop(new_os_window_trigger, None)
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 startup_first_child(self, os_window_id: Optional[int]) -> None:
from kitty.launch import load_watch_modules
startup_sessions = create_sessions(self.opts, self.args, default_session=self.opts.startup_session)
watchers = load_watch_modules(self.args.watcher)
for startup_session in startup_sessions:
if watchers is not None and watchers.has_watchers:
startup_session.add_watchers_to_all_windows(watchers)
self.add_os_window(startup_session, os_window_id=os_window_id)
os_window_id = None
if self.args.start_as != 'normal':
@@ -194,11 +197,11 @@ class Boss:
startup_id: Optional[str] = None
) -> int:
if os_window_id is None:
opts_for_size = opts_for_size or getattr(startup_session, 'os_window_size', None) or self.opts
wclass = wclass or self.args.cls or appname
size_data = get_os_window_sizing_data(opts_for_size or self.opts, startup_session)
wclass = wclass or getattr(startup_session, 'os_window_class', None) or self.args.cls or appname
with startup_notification_handler(do_notify=startup_id is not None, startup_id=startup_id) as pre_show_callback:
os_window_id = create_os_window(
initial_window_size_func(opts_for_size, self.cached_values),
initial_window_size_func(size_data, self.cached_values),
pre_show_callback,
self.args.title or appname, wname or self.args.name or wclass, wclass)
tm = TabManager(os_window_id, self.opts, self.args, startup_session)
@@ -212,6 +215,7 @@ class Boss:
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)),
}
@@ -347,8 +351,10 @@ class Boss:
return response
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
from .rc.base import command_for_name, parse_subcommand_cli, PayloadGetter
try:
global_opts, items = parse_rc_args(['@'] + list(args))
if not items:
@@ -382,6 +388,8 @@ class Boss:
args.directory = os.path.join(data['cwd'], args.directory)
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)
if opts.background_opacity != self.opts.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'])
else:
@@ -442,8 +450,34 @@ class Boss:
def close_tab(self, tab: Optional[Tab] = None) -> None:
tab = tab or self.active_tab
if tab:
for window in tab:
self.close_window(window)
self.confirm_tab_close(tab)
def confirm_tab_close(self, tab: Tab) -> None:
windows = tuple(tab)
needs_confirmation = self.opts.confirm_os_window_close > 0 and len(windows) >= self.opts.confirm_os_window_close
if not needs_confirmation:
self.close_tab_no_confirm(tab)
return
self._run_kitten('ask', ['--type=yesno', '--message', _(
'Are you sure you want to close this tab, it has {}'
' windows running?').format(len(windows))],
window=tab.active_window,
custom_callback=partial(self.handle_close_tab_confirmation, tab.id)
)
def handle_close_tab_confirmation(self, tab_id: int, data: Dict[str, Any], *a: Any) -> None:
if data['response'] != 'y':
return
for tab in self.all_tabs:
if tab.id == tab_id:
break
else:
return
self.close_tab_no_confirm(tab)
def close_tab_no_confirm(self, tab: Tab) -> None:
for window in tab:
self.close_window(window)
def toggle_fullscreen(self) -> None:
toggle_fullscreen()
@@ -851,6 +885,9 @@ class Boss:
add_history='history' in type_of_input,
add_wrap_markers='screen' in type_of_input
).encode('utf-8')
elif type_of_input == 'selection':
sel = self.data_for_at(which='@selection', window=w)
data = sel.encode('utf-8') if sel else None
elif type_of_input is None:
data = None
else:
@@ -858,9 +895,16 @@ class Boss:
else:
data = input_data if isinstance(input_data, bytes) else input_data.encode('utf-8')
copts = common_opts_as_dict(self.opts)
final_args: List[str] = []
for x in args:
if x == '@selection':
sel = self.data_for_at(which='@selection', window=w)
if sel:
x = sel
final_args.append(x)
overlay_window = tab.new_special_window(
SpecialWindow(
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + args,
[kitty_exe(), '+runpy', 'from kittens.runner import main; main()'] + final_args,
stdin=data,
env={
'KITTY_COMMON_OPTS': json.dumps(copts),
@@ -890,6 +934,9 @@ class Boss:
kargs = shlex.split(cmdline) if cmdline else []
self._run_kitten(kitten, kargs)
def run_kitten(self, kitten: str, *args: str) -> None:
self._run_kitten(kitten, args)
def on_kitten_finish(self, target_window_id: str, end_kitten: Callable, source_window: Window) -> None:
output = self.get_output(source_window, num_lines=None)
from kittens.runner import deserialize
@@ -973,13 +1020,29 @@ class Boss:
tab.set_active_window(window_id)
def open_url(self, url: str, program: Optional[Union[str, List[str]]] = None, cwd: Optional[str] = None) -> None:
if url:
if isinstance(program, str):
program = to_cmdline(program)
if not url:
return
if isinstance(program, str):
program = to_cmdline(program)
found_action = False
if program is None:
from .open_actions import actions_for_url
actions = list(actions_for_url(url))
if actions:
found_action = True
self.dispatch_action(actions.pop(0))
if actions:
self.drain_actions(actions)
if not found_action:
open_url(url, program or self.opts.open_url_with, cwd=cwd)
def open_url_lines(self, lines: Iterable[str], program: Optional[Union[str, List[str]]] = None) -> None:
self.open_url(''.join(lines), program)
def drain_actions(self, actions: List) -> None:
def callback(timer_id: Optional[int]) -> None:
self.dispatch_action(actions.pop(0))
if actions:
self.drain_actions(actions)
add_timer(callback, 0, False)
def destroy(self) -> None:
self.shutting_down = True
@@ -1243,7 +1306,7 @@ class Boss:
self._new_window(list(args), cwd_from=cwd_from)
def launch(self, *args: str) -> None:
from kitty.launch import parse_launch_args, launch
from kitty.launch import launch, parse_launch_args
opts, args_ = parse_launch_args(args)
launch(self, opts, args_)
@@ -1316,7 +1379,9 @@ class Boss:
log_error('Failed to process update check data {!r}, with error: {}'.format(raw, e))
def dbus_notification_callback(self, activated: bool, a: int, b: Union[int, str]) -> None:
from .notify import dbus_notification_created, dbus_notification_activated
from .notify import (
dbus_notification_activated, dbus_notification_created
)
if activated:
assert isinstance(b, str)
dbus_notification_activated(a, b)
@@ -1333,7 +1398,9 @@ class Boss:
self.show_error(_('Errors in kitty.conf'), msg)
def set_colors(self, *args: str) -> None:
from kitty.rc.base import parse_subcommand_cli, command_for_name, PayloadGetter
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')
opts, items = parse_subcommand_cli(c, ['set-colors'] + list(args))
@@ -1495,3 +1562,21 @@ class Boss:
set_background_image(path, os_windows, configured)
for os_window_id in os_windows:
self.default_bg_changed_for(os_window_id)
# Can be called with kitty -o "map f1 send_test_notification"
def send_test_notification(self) -> None:
from time import monotonic
from .notify import notify
now = monotonic()
ident = f'test-notify-{now}'
notify(f'Test {now}', f'At: {now}', identifier=ident, subtitle=f'Test subtitle {now}')
def notification_activated(self, identifier: str, window_id: int, focus: bool, report: bool) -> None:
w = self.window_id_map.get(window_id)
if w is None:
return
if focus:
self.set_active_window(w, switch_os_window_if_needed=True)
if report:
w.report_notification_activated(identifier)

View File

@@ -40,7 +40,7 @@ static void (*parse_func)(Screen*, PyObject*, monotonic_t);
typedef struct {
char *data;
size_t sz;
int fd;
id_type peer_id;
} Message;
typedef struct {
@@ -71,9 +71,8 @@ static const Child EMPTY_CHILD = {0};
pthread_mutex_##op(&screen->which##_buf_lock);
#define children_mutex(op) \
pthread_mutex_##op(&children_lock);
#define peer_mutex(op) \
pthread_mutex_##op(&talk_data.peer_lock);
#define talk_mutex(op) \
pthread_mutex_##op(&talk_lock);
static Child children[MAX_CHILDREN] = {{0}};
@@ -82,7 +81,7 @@ static Child add_queue[MAX_CHILDREN] = {{0}}, remove_queue[MAX_CHILDREN] = {{0}}
static unsigned long remove_notify[MAX_CHILDREN] = {0};
static size_t add_queue_count = 0, remove_queue_count = 0;
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
static pthread_mutex_t children_lock;
static pthread_mutex_t children_lock, talk_lock;
static bool kill_signal_received = false;
static ChildMonitor *the_monitor = NULL;
static uint8_t drain_buf[1024];
@@ -131,6 +130,10 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
return NULL;
}
if ((ret = pthread_mutex_init(&talk_lock, NULL)) != 0) {
PyErr_Format(PyExc_RuntimeError, "Failed to create talk_lock mutex: %s", strerror(ret));
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);
@@ -152,7 +155,13 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
static void
dealloc(ChildMonitor* self) {
if (self->messages) {
for (size_t i = 0; i < self->messages_count; i++) free(self->messages[i].data);
free(self->messages); self->messages = NULL;
self->messages_count = 0; self->messages_capacity = 0;
}
pthread_mutex_destroy(&children_lock);
pthread_mutex_destroy(&talk_lock);
Py_CLEAR(self->dump_callback);
Py_CLEAR(self->death_notify);
Py_TYPE(self)->tp_free((PyObject*)self);
@@ -174,7 +183,7 @@ wakeup_io_loop(ChildMonitor *self, bool in_signal_handler) {
static void* io_loop(void *data);
static void* talk_loop(void *data);
static void send_response(int fd, const char *msg, size_t msg_sz);
static void send_response(id_type peer_id, const char *msg, size_t msg_sz);
static void wakeup_talk_loop(bool);
static bool talk_thread_started = false;
@@ -227,7 +236,7 @@ schedule_write_to_child(unsigned long id, unsigned int num, ...) {
va_list ap;
va_start(ap, num);
for (unsigned int i = 0; i < num; i++) {
data = va_arg(ap, const char*);
va_arg(ap, const char*);
sz += va_arg(ap, size_t);
}
va_end(ap);
@@ -324,7 +333,6 @@ parse_input(ChildMonitor *self) {
size_t count = 0, remove_count = 0;
bool input_read = false;
monotonic_t now = monotonic();
PyObject *msg = NULL;
children_mutex(lock);
while (remove_queue_count) {
remove_queue_count--;
@@ -333,18 +341,6 @@ parse_input(ChildMonitor *self) {
FREE_CHILD(remove_queue[remove_queue_count]);
}
if (UNLIKELY(self->messages_count)) {
msg = PyTuple_New(self->messages_count);
if (msg) {
for (size_t i = 0; i < self->messages_count; i++) {
Message *m = self->messages + i;
PyTuple_SET_ITEM(msg, i, Py_BuildValue("y#i", m->data, (int)m->sz, m->fd));
free(m->data); m->data = NULL; m->sz = 0;
}
self->messages_count = 0;
} else fatal("Out of memory");
}
if (UNLIKELY(kill_signal_received)) {
global_state.quit_request = IMPERATIVE_CLOSE_REQUESTED;
global_state.has_pending_closes = true;
@@ -358,15 +354,35 @@ parse_input(ChildMonitor *self) {
}
}
children_mutex(unlock);
if (msg) {
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(msg); i++) {
PyObject *resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "O", PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 0));
int peer_fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 1));
if (resp && PyBytes_Check(resp)) send_response(peer_fd, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
else { send_response(peer_fd, NULL, 0); if (!resp) PyErr_Print(); }
Message *msgs = NULL;
size_t msgs_count = 0;
talk_mutex(lock);
if (UNLIKELY(self->messages_count)) {
msgs = malloc(sizeof(Message) * self->messages_count);
if (msgs) {
memcpy(msgs, self->messages, sizeof(Message) * self->messages_count);
msgs_count = self->messages_count;
}
memset(self->messages, 0, sizeof(Message) * self->messages_capacity);
self->messages_count = 0;
}
talk_mutex(unlock);
if (msgs_count) {
for (size_t i = 0; i < msgs_count; i++) {
Message *msg = msgs + i;
PyObject *resp = NULL;
if (msg->data) {
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#", msg->data, (int)msg->sz);
free(msg->data);
if (!resp) PyErr_Print();
}
if (resp && PyBytes_Check(resp)) send_response(msg->peer_id, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
else send_response(msg->peer_id, NULL, 0);
Py_CLEAR(resp);
}
Py_CLEAR(msg);
free(msgs); msgs = NULL;
}
while(remove_count) {
@@ -503,7 +519,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
int d = monotonic_t_to_ms(OPT(cursor_blink_interval));
int n = t / d;
do_draw_cursor = n % 2 == 0 ? true : false;
monotonic_t bucket = ms_to_monotonic_t((n + 1) * d);
monotonic_t bucket = ms_to_monotonic_t((monotonic_t)(n + 1) * d);
monotonic_t delay = bucket - time_since_start_blink;
set_maximum_wait(delay);
}
@@ -766,7 +782,7 @@ thread_write(void *x) {
if (pos < data->sz) {
log_error("Failed to write all data to STDIN of child process with error: %s", strerror(errno));
}
safe_close(data->fd);
safe_close(data->fd, __FILE__, __LINE__);
free_twd(data);
return 0;
}
@@ -783,7 +799,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
data->fd = fd;
memcpy(data->buf, buf, data->sz);
int ret = pthread_create(&thread, NULL, thread_write, data);
if (ret != 0) { safe_close(fd); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
if (ret != 0) { safe_close(fd, __FILE__, __LINE__); free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
pthread_detach(thread);
Py_RETURN_NONE;
}
@@ -1017,7 +1033,7 @@ hangup(pid_t pid) {
static inline void
cleanup_child(ssize_t i) {
safe_close(children[i].fd);
safe_close(children[i].fd, __FILE__, __LINE__);
hangup(children[i].pid);
}
@@ -1279,36 +1295,33 @@ io_loop(void *data) {
// {{{ Talk thread functions
typedef struct {
char *data;
size_t capacity, used;
id_type id;
size_t num_of_unresponded_messages_sent_to_main_thread;
bool finished_reading;
int fd;
bool finished, close_socket, is_peer_command;
} PeerReadData;
static PeerReadData empty_prd = {.fd = -1, 0};
struct {
char *data;
size_t capacity, used, command_end;
bool finished;
} read;
struct {
char *data;
size_t capacity, used;
bool failed;
} write;
} Peer;
static id_type peer_id_counter = 0;
typedef struct {
char *data;
size_t sz, pos;
int fd;
bool finished;
} PeerWriteData;
static PeerWriteData empty_pwd = {.fd = -1, 0};
typedef struct {
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
struct pollfd *fds;
PeerReadData *reads;
PeerWriteData *writes;
PeerWriteData *queued_writes;
size_t num_peers, peers_capacity;
Peer *peers;
LoopData loop_data;
pthread_mutex_t peer_lock;
} TalkData;
static TalkData talk_data = {0};
typedef struct pollfd PollFD;
#define PEER_LIMIT 256
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s); }
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
static inline bool
accept_peer(int listen_fd, bool shutting_down) {
@@ -1318,13 +1331,12 @@ accept_peer(int listen_fd, bool shutting_down) {
if (!shutting_down) perror("accept() on talk socket failed!");
return false;
}
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
talk_data.num_talk_fds++;
if (talk_data.num_peers < PEER_LIMIT) {
ensure_space_for(&talk_data, peers, Peer, talk_data.num_peers + 8, peers_capacity, 8, false);
Peer *p = talk_data.peers + talk_data.num_peers++;
memset(p, 0, sizeof(Peer));
p->fd = peer; p->id = ++peer_id_counter;
if (!p->id) p->id = ++peer_id_counter;
} else {
log_error("Too many peers want to talk, ignoring one.");
nuke_socket(peer);
@@ -1332,148 +1344,102 @@ accept_peer(int listen_fd, bool shutting_down) {
return true;
}
static inline void
free_peer(Peer *peer) {
free(peer->read.data); peer->read.data = NULL;
free(peer->write.data); peer->write.data = NULL;
if (peer->fd > -1) { nuke_socket(peer->fd); peer->fd = -1; }
}
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
static inline void
queue_peer_message(ChildMonitor *self, char *buf, size_t sz, int fd) {
children_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
queue_peer_message(ChildMonitor *self, Peer *peer) {
talk_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
Message *m = self->messages + self->messages_count++;
m->data = buf; m->sz = sz; m->fd = fd;
children_mutex(unlock);
memset(m, 0, sizeof(Message));
if (peer->read.used) {
m->data = malloc(peer->read.used);
if (m->data) {
memcpy(m->data, peer->read.data, peer->read.used);
m->sz = peer->read.used;
}
}
m->peer_id = peer->id;
peer->num_of_unresponded_messages_sent_to_main_thread++;
talk_mutex(unlock);
wakeup_main_loop();
}
static inline void
dispatch_peer_command(ChildMonitor *self, PeerReadData *rd, int fd) {
size_t end = 0;
for (size_t i = 0; i < rd->used - 1; i++) {
if (rd->data[i] == 0x1b && rd->data[i+1] == '\\') {
end = i + 2;
break;
static inline bool
has_complete_peer_command(Peer *peer) {
peer->read.command_end = 0;
if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) {
for (size_t i = sizeof(KITTY_CMD_PREFIX)-1; i < peer->read.used - 1; i++) {
if (peer->read.data[i] == 0x1b && peer->read.data[i+1] == '\\') {
peer->read.command_end = i + 2;
break;
}
}
}
if (!end) return;
char *buf = malloc(end + 8);
if (buf) {
memcpy(buf, rd->data, end);
queue_peer_message(self, buf, end, fd);
}
rd->is_peer_command = false;
if (rd->used > end) {
rd->used -= end;
memmove(rd->data, rd->data + end, rd->used);
if (rd->used >= sizeof(KITTY_CMD_PREFIX) - 1 && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
} else rd->used = 0;
return peer->read.command_end ? true : false;
}
static inline bool
read_from_peer(ChildMonitor *self, int s) {
bool read_finished = false;
for (size_t i = 0; i < talk_data.num_reads; i++) {
PeerReadData *rd = talk_data.reads + i;
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
if (rd->fd == s) {
if (rd->used >= rd->capacity) {
if (rd->capacity >= 64 * 1024) failed("Ignoring too large message from peer");
rd->capacity = MAX(8192u, rd->capacity * 2);
rd->data = realloc(rd->data, rd->capacity);
if (!rd->data) failed("Out of memory");
}
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
if (n == 0) {
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
read_finished = true; rd->finished = true;
if (rd->used) queue_peer_message(self, rd->data, rd->used, s);
else free(rd->data);
rd->data = NULL;
} else if (n < 0) {
if (errno != EINTR) {
perror("Error reading from talk peer");
failed("");
}
} else {
if (!rd->used && memcmp(rd->data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) rd->is_peer_command = true;
rd->used += n;
while (rd->is_peer_command) dispatch_peer_command(self, rd, s);
}
break;
}
static inline void
dispatch_peer_command(ChildMonitor *self, Peer *peer) {
if (peer->read.command_end) {
size_t used = peer->read.used;
peer->read.used = peer->read.command_end;
queue_peer_message(self, peer);
peer->read.used = used;
if (peer->read.used > peer->read.command_end) {
peer->read.used -= peer->read.command_end;
memmove(peer->read.data, peer->read.data + peer->read.command_end, peer->read.used);
} else peer->read.used = 0;
peer->read.command_end = 0;
}
}
static inline void
read_from_peer(ChildMonitor *self, Peer *peer) {
#define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; }
if (peer->read.used >= peer->read.capacity) {
if (peer->read.capacity >= 64 * 1024) failed("Ignoring too large message from peer");
peer->read.capacity = MAX(8192u, peer->read.capacity * 2);
peer->read.data = realloc(peer->read.data, peer->read.capacity);
if (!peer->read.data) failed("Out of memory");
}
ssize_t n = recv(peer->fd, peer->read.data + peer->read.used, peer->read.capacity - peer->read.used, 0);
if (n == 0) {
peer->read.finished = true;
shutdown(peer->fd, SHUT_RD);
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
queue_peer_message(self, peer);
free(peer->read.data); peer->read.data = NULL;
peer->read.used = 0; peer->read.capacity = 0;
} else if (n < 0) {
if (errno != EINTR) failed(strerror(errno));
} else {
peer->read.used += n;
while (has_complete_peer_command(peer)) dispatch_peer_command(self, peer);
}
#undef failed
return read_finished;
}
static inline bool
write_to_peer(int fd) {
bool write_finished = false;
for (size_t i = 0; i < talk_data.num_writes; i++) {
PeerWriteData *wd = talk_data.writes + i;
#define failed(msg) { write_finished = true; log_error("%s", msg); wd->finished = true; break; }
if (wd->fd == fd) {
ssize_t n = send(fd, wd->data + wd->pos, wd->sz - wd->pos, MSG_NOSIGNAL);
if (n == 0) { failed("send() to peer failed to send any data"); }
else if (n < 0) {
if (errno != EINTR) { perror("write() to peer socket failed with error"); failed(""); }
} else {
wd->pos += n;
if (wd->pos >= wd->sz) { write_finished = true; wd->finished = true; }
}
break;
}
}
#undef failed
return write_finished;
}
static inline void
remove_poll_fd(int fd) {
size_t count = talk_data.num_talk_fds + talk_data.num_listen_fds;
for (size_t i = talk_data.num_listen_fds; i < count; i++) {
struct pollfd *pfd = talk_data.fds + i;
if (pfd->fd == fd) {
size_t num_to_right = count - 1 - i;
if (num_to_right) memmove(talk_data.fds + i, talk_data.fds + i + 1, num_to_right * sizeof(struct pollfd));
talk_data.num_talk_fds--;
break;
}
}
}
static inline void
prune_finished_reads(void) {
if (!talk_data.num_reads) return;
for (ssize_t i = talk_data.num_reads - 1; i >= 0; i--) {
PeerReadData *rd = talk_data.reads + i;
if (rd->finished) {
remove_poll_fd(rd->fd);
if (rd->close_socket) { nuke_socket(rd->fd); }
else shutdown(rd->fd, SHUT_RD);
free(rd->data);
ssize_t num_to_right = talk_data.num_reads - 1 - i;
if (num_to_right > 0) memmove(talk_data.reads + i, talk_data.reads + i + 1, num_to_right * sizeof(PeerReadData));
else talk_data.reads[i] = empty_prd;
talk_data.num_reads--;
}
}
}
static inline void
prune_finished_writes(void) {
if (!talk_data.num_writes) return;
for (ssize_t i = talk_data.num_writes - 1; i >= 0; i--) {
PeerWriteData *wd = talk_data.writes + i;
if (wd->finished) {
remove_poll_fd(wd->fd);
shutdown(wd->fd, SHUT_WR); safe_close(wd->fd);
free(wd->data);
ssize_t num_to_right = talk_data.num_writes - 1 - i;
if (num_to_right > 0) memmove(talk_data.writes + i, talk_data.writes + i + 1, num_to_right * sizeof(PeerWriteData));
else talk_data.writes[i] = empty_pwd;
talk_data.num_writes--;
}
write_to_peer(Peer *peer) {
talk_mutex(lock);
ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL);
if (n == 0) { log_error("send() to peer failed to send any data"); peer->write.used = 0; peer->write.failed = true; }
else if (n < 0) {
if (errno != EINTR) { log_error("write() to peer socket failed with error: %s", strerror(errno)); peer->write.used = 0; peer->write.failed = true; }
} else {
if ((size_t)n > peer->write.used) memmove(peer->write.data, peer->write.data + n, peer->write.used - n);
peer->write.used -= n;
}
talk_mutex(unlock);
}
static void
@@ -1481,92 +1447,107 @@ wakeup_talk_loop(bool in_signal_handler) {
if (talk_thread_started) wakeup_loop(&talk_data.loop_data, in_signal_handler, "talk_loop");
}
static inline void
move_queued_writes(void) {
while (talk_data.num_queued_writes) {
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
talk_data.writes[talk_data.num_writes++] = *src;
talk_data.num_talk_fds++;
} else {
log_error("Cannot send response to peer, too many peers");
free(src->data); nuke_socket(src->fd);
prune_peers(void) {
for (size_t i = 0; i < talk_data.num_peers; i++) {
size_t idx = talk_data.num_peers - 1 - i;
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);
remove_i_from_array(talk_data.peers, idx, talk_data.num_peers);
}
*src = empty_pwd;
}
}
static void*
talk_loop(void *data) {
// The talk thread loop
ChildMonitor *self = (ChildMonitor*)data;
set_thread_name("KittyPeerMon");
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
if (!init_loop_data(&talk_data.loop_data)) { log_error("Failed to create wakeup fd for talk thread with error: %s", strerror(errno)); }
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
PollFD fds[PEER_LIMIT + 8] = {0};
size_t num_listen_fds = 0, num_peer_fds = 0;
#define add_listener(which) \
if (self->which > -1) { \
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
fds[num_listen_fds].fd = self->which; fds[num_listen_fds++].events = POLLIN; \
}
add_listener(talk_fd); add_listener(listen_fd);
#undef add_listener
talk_data.fds[talk_data.num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN;
fds[num_listen_fds].fd = talk_data.loop_data.wakeup_read_fd; fds[num_listen_fds++].events = POLLIN;
while (LIKELY(!self->shutting_down)) {
for (size_t i = 0; i < talk_data.num_listen_fds + talk_data.num_talk_fds; i++) { talk_data.fds[i].revents = 0; }
int ret = poll(talk_data.fds, talk_data.num_listen_fds + talk_data.num_talk_fds, -1);
num_peer_fds = 0;
if (talk_data.num_peers > 0) {
talk_mutex(lock);
prune_peers();
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;
int flags = 0;
if (!p->read.finished) flags |= POLLIN;
if (p->write.used) flags |= POLLOUT;
fds[num_listen_fds + num_peer_fds++].events = flags;
}
}
talk_mutex(unlock);
}
for (size_t i = 0; i < num_listen_fds; i++) fds[i].revents = 0;
int ret = poll(fds, num_listen_fds + num_peer_fds, -1);
if (ret > 0) {
bool has_finished_reads = false, has_finished_writes = false;
for (size_t i = 0; i < talk_data.num_listen_fds - 1; i++) {
if (talk_data.fds[i].revents & POLLIN) {if (!accept_peer(talk_data.fds[i].fd, self->shutting_down)) goto end; }
for (size_t i = 0; i < num_listen_fds - 1; i++) {
if (fds[i].revents & POLLIN) {
if (!accept_peer(fds[i].fd, self->shutting_down)) goto end;
}
}
if (talk_data.fds[talk_data.num_listen_fds - 1].revents & POLLIN) drain_fd(talk_data.fds[talk_data.num_listen_fds - 1].fd); // wakeup
for (size_t i = talk_data.num_listen_fds; i < talk_data.num_talk_fds + talk_data.num_listen_fds; i++) {
if (talk_data.fds[i].revents & (POLLIN | POLLHUP)) { if (read_from_peer(self, talk_data.fds[i].fd)) has_finished_reads = true; }
if (talk_data.fds[i].revents & POLLOUT) { if (write_to_peer(talk_data.fds[i].fd)) has_finished_writes = true; }
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++) {
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 (has_finished_reads) prune_finished_reads();
if (has_finished_writes) prune_finished_writes();
peer_mutex(lock);
if (talk_data.num_queued_writes) move_queued_writes();
peer_mutex(unlock);
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
}
end:
free_loop_data(&talk_data.loop_data);
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
for (size_t i = 0; i < talk_data.num_peers; i++) free_peer(talk_data.peers + i);
free(talk_data.peers);
return 0;
}
static inline bool
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
bool ok = false;
peer_mutex(lock);
if (talk_data.num_queued_writes < PEER_LIMIT) {
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
memcpy(talk_data.queued_writes[talk_data.num_queued_writes].data, msg, msg_sz);
talk_data.queued_writes[talk_data.num_queued_writes].sz = msg_sz;
talk_data.queued_writes[talk_data.num_queued_writes++].fd = fd;
ok = true;
}
} else log_error("Cannot send response to peer, too many peers");
peer_mutex(unlock);
return ok;
}
static void
send_response(int fd, const char *msg, size_t msg_sz) {
if (msg == NULL) { shutdown(fd, SHUT_WR); safe_close(fd); return; }
if (!add_peer_writer(fd, msg, msg_sz)) { shutdown(fd, SHUT_WR); safe_close(fd); }
else wakeup_talk_loop(false);
send_response(id_type peer_id, const char *msg, size_t msg_sz) {
bool wakeup = false;
talk_mutex(lock);
for (size_t i = 0; i < talk_data.num_peers; i++) {
Peer *peer = talk_data.peers + i;
if (peer->id == peer_id) {
if (peer->num_of_unresponded_messages_sent_to_main_thread) peer->num_of_unresponded_messages_sent_to_main_thread--;
if (!peer->write.failed) {
if (peer->write.capacity - peer->write.used < msg_sz) {
void *data = realloc(peer->write.data, peer->write.capacity + msg_sz);
if (data) {
peer->write.data = data;
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;
}
wakeup = true;
break;
}
}
talk_mutex(unlock);
if (wakeup) wakeup_talk_loop(false);
}
// }}}

View File

@@ -99,28 +99,28 @@ spawn(PyObject *self UNUSED, PyObject *args) {
// 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);
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 (stdin_read_fd > -1) {
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
safe_close(stdin_read_fd);
safe_close(stdin_write_fd);
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");
}
safe_close(slave);
safe_close(master);
safe_close(slave, __FILE__, __LINE__);
safe_close(master, __FILE__, __LINE__);
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
safe_close(ready_write_fd);
safe_close(ready_write_fd, __FILE__, __LINE__);
wait_for_terminal_ready(ready_read_fd);
safe_close(ready_read_fd);
safe_close(ready_read_fd, __FILE__, __LINE__);
// Close any extra fds inherited from parent
for (int c = 3; c < 201; c++) safe_close(c);
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,

View File

@@ -210,6 +210,8 @@ class Child:
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
if env is None:
env = self._final_env = default_env().copy()
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not sys._xoptions.get('lc_ctype_before_python'):
del env['LC_CTYPE']
env.update(self.env)
env['TERM'] = self.opts.term
env['COLORTERM'] = 'truecolor'

View File

@@ -544,6 +544,15 @@ def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional
return leftover_args
WATCHER_DEFINITION = '''
--watcher -w
type=list
Path to a python file. Appropriately named functions in this file will be called
for various events, such as when the window is resized, focused or closed. See the section
on watchers in the launch command documentation :doc:`launch`. Relative paths are
resolved relative to the kitty config directory.'''
def options_spec() -> str:
if not hasattr(options_spec, 'ans'):
OPTIONS = '''
@@ -593,6 +602,11 @@ Path to a file containing the startup :italic:`session` (tabs, windows, layout,
Use - to read from STDIN. See the README file for details and an example.
{watcher}
Note that this watcher will be added only to all initially created windows, not new windows
created after startup.
--hold
type=bool-set
Remain open after child process exits. Note that this only affects the first
@@ -695,8 +709,8 @@ type=bool-set
!
'''
setattr(options_spec, 'ans', OPTIONS.format(
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
watcher=WATCHER_DEFINITION
))
ans: str = getattr(options_spec, 'ans')
return ans

View File

@@ -12,7 +12,8 @@ class CLIOptions:
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
QueryTerminalCLIOptions = CLIOptions
def generate_stub() -> None:
@@ -35,6 +36,9 @@ def generate_stub() -> None:
from kittens.ask.main import option_text
do(option_text(), 'AskCLIOptions')
from kittens.remote_file.main import option_text
do(option_text(), 'RemoteFileCLIOptions')
from kittens.clipboard.main import OPTIONS
do(OPTIONS(), 'ClipboardCLIOptions')
@@ -47,6 +51,9 @@ def generate_stub() -> None:
from kittens.icat.main import options_spec
do(options_spec(), 'IcatCLIOptions')
from kittens.query_terminal.main import options_spec
do(options_spec(), 'QueryTerminalCLIOptions')
from kittens.panel.main import OPTIONS
do(OPTIONS(), 'PanelCLIOptions')

View File

@@ -34,6 +34,14 @@ def screen_bell() -> None:
pass
def screen_normal_keypad_mode() -> None:
write('\x1b>')
def screen_alternate_keypad_mode() -> None:
write('\x1b=')
def screen_cursor_position(y: int, x: int) -> None:
write(CSI + '%s;%sH' % (y, x))
@@ -80,6 +88,18 @@ def screen_indexn(n: int) -> None:
write(CSI + '%dS' % n)
def screen_delete_characters(count: int) -> None:
write(CSI + '%dP' % count)
def screen_insert_characters(count: int) -> None:
write(CSI + '%d@' % count)
def screen_scroll(count: int) -> None:
write(CSI + '%dS' % count)
def screen_erase_in_display(how: int, private: bool) -> None:
write(CSI + ('?' if private else '') + str(how) + 'J')
@@ -108,6 +128,10 @@ def screen_linefeed() -> None:
write('\n')
def screen_tab() -> None:
write('\t')
def screen_backspace() -> None:
write('\x08')

View File

@@ -9,6 +9,9 @@
#include "state.h"
#include "monotonic.h"
#include <Cocoa/Cocoa.h>
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
#include <UserNotifications/UserNotifications.h>
#endif
#include <AvailabilityMacros.h>
// Needed for _NSGetProgname
@@ -133,6 +136,7 @@ get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
}
static PyObject *notification_activated_callback = NULL;
static PyObject*
set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
@@ -141,6 +145,8 @@ set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
Py_RETURN_NONE;
}
#ifdef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
@interface NotificationDelegate : NSObject <NSUserNotificationCenterDelegate>
@end
@@ -170,32 +176,15 @@ set_notification_activated_callback(PyObject *self UNUSED, PyObject *callback) {
static PyObject*
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
char *identifier = NULL, *title = NULL, *subtitle = NULL, *informativeText = NULL, *path_to_image = NULL;
if (!PyArg_ParseTuple(args, "zssz|z", &identifier, &title, &informativeText, &path_to_image, &subtitle)) return NULL;
char *identifier = NULL, *title = NULL, *informativeText = NULL, *subtitle = NULL;
if (!PyArg_ParseTuple(args, "zsz|z", &identifier, &title, &informativeText, &subtitle)) return NULL;
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
NSUserNotification *n = [NSUserNotification new];
NSImage *img = nil;
if (path_to_image) {
NSString *p = @(path_to_image);
NSURL *url = [NSURL fileURLWithPath:p];
img = [[NSImage alloc] initWithContentsOfURL:url];
[url release]; [p release];
if (img) {
[n setValue:img forKey:@"_identityImage"];
[n setValue:@(false) forKey:@"_identityImageHasBorder"];
}
[img release];
}
#define SET(x) { \
if (x) { \
NSString *t = @(x); \
n.x = t; \
[t release]; \
}}
SET(title); SET(subtitle); SET(informativeText);
#undef SET
if (title) n.title = @(title);
if (subtitle) n.subtitle = @(subtitle);
if (informativeText) n.informativeText = @(informativeText);
if (identifier) {
n.userInfo = @{@"user_id": @(identifier)};
}
@@ -203,22 +192,127 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE;
}
#else
@interface NotificationDelegate : NSObject <UNUserNotificationCenterDelegate>
@end
@implementation NotificationDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
(void)(center);
if (notification_activated_callback) {
NSString *identifier = [[[response notification] request] identifier];
PyObject *ret = PyObject_CallFunction(notification_activated_callback, "z",
identifier ? [identifier UTF8String] : NULL);
if (ret == NULL) PyErr_Print();
else Py_DECREF(ret);
}
completionHandler();
}
@end
static void
schedule_notification(const char *identifier, const char *title, const char *body, const char *subtitle) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
if (!center) return;
// Configure the notification's payload.
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
if (title) content.title = @(title);
if (body) content.body = @(body);
if (subtitle) content.subtitle = @(subtitle);
// Deliver the notification
static unsigned long counter = 1;
UNNotificationRequest* request = [
UNNotificationRequest requestWithIdentifier:(identifier ? @(identifier) : [NSString stringWithFormat:@"Id_%lu", counter++])
content:content trigger:nil];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
log_error("Failed to show notification: %s", [[error localizedDescription] UTF8String]);
}
}];
[content release];
}
typedef struct {
char *identifier, *title, *body, *subtitle;
} QueuedNotification;
typedef struct {
QueuedNotification *notifications;
size_t count, capacity;
} NotificationQueue;
static NotificationQueue notification_queue = {0};
static void
queue_notification(const char *identifier, const char *title, const char* body, const char* subtitle) {
ensure_space_for((&notification_queue), notifications, QueuedNotification, notification_queue.count + 16, capacity, 16, true);
QueuedNotification *n = notification_queue.notifications + notification_queue.count++;
n->identifier = identifier ? strdup(identifier) : NULL;
n->title = title ? strdup(title) : NULL;
n->body = body ? strdup(body) : NULL;
n->subtitle = subtitle ? strdup(subtitle) : NULL;
}
static void
drain_pending_notifications(BOOL granted) {
if (granted) {
for (size_t i = 0; i < notification_queue.count; i++) {
QueuedNotification *n = notification_queue.notifications + i;
schedule_notification(n->identifier, n->title, n->body, n->subtitle);
}
}
while(notification_queue.count) {
QueuedNotification *n = notification_queue.notifications + --notification_queue.count;
free(n->identifier); free(n->title); free(n->body); free(n->subtitle);
n->identifier = NULL; n->title = NULL; n->body = NULL; n->subtitle = NULL;
}
}
static PyObject*
cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
char *identifier = NULL, *title = NULL, *body = NULL, *subtitle = NULL;
if (!PyArg_ParseTuple(args, "zsz|z", &identifier, &title, &body, &subtitle)) return NULL;
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
if (!center) Py_RETURN_NONE;
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
queue_notification(identifier, title, body, subtitle);
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (error != nil) {
log_error("Failed to request permission for showing notification: %s", [[error localizedDescription] UTF8String]);
}
dispatch_async(dispatch_get_main_queue(), ^{
drain_pending_notifications(granted);
});
}
];
Py_RETURN_NONE;
}
#endif
@interface ServiceProvider : NSObject
@end
@implementation ServiceProvider
- (void)openTab:(NSPasteboard*)pasteboard
- (BOOL)openTab:(NSPasteboard*)pasteboard
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
[self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
return [self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
}
- (void)openOSWindow:(NSPasteboard*)pasteboard
- (BOOL)openOSWindow:(NSPasteboard*)pasteboard
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
[self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
return [self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
}
- (void)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
- (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
for (NSURL *url in filePathArray) {
@@ -231,6 +325,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
set_cocoa_pending_action(type, [path UTF8String]);
}
}
return YES;
}
@end
@@ -390,6 +485,12 @@ cocoa_focus_window(void *w) {
[window makeKeyWindow];
}
long
cocoa_window_number(void *w) {
NSWindow *window = (NSWindow*)w;
return [window windowNumber];
}
size_t
cocoa_get_workspace_ids(void *w, size_t *workspace_ids, size_t array_sz) {
NSWindow *window = (NSWindow*)w;
@@ -491,8 +592,15 @@ cleanup() {
if (dockMenu) [dockMenu release];
dockMenu = nil;
if (notification_activated_callback) Py_DECREF(notification_activated_callback);
notification_activated_callback = NULL;
Py_CLEAR(notification_activated_callback);
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
drain_pending_notifications(NO);
free(notification_queue.notifications);
notification_queue.notifications = NULL;
notification_queue.capacity = 0;
#endif
} // autoreleasepool
}
@@ -531,6 +639,11 @@ cocoa_hide_titlebar(void *w)
} // autoreleasepool
}
void
cocoa_system_beep(void) {
NSBeep();
}
static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},

View File

@@ -254,6 +254,8 @@ def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix:
complete_files_and_dirs(ans, prefix, files_group_name='Config files', predicate=is_conf_file)
elif dest == 'session':
complete_files_and_dirs(ans, prefix, files_group_name='Session files')
elif dest == 'watcher':
complete_files_and_dirs(ans, prefix, files_group_name='Watcher files')
elif dest == 'directory':
complete_files_and_dirs(ans, prefix, files_group_name='Directories', predicate=os.path.isdir)
elif dest == 'start_as':
@@ -392,10 +394,10 @@ def complete_files_and_dirs(
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
from mimetypes import guess_type
from .guess_mime_type import guess_type
def icat_file_predicate(filename: str) -> bool:
mt = guess_type(filename)[0]
mt = guess_type(filename)
if mt and mt.startswith('image/'):
return True
return False

View File

@@ -11,7 +11,7 @@ from typing import (
)
from ..rgb import Color, to_color as as_color
from ..utils import log_error
from ..utils import log_error, expandvars
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
T = TypeVar('T')
@@ -53,13 +53,36 @@ def to_bool(x: str) -> bool:
return x.lower() in ('y', 'yes', 'true')
def to_cmdline(x: str) -> List[str]:
return list(
map(
lambda y: os.path.expandvars(os.path.expanduser(y)),
shlex.split(x)
class ToCmdline:
def __init__(self) -> None:
self.override_env: Optional[Dict[str, str]] = None
def __enter__(self) -> 'ToCmdline':
return self
def __exit__(self, *a: Any) -> None:
self.override_env = None
def filter_env_vars(self, *a: str) -> 'ToCmdline':
remove = frozenset(a)
self.override_env = {k: v for k, v in os.environ.items() if k not in remove}
return self
def __call__(self, x: str) -> List[str]:
return list(
map(
lambda y: expandvars(
os.path.expanduser(y),
os.environ if self.override_env is None else self.override_env,
fallback_to_os_env=False
),
shlex.split(x)
)
)
)
to_cmdline = ToCmdline()
def python_string(text: str) -> str:

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