Compare commits

..

320 Commits

Author SHA1 Message Date
Kovid Goyal
eb5dd364ae version 0.32.0 2024-01-19 10:50:55 +05:30
Kovid Goyal
4a64f812ad Merge branch 'boss-window-args' of https://github.com/ad-chaos/kitty 2024-01-19 07:46:36 +05:30
ad-chaos
c2acc2460b expose glfw{Get,Set}WindowPos to python 2024-01-18 22:21:39 +05:30
Kovid Goyal
bea8fd25a7 Make argument handling of create_os_window() to be more robust and match its python signature 2024-01-18 21:58:39 +05:30
Kovid Goyal
1593baa9f9 remove unused include 2024-01-18 21:38:18 +05:30
Kovid Goyal
0447b17af2 Merge branch 'nth_os_window-fix' of https://github.com/jackielii/kitty 2024-01-18 19:08:58 +05:30
Jackie Li
0d3c5497ff for #7009
Oops, the num is already negative index, no need to reverse here.

Alternatively this is shorter:

```
    def nth_os_window(self, num: int = 1) -> None:
        if not self.os_window_map:
            return

        if num == 0:
            os_window_id = current_focused_os_window_id() or last_focused_os_window_id()
        elif num > 0:
            ids = tuple(self.os_window_map.keys())
            os_window_id = ids[min(num, len(ids)) - 1]
        else:
            fc_map = os_window_focus_counters()
            ids = sorted(fc_map.keys(), key=fc_map.__getitem__, reverse=True)
            os_window_id = ids[min(-num, len(ids)-1)]
        focus_os_window(os_window_id, True)
```
2024-01-18 09:53:47 +00:00
Kovid Goyal
7826fefe30 Merge branch 'nth_os_window-typo' of https://github.com/jackielii/kitty 2024-01-18 14:43:02 +05:30
Jackie Li
10ca74f502 fix typo and add a couple examples 2024-01-18 09:07:53 +00:00
Kovid Goyal
715548b144 Make test robust against wezterm's system wide shell integration
Not only is it system wide but it runs by default, even outside wezterm,
sigh.
2024-01-18 12:52:54 +05:30
Kovid Goyal
43df7be977 Add a note that themes can override cursor color
See #6987
2024-01-18 07:56:50 +05:30
Kovid Goyal
9b5f665218 Allow focusing previously active OS windows via nth_os_window
Fixes #7009
Fixes #7008
2024-01-18 07:54:15 +05:30
Kovid Goyal
5e934c081e Fix #7004 2024-01-17 08:39:00 +05:30
Kovid Goyal
22dbc94c5f Merge branch 'patch-1' of https://github.com/davidbrochart/kitty 2024-01-16 17:36:25 +05:30
David Brochart
2df1273d53 Remove duplicated line 2024-01-16 13:01:47 +01:00
Kovid Goyal
54900183ec Fix #6997 2024-01-16 13:38:12 +05:30
Kovid Goyal
1c72a94b2f Fix universal build with cf-protection failing 2024-01-15 12:45:55 +05:30
Kovid Goyal
46bb027d14 Fix building on old gcc/clang 2024-01-15 11:23:42 +05:30
Kovid Goyal
37f0c8c0a8 Fix #6994 2024-01-15 10:18:03 +05:30
Kovid Goyal
cad7047a7a Make the text for kitty.scrollback.nvim fit with the rest 2024-01-10 09:39:29 +05:30
Kovid Goyal
e6acc69460 Merge branch 'add_integration_kitty_scrollback_nvim' of https://github.com/mikesmithgh/kitty 2024-01-10 09:39:09 +05:30
Mike Smith
40d8111717 Document integration with tool kitty-scrollback.nvim 2024-01-09 22:43:44 -05:00
Kovid Goyal
970cc9ba7a Merge branch 'dependabot/go_modules/all-go-deps-4690a2b430' of https://github.com/kovidgoyal/kitty 2024-01-08 09:13:43 +05:30
dependabot[bot]
6e685d01b0 Bump the all-go-deps group with 2 updates
Bumps the all-go-deps group with 2 updates: [golang.org/x/image](https://github.com/golang/image) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/image` from 0.14.0 to 0.15.0
- [Commits](https://github.com/golang/image/compare/v0.14.0...v0.15.0)

Updates `golang.org/x/sys` from 0.15.0 to 0.16.0
- [Commits](https://github.com/golang/sys/compare/v0.15.0...v0.16.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-08 03:37:59 +00:00
Kovid Goyal
b8c5c62585 Graphics protocol: Specify some edge case behavior for image display
See https://github.com/kovidgoyal/kitty/discussions/6979
2024-01-08 08:46:17 +05:30
Kovid Goyal
7427e65f60 When copying env for SSH sanitize problematic env vars 2024-01-06 13:53:20 +05:30
Kovid Goyal
e656a75d5e Move implementation of --hold into Child
DRYer. Also fixed use of --hold with launch --cwd=current
2024-01-06 13:14:48 +05:30
Kovid Goyal
1e249035c7 Fix focus_visible_window not switching to other window in stack layout when only two windows are present
Fixes #6970
2024-01-05 21:41:21 +05:30
Kovid Goyal
044f53b35b Merge branch 'fix-wayland-cursor-shape' of https://github.com/jinliu/kitty 2024-01-04 17:37:50 +05:30
Jin Liu
e20eff277b Fix Wayland cursor-shape-v1 cursor not updating
According to https://wayland.app/protocols/cursor-shape-v1#wp_cursor_shape_device_v1:request:set_shape
> The serial parameter must match the latest wl_pointer.enter or
> zwp_tablet_tool_v2.proximity_in serial number sent to the client.

So we can't use wl.serial or wl.pointer_serial, because they are also updated in other places.
2024-01-04 19:54:03 +08:00
Kovid Goyal
5ea1d14617 Merge branch 'dependabot/go_modules/all-go-deps-c69f2790a2' of https://github.com/kovidgoyal/kitty 2024-01-01 08:50:31 +05:30
dependabot[bot]
1bea64768e Bump the all-go-deps group with 1 update
Bumps the all-go-deps group with 1 update: [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil).


Updates `github.com/shirou/gopsutil/v3` from 3.23.11 to 3.23.12
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.11...v3.23.12)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 03:10:29 +00:00
Kovid Goyal
ab2af0c141 ... 2023-12-26 22:53:57 +05:30
Kovid Goyal
0a2fcf1805 Mouse reporting: Fix incorrect position reported for windows with padding
Fixes #6950
2023-12-26 22:27:20 +05:30
Kovid Goyal
858f0c1073 GNOME Wayland: Fix remembered window size smaller than actual size
Fixes #6946
2023-12-25 19:29:33 +05:30
Kovid Goyal
fb1124b1b9 ... 2023-12-25 17:26:06 +05:30
Kovid Goyal
c76db4bfb4 Wayland: Redraw titlebar title on font size change
Fixes #6945
2023-12-25 17:20:52 +05:30
Kovid Goyal
1e5d14c834 Half the length of the style prefix 2023-12-25 09:43:01 +05:30
Kovid Goyal
5583f60289 Fix #6943 2023-12-25 08:33:54 +05:30
Kovid Goyal
4519b3abee Enable cursor shape on Wayland
Cant replicate the hyprland crash, so am not going to bother about it.

Fixes #6914
2023-12-24 22:56:01 +05:30
Kovid Goyal
cff490f881 Wayland: Add support for the new cursor-shape protocol
It is currently disabled because no compositor seems to support it.
Hyprland reports it as available but using it causes Hyprland to crash.
Plasma 6 is supposed to have it but I am not installing a beta just for
this.

Typical Wayland.
2023-12-24 18:45:17 +05:30
Kovid Goyal
7d8c017215 DRYer 2023-12-24 15:03:56 +05:30
Kovid Goyal
91cdf4af00 kitty keyboard protocol: Specify the behavior of the modifier bits during modifier key events
Fixes #6913
2023-12-24 12:09:13 +05:30
Kovid Goyal
79db8b43e0 Make mypy happy 2023-12-22 06:43:12 +05:30
Kovid Goyal
c03d99e744 Update changelog 2023-12-22 06:20:38 +05:30
Kovid Goyal
7c0cb5481f Merge branch 'master' of https://github.com/FelixKratz/kitty 2023-12-22 06:19:38 +05:30
Felix Kratz
912c5ce4f9 dont reuse cascade point on another display
fix wording

reset cascade point to zero point
2023-12-21 11:30:50 +01:00
Kovid Goyal
39a3c38037 Merge branch 'patch-1' of https://github.com/arthurbacci/kitty 2023-12-21 08:08:59 +05:30
Kovid Goyal
5dfe4427cf Note that the rio terminal also supports the kitty keyboard protocol 2023-12-21 08:07:27 +05:30
Arthur Bacci
b2eac37164 Fix copy-paste typo in graphics-protocol.rst 2023-12-20 15:44:53 -03:00
Kovid Goyal
ec8b7853c5 Improve docs for resize_on_debounce 2023-12-19 17:39:54 +05:30
Kovid Goyal
d9903f5283 Add a note for what to do instead of --detach on macOS 2023-12-18 12:04:23 +05:30
Kovid Goyal
ccfb979218 Merge branch 'dependabot/go_modules/all-go-deps-f5d2d9a206' of https://github.com/kovidgoyal/kitty 2023-12-18 09:52:42 +05:30
dependabot[bot]
ff8387f8e7 Bump the all-go-deps group with 1 update
Bumps the all-go-deps group with 1 update: [github.com/google/uuid](https://github.com/google/uuid).

- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.4.0...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 03:31:14 +00:00
Kovid Goyal
017947de7f panel kitten: Fix rendering with non-zero margin.padding in kitty.conf
Fixes #6923
2023-12-17 11:12:49 +05:30
Kovid Goyal
2a5ba519f5 Fix #6923 2023-12-17 10:50:18 +05:30
Kovid Goyal
b2587c1d54 Fix modifier reporting on macOS
We make use of the fact that the device dependent modifiers flags
actually report left/right modifier. Code taken form SDL, with thanks.
2023-12-14 11:31:48 +05:30
Kovid Goyal
7e5230e6f4 more grammar 2023-12-14 09:06:16 +05:30
Kovid Goyal
64cfe8171f ... 2023-12-14 08:55:43 +05:30
Kovid Goyal
a9b424e307 Keyboard protocol: Clarify the behavior of the modifier bits during modifier key events
I cant find any relevant standards for this, so am just picking the
macOS behavior as it seems more sensible to me.

Fixes #6913
2023-12-14 08:50:19 +05:30
Kovid Goyal
d9ccbcd0ce Font fallback: Fix the font used to render a character sometimes dependent on the order in which characters appear on screen
We ameliorate the performance hit by storing a hash table mapping cell
text to the loaded fallback font index so that lookups for previously
seen text are still fast.

Fixes #6865
2023-12-11 20:27:21 +05:30
Kovid Goyal
7cec82f453 Merge branch 'dependabot/go_modules/all-go-deps-8acd87a792' of https://github.com/kovidgoyal/kitty 2023-12-11 11:04:46 +05:30
dependabot[bot]
18c0a449d2 Bump the all-go-deps group with 1 update
Bumps the all-go-deps group with 1 update: [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma).

- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.11.1...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 03:22:52 +00:00
Kovid Goyal
00f8f340bf macOS: Fix returning from full screen via the button when the titlebar is hidden not hiding the buttons
Fixes #6883
2023-12-10 22:30:15 +05:30
Kovid Goyal
e9e889457d macOS: Fix some combining characters not being rendered
Use Harfbuzz for positioning instead of Core Text as Core Text doesn't
position combining chars correctly anymore. This may mean we need to
redo the cell metrics calculation as well, we will see. Core Text is
still used for rendering but at positions specified by Harfbuzz.

Fixes #6898
2023-12-10 20:30:24 +05:30
Kovid Goyal
d41138c4c6 Make units_per_em available in do_render 2023-12-10 15:29:03 +05:30
Kovid Goyal
69a5c7e3b2 Wayland: Fix a regression in the previous release that broke copying to clipboard under wl-roots based compositors in some circumstances
As is usual in Wayland land, utter chaos. Divergent implementations,
incorrect interpretations of the spec, bla bla.

Fixes #6890
2023-12-08 08:38:08 +05:30
Kovid Goyal
92befa26db Fix #6889 2023-12-07 09:30:04 +05:30
Kovid Goyal
22ac57c374 Another terminal emulator adds support for the kitty keyboard protocol 2023-12-06 16:32:27 +05:30
Kovid Goyal
392a301cd8 Note how to to use hints to open hyperlinks 2023-12-05 07:50:35 +05:30
Kovid Goyal
a1f2a7df4d Port new shlex code to Go 2023-12-04 14:14:11 +05:30
Kovid Goyal
04eafbea9b Implement better syntax highlighting for the new map 2023-12-04 11:54:49 +05:30
Kovid Goyal
cf6c00cebe Merge branch 'dependabot/go_modules/all-go-deps-e63ccaec5d' of https://github.com/kovidgoyal/kitty 2023-12-04 08:59:17 +05:30
dependabot[bot]
08da87a622 Bump the all-go-deps group with 2 updates
Bumps the all-go-deps group with 2 updates: [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) and [howett.net/plist](https://github.com/DHowett/go-plist).


Updates `github.com/shirou/gopsutil/v3` from 3.23.10 to 3.23.11
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.10...v3.23.11)

Updates `howett.net/plist` from 1.0.0 to 1.0.1
- [Commits](https://github.com/DHowett/go-plist/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-go-deps
- dependency-name: howett.net/plist
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 03:25:11 +00:00
Kovid Goyal
59e3b202b9 Update changelog 2023-12-03 21:11:52 +05:30
Kovid Goyal
a7bdbb11f2 Document the new modal keymaps 2023-12-03 21:09:26 +05:30
Kovid Goyal
b9fabc62a5 ... 2023-12-03 21:07:01 +05:30
Kovid Goyal
d483c3eb33 Fix literal field parsing 2023-12-03 20:55:47 +05:30
Kovid Goyal
d191896528 Allow more options for what to do in a custom keyboard mode on unknown and action events 2023-12-03 20:07:29 +05:30
Kovid Goyal
ca6c607148 pop_keyboard_mode should return True when nothing is done 2023-12-03 19:12:58 +05:30
Kovid Goyal
cc5424c054 ... 2023-12-03 19:02:46 +05:30
Kovid Goyal
77551cdfc1 Weird macOS docs build failure 2023-12-03 15:06:12 +05:30
Kovid Goyal
c6eab841f9 Send repeat events instead of repeated presses 2023-12-03 13:29:13 +05:30
Kovid Goyal
99995fd9dc Remote control API for send-key 2023-12-03 13:23:01 +05:30
Kovid Goyal
74388b4183 A simple action to remap key presses sent to programs running in kitty 2023-12-03 13:08:53 +05:30
Kovid Goyal
f1fc2126bc Refactor the mapping docs 2023-12-03 12:25:15 +05:30
Kovid Goyal
9a0e84293f ... 2023-12-02 16:35:30 +05:30
Kovid Goyal
847ef008e2 Allow spaces in --when-focus-on 2023-12-02 16:23:43 +05:30
Kovid Goyal
3c4f2aa1b8 shlex.split -> shlex_split 2023-12-02 15:17:08 +05:30
Kovid Goyal
6c8f499303 Merge branch 'cursor_non_gnome' of https://github.com/jinliu/kitty 2023-12-02 15:00:50 +05:30
Kovid Goyal
b0ba4b4a42 Fast and robust implementation of shlex.split
Also returns position of words in src string which we will need for
keymap parsing.
2023-12-02 14:57:02 +05:30
Jin Liu
293cd40509 Get cursor theme from desktop portal in non-GNOME desktops
Even if current DE is not GNOME, it may still use the GNOME
desktop portal as a fallback for Settings (e.g. KDE). So if
the user installed xdg-desktop-portal-gtk, and KDE synced
its cursor theme with GNOME, then we can still get it from
the GNOME namespace in the Settings.ReadAll result from the
portal.
2023-12-02 15:00:03 +08:00
Kovid Goyal
0d10ee1a8c DRYer 2023-12-02 08:07:25 +05:30
Kovid Goyal
d63b852b90 Un-matched modifier keys should not pop keyboard mode 2023-12-01 19:34:20 +05:30
Kovid Goyal
9560968a7d Sequences should not participate in global shortcuts 2023-12-01 19:31:14 +05:30
Kovid Goyal
53980d00f0 a couple more tests 2023-12-01 18:16:22 +05:30
Kovid Goyal
5bc2cde454 Add --debug-input output for sequence prefix matches 2023-12-01 17:19:08 +05:30
Kovid Goyal
d4ff54e0d8 Dont use branch-protection=standard on linux ARM as it reportedly causes crashes
See https://github.com/kovidgoyal/kitty/issues/6845#issuecomment-1835886938
2023-12-01 16:48:30 +05:30
Kovid Goyal
f2075f99fd More comprehensive is_arm check
Apparently on some Linux machines platform.machine() is 'aarch64' not
'arm64'.
2023-12-01 16:36:34 +05:30
Kovid Goyal
f46425c2f9 Fix #6876 2023-12-01 16:28:48 +05:30
Kovid Goyal
ad4e9bb42c Add a check that the output buffer is actually sRGB 2023-12-01 13:58:09 +05:30
Kovid Goyal
97f5cad335 Request an SRGB output buffer from the window system explicitly
Possible fix for #6845
2023-12-01 13:50:11 +05:30
Kovid Goyal
788295e534 Turn on control flow protection build options 2023-12-01 07:36:02 +05:30
Kovid Goyal
336035f507 Merge branch 'feature/fix-build-problems-nix' of https://github.com/gabyx/kitty 2023-12-01 07:06:13 +05:30
Gabriel Nützi
1883208cf6 fix: Correct Nix shell for building correctly 2023-11-30 22:09:15 +01:00
Kovid Goyal
8d1fde18f8 Fix sequence mapping 2023-11-30 21:17:02 +05:30
Kovid Goyal
d7633b95d0 Get debug config printing the changes in all keyboard modes 2023-11-30 20:47:30 +05:30
Kovid Goyal
165f1ccfd1 Port the test 2023-11-30 20:06:20 +05:30
Kovid Goyal
063a663958 Beep when multi-key sequence is aborted by mismatch 2023-11-30 20:02:25 +05:30
Kovid Goyal
66bc86e4f2 Report invalid --when-focus-on expressions to user 2023-11-30 20:00:49 +05:30
Kovid Goyal
15eb03c5e2 Add no-op to paste_actions 2023-11-30 19:54:01 +05:30
Kovid Goyal
c064a2e559 Port visual_window_select to use a keyboard mode 2023-11-30 19:50:57 +05:30
Kovid Goyal
9e815212dc Implement modal keyboard handling 2023-11-30 19:44:41 +05:30
Kovid Goyal
cb418a0040 Infrastructure for more map options 2023-11-30 13:36:29 +05:30
Kovid Goyal
716bf714db Fix a couple of tests 2023-11-30 11:53:09 +05:30
Kovid Goyal
f11f770011 Allow creating key mappings that depend on the state of the focused window, such as what program is running inside it 2023-11-30 11:35:30 +05:30
Kovid Goyal
8a1571f62c Add full docs for matching windows/tabs to the remote control page 2023-11-29 21:44:52 +05:30
Kovid Goyal
3876d518bd Fix setting swap interval for newly created OS window not always working 2023-11-29 21:13:10 +05:30
Kovid Goyal
72e14053f5 Simplify unmapping key/mouse button events
No need to specify the no-op action just specify no action. More
intuitive.
2023-11-28 12:37:27 +05:30
Kovid Goyal
bd7f38eb7d rg changed its help output to conform more closely to GNU conventions
Which is good I suppose, but it breaks hyperlinked_greps --help parsing.
I have updated it to match current rg which of course means it fails on
older rg.
2023-11-27 22:08:03 +05:30
Kovid Goyal
e19335377f Merge branch 'update-ask-kitten-help-msg' of https://github.com/lemontheme/kitty 2023-11-27 14:54:02 +05:30
adriaan
382d2f1ed0 Update help in main.py 2023-11-26 16:53:31 +01:00
Kovid Goyal
9bb85bd294 ask kitten: Better error message when choice letter is not present in choice text. Fixes #6855 2023-11-26 10:10:50 +05:30
Kovid Goyal
c10ea63818 Implement local filename completion for the transfer kitten 2023-11-25 07:46:30 +05:30
Kovid Goyal
4b3d29ecc3 Cant wait to drop python 3.8
Fixes #6848
2023-11-24 16:49:18 +05:30
Kovid Goyal
b297c27e09 ... 2023-11-23 19:43:37 +05:30
Kovid Goyal
6a09804910 Fix loading of window logo images via @launch
Use the path when either the pointer ORE the size is false. Apparently
some code paths pass pointers from a python y# conversion with an empty
bytestring as no value.

Fixes #6844
2023-11-23 19:34:35 +05:30
Kovid Goyal
c2b4df0666 No need to use fmemopen 2023-11-23 19:30:43 +05:30
Kovid Goyal
79f3692f1e Better PNG load error reporting 2023-11-23 19:16:43 +05:30
Kovid Goyal
35b2dcb065 ... 2023-11-23 17:25:52 +05:30
Kovid Goyal
2b751f56bd Port test 2023-11-22 10:50:05 +05:30
Kovid Goyal
882d471c90 Make config line parsing in Go use same algorithm as in python 2023-11-22 10:01:45 +05:30
Kovid Goyal
fc64ef41b3 when parsing conf files in go accept both space and tab as key separators 2023-11-22 09:49:33 +05:30
Kovid Goyal
b1e4c06220 Cleanup matching code for @ls and @send-text
Use the same utility function as other commands uses. Fixes #6835
2023-11-21 07:49:33 +05:30
Kovid Goyal
ef9dada80d Failure to change to cwd should not be fatal in bootstrap.py to match bootstrap.sh 2023-11-20 19:35:41 +05:30
Kovid Goyal
6d49686aee Merge branch 'patch-1' of https://github.com/Notarin/kitty 2023-11-19 09:32:54 +05:30
Notarin
3cbb8d5c8f Update graphics-protocol.rst
Fixed a very small typo
2023-11-18 22:36:39 -05:00
Kovid Goyal
ea28e20915 Show repology badge in 3 columns 2023-11-17 18:43:06 +05:30
Kovid Goyal
4897c4df00 Merge branch 'fish-sudo' of https://github.com/sersorrel/kitty 2023-11-17 08:39:04 +05:30
ash
b3c5b1185c Improve resilience of shell integration for sudo in fish
Running `sudo --version` will now not error out.
2023-11-17 00:24:40 +00:00
Kovid Goyal
b961e65f22 Clean up previous PR 2023-11-14 20:46:04 +05:30
Kovid Goyal
6d9593944a Merge branch 'notify_on_cmd_finish_use_bell' of https://github.com/jinliu/kitty 2023-11-14 20:40:11 +05:30
Jin Liu
a0d5f7a07b Add bell and command option to notify_on_cmd_finish
Instead of sending a desktop notification, it can be set to
ring the terminal bell or run a user-specified command.
2023-11-14 20:31:28 +08:00
Kovid Goyal
69e3e5c727 Use a boolean rather than an int 2023-11-14 16:35:01 +05:30
Kovid Goyal
04506975e5 Cleanup previous PR 2023-11-14 14:55:43 +05:30
Kovid Goyal
b5067c1369 Merge branch 'done-notification' of https://github.com/jinliu/kitty 2023-11-14 14:34:20 +05:30
Jin Liu
0f52b69372 launch watcher: add on_cmd_startstop event 2023-11-14 16:46:46 +08:00
Jin Liu
24bb28b773 Allow sending notification when a command finishes
When the option `notify_on_command_finish` is set, and a command
takes more than `notify_on_command_finish_min_duration` seconds
to execute, then when it finishes, send a desktop notification.

The value of `notify_on_command_finish` can be:
never | unfocused | invisible | always
the latter three are interpreted the same way as in the `o=` option
of the desktop notification protocol.
2023-11-14 16:46:38 +08:00
Kovid Goyal
553d833fd4 Merge branch 'dependabot/go_modules/all-go-deps-81e38e3cc5' of https://github.com/kovidgoyal/kitty 2023-11-13 08:45:40 +05:30
dependabot[bot]
2d5ce6191e Bump the all-go-deps group with 2 updates
Bumps the all-go-deps group with 2 updates: [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) and [golang.org/x/image](https://github.com/golang/image).


Updates `github.com/alecthomas/chroma/v2` from 2.10.0 to 2.11.1
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.10.0...v2.11.1)

Updates `golang.org/x/image` from 0.13.0 to 0.14.0
- [Commits](https://github.com/golang/image/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 03:09:09 +00:00
Kovid Goyal
5fe77e38b7 ... 2023-11-13 01:10:38 +05:30
Kovid Goyal
9a08489112 Fix config file reloading not working is a system config file is set and no user config file is present at startup
Fixes #6801
2023-11-13 01:02:02 +05:30
Kovid Goyal
2047ea8eec Allow package build to complete without docs if user chose to skip building kitten 2023-11-12 08:16:26 +05:30
Kovid Goyal
4d510e3318 Fix searching for kitten to build man pages on macOS 2023-11-12 07:56:03 +05:30
Kovid Goyal
87f7bd2f9e See what's going on with finding kitten in CI 2023-11-11 20:11:40 +05:30
Kovid Goyal
2ffe03ee48 mypy again 2023-11-11 20:01:28 +05:30
Kovid Goyal
4c56e76840 Fix #6812 2023-11-11 20:00:46 +05:30
Kovid Goyal
a3c8f32c1a Fix Linux CI build 2023-11-11 17:36:10 +05:30
Kovid Goyal
176ffc7f51 ... 2023-11-11 17:14:09 +05:30
Kovid Goyal
70bc4f1033 Generate man pages for kitten and all its sub-commands recursively
Fixes #6808
2023-11-11 17:09:23 +05:30
Kovid Goyal
0f2196357c Make mypy happy 2023-11-11 16:07:18 +05:30
Kovid Goyal
2759ec1fe1 Add an option to setup.py to skip building kitten
Fixes #6809
2023-11-11 15:55:56 +05:30
Kovid Goyal
dee2e83fb4 Remove no longer needed monkeypatch 2023-11-11 13:44:34 +05:30
Kovid Goyal
65754a2032 ... 2023-11-11 08:45:39 +05:30
Kovid Goyal
77292a16d6 Make shebangs consistent
Follow PEP 0394 and use /usr/bin/env python so that the python in the
users venv is respected. Not that the kitty python files are meant to be
executed standalone anyway, but, whatever.

Fixes #6810
2023-11-11 08:32:05 +05:30
Kovid Goyal
2402c6f253 Fix failing tests 2023-11-10 08:56:22 +05:30
Kovid Goyal
77140fc798 Fix #6803 2023-11-10 08:49:33 +05:30
Kovid Goyal
cda97b5451 string changes 2023-11-08 19:35:02 +05:30
Kovid Goyal
b247fda672 version 0.31.0 2023-11-08 13:15:45 +05:30
Kovid Goyal
8dfe1fcca9 Ensure clenup is run even when ssh child is killed by interrupt 2023-11-06 22:02:06 +05:30
Kovid Goyal
a6f13a64dc Allow only printable ascii in echo 2023-11-06 21:28:25 +05:30
Kovid Goyal
bd5fcb00e0 Fix regression that broke quick exit from ssh kitten 2023-11-06 21:22:41 +05:30
Kovid Goyal
ac2ec44a7f python 3.10+ requires PY_SSIZE_T_CLEAN 2023-11-06 14:41:45 +05:30
Kovid Goyal
d2444bae80 Merge branch 'dependabot/go_modules/all-go-deps-32cfe409cb' of https://github.com/kovidgoyal/kitty 2023-11-06 09:20:13 +05:30
dependabot[bot]
a6571ba8db Bump the all-go-deps group with 2 updates
Bumps the all-go-deps group with 2 updates: [github.com/shirou/gopsutil/v3](https://github.com/shirou/gopsutil) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `github.com/shirou/gopsutil/v3` from 3.23.9 to 3.23.10
- [Release notes](https://github.com/shirou/gopsutil/releases)
- [Commits](https://github.com/shirou/gopsutil/compare/v3.23.9...v3.23.10)

Updates `golang.org/x/sys` from 0.13.0 to 0.14.0
- [Commits](https://github.com/golang/sys/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: github.com/shirou/gopsutil/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-go-deps
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 03:44:18 +00:00
Kovid Goyal
5fab30b36f Bump version of bundled python 2023-11-05 17:59:19 +05:30
Kovid Goyal
9a179a7e90 Add focus reporting capabilities to terminfo
See https://lists.gnu.org/archive/html/bug-ncurses/2023-10/msg00117.html
2023-11-05 15:00:13 +05:30
Kovid Goyal
bc1da5525e ssh kitten: Fix restore state not being called after interrupt 2023-11-05 08:38:04 +05:30
Kovid Goyal
995112d952 Add non-interactive options for paste sanitization 2023-11-04 07:34:46 +05:30
Kovid Goyal
169315d33d Improve paste sanitization
Replace C0 chars with their graphical equivalents and \n with \eE
which has the same visual effect. C1 chars are replaced by reverse
question mark.
2023-11-04 07:23:59 +05:30
Kovid Goyal
114b8dff51 ... 2023-11-03 20:01:33 +05:30
Kovid Goyal
61429c73c7 Wayland: Fix primary selections not working with the river compositor
Fixes #6785
2023-11-03 19:57:54 +05:30
Kovid Goyal
2aa37de6ff Only alias sudo if no systemwide terminfo db for xterm-kitty is found 2023-11-03 12:30:29 +05:30
Kovid Goyal
a54586dfa9 Add a note about dynamic config reload behavior of underline_hyperlinks 2023-11-03 09:12:17 +05:30
Kovid Goyal
b4f88b4f81 A new option to control when hyperlinks are underlined
While kitty is never going to underline detected URLs as the performance
of that is absurd, underlining hyperlinks specifically is acceptable,
since they dont require detection.

See #6766
2023-11-03 08:51:58 +05:30
Kovid Goyal
954b7f87a5 Add some docs on why no-sudo might be needed 2023-11-03 08:26:54 +05:30
Kovid Goyal
d113a6c2cf ssh kitten: Fix a regression that broken ctrl+space mapping in zsh
Fixes #6780
2023-11-03 07:16:07 +05:30
Kovid Goyal
52cebf0150 DRYer 2023-11-02 08:09:51 +05:30
Kovid Goyal
827a7d5094 Add a new interactive action to set the active window title 2023-11-02 08:05:49 +05:30
Kovid Goyal
a04d19df4a ... 2023-10-31 17:59:08 +05:30
Kovid Goyal
7e1580ef09 fish integration: Dont clobber user defined sudo function 2023-10-31 16:49:31 +05:30
Kovid Goyal
f3ece8b7c4 Update changelog 2023-10-31 16:29:04 +05:30
Kovid Goyal
be3b8fcfb7 Also use a function for sudo in fish to avoid the --edit issue 2023-10-31 16:27:21 +05:30
Kovid Goyal
9d5bb3b2f2 bash integration: Also make sudo a function
There is less need in bash since its sudo completion is not as buggy,
but it does fix the sudo --edit issue
2023-10-31 15:53:45 +05:30
Kovid Goyal
492ec3dfbf zsh integration: Use a function for sudo
This fixes sudo --edit and works around the zsh sudo completions bug:
https://www.zsh.org/mla/workers/2023/msg00983.html
2023-10-31 12:24:35 +05:30
Kovid Goyal
309a6e9319 ... 2023-10-31 11:50:34 +05:30
Kovid Goyal
12db4683ac Run local build before building docs 2023-10-31 06:18:23 +05:30
Kovid Goyal
30b736eed7 Merge branch 'override_warning' of https://github.com/joveian/kitty 2023-10-30 22:44:51 +05:30
joveian
4d085a00e1 Warn of possible graphics issues with text_fg_override_threshold
Issue #6767
2023-10-30 09:04:27 -07:00
Kovid Goyal
dfd84d85a2 Merge branch 'dependabot/go_modules/all-go-deps-9450db85af' of https://github.com/kovidgoyal/kitty 2023-10-30 08:46:48 +05:30
dependabot[bot]
cb139692f5 Bump the all-go-deps group with 2 updates
Bumps the all-go-deps group with 2 updates: [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) and [github.com/google/uuid](https://github.com/google/uuid).


Updates `github.com/alecthomas/chroma/v2` from 2.9.1 to 2.10.0
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.9.1...v2.10.0)

Updates `github.com/google/uuid` from 1.3.1 to 1.4.0
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.1...v1.4.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 03:14:51 +00:00
Kovid Goyal
47836f5086 ... 2023-10-29 08:50:27 +05:30
Kovid Goyal
778adfcd3d Report invalid effective listen_on as a bad config error 2023-10-29 08:44:41 +05:30
Kovid Goyal
24d9d502b1 Set window title when showing errors 2023-10-29 08:24:08 +05:30
Kovid Goyal
8dadcf568c A bad listen_on value should not prevent startup
Ideally we should report the error more verbosely,
maybe someday when I have more time.
2023-10-28 19:40:43 +05:30
Kovid Goyal
0f02725f50 ... 2023-10-28 07:59:45 +05:30
Kovid Goyal
3beb1e454a Fix #6761 2023-10-27 21:54:13 +05:30
Kovid Goyal
c174c3cb38 ... 2023-10-27 15:49:37 +05:30
Kovid Goyal
f05a58f363 Note cursor movement behavior for relative placements in the spec 2023-10-27 15:43:15 +05:30
Kovid Goyal
9f42e915c8 Clarify relative placement spec 2023-10-27 15:38:56 +05:30
Kovid Goyal
52d5a4679f Graphics protocol: Support for positioning images relative to other images
Fixes #6617
2023-10-27 15:27:30 +05:30
Kovid Goyal
aee14a49f0 ... 2023-10-27 14:19:35 +05:30
Kovid Goyal
acf586867c Fix empty emoticons panel in unicode input kitten
Fixes #6760
2023-10-27 14:18:46 +05:30
Kovid Goyal
7cb392c7ab Use a hash table for images 2023-10-26 20:40:02 +05:30
Kovid Goyal
f8d5e30300 Use a hash table for image placements 2023-10-26 19:42:32 +05:30
Kovid Goyal
be5a0e8559 Same treatment for image and render data arrays 2023-10-26 16:29:13 +05:30
Kovid Goyal
7cffb2a714 Prepare for fast image/ref lookup via hashmap 2023-10-26 15:45:06 +05:30
Kovid Goyal
6111bc8ed6 Cleanup changelog 2023-10-26 09:00:37 +05:30
Kovid Goyal
1c1519c6e4 micro optimization 2023-10-25 18:41:27 +05:30
Kovid Goyal
b55883591d ... 2023-10-25 18:37:10 +05:30
Kovid Goyal
ae4a13b249 Better fix for macOS deadlock
Fix it in glfw. Dont hold the tick lock when calling the tick callback
as the tick callback might be waiting on a lock.
2023-10-25 18:32:07 +05:30
Kovid Goyal
61c4f80e90 Fix deadlock on macOS caused by recent support for pipe peers
Apparently on macOS we cant post an event to the main loop if the main loop is
waiting on a lock we are holding. Absurd.

Fixes #6751
2023-10-25 18:16:13 +05:30
Kovid Goyal
0ce996120a Allow o key to take effect in any chunk of OSC 99 2023-10-25 15:50:58 +05:30
Kovid Goyal
47b8b442dc Document when o key was added to desktop notifications spec 2023-10-25 15:42:57 +05:30
Kovid Goyal
32d23921df ... 2023-10-25 13:54:22 +05:30
Kovid Goyal
19374208e0 desktop notification protocol: Allow applications sending notifications to specify that the notification should only be displayed if the window is currently unfocused
Fixes #6755
2023-10-25 13:52:32 +05:30
Kovid Goyal
8c83284d5e Fix #6750 2023-10-25 12:14:36 +05:30
Kovid Goyal
9c25a183db On second thoughts dont use foreground process env vars for kitten @ ls
Since the purpose of the env vars is mostly to recognize windows the
original env vars make more sense. Also I dislike changing behavior for
no good reason.
2023-10-25 12:12:16 +05:30
Kovid Goyal
24895f0225 ... 2023-10-25 12:00:44 +05:30
Kovid Goyal
eefb865e6e kitten @ ls: Return environ of foreground process
This is needed on macOS where we now run the shell via login and we
aren't allowed to read the environ of login because its setuid.

Fixes #6749
2023-10-25 09:54:12 +05:30
Kovid Goyal
b1ec1c2678 ... 2023-10-24 18:39:28 +05:30
Kovid Goyal
ce583ea460 Render Private Use Unicode symbols using two cells if the second cell contains a non-breaking space as well as a normal space
There is some software out there that uses nbsp as a separator,
presumably as some kind of hack.

https://github.com/ibhagwan/fzf-lua/issues/916
2023-10-24 17:38:49 +05:30
Kovid Goyal
539a8706dc Update bundled harfbuzz version
The old version shows very poor perfromance shaping some fonts with
ligatures on macOS.

Fixes #6743
2023-10-24 16:39:29 +05:30
Kovid Goyal
467e7e5041 function to convert monotonic_t to microsecs 2023-10-24 16:38:17 +05:30
Kovid Goyal
d94a5bd386 Fix #6746 2023-10-24 07:21:30 +05:30
Kovid Goyal
480737a355 Merge branch 'dependabot/go_modules/all-go-deps-abad278ef8' of https://github.com/kovidgoyal/kitty 2023-10-23 08:50:15 +05:30
dependabot[bot]
d63739a598 Bump the all-go-deps group with 1 update
Bumps the all-go-deps group with 1 update: [github.com/bmatcuk/doublestar/v4](https://github.com/bmatcuk/doublestar).

- [Release notes](https://github.com/bmatcuk/doublestar/releases)
- [Commits](https://github.com/bmatcuk/doublestar/compare/v4.6.0...v4.6.1)

---
updated-dependencies:
- dependency-name: github.com/bmatcuk/doublestar/v4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 03:19:21 +00:00
Kovid Goyal
3526e59d3b Fix stripping of escape codes not stripping \r when bracketed paste is not active 2023-10-21 08:15:15 +05:30
Kovid Goyal
7292d1c9df Clean up mouse-demo kitten
Fixes #6738
2023-10-21 07:55:23 +05:30
Kovid Goyal
c9a95cacd9 ... 2023-10-20 18:24:21 +05:30
Kovid Goyal
8853f6bae2 Consider \r dangerous without bracketed paste 2023-10-20 13:38:35 +05:30
Kovid Goyal
beb18cc250 Add window titles to various ask kitten invocations 2023-10-20 13:36:02 +05:30
Kovid Goyal
321f1a6650 Nicer formatting for paste sanitization confirmation message 2023-10-20 13:22:58 +05:30
Kovid Goyal
8d8d89573c ... 2023-10-20 13:14:25 +05:30
Kovid Goyal
89ee128a92 ... 2023-10-20 13:05:21 +05:30
Kovid Goyal
defa2e29ac Always ask for confirmation when pasting text with control codes in it 2023-10-20 13:02:28 +05:30
Kovid Goyal
56963c693e When pasting in bracketed paste mode and the cursor is at a shell prompt, strip out C0 control codes
Some shells incorrectly interpret these allowing escape from bracketed paste mode. Thanks to David Leadbetter for discovering.
2023-10-20 12:17:13 +05:30
Kovid Goyal
f098240ace ... 2023-10-20 08:43:45 +05:30
Kovid Goyal
4b997a961c A new option single_window_padding_width to use a different padding when only a single window is visible
Fixes #6734
2023-10-20 08:37:45 +05:30
Kovid Goyal
512a672398 ... 2023-10-19 08:12:04 +05:30
Kovid Goyal
6cfb451ec8 Two new event types for watchers
on_title_change and on_set_user_var
2023-10-19 07:54:33 +05:30
Kovid Goyal
4a463f7712 More kitty @ -> kitten @ 2023-10-18 21:16:12 +05:30
Kovid Goyal
5ea9700c82 More kitty @ -> kitten @ 2023-10-18 20:40:39 +05:30
Kovid Goyal
1332cf8ac7 Create an easy to use alias for running remote control scripts 2023-10-18 20:29:50 +05:30
Kovid Goyal
f8ab5f3f67 ... 2023-10-18 20:15:05 +05:30
Kovid Goyal
822311d523 Change kitty @ to kitten @ in the docs 2023-10-18 20:13:49 +05:30
Kovid Goyal
713569fcfa Advertise the ability to run remote control scripts without turning on remote control 2023-10-18 20:10:17 +05:30
Kovid Goyal
7d32a77fe4 ... 2023-10-18 20:00:41 +05:30
Kovid Goyal
314fe4fe4a Allow launched background process to work with --allow-remote-control
Use a dedicated socketpair for such processes. Fixes #6712
2023-10-18 19:56:58 +05:30
Kovid Goyal
a9b412baba Fix a regression that broke kitten update-self
Fixes #6729
2023-10-18 19:19:35 +05:30
Kovid Goyal
0300a355d0 update docs for remote_kitty 2023-10-18 17:31:44 +05:30
Kovid Goyal
fa53ac7896 Fix deprecation warning for compositing operation 2023-10-18 06:12:31 +05:30
Kovid Goyal
cb15b98afd Add presenterm to the list of integrations 2023-10-17 21:52:18 +05:30
Kovid Goyal
6a50af12d3 Make set_pointer_shapes private 2023-10-17 21:47:03 +05:30
Kovid Goyal
45a98b19c5 Mention that pointer shapes can be demo-ed with the mouse_demo kitten 2023-10-17 21:17:45 +05:30
Kovid Goyal
e9e1a7a7c3 Fix the diagonal resize shapes on Linux 2023-10-17 21:15:20 +05:30
Kovid Goyal
6804d16519 Show pointer shapes in mouse_demo kitten 2023-10-17 21:07:57 +05:30
Kovid Goyal
ee8399ba56 Port the mouse_demo kitten to Go 2023-10-17 20:21:22 +05:30
Kovid Goyal
c03dff2322 Fix help text and short desc for the two wrapper kittens 2023-10-17 19:50:47 +05:30
Kovid Goyal
dc6edf9191 Improve the docs for how to match multi-line hints
See #6692
2023-10-17 15:23:58 +05:30
Kovid Goyal
70a588cb36 ... 2023-10-17 15:23:43 +05:30
Kovid Goyal
81b032a161 Dont expand cwd=current in rc launch
This is easily done at the command line. And its semantics are
are to refer to cwd of active window.
2023-10-17 05:01:28 +05:30
Kovid Goyal
ddb121b418 Remote control launch: Fix the --copy-env option not copying current environment variables
Fixes #6724
2023-10-16 22:32:51 +05:30
Kovid Goyal
d2026574b8 Add a sentence emphasizing the pointer shapes are independent of mouse reporting 2023-10-16 20:36:09 +05:30
Kovid Goyal
187fa996f8 Add the cell pointer shape 2023-10-16 20:33:12 +05:30
Kovid Goyal
870522a792 Merge branch 'dependabot/go_modules/all-go-deps-6213d6d4c1' of https://github.com/kovidgoyal/kitty 2023-10-16 08:53:53 +05:30
dependabot[bot]
5478f6665a Bump the all-go-deps group with 1 update
Bumps the all-go-deps group with 1 update: [github.com/google/go-cmp](https://github.com/google/go-cmp).

- [Release notes](https://github.com/google/go-cmp/releases)
- [Commits](https://github.com/google/go-cmp/compare/v0.5.9...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/google/go-cmp
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 03:20:56 +00:00
Kovid Goyal
5d5bbe9b96 Add a paragraph on legacy xterm compat for pointer shapes 2023-10-15 21:59:04 +05:30
Kovid Goyal
3a6c7e78df Note when support for pointer shapes was added 2023-10-15 21:51:10 +05:30
Kovid Goyal
10a2f246bc ... 2023-10-15 21:48:57 +05:30
Kovid Goyal
17ce474b79 Use hand pointer when hovering over buttons in ask kitten 2023-10-15 21:35:51 +05:30
Kovid Goyal
d66074f19f Add pointer shape enum to kittens 2023-10-15 21:03:41 +05:30
Kovid Goyal
1693107608 A new escape code to change the shape of the mouse pointer
Fixes #6711
2023-10-15 19:57:36 +05:30
Kovid Goyal
e15b16b072 Mention behavior of focus_follows_mouse in docs on macOS 2023-10-15 14:25:16 +05:30
Kovid Goyal
9375e671bc dedup choice literals 2023-10-15 10:10:05 +05:30
Kovid Goyal
0e11174aa5 We require python 3.8 so no need to guard typing.Literal 2023-10-15 09:58:19 +05:30
Kovid Goyal
baddc966dc Ignore long lines in another generated file 2023-10-15 09:57:58 +05:30
Kovid Goyal
119582a9d4 Make relative imports work in gen scripts even when directly executed 2023-10-15 09:51:03 +05:30
Kovid Goyal
792b74503c Implement mouse shape support for macOS
Code for loading hidden system cursors not available via NCursor comes
from the SDL library, with thanks.
2023-10-15 09:42:06 +05:30
Kovid Goyal
4f1971c480 Rationalize mouse cursor shape handling
Now can use the full range of standard mouse cursor shapes similar to
those supported by browsers via the CSS cursor property.

Still needs to be fully implemented for cocoa backend.
2023-10-15 09:17:31 +05:30
Kovid Goyal
a8a1571ed1 Fix #6715 2023-10-14 08:49:46 +05:30
Kovid Goyal
a79dd3996a Also move data files for gen scripts into gen dir 2023-10-14 08:04:37 +05:30
Kovid Goyal
e6ef2fceea py3.8 support 2023-10-14 07:57:03 +05:30
Kovid Goyal
56063b96fd Move gen scripts into their own package 2023-10-14 07:44:18 +05:30
Kovid Goyal
cae19bba60 ... 2023-10-14 06:05:32 +05:30
Kovid Goyal
1f91250a40 Fix trailing bracket not ignored when detecting a multi-line URL with the trailing bracket as the first character on the last line
Fixes #6710
2023-10-13 10:44:58 +05:30
Kovid Goyal
ee1e65e619 Remove pre python 3.8 compat shim as we now require 3.8 2023-10-12 20:40:33 +05:30
Kovid Goyal
00dc5a8dc5 Fix a regression caused by rewrite of kittens to Go that made various kittens reset colors in a terminal when the colors were changed by escape code
Fixes #6708
2023-10-12 20:19:28 +05:30
Kovid Goyal
4d230f5035 Fix #6695 2023-10-10 09:38:52 +05:30
Kovid Goyal
73bc6e4bfc ... 2023-10-10 06:10:19 +05:30
Kovid Goyal
b78264183f Centralize freeing of opts object allocs 2023-10-10 06:08:28 +05:30
Kovid Goyal
455d0a6048 Dont show hidden sub-commands during completion 2023-10-10 05:41:27 +05:30
Kovid Goyal
5d0dabe51c completion: match exe on basename alone 2023-10-10 05:22:40 +05:30
Kovid Goyal
16e7ca3a95 kitten @ set-background-opacity --toggle
Fixes #6691
2023-10-10 05:04:50 +05:30
Kovid Goyal
70f3909cdc Add background_opacity to @ ls output 2023-10-10 04:54:27 +05:30
Kovid Goyal
f73d32e15d A new option menu_map that allows adding entries to the global menubar on macOS 2023-10-09 19:47:25 +05:30
Kovid Goyal
3338e4fb25 ... 2023-10-09 15:28:16 +05:30
Kovid Goyal
726d4ca9bc Typecheck map_type 2023-10-09 11:56:25 +05:30
Kovid Goyal
32b7be2201 Proper fix for braindead macOS login
Always run it in the home dir as it seems to expect that. Change the
working dir after running login
2023-10-09 11:29:32 +05:30
Kovid Goyal
dc26db7759 Merge branch 'dependabot/go_modules/all-go-deps-92d01672c5' of https://github.com/kovidgoyal/kitty 2023-10-09 09:12:37 +05:30
dependabot[bot]
8fd5b62560 Bump the all-go-deps group with 2 updates
Bumps the all-go-deps group with 2 updates: [golang.org/x/image](https://github.com/golang/image) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/image` from 0.12.0 to 0.13.0
- [Commits](https://github.com/golang/image/compare/v0.12.0...v0.13.0)

Updates `golang.org/x/sys` from 0.12.0 to 0.13.0
- [Commits](https://github.com/golang/sys/compare/v0.12.0...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
- dependency-name: golang.org/x/sys
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-go-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 03:37:48 +00:00
Kovid Goyal
619d4e3dc4 Run login on macOS with -q
Fixes #6689
2023-10-09 07:05:43 +05:30
Kovid Goyal
f03027c33d Merge branch 'nolto' of https://github.com/bmwiedemann/kitty 2023-10-08 08:33:05 +05:30
Kovid Goyal
a95dc0ceb8 Merge branch 'reproducible' of https://github.com/bmwiedemann/kitty 2023-10-08 08:27:21 +05:30
Bernhard M. Wiedemann
17b7703dab Fix compilation without LTO
When building without link-time-optimization through the
KITTY_NO_LTO env var or the setup.py --disable-link-time-optimization option
compilation failed with a warning about freeing "ans" memory.

Chances are, this is dead code that gets optimized out by LTO.
2023-10-08 04:52:24 +02:00
Bernhard M. Wiedemann
50968c12b1 Make build reproducible
This needs 3 fixes:
* for an ordering issue in docs_ref_map_generated.h
* for a filesystem-order issue in uniforms_generated.h
* to normalize mtimes in the data_generated.bin tar

This patch was done while working on reproducible builds for openSUSE.
2023-10-08 04:48:51 +02:00
Kovid Goyal
edb9c924fe Note that fzf now supports the kitty graphics protocol for image previews 2023-10-08 07:22:54 +05:30
Kovid Goyal
805bc499ee Fix documentation of word_and_line_from_point 2023-10-06 10:01:24 +05:30
Kovid Goyal
baa0270ef3 Merge branch 'mouse-selection-line-from-word-feature' of https://github.com/warpfork/kitty 2023-10-06 09:43:07 +05:30
Eric Myhre
d310acc780 Add word-and-line-from-point feature to possible mouse selection actions. 2023-10-05 17:47:53 +02:00
231 changed files with 6472 additions and 2104 deletions

View File

@@ -11,4 +11,4 @@ https://www.reddit.com/r/KittyTerminal[Reddit community]
Packaging status in various repositories:
image:https://repology.org/badge/vertical-allrepos/kitty.svg["Packaging status", link="https://repology.org/project/kitty/versions"]
image:https://repology.org/badge/vertical-allrepos/kitty.svg?columns=3&header=kitty["Packaging status", link="https://repology.org/project/kitty/versions"]

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -147,9 +147,9 @@
{
"name": "python",
"unix": {
"filename": "Python-3.9.4.tar.xz",
"hash": "sha256:4b0e6644a76f8df864ae24ac500a51bbf68bd098f6a173e27d3b61cdca9aa134",
"urls": ["https://www.python.org/ftp/python/3.9.4/{filename}"]
"filename": "Python-3.11.6.tar.xz",
"hash": "sha256:0fab78fa7f133f4f38210c6260d90d7c0d5c7198446419ce057ec7ac2e6f5f38",
"urls": ["https://www.python.org/ftp/python/3.11.6/{filename}"]
}
},
@@ -244,9 +244,9 @@
{
"name": "harfbuzz",
"unix": {
"filename": "harfbuzz-2.7.4.tar.xz",
"hash": "sha256:6ad11d653347bd25d8317589df4e431a2de372c0cf9be3543368e07ec23bb8e7",
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/2.7.4/{filename}"]
"filename": "harfbuzz-8.2.2.tar.xz",
"hash": "sha256:e433ad85fbdf57f680be29479b3f964577379aaf319f557eb76569f0ecbc90f3",
"urls": ["https://github.com/harfbuzz/harfbuzz/releases/download/8.2.2/{filename}"]
}
},

View File

@@ -12,6 +12,6 @@ for line in cp.stdout.decode().splitlines():
fname = line.split(':', 1)[0]
all_files.discard(fname)
all_files -= {'nerd-fonts-glyphs.txt', 'rowcolumn-diacritics.txt'}
all_files -= {'gen/nerd-fonts-glyphs.txt', 'gen/rowcolumn-diacritics.txt'}
cp = subprocess.run(['cloc', '--list-file', '-'], input='\n'.join(all_files).encode())
raise SystemExit(cp.returncode)

View File

@@ -6,5 +6,6 @@ Mappable actions
The actions described below can be mapped to any key press or mouse action
using the ``map`` and ``mouse_map`` directives in :file:`kitty.conf`. For
configuration examples, see the default shortcut links for each action.
To read about keyboard mapping in more detail, see :doc:`mapping`.
.. include:: /generated/actions.rst

View File

@@ -124,7 +124,9 @@ Other keyboard shortcuts
----------------------------------
The full list of actions that can be mapped to key presses is available
:doc:`here </actions>`.
:doc:`here </actions>`. To learn how to do more sophisticated keyboard
mappings, such as modal mappings, per application mappings, etc. see
:doc:`mapping`.
================================== =======================
Action Shortcut

View File

@@ -43,6 +43,98 @@ The :doc:`ssh kitten <kittens/ssh>` is redesigned with powerful new features:
Detailed list of changes
-------------------------------------
0.32.0 [2024-01-19]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- :ref:`conditional_mappings`
- Support for :ref:`modal_mappings` such as in modal editors like vim
- A new option :opt:`notify_on_cmd_finish` to show a desktop notification when a long running command finishes (:pull:`6817`)
- A new action :ac:`send_key` to simplify mapping key presses to other keys without needing :ac:`send_text`
- Allow focusing previously active OS windows via :ac:`nth_os_window` (:pull:`7009`)
- Wayland: Fix a regression in the previous release that broke copying to clipboard under wl-roots based compositors in some circumstances
(:iss:`6890`)
- macOS: Fix some combining characters not being rendered (:iss:`6898`)
- macOS: Fix returning from full screen via the button when the titlebar is hidden not hiding the buttons (:iss:`6883`)
- macOS: Fix newly created OS windows not always appearing on the "active" monitor (:pull:`6932`)
- Font fallback: Fix the font used to render a character sometimes dependent on the order in which characters appear on screen (:iss:`6865`)
- panel kitten: Fix rendering with non-zero margin/padding in kitty.conf (:iss:`6923`)
- kitty keyboard protocol: Specify the behavior of the modifier bits during modifier key events (:iss:`6913`)
- Wayland: Enable support for the new cursor-shape protocol so that the mouse cursor is always rendered at the correct size in compositors that support this protocol (:iss:`6914`)
- GNOME Wayland: Fix remembered window size smaller than actual size (:iss:`6946`)
- Mouse reporting: Fix incorrect position reported for windows with padding (:iss:`6950`)
- Fix :ac:`focus_visible_window` not switching to other window in stack layout
when only two windows are present (:iss:`6970`)
0.31.0 [2023-11-08]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Allow :ac:`easily running arbitrarily complex remote control scripts <remote_control_script>` without needing to turn on remote control (:iss:`6712`)
- A new option :opt:`menu_map` that allows adding entries to the global menubar on macOS (:disc:`6680`)
- A new :doc:`escape code <pointer-shapes>` that can be used by programs running in the terminal to change the shape of the mouse pointer (:iss:`6711`)
- Graphics protocol: Support for positioning :ref:`images relative to other images <relative_image_placement>` (:iss:`6400`)
- A new option :opt:`single_window_padding_width` to use a different padding when only a single window is visible (:iss:`6734`)
- A new mouse action ``mouse_selection word_and_line_from_point`` to select the current word under the mouse cursor and extend to end of line (:pull:`6663`)
- A new option :opt:`underline_hyperlinks` to control when hyperlinks are underlined (:iss:`6766`)
- Allow using the full range of standard mouse cursor shapes when customizing the mouse cursor
- macOS: When running the default shell with the login program fix :file:`~/.hushlogin` not being respected when opening windows not in the home directory (:iss:`6689`)
- macOS: Fix poor performance when using ligatures with some fonts, caused by slow harfbuzz shaping (:iss:`6743`)
- :option:`kitten @ set-background-opacity --toggle` - a new flag to easily switch opacity between the specified value and the default (:iss:`6691`)
- Fix a regression caused by rewrite of kittens to Go that made various kittens reset colors in a terminal when the colors were changed by escape code (:iss:`6708`)
- Fix trailing bracket not ignored when detecting a multi-line URL with the trailing bracket as the first character on the last line (:iss:`6710`)
- Fix the :option:`kitten @ launch --copy-env` option not copying current environment variables (:iss:`6724`)
- Fix a regression that broke :program:`kitten update-self` (:iss:`6729`)
- Two new event types for :ref:`watchers <watchers>`, :code:`on_title_change` and :code:`on_set_user_var`
- When pasting, if the text contains terminal control codes ask the user for permission. See :opt:`paste_actions` for details. Thanks to David Leadbeater for discovering this.
- Render Private Use Unicode symbols using two cells if the second cell contains an en-space as well as a normal space
- macOS: Fix a regression in the previous release that caused kitten @ ls to not report the environment variables for the default shell (:iss:`6749`)
- :doc:`Desktop notification protocol </desktop-notifications>`: Allow applications sending notifications to specify that the notification should only be displayed if the window is currently unfocused (:iss:`6755`)
- :doc:`unicode_input kitten </kittens/unicode_input>`: Fix a regression that broke the "Emoticons" tab (:iss:`6760`)
- Shell integration: Fix ``sudo --edit`` not working and also fix completions for sudo not working in zsh (:iss:`6754`, :iss:`6771`)
- A new action :ac:`set_window_title` to interactively change the title of the active window
- ssh kitten: Fix a regression that broken :kbd:`ctrl+space` mapping in zsh (:iss:`6780`)
- Wayland: Fix primary selections not working with the river compositor (:iss:`6785`)
0.30.1 [2023-10-05]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1304,10 +1396,10 @@ Detailed list of changes
- Fix deleting windows that are not the last window via remote control leaving
no window focused (:iss:`3619`)
- Add an option :option:`kitty @ get-text --add-cursor` to also get the current
- Add an option :option:`kitten @ get-text --add-cursor` to also get the current
cursor position and state as ANSI escape codes (:iss:`3625`)
- Add an option :option:`kitty @ get-text --add-wrap-markers` to add line wrap
- Add an option :option:`kitten @ get-text --add-wrap-markers` to add line wrap
markers to the output (:pull:`3633`)
- Improve rendering of curly underlines on HiDPI screens (:pull:`3637`)
@@ -2911,7 +3003,7 @@ Detailed list of changes
- diff kitten: Fix error when right hand side file is binary and left hand side
file is text (:pull:`752`)
- kitty @ new-window: Add a new option :option:`kitty @ new-window --window-type`
- kitty @ new-window: Add a new option :option:`kitten @ new-window --window-type`
to create top-level OS windows (:iss:`770`)
- macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows
@@ -3028,7 +3120,7 @@ Detailed list of changes
- diff kitten: Fix default foreground/background colors not being restored when
kitten quits (:iss:`637`)
- Fix :option:`kitty @ set-colors --all` not working when more than one window
- Fix :option:`kitten @ set-colors --all` not working when more than one window
present (:iss:`632`)
- Fix a regression that broke the legacy increase/decrease_font_size actions

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# vim:fileencoding=utf-8
#
# Configuration file for the Sphinx documentation builder.
@@ -7,18 +7,19 @@
# full list see the documentation:
# https://www.sphinx-doc.org/en/master/config
import glob
import os
import re
import subprocess
import sys
import time
from functools import lru_cache, partial
from typing import Any, Callable, Dict, Iterable, List, Tuple
from typing import Any, Callable, Dict, Iterable, Iterator, List, Tuple
from docutils import nodes
from docutils.parsers.rst.roles import set_classes
from pygments.lexer import RegexLexer, bygroups # type: ignore
from pygments.token import Comment, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
from pygments.token import Comment, Error, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
from sphinx import addnodes, version_info
from sphinx.util.logging import getLogger
@@ -27,7 +28,8 @@ if kitty_src not in sys.path:
sys.path.insert(0, kitty_src)
from kitty.conf.types import Definition, expand_opt_references # noqa
from kitty.constants import str_version, website_url # noqa
from kitty.constants import str_version, website_url # noqa
from kitty.fast_data_types import Shlex # noqa
# config {{{
# -- Project information -----------------------------------------------------
@@ -176,8 +178,8 @@ manpages_url = 'https://man7.org/linux/man-pages/man{section}/{page}.{section}.h
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('invocation', 'kitty', 'kitty Documentation', [author], 1),
('conf', 'kitty.conf', 'kitty terminal emulator configuration file', [author], 5)
('invocation', 'kitty', 'The fast, feature rich terminal emulator', [author], 1),
('conf', 'kitty.conf', 'Configuration file for kitty', [author], 5)
]
@@ -251,20 +253,20 @@ if you specify a program-to-run you can use the special placeholder
as_rst = partial(option_spec_as_rst, heading_char='_')
from kitty.rc.base import all_command_names, command_for_name
from kitty.remote_control import cli_msg, global_options_spec
with open('generated/cli-kitty-at.rst', 'w') as f:
with open('generated/cli-kitten-at.rst', 'w') as f:
p = partial(print, file=f)
p('kitty @')
p('kitten @')
p('-' * 80)
p('.. program::', 'kitty @')
p('.. program::', 'kitten @')
p('\n\n' + as_rst(
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
global_options_spec, message=cli_msg, usage='command ...', appname='kitten @'))
from kitty.rc.base import cli_params_for
for cmd_name in sorted(all_command_names()):
func = command_for_name(cmd_name)
p(f'.. _at-{func.name}:\n')
p('kitty @', func.name)
p('kitten @', func.name)
p('-' * 120)
p('.. program::', 'kitty @', func.name)
p('.. program::', 'kitten @', func.name)
p('\n\n' + as_rst(*cli_params_for(func)))
from kittens.runner import get_kitten_cli_docs
@@ -344,14 +346,70 @@ class ConfLexer(RegexLexer): # type: ignore
aliases = ['conf']
filenames = ['*.conf']
def map_flags(self: RegexLexer, val: str, start_pos: int) -> Iterator[Tuple[int, Any, str]]:
expecting_arg = ''
s = Shlex(val)
from kitty.options.utils import allowed_key_map_options
last_pos = 0
while (tok := s.next_word())[0] > -1:
x = tok[1]
if tok[0] > last_pos:
yield start_pos + last_pos, Whitespace, ' ' * (tok[0] - last_pos)
last_pos = tok[0] + len(x)
tok_start = start_pos + tok[0]
if expecting_arg:
yield tok_start, String, x
expecting_arg = ''
elif x.startswith('--'):
expecting_arg = x[2:]
k, sep, v = expecting_arg.partition('=')
k = k.replace('-', '_')
expecting_arg = k
if expecting_arg not in allowed_key_map_options:
yield tok_start, Error, x
elif sep == '=':
expecting_arg = ''
yield tok_start, Name, x
else:
yield tok_start, Name, x
else:
break
def mapargs(self: RegexLexer, match: 're.Match[str]') -> Iterator[Tuple[int, Any, str]]:
start_pos = match.start()
val = match.group()
parts = val.split(maxsplit=1)
if parts[0].startswith('--'):
seen = 0
for (pos, token, text) in self.map_flags(val, start_pos):
yield pos, token, text
seen += len(text)
start_pos += seen
val = val[seen:]
parts = val.split(maxsplit=1)
if not val:
return
yield start_pos, Literal, parts[0] # key spec
if len(parts) == 1:
return
start_pos += len(parts[0])
val = val[len(parts[0]):]
m = re.match(r'(\s+)(\S+)', val)
if m is None:
return
yield start_pos, Whitespace, m.group(1)
yield start_pos + m.start(2), Name.Function, m.group(2) # action function
yield start_pos + m.end(2), String, val[m.end(2):]
tokens = {
'root': [
(r'#.*?$', Comment.Single),
(r'\s+$', Whitespace),
(r'\s+', Whitespace),
(r'(include)(\s+)(.+?)$', bygroups(Comment.Preproc, Whitespace, Name.Namespace)),
(r'(map)(\s+)(\S+)(\s+)', bygroups(
Keyword.Declaration, Whitespace, String, Whitespace), 'action'),
(r'(map)(\s+)', bygroups(
Keyword.Declaration, Whitespace), 'mapargs'),
(r'(mouse_map)(\s+)(\S+)(\s+)(\S+)(\s+)(\S+)(\s+)', bygroups(
Keyword.Declaration, Whitespace, String, Whitespace, Name.Variable, Whitespace, String, Whitespace), 'action'),
(r'(symbol_map)(\s+)(\S+)(\s+)(.+?)$', bygroups(
@@ -363,6 +421,9 @@ class ConfLexer(RegexLexer): # type: ignore
(r'[a-z_0-9]+$', Name.Function, 'root'),
(r'[a-z_0-9]+', Name.Function, 'args'),
],
'mapargs': [
(r'.+$', mapargs, 'root'),
],
'args': [
(r'\s+', Whitespace, 'args'),
(r'\b(yes|no)\b$', Number.Bin, 'root'),
@@ -529,6 +590,18 @@ def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
from kitty.actions import as_rst
with open('generated/actions.rst', 'w', encoding='utf-8') as f:
f.write(as_rst())
from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
with open('generated/matching.rst', 'w') as f:
print('Matching windows', file=f)
print('______________________________', file=f)
w = 'm' + MATCH_WINDOW_OPTION[MATCH_WINDOW_OPTION.find('Match') + 1:]
print('When matching windows,', w, file=f)
print('Matching tabs', file=f)
print('______________________________', file=f)
w = 'm' + MATCH_TAB_OPTION[MATCH_TAB_OPTION.find('Match') + 1:]
print('When matching tabs,', w, file=f)
# }}}
@@ -549,10 +622,13 @@ def add_html_context(app: Any, pagename: str, templatename: str, context: Any, d
@lru_cache
def monkeypatch_man_writer() -> None:
'''
Monkeypatch the docutils man translator to output better tables
Monkeypatch the docutils man translator to be nicer
'''
from docutils.nodes import Element
from docutils.writers.manpage import Table, Translator
from sphinx.writers.manpage import ManualPageTranslator
# Generate nicer tables https://sourceforge.net/p/docutils/bugs/475/
class PatchedTable(Table): # type: ignore
_options: list[str]
def __init__(self) -> None:
@@ -572,15 +648,103 @@ def monkeypatch_man_writer() -> None:
del ans[3] # top border
del ans[-2] # bottom border
return ans
def visit_table(self: Translator, node: object) -> None:
def visit_table(self: ManualPageTranslator, node: object) -> None:
setattr(self, '_active_table', PatchedTable())
setattr(Translator, 'visit_table', visit_table)
setattr(ManualPageTranslator, 'visit_table', visit_table)
# Improve header generation
def header(self: ManualPageTranslator) -> str:
di = getattr(self, '_docinfo')
di['ktitle'] = di['title'].replace('_', '-')
th = (".TH \"%(ktitle)s\" %(manual_section)s"
" \"%(date)s\" \"%(version)s\"") % di
if di["manual_group"]:
th += " \"%(manual_group)s\"" % di
th += "\n"
sh_tmpl: str = (".SH Name\n"
"%(ktitle)s \\- %(subtitle)s\n")
return th + sh_tmpl % di # type: ignore
setattr(ManualPageTranslator, 'header', header)
def visit_image(self: ManualPageTranslator, node: Element) -> None:
pass
def depart_image(self: ManualPageTranslator, node: Element) -> None:
pass
def depart_figure(self: ManualPageTranslator, node: Element) -> None:
self.body.append(' (images not supported)\n')
Translator.depart_figure(self, node)
setattr(ManualPageTranslator, 'visit_image', visit_image)
setattr(ManualPageTranslator, 'depart_image', depart_image)
setattr(ManualPageTranslator, 'depart_figure', depart_figure)
orig_astext = Translator.astext
def astext(self: Translator) -> Any:
b = []
for line in self.body:
if line.startswith('.SH'):
x, y = line.split(' ', 1)
parts = y.splitlines(keepends=True)
parts[0] = parts[0].capitalize()
line = x + ' ' + '\n'.join(parts)
b.append(line)
self.body = b
return orig_astext(self)
setattr(Translator, 'astext', astext)
def setup_man_pages() -> None:
from kittens.runner import get_kitten_cli_docs
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
for x in glob.glob(os.path.join(base, 'docs/kittens/*.rst')):
kn = os.path.basename(x).rpartition('.')[0]
if kn == 'custom':
continue
cd = get_kitten_cli_docs(kn) or {}
khn = kn.replace('_', '-')
man_pages.append((f'kittens/{kn}', 'kitten-' + khn, cd.get('short_desc', 'kitten Documentation'), [author], 1))
monkeypatch_man_writer()
def build_extra_man_pages() -> None:
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
kitten = os.environ.get('KITTEN_EXE_FOR_DOCS', os.path.join(base, 'kitty/launcher/kitten'))
if not os.path.exists(kitten):
kitten = os.path.join(base, 'kitty/launcher/kitty.app/Contents/MacOS/kitten')
if not os.path.exists(kitten):
subprocess.call(['find', os.path.join(base, 'kitty/launcher')])
raise Exception(f'The kitten binary {kitten} is not built cannot generate man pages')
raw = subprocess.check_output([kitten, '-h']).decode()
started = 0
names = set()
for line in raw.splitlines():
if line.strip() == '@':
started = len(line.rstrip()[:-1])
q = line.strip()
if started and len(q.split()) == 1 and not q.startswith('-') and ':' not in q:
if len(line) - len(line.lstrip()) == started:
if not os.path.exists(os.path.join(base, f'docs/kittens/{q}.rst')):
names.add(q)
cwd = os.path.join(base, 'docs/_build/man')
subprocess.check_call([kitten, '__generate_man_pages__'], cwd=cwd)
subprocess.check_call([kitten, '__generate_man_pages__'] + list(names), cwd=cwd)
if building_man_pages:
setup_man_pages()
def build_finished(*a: Any, **kw: Any) -> None:
if building_man_pages:
build_extra_man_pages()
def setup(app: Any) -> None:
os.makedirs('generated/conf', exist_ok=True)
from kittens.runner import all_kitten_names
monkeypatch_man_writer()
kn = all_kitten_names()
write_cli_docs(kn)
write_remote_control_protocol_docs()
@@ -589,10 +753,7 @@ def setup(app: Any) -> None:
app.connect('source-read', replace_string)
app.add_config_value('analytics_id', '', 'env')
app.connect('html-page-context', add_html_context)
app.connect('build-finished', build_finished)
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
app.add_role('link', link_role)
app.add_role('commit', commit_role)
# monkey patch sphinx_inline_tabs to avoid a warning about parallel reads
# see https://github.com/pradyunsg/sphinx-inline-tabs/issues/26
inline_tabs = app.extensions['sphinx_inline_tabs']
inline_tabs.parallel_read_safe = inline_tabs.parallel_write_safe = True

View File

@@ -94,9 +94,9 @@ to display it based on what it does understand.
revisions.
======= ==================== ========= =================
======= ==================== ========== =================
Key Value Default Description
======= ==================== ========= =================
======= ==================== ========== =================
``a`` Comma separated list ``focus`` What action to perform when the
of ``report``, notification is clicked
``focus``, with
@@ -113,7 +113,19 @@ Key Value Default Description
``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.
======= ==================== ========= =================
``o`` One of ``always``, ``always`` When to honor the notification request. ``unfocused`` means when the window
``unfocused`` or the notification is sent on does not have keyboard focus. ``invisible``
``invisible`` means the window both is unfocused
and not visible to the user, for example, because it is in an inactive tab or
its OS window is not currently active.
``always`` is the default and always honors the request.
======= ==================== ========== =================
.. note::
Support for the ``o`` key to prevent notifications from focused windows
was added in kitty version 0.31.0
.. note::

View File

@@ -19,9 +19,9 @@ use Unicode characters from the private use area to represent symbols. Often
these symbols are wide and should be rendered in two cells. However, since
private use area symbols all have their width set to one in the Unicode
standard, |kitty| renders them either smaller or truncated. The exception is if
these characters are followed by a space or empty cell in which case kitty
makes use of the extra cell to render them in two cells. This behavior can be
turned off for specific symbols using :opt:`narrow_symbols`.
these characters are followed by a space or en-space (U+2002) in which case
kitty makes use of the extra cell to render them in two cells. This behavior
can be turned off for specific symbols using :opt:`narrow_symbols`.
Using a color theme with a background color does not work well in vim?
@@ -389,20 +389,14 @@ How do I map key presses in kitty to different keys in the terminal program?
This is accomplished by using ``map`` with :sc:`send_text <send_text>` in :file:`kitty.conf`.
For example::
map alt+s send_text normal,application \x13
map alt+s send_key ctrl+s
This maps :kbd:`alt+s` to :kbd:`ctrl+s`. To figure out what bytes to use for
the :sc:`send_text <send_text>` you can use the ``show_key`` kitten. Run::
This causes the program running in kitty to receive the :kbd:`ctrl+s` key when
you press the :kbd:`alt+s` key. To see this in action, run::
kitten show_key
kitten show-key -m kitty
Then press the key you want to emulate. Note that this kitten will only show
keys that actually reach the terminal program, in particular, keys mapped to
actions in kitty will not be shown. To check those first map them to
:ac:`no_op`. You can also start a kitty instance without any shortcuts to
interfere::
kitty -o clear_all_shortcuts=yes kitten show_key
Which will print out what key events it receives.
How do I open a new window or tab with the same working directory as the current window?

View File

@@ -120,7 +120,7 @@ Variables that influence kitty behavior
.. envvar:: KITTY_RC_PASSWORD
Set this to a pass phrase to use the ``kitty @`` remote control command with
Set this to a pass phrase to use the ``kitten @`` remote control command with
:opt:`remote_control_password`.
@@ -179,8 +179,10 @@ Variables that kitty sets when running child programs
Set when the :doc:`remote control <remote-control>` facility is enabled and
the a socket is used for control via :option:`kitty --listen-on` or :opt:`listen_on`.
Contains the path to the socket. Avoid the need to use :option:`kitty @ --to` when
issuing remote control commands.
Contains the path to the socket. Avoid the need to use :option:`kitten @ --to` when
issuing remote control commands. Can also be a file descriptor of the form
fd:num instead of a socket address, in which case, remote control
communication should proceed over the specified file descriptor.
.. envvar:: KITTY_PIPE_DATA

View File

@@ -48,6 +48,7 @@ Some programs and libraries that use the kitty graphics protocol:
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
* `twitch-tui <https://github.com/Xithrius/twitch-tui>`_ - Twitch chat in the terminal
* `awrit <https://github.com/chase/awrit>`_ - Chromium-based web browser rendered in Kitty with mouse and keyboard support
* `fzf <https://github.com/junegunn/fzf/commit/d8188fce7b7bea982e7f9050c35e488e49fb8fd0>`_ - A command line fuzzy finder
Other terminals that have implemented the graphics protocol:
@@ -478,18 +479,22 @@ Controlling displayed image layout
The image is rendered at the current cursor position, from the upper left corner of
the current cell. You can also specify extra ``X=3`` and ``Y=4`` pixel offsets to display from
a different origin within the cell. Note that the offsets must be smaller that the size of the cell.
a different origin within the cell. Note that the offsets must be smaller than the size of the cell.
By default, the entire image will be displayed (images wider than the available
width will be truncated on the right edge). You can choose a source rectangle (in pixels)
as the part of the image to display. This is done with the keys: ``x, y, w, h`` which specify
the top-left corner, width and height of the source rectangle.
the top-left corner, width and height of the source rectangle. The displayed
area is the intersection of the specified rectangle with the source image
rectangle.
You can also ask the terminal emulator to display the image in a specified rectangle
(num of columns / num of lines), using the control codes ``c,r``. ``c`` is the number of columns
and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit
the specified area. Note that if you specify a start cell offset via the ``X,Y`` keys, it is not
added to the number of rows/columns.
added to the number of rows/columns. If only one of either ``r`` or ``c`` is
specified, the other one is computed based on the source image aspect ratio, so
that the image is displayed without distortion.
Finally, you can specify the image *z-index*, i.e. the vertical stacking order. Images
placed in the same location with different z-index values will be blended if
@@ -554,7 +559,7 @@ The image will eventually be fit to the specified rectangle, its aspect ratio
preserved. Finally, the image can be actually displayed by using the
placeholder character, encoding the image ID in its foreground color. The row
and column values are specified with diacritics listed in
:download:`rowcolumn-diacritics.txt <../rowcolumn-diacritics.txt>`. For
:download:`rowcolumn-diacritics.txt <../gen/rowcolumn-diacritics.txt>`. For
example, here is how you can print a ``2x2`` placeholder for image ID ``42``:
.. code-block:: sh
@@ -639,6 +644,70 @@ placements from the protocol perspective. They cannot be manipulated using
graphics commands, instead they should be moved, deleted, or modified by
manipulating the underlying Unicode placeholder as normal text.
.. _relative_image_placement:
Relative placements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 0.31.0
Support for positioning images relative to other images
You can specify that a placement is positioned relative to another placement.
This is particularly useful in combination with
:ref:`graphics_unicode_placeholders` above. It can be used to specify a single
transparent pixel image using a Unicode placeholder, which moves around
naturally with the text, the real image(s) can base their position relative to
the placeholder.
To specify that a placement should be relative to another, use the
``P=<image_id>,Q=<placement_id>`` keys, when creating the relative placement.
For example::
<ESC>_Ga=p,i=<image_id>,p=<placement_id>,P=<parent_img_id>,Q=<parent_placement_id><ESC>\
This will create a *relative placement* that refers to the *parent placement*
specified by the ``P`` and ``Q`` keys. When the parent placement moves, the
relative placement moves along with it. The relative placement can be offset
from the parent's location by a specified number of cells, using the ``H`` and
``V`` keys for horizontal and vertical displacement. Positive values move right
and down. Negative values move left and up. The origin is the top left cell of
the parent placement.
The lifetime of a relative placement is tied to the lifetime of its parent. If
its parent is deleted, it is deleted as well. If the image that the relative
placement is a placement of, has no more placements, the image is deleted as
well. Thus, a parent and its relative placements form a *group* that is managed
together.
A relative placement can refer to another relative placement as its parent.
Thus the relative placements can form a chain. It is implementation dependent
how long a chain of such placements is allowed, but implementation must allow
a chain of length at least 8. If the implementation max depth is exceeded, the
terminal must respond with the ``ETOODEEP`` error code.
Virtual placements created for Unicode placeholder based images cannot also be
relative placements. However, a relative placement can refer to a virtual
placement as its parent. When a virtual placement is the parent, its position
is derived from all the actual Unicode placeholder images that refer to it.
The x position is the minimum of all the placeholder x positions and the y
position is the minimum of all the placeholder y positions. If a client
attempts to make a virtual placement relative the terminal must respond with
the ``EINVAL`` error code.
Terminals are required to reject the creation of a relative placement
that would create a cycle, such as when A is relative to B and B is relative to
C and C is relative to A. In such cases, the terminal must respond with the
``ECYCLE`` error code.
If a client attempts to create a reference to a placement that does not exist
the terminal must respond with the ``ENOPARENT`` error code.
.. note::
Since a relative placement gets its position specified based on another
placement, instead of the cursor, the cursor must not move after a relative
position, regardless of the value of the ``C`` key to control cursor
movement.
Deleting images
---------------------
@@ -959,8 +1028,11 @@ Key Value Default Description
``C`` Positive integer ``0`` Cursor movement policy. ``0`` is the default, to move the cursor to after the image.
``1`` is to not move the cursor at all when placing the image.
``U`` Positive integer ``0`` Set to ``1`` to create a virtual placement for a Unicode placeholder.
``1`` is to not move the cursor at all when placing the image.
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
``P`` Positive integer ``0`` The id of a parent image for relative placement
``Q`` Positive integer ``0`` The id of a placement in the parent image for relative placement
``H`` 32-bit integer ``0`` The offset in cells in the horizontal direction for relative placement
``V`` 32-bit integer ``0`` The offset in cells in the vertical direction for relative placement
**Keys for animation frame loading**
-----------------------------------------------------------

View File

@@ -47,6 +47,13 @@ graphics protocol.
Another terminal file manager, with previews of file contents powered by kitty's
graphics protocol.
.. _tool_presentterm:
`presenterm <https://github.com/mfontanini/presenterm>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Show markdown based slides with images in your terminal, powered by the
kitty graphics protocol.
.. _tool_term_image:
`term-image <https://github.com/AnonymouX47/term-image>`__
@@ -232,16 +239,23 @@ running around inside nvim <https://github.com/giusgad/pets.nvim>`__.
Scrollback manipulation
-------------------------
.. tool_kitty_scrollback_nvim:
`kitty-scrollback.nvim <https://github.com/mikesmithgh/kitty-scrollback.nvim>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Browse the scrollback buffer with Neovim, with simple key actions for efficient
copy/paste and even execution of commands.
.. tool_kitty_search:
`kitty-search <https://github.com/trygveaa/kitty-kitten-search>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Live incremental search of the scrollback buffer.
.. tool_kitty_grab:
`kitty-grab <https://github.com/yurikhan/kitty_grab>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Keyboard based text selection for the kitty scrollback buffer.

View File

@@ -36,6 +36,8 @@ In addition to kitty, this protocol is also implemented in:
* The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__
* The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__
* The `alacritty terminal <https://github.com/alacritty/alacritty/pull/7125>`__
* The `rio terminal <https://github.com/raphamorim/rio/commit/cd463ca37677a0fc48daa8795ea46dadc92b1e95>`__
* The `notcurses library
<https://github.com/dankamongmen/notcurses/issues/2131>`__
* The `crossterm library
@@ -184,6 +186,12 @@ In the escape code, the modifier value is encoded as a decimal number which is
and so on. If the modifier field is not present in the escape code, its default
value is ``1`` which means no modifiers.
When the key event is related to an actual modifier key, the corresponding
modifier's bit must be set to the modifier state including the effect for the
current event. For example, when pressing the :kbd:`LEFT_CONTROL` key, the
``ctrl`` bit must be set and when releasing it, it must be reset. When both
left and right control keys are pressed and one is released, the release event
must have the ``ctrl`` bit set. See :iss:`6913` for discussion of this design.
.. _event_types:

View File

@@ -1,6 +1,11 @@
broadcast
==================================================
.. only:: man
Overview
--------------
*Type text in all kitty windows simultaneously*
The ``broadcast`` kitten can be used to type text simultaneously in all

View File

@@ -1,6 +1,11 @@
clipboard
==================================================
.. only:: man
Overview
--------------
*Copy/paste to the system clipboard from shell scripts*
.. highlight:: sh

View File

@@ -56,7 +56,7 @@ Kittens have full access to internal kitty APIs. However these are neither
entirely stable nor documented. You can instead use the kitty
:doc:`Remote control API </remote-control>`. Simply call
:code:`boss.call_remote_control()`, with the same arguments you
would pass to ``kitty @``. For example:
would pass to ``kitten @``. For example:
.. code-block:: python
@@ -337,9 +337,10 @@ You can parse and read the options in your kitten using the following code:
return ans
overrides = tuple(overrides) if overrides is not None else ()
opts_dict, paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
opts_dict, found_paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
opts = Options(opts_dict)
opts.config_paths = paths
opts.config_paths = found_paths
opts.all_config_paths = paths
opts.config_overrides = overrides
return opts

View File

@@ -1,6 +1,12 @@
Hints
==========
.. only:: man
Overview
--------------
|kitty| has a *hints mode* to select and act on arbitrary text snippets
currently visible on the screen. For example, you can press :sc:`open_url`
to choose any URL visible on the screen and then open it using your default web

View File

@@ -1,6 +1,12 @@
Hyperlinked grep
=================
.. only:: man
Overview
--------------
.. note::
As of ripgrep versions newer that 13.0 it supports hyperlinks
@@ -39,7 +45,8 @@ Now, run a search with::
Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to
open the file in :program:`vim` at the matching line. If you use some editor
other than :program:`vim`, you should adjust the :file:`open-actions.conf` file
accordingly.
accordingly. TO open links with the keyboard instead, use
:sc:`open_selected_hyperlink`.
Finally, add an alias to your shell's rc files to invoke the kitten as
:command:`hg`::

View File

@@ -1,13 +1,18 @@
icat
========================================
.. only:: man
Overview
--------------
*Display images in the terminal*
The ``icat`` kitten can be used to display arbitrary images in the |kitty|
terminal. Using it is as simple as::
kitten icat image.jpeg
kitten icat image.jpeg
It supports all image types supported by `ImageMagick
<https://www.imagemagick.org>`__. It even works over SSH. For details, see the

View File

@@ -3,6 +3,11 @@ Draw a GPU accelerated dock panel on your desktop
.. highlight:: sh
.. only:: man
Overview
--------------
You can use this kitten to draw a GPU accelerated panel on the edge of your
screen, that shows the output from an arbitrary terminal program.

View File

@@ -1,6 +1,12 @@
Query terminal
=================
.. only:: man
Overview
--------------
This kitten is used to query |kitty| from terminal programs about version, values
of various runtime options controlling its features, etc.

View File

@@ -1,6 +1,12 @@
Remote files
==============
.. only:: man
Overview
--------------
|kitty| has the ability to easily *Edit*, *Open* or *Download* files from a
computer into which you are SSHed. In your SSH session run::

View File

@@ -1,13 +1,18 @@
Truly convenient SSH
=========================================
.. only:: man
Overview
----------------
* Automatic :ref:`shell_integration` on remote hosts
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote hosts
* Automatic :opt:`re-use of existing connections <kitten-ssh.share_connections>` to avoid connection setup latency
* Make kitty itself available in the remote host :opt:`on demand <kitten-ssh.remote_kitty>`
* Make the kitten binary available in the remote host :opt:`on demand <kitten-ssh.remote_kitty>`
* Easily :opt:`change terminal colors <kitten-ssh.color_scheme>` when connecting to remote hosts

View File

@@ -1,6 +1,12 @@
Changing kitty colors
========================
.. only:: man
Overview
--------------
The themes kitten allows you to easily change color themes, from a collection of
over three hundred pre-built themes available at `kitty-themes
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::

View File

@@ -1,6 +1,12 @@
Transfer files
================
.. only:: man
Overview
--------------
.. versionadded:: 0.30.0
.. _rsync: https://en.wikipedia.org/wiki/Rsync

View File

@@ -1,6 +1,12 @@
Unicode input
================
.. only:: man
Overview
--------------
You can input Unicode characters by name, hex code, recently used and even an
editable favorites list. Press :sc:`input_unicode_character` to start the
unicode input kitten, shown below.

View File

@@ -83,7 +83,7 @@ the command line:
data to STDIN.
``@input-line-number``
Replaced the number of lines a pager should scroll to match the current
Replaced by the number of lines a pager should scroll to match the current
scroll position in kitty. See :opt:`scrollback_pager` for details.
``@scrolled-by``
@@ -133,8 +133,21 @@ functions for the events you are interested in, for example:
def on_close(boss: Boss, window: Window, data: Dict[str, Any])-> None:
# called when window is closed, typically when the program running in
# it exits.
# it exits
def on_set_user_var(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# called when a "user variable" is set or deleted on a window. Here
# data will contain key and value
def on_title_change(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# called when the window title is changed on a window. Here
# data will contain title and from_child. from_child will be True
# when a title change was requested via escape code from the program
# running in the terminal
def on_cmd_startstop(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
# called when the shell starts/stops executing a command. Here
# data will contain is_start and time.
Every callback is passed a reference to the global ``Boss`` object as well as
the ``Window`` object the action is occurring on. The ``data`` object is a dict

321
docs/mapping.rst Normal file
View File

@@ -0,0 +1,321 @@
:orphan:
Making your keyboard dance
==============================
.. highlight:: conf
kitty has extremely powerful facilities for mapping keyboard actions.
Things like combining actions, multi-key mappings, modal mappings,
mappings that send arbitrary text, and mappings dependent on the program
currently running in kitty.
Let's start with the basics. You can map a key press to an action in kitty using
the following syntax::
map ctrl+a new_window_with_cwd
This will map the key press :kbd:`Ctrl+a` to open a new :term:`window`
with the working directory set to the working directory of the current window.
This is the basic operation of the map directive, the tip of the iceberg, for
more read the sections below.
Combining multiple actions on a single keypress
-----------------------------------------------------
Multiple actions can be combined on a single keypress, like a macro. To do this
map the key press to the :ac:`combine` action::
map key combine <separator> action1 <separator> action2 <separator> action3 ...
For example::
map kitty_mod+e combine : new_window : next_layout
This will create a new window and switch to the next available layout. You can
also run arbitrarily powerful scripts on a key press. There are two major
techniques for doing this, using remote control scripts or using kittens.
Remote control scripts
^^^^^^^^^^^^^^^^^^^^^^^^^
These can be written in any language and use the "kitten" binary to control
kitty via its extensive :doc:`Remote control <remote-control>` API. First,
if you just want to run a single remote control command on a key press,
you can just do::
map f1 remote_control set-spacing margin=30
This will run the ``set-spacing`` command, changing window margins to 30 pixels. For
more complex scripts, write a script file in any language you like and save it
somewhere, preferably in the kitty configuration directory. Do not forget to make it
executable. In the script file you run remote control commands by running the
"kitten" binary, for example:
.. code-block:: sh
#!/bin/sh
kitten @ set-spacing margin=30
kitten @ new_window
...
The script can perform arbitrarily complex logic and actions, limited only by
the remote control API, that you can browse by running ``kitten @ --help``.
To run the script you created on a key press, use::
map f1 remote_control_script /path/to/myscript
Kittens
^^^^^^^^^^^^^
Here, kittens refer to Python scripts. The scripts have two parts, one that
runs as a regular command line program inside a kitty window to, for example,
ask the user for some input and a second part that runs inside the kitty
process itself and can perform any operation on the kitty UI, which is itself
implemented in Python. However, the kitty internal API is not documented and
can (very rarely) change, so kittens are harder to get started with than remote
control scripts. To run a kitten on a key press::
map f1 kitten mykitten.py
Many of kitty;s features are themselves implemented as kittens, for example,
:doc:`/kittens/unicode_input`, :doc:`/kittens/hints` and
:doc:`/kittens/themes`. To learn about writing your own kittens, see
:doc:`/kittens/custom`.
Syntax for specifying keys
-----------------------------
A mapping maps a key press to some action. In their most basic form, keypresses
are :code:`modifier+key`. Keys are identified simply by their lowercase Unicode
characters. For example: :code:`a` for the :kbd:`A` key, :code:`[` for the left
square bracket key, etc. For functional keys, such as :kbd:`Enter` or
:kbd:`Escape`, the names are present at :ref:`Functional key definitions
<functional>`. For modifier keys, the names are :kbd:`ctrl` (:kbd:`control`,
:kbd:`⌃`), :kbd:`shift` (:kbd:`⇧`), :kbd:`alt` (:kbd:`opt`, :kbd:`option`,
:kbd:`⌥`), :kbd:`super` (:kbd:`cmd`, :kbd:`command`, :kbd:`⌘`).
Additionally, you can use the name :opt:`kitty_mod` as a modifier, the default
value of which is :kbd:`ctrl+shift`. The default kitty shortcuts are defined
using this value, so by changing it in :file:`kitty.conf` you can change
all the modifiers used by all the default shortcuts.
On Linux, you can also use XKB names for functional keys that don't have kitty
names. See :link:`XKB keys
<https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h>`
for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
prefix. Note that you can only use an XKB key name for keys that are not known
as kitty keys.
Finally, you can use raw system key codes to map keys, again only for keys that
are not known as kitty keys. To see the system key code for a key, start kitty
with the :option:`kitty --debug-input` option, kitty will output some debug text
for every key event. In that text look for :code:`native_code`, the value
of that becomes the key name in the shortcut. For example:
.. code-block:: none
on_key_input: glfw key: 0x61 native_code: 0x61 action: PRESS mods: none text: 'a'
Here, the key name for the :kbd:`A` key is :code:`0x61` and you can use it with::
map ctrl+0x61 something
This maps :kbd:`Ctrl+A` to something.
Multi-key mappings
--------------------
A mapping in kitty can involve pressing multiple keys in sequence, with the
syntax shown below::
map key1>key2>key3 action
For example::
map ctrl+f>2 set_font_size 20
The default mappings to run the :doc:`hints kitten </kittens/hints>` to select text on the screen are
examples of multi-key mappings.
Unmapping default shortcuts
-----------------------------
kitty comes with dozens of default keyboard mappings for common operations. See
:doc:`actions` for the full list of actions and the default shortcuts that map
to them. You can unmap an individual shortcut, so that it is passed on to the
program running inside kitty, by mapping it to nothing, for example::
map kitty_mod+enter
This unmaps the default shortcut :sc:`new_window` to open a new window. Almost
all default shortcuts are of the form ``modifier + key`` where the
modifier defaults to :kbd:`Ctrl+Shift` and can be changed using the :opt:`kitty_mod` setting
in :file:`kitty.conf`.
If you want to clear all default shortcuts, you can use
:opt:`clear_all_shortcuts` in :file:`kitty.conf`.
If you would like kitty to completely ignore a key event, not even sending it to
the program running in the terminal, map it to :ac:`discard_event`::
map kitty_mod+f1 discard_event
.. _conditional_mappings:
Conditional mappings depending on the state of the focused window
----------------------------------------------------------------------
Sometimes, you may want different mappings to be active when running a
particular program in kitty, perhaps because it has some native functionality
that duplicates kitty functions or there is a conflict, etc. kitty has the
ability to create mappings that work only when the currently focused window
matches some criteria, such as when it has a particular title or user variable.
Let's see some examples::
map --when-focus-on title:keyboard.protocol kitty_mod+t
This will cause :kbd:`kitty_mod+t` (the default shortcut for opening a new tab)
to be unmapped only when the focused window
has :code:`keyboard protocol` in its title. Run the show-key kitten as::
kitten show-key -m kitty
Press :kbd:`ctrl+shift+t` and instead of a new tab opening, you will
see the key press being reported by the kitten. :code:`--when-focus-on` can test
the focused window using very powerful criteria, see :ref:`search_syntax` for
details. A more practical example unmaps the key when the focused window is running vim::
map --when-focus-on var:in_editor
In order to make this work, you need the following lines in your :file:`.vimrc`::
let &t_ti = &t_ti . "\\033]1337;SetUserVar=in_editor=MQo\\007"
let &t_te = &t_te . "\\033]1337;SetUserVar=in_editor\\007"
These cause vim to set the :code:`in_editor` variable in kitty and unset it when leaving vim.
Sending arbitrary text or keys to the program running in kitty
--------------------------------------------------------------------------------
This is accomplished by using ``map`` with :sc:`send_text <send_text>` in :file:`kitty.conf`.
For example::
map f1 send_text normal,application Hello, world!
Now, pressing :kbd:`f1` will cause ``Hello, world!`` to show up at your shell
prompt. To have the shell execute a command sent via ``send_text`` you need to
also simulate pressing the enter key which is ``\r``. For example::
map f1 send_text normal,application echo Hello, world!\r
Now, if you press :kbd:`f1` when at shell prompt it will run the ``echo Hello,
world!`` command.
To have one key press send another key press, use :ac:`send_key`::
map alt+s send_key ctrl+s
This causes the program running in kitty to receive the :kbd:`ctrl+s` key when
you press the :kbd:`alt+s` key. To see this in action, run::
kitten show-key -m kitty
Which will print out what key events it receives.
.. _modal_mappings:
Modal mappings
--------------------------
kitty has the ability, like vim, to use *modal* key maps. Except that unlike
vim it allows you to define your own arbitrary number of modes. To create a new
mode, use ``map --new-mode <my mode name> <shortcut to enter mode>``. For
example, lets create a mode to manage windows: switching focus, moving the window, etc.::
# Create a new "manage windows" mode (mw)
map --new-mode mw kitty_mod+f7
# Switch focus to the neighboring window in the indicated direction using arrow keys
map --mode mw left neighboring_window left
map --mode mw right neighboring_window right
map --mode mw up neighboring_window up
map --mode mw down neighboring_window down
# Move the active window in the indicated direction
map --mode mw shift+up move_window up
map --mode mw shift+left move_window left
map --mode mw shift+right move_window right
map --mode mw shift+down move_window down
# Resize the active window
map --mode mw n resize_window narrower
map --mode mw w resize_window wider
map --mode mw t resize_window taller
map --mode mw s resize_window shorter
# Exit the manage window mode
map --mode mw esc pop_keyboard_mode
Now, if you run kitty as:
.. code-block:: sh
kitty -o enabled_layouts=vertical --session <(echo "launch\nlaunch\nlaunch")
Press :kbd:`Ctrl+Shift+F7` to enter the mode and then press the up and
down arrow keys to focus the next/previous window. Press :kbd:`Shift+Up` or
:kbd:`Shift+Down` to move the active window up and down. Press :kbd:`t` to make
the active window taller and :kbd:`s` to make it shorter. To exit the mode
press :kbd:`Esc`.
Pressing an unknown key while in a custom keyboard mode by default
beeps. This can be controlled by the ``map --on-unknown`` option as shown
below::
# Beep on unknown keys
map --new-mode XXX --on-unknown beep ...
# Ingore unknown keys silently
map --new-mode XXX --on-unknown ignore ...
# Beep and exit the keyboard mode on unknown key
map --new-mode XXX --on-unknown end ...
# Pass unknown keys to the program running in the active window
map --new-mode XXX --on-unknown passthrough ...
When a key matches an action in a custom keyboard mode, the action is performed
and the custom keyboard mode remains in effect. If you would rather have the
keyboard mode end after the action you can use ``map --on-action`` as shown
below::
# Have this keyboard mode automatically exit after performing any action
map --new-mode XXX --on-action end ...
All mappable actions
------------------------
There is a list of :doc:`all mappable actions <actions>`.
Debugging mapping issues
------------------------------
To debug mapping issues, kitty has several facilities. First, when you run
kitty with the ``--debug-input`` command line flag it outputs details
about all key events it receives form the system and how they are handled.
To see what key events are sent to applications, run kitty like this::
kitty kitten show-key
Press the keys you want to debug and the kitten will print out the bytes it
receives. Note that this uses the legacy terminal keyboard protocol that does
not support all keys and key events. To debug the :doc:`full kitty keyboard
protocol that <keyboard-protocol>` that is nowadays being adopted by more and
more programs, use::
kitty kitten show-key -m kitty

View File

@@ -98,6 +98,10 @@ You can :doc:`create your own kittens to scratch your own itches
For a list of all the builtin kittens, :ref:`see here <kittens>`.
Additionally, you can use the :ref:`watchers <Watchers>` framework
to create Python scripts that run in response to various events such as windows
being resized, closing, having their titles changed, etc.
.. toctree::
:hidden:

174
docs/pointer-shapes.rst Normal file
View File

@@ -0,0 +1,174 @@
Mouse pointer shapes
=======================
.. versionadded:: 0.31.0
This is a simple escape code that can be used by terminal programs to change
the shape of the mouse pointer. This is useful for buttons/links, dragging to
resize panes, etc. It is based on the original escape code proposal from xterm
however, it properly specifies names for the different shapes in a system
independent manner, adds a stack for easy push/pop of shapes, allows programs
to query support and specifies interaction with other terminal state.
The escape code is of the form::
<OSC> 22 ; <optional first char> <comma-separates list of shape names> <ESC>\
Here, ``<OSC>`` is the bytes ``<ESC>]`` and ``<ESC>`` is the byte ``0x1b``.
Spaces in the above are present for clarity only and should not be actually used.
First some examples::
# Set the pointer to a pointing hand
<OSC> 22 ; pointer <ESC>\
# Reset the pointer to default
<OSC> 22 ; <ESC>\
# Push a shape onto the stack making it the current shape
<OSC> 22 ; >wait <ESC>\
# Pop a shape off the stack restoring to the previous shape
<OSC> 22 ; < <ESC>\
# Query the terminal for what the currently set shape is
<OSC> 22 ; ?__current__ <ESC>\
To demo the various shapes, simply run the following command inside kitty::
kitten mouse-demo
For more details see below.
Setting the pointer shape
-------------------------------
For set operations, the optional first char can be either ``=`` or omitted.
Follow the first char with the name of the shape. See the
:ref:`pointer_shape_names` table.
Pushing and popping shapes onto the stack
---------------------------------------------
The terminal emulator maintains a stack of shapes. To add shapes to the stack,
the optional first char must be ``>`` followed by a comma separated list of
shape names. See the :ref:`pointer_shape_names` table. All the specified names
are added to the stack, with the last name being the top of the stack and the
current shape. If the stack is full, the entry at the bottom of the stack is
evicted. Terminal implementations are free to choose an appropriate maximum
stack size, with a minimum stack size of 16.
To pop shapes of the top of the stack the optional first char must be ``<``.
The comma separated list of names is ignored. Once the stack is empty further
pops have no effect. An empty stack means the terminal is free to use whatever
pointer shape it likes.
Querying support
-------------------
Terminal programs can ask the terminal about this feature by setting the
optional first char to ``?``. The comma separated list of names is then
considered the query to which the terminal must respond with an OSC 22 code.
For example::
<OSC> 22 ; ?__current__ <ESC>\
results in
<OSC> 22 ; shape_name <ESC>\
Here, ``shape_name`` will be a name from the table of shape names below or ``0``
if the stack is empty, i.e., no shape is currently set.
To check if the terminal supports some shapes, pass the shape names and the
terminal will reply with a comma separated list of zeros and ones where 1 means
the shape name is supported and zero means it is not. For example::
<OSC> 22 ; ?pointer,crosshair,no-such-name,wait <ESC>\
results in
<OSC> 22 ; 1,1,0,1 <ESC>\
In addition to ``__current__`` there are a couple of other special names::
__default__ - The terminal responds with the shape name of the shape used by default
__grabbed__ - The terminal responds with the shape name of the shape used when the mouse is "grabbed"
Interaction with other terminal features
---------------------------------------------
The terminal must maintain separate shape stacks for the *main* and *alternate*
screens. This allows full screen programs, which are likely to be the main
consumers of this feature, to easily temporarily switch back from the alternate screen,
without needing to worry about pointer shape state. Think of suspending a
terminal editor to get back to the shell, for example.
Resetting the terminal must empty both the shape stacks.
When dragging to select text, the terminal is free to ignore any mouse pointer
shape specified using this escape code in favor of one appropriate for
dragging. Similarly, when hovering over a URL or OSC 8 based hyperlink, the
terminal may choose to change the mouse pointer regardless of the value set by
this escape code.
This feature is independent of mouse reporting. The changed pointer shapes apply
regardless of whether the terminal program has enabled mouse reporting or not.
.. _pointer_shape_names:
Pointer shape names
----------------------------------
There is a well defined set of shape names that all conforming terminal
emulators must support. The list is based on the names used by the `cursor
property in the CSS standard
<https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>`__, click the link to
see representative images for the names. Valid names must consist of only the
characters from the set ``a-z0-9_-``.
.. start list of shape css names (auto generated by gen-key-constants.py do not edit)
#. alias
#. cell
#. copy
#. crosshair
#. default
#. e-resize
#. ew-resize
#. grab
#. grabbing
#. help
#. move
#. n-resize
#. ne-resize
#. nesw-resize
#. no-drop
#. not-allowed
#. ns-resize
#. nw-resize
#. nwse-resize
#. pointer
#. progress
#. s-resize
#. se-resize
#. sw-resize
#. text
#. vertical-text
#. w-resize
#. wait
#. zoom-in
#. zoom-out
.. end list of shape css names
To demo the various shapes, simply run the following command inside kitty::
kitten mouse-demo
Legacy xterm compatibility
----------------------------
The original xterm proposal for this escape code used shape names from the
file:`X11/cursorfont.h` header on X11 based systems. Terminal implementations
wishing to maintain compatibility with xterm can also implement these names as
aliases for the CSS based names defined in the :ref:`pointer_shape_names` table.
The simplest mode of operation of this escape code, which is no leading
optional char and a single shape name is compatible with xterm.

View File

@@ -29,6 +29,7 @@ please do so by opening issues in the `GitHub bug tracker
keyboard-protocol
file-transfer-protocol
desktop-notifications
pointer-shapes
unscroll
color-stack
deccara

View File

@@ -23,25 +23,25 @@ turn it on explicitly at the command line.
Now, in the new |kitty| window, enter the command::
kitty @ launch --title Output --keep-focus cat
kitten @ launch --title Output --keep-focus cat
This will open a new window, running the :program:`cat` program that will appear
next to the current window.
Let's send some text to this new window::
kitty @ send-text --match cmdline:cat Hello, World
kitten @ send-text --match cmdline:cat Hello, World
This will make ``Hello, World`` show up in the window running the :program:`cat`
program. The :option:`kitty @ send-text --match` option is very powerful, it
program. The :option:`kitten @ send-text --match` option is very powerful, it
allows selecting windows by their titles, the command line of the program
running in the window, the working directory of the program running in the
window, etc. See :ref:`kitty @ send-text --help <at-send-text>` for details.
window, etc. See :ref:`kitten @ send-text --help <at-send-text>` for details.
More usefully, you can pipe the output of a command running in one window to
another window, for example::
ls | kitty @ send-text --match 'title:^Output' --stdin
ls | kitten @ send-text --match 'title:^Output' --stdin
This will show the output of :program:`ls` in the output window instead of the
current window. You can use this technique to, for example, show the output of
@@ -50,56 +50,55 @@ are endless.
You can even have things you type show up in a different window. Run::
kitty @ send-text --match 'title:^Output' --stdin
kitten @ send-text --match 'title:^Output' --stdin
And type some text, it will show up in the output window, instead of the current
window. Type :kbd:`Ctrl+D` when you are ready to stop.
Now, let's open a new tab::
kitty @ launch --type=tab --tab-title "My Tab" --keep-focus bash
kitten @ launch --type=tab --tab-title "My Tab" --keep-focus bash
This will open a new tab running the bash shell with the title "My Tab".
We can change the title of the tab to "New Title" with::
kitty @ set-tab-title --match 'title:^My' New Title
kitten @ set-tab-title --match 'title:^My' New Title
Let's change the title of the current tab::
kitty @ set-tab-title Master Tab
kitten @ set-tab-title Master Tab
Now lets switch to the newly opened tab::
kitty @ focus-tab --match 'title:^New'
kitten @ focus-tab --match 'title:^New'
Similarly, to focus the previously opened output window (which will also switch
back to the old tab, automatically)::
kitty @ focus-window --match 'title:^Output'
kitten @ focus-window --match 'title:^Output'
You can get a listing of available tabs and windows, by running::
kitty @ ls
kitten @ ls
This outputs a tree of data in JSON format. The top level of the tree is all
:term:`OS windows <os_window>`. Each OS window has an id and a list of
:term:`tabs <tab>`. Each tab has its own id, a title and a list of :term:`kitty
windows <window>`. Each window has an id, title, current working directory,
process id (PID) and command-line of the process running in the window. You can
use this information with :option:`kitty @ focus-window --match` to control
use this information with :option:`kitten @ focus-window --match` to control
individual windows.
As you can see, it is very easy to control |kitty| using the ``kitty @``
As you can see, it is very easy to control |kitty| using the ``kitten @``
messaging system. This tutorial touches only the surface of what is possible.
See ``kitty @ --help`` for more details.
See ``kitten @ --help`` for more details.
In the example's above, ``kitty @`` messaging works only when run
In the example's above, ``kitten @`` messaging works only when run
inside a |kitty| window, not anywhere. But, within a |kitty| window it even
works over SSH. If you want to control |kitty| from programs/scripts not running
inside a |kitty| window, see the section on :ref:`using a socket for remote control <rc_via_socket>`
below.
Note that if all you want to do is run a single |kitty| "daemon" and have
subsequent |kitty| invocations appear as new top-level windows, you can use the
simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that.
@@ -115,23 +114,23 @@ First, start |kitty| as::
The :option:`kitty --listen-on` option tells |kitty| to listen for control
messages at the specified UNIX-domain socket. See ``kitty --help`` for details.
Now you can control this instance of |kitty| using the :option:`kitty @ --to`
command line argument to ``kitty @``. For example::
Now you can control this instance of |kitty| using the :option:`kitten @ --to`
command line argument to ``kitten @``. For example::
kitty @ --to unix:/tmp/mykitty ls
kitten @ --to unix:/tmp/mykitty ls
The builtin kitty shell
--------------------------
You can explore the |kitty| command language more easily using the builtin
|kitty| shell. Run ``kitty @`` with no arguments and you will be dropped into
|kitty| shell. Run ``kitten @`` with no arguments and you will be dropped into
the |kitty| shell with completion for |kitty| command names and options.
You can even open the |kitty| shell inside a running |kitty| using a simple
keyboard shortcut (:sc:`kitty_shell` by default).
.. note:: This has the added advantage that you don't need to use
.. note:: Using the keyboard shortcut has the added advantage that you don't need to use
:opt:`allow_remote_control` to make it work.
@@ -144,7 +143,7 @@ create a shortcut such as::
map ctrl+k launch --allow-remote-control some_program
Then programs running in windows created with that shortcut can use ``kitty @``
Then programs running in windows created with that shortcut can use ``kitten @``
to control kitty. Note that any program with the right level of permissions can
still write to the pipes of any other program on the same computer and therefore
can control |kitty|. It can, however, be useful to block programs running on
@@ -156,7 +155,7 @@ other computers (for example, over SSH) or as other users.
kitty, as if you were running with :opt:`allow_remote_control` turned on.
You can further restrict what is allowed in these windows by using
:option:`kitty @ launch --remote-control-password`.
:option:`kitten @ launch --remote-control-password`.
Fine grained permissions for remote control
@@ -181,19 +180,19 @@ Let's see some examples:
Now, using this password, you can, in scripts run the command::
kitty @ --password="control colors" set-colors background=red
kitten @ --password="control colors" set-colors background=red
Any script with access to the password can now change colors in kitty using
remote control, but only that and nothing else. You can even supply the
password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the
file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly.
See :option:`kitty @ --password-file` and :option:`kitty @ --password-env`.
See :option:`kitten @ --password-file` and :option:`kitten @ --password-env`.
The :opt:`remote_control_password` can be specified multiple times to create
different passwords with different capabilities. Run the following to get a
list of all action names::
kitty @ --help
kitten @ --help
You can even use glob patterns to match action names, for example:
@@ -203,7 +202,7 @@ You can even use glob patterns to match action names, for example:
If no action names are specified, all actions are allowed.
If ``kitty @`` is run with a password that is not present in
If ``kitten @`` is run with a password that is not present in
:file:`kitty.conf`, then kitty will interactively prompt the user to allow or
disallow the remote control request. The user can choose to allow or disallow
either just that request or all requests using that password. The user's
@@ -216,7 +215,7 @@ decision is remembered for the duration of that kitty instance.
using a password, :ref:`rc_crypto` is used to ensure the password
is kept secure. This does mean that using password based authentication
is slower as the entire command is encrypted before transmission. This
can be noticeable when using a command like ``kitty @ set-background-image``
can be noticeable when using a command like ``kitten @ set-background-image``
which transmits large amounts of image data. Also, the clock on the remote
system must match (within a few minutes) the clock on the local system.
kitty uses a time based nonce to minimise the potential for replay attacks.
@@ -274,7 +273,7 @@ you can map it in :file:`kitty.conf`. For example::
Then pressing the :kbd:`F1` key will set the active window margins to
:code:`30`. The syntax for what follows :ac:`remote_control` is exactly the same
as the syntax for what follows :code:`kitty @` above.
as the syntax for what follows :code:`kitten @` above.
If you wish to ignore errors from the command, prefix the command with an
``!``. For example, the following will not return an error when no windows
@@ -282,6 +281,17 @@ are matched::
map f1 remote_control !focus-window --match XXXXXX
If you wish to run a more complex script, you can use::
map f1 remote_control_script /path/to/myscript
In this script you can use ``kitten @`` to run as many remote
control commands as you like and process their output.
:ac:`remote_control_script` is really just an alias for the
:ac:`launch` command with ``--type=background --allow-remote-control``.
For more advanced usage, including fine grained permissions, setting
env vars, etc. see the docs for the :doc:`launch <launch>` command.
.. note:: You do not need :opt:`allow_remote_control` to use these mappings,
as they are not actual remote programs, but are simply a way to reuse the
remote control infrastructure via keybings.
@@ -320,7 +330,7 @@ Matching windows and tabs
Many remote control operations operate on windows or tabs. To select these, the
:code:`--match` option is often used. This allows matching using various
sophisticated criteria such as title, ids, cmdlines, etc. These criteria are
sophisticated criteria such as title, ids, command lines, etc. These criteria are
expressions of the form :code:`field:query`. Where :italic:`field` is the field
against which to match and :italic:`query` is the expression to match. They can
be further combined using Boolean operators, best illustrated with some
@@ -331,9 +341,11 @@ examples::
not id:1
(id:2 or id:3) and title:something
.. include:: generated/matching.rst
.. toctree::
:hidden:
rc_protocol
.. include:: generated/cli-kitty-at.rst
.. include:: generated/cli-kitten-at.rst

View File

@@ -93,7 +93,11 @@ no-complete
no-sudo
Do not alias :program:`sudo` to ensure the kitty terminfo files are
available in the sudo environment
available in the sudo environment. This is needed if you have sudo
configured to disable setting of environment variables on the command line.
By default, if sudo is configured to allow all commands for the current
user, setting of environment variables at the command line is also allowed.
Only if commands are restricted is this needed.
More ways to browse command output

4
gen/README.rst Normal file
View File

@@ -0,0 +1,4 @@
Scripts to generate code for various things like keys, mouse cursors, unicode
data etc. Some of these generate code that is checked into version control.
Some generate ephemeral code used during builds. Ephemeral code is in files
with a _generated.[h|go|c] extension.

43
gen/__main__.py Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from typing import List
def main(args: List[str]=sys.argv) -> None:
os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.getcwd())
if len(args) == 1:
raise SystemExit('usage: python gen which')
which = args[1]
del args[1]
if which == 'apc-parsers':
from gen.apc_parsers import main
main(args)
elif which == 'config':
from gen.config import main
main(args)
elif which == 'srgb-lut':
from gen.srgb_lut import main
main(args)
elif which == 'key-constants':
from gen.key_constants import main
main(args)
elif which == 'go-code':
from gen.go_code import main
main(args)
elif which == 'wcwidth':
from gen.wcwidth import main
main(args)
elif which == 'cursors':
from gen.cursors import main
main(args)
else:
raise SystemExit(f'Unknown which: {which}')
if __name__ == '__main__':
main()

View File

@@ -1,11 +1,17 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess
import sys
from collections import defaultdict
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]]
@@ -276,9 +282,20 @@ def graphics_parser() -> None:
'z': ('z_index', 'int'),
'C': ('cursor_movement', 'uint'),
'U': ('unicode_placement', 'uint'),
'P': ('parent_id', 'uint'),
'Q': ('parent_placement_id', 'uint'),
'H': ('offset_from_parent_x', 'int'),
'V': ('offset_from_parent_y', 'int'),
}
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
write_header(text, 'kitty/parse-graphics-command.h')
graphics_parser()
def main(args: List[str]=sys.argv) -> None:
graphics_parser()
if __name__ == '__main__':
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'apc-parsers'])

View File

@@ -2,12 +2,19 @@
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import subprocess
import sys
from typing import List
from kitty.conf.generate import write_output
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
with open(path, 'r+') as f:
@@ -33,7 +40,7 @@ def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ')
subprocess.check_call(['gofmt', '-w', path])
def main() -> None:
def main(args: List[str]=sys.argv) -> None:
from kitty.options.definition import definition
write_output('kitty', definition)
nullable_colors = []
@@ -51,4 +58,6 @@ def main() -> None:
if __name__ == '__main__':
main()
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'config'])

154
gen/cursors.py Executable file
View File

@@ -0,0 +1,154 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
import os
import subprocess
import sys
from typing import List
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from .key_constants import patch_file
# References for these names:
# CSS:choices_for_{option.name} https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
# XCursor: https://tronche.com/gui/x/xlib/appendix/b/ + Absolute chaos
# Wayland: https://wayland.app/protocols/cursor-shape-v1
# Cocoa: https://developer.apple.com/documentation/appkit/nscursor + secret apple selectors + SDL_cocoamouse.m
# kitty_names CSS_name XCursor_names Wayland_name Cocoa_name
cursors = '''\
arrow default default,!left_ptr default arrowCursor
beam,text text text,!xterm,ibeam text IBeamCursor
pointer,hand pointer pointing_hand,pointer,!hand2,hand pointer pointingHandCursor
help help help,!question_arrow,whats_this help help:arrowCursor
wait wait wait,!clock,watch wait busybutclickable:arrowCursor
progress progress progress,half-busy,left_ptr_watch progress busybutclickable:arrowCursor
crosshair crosshair crosshair,!tcross crosshair crosshairCursor
cell cell cell,!plus,!cross cell cell:crosshairCursor
vertical-text vertical-text vertical-text vertical-text IBeamCursorForVerticalLayout
move move move,!fleur,pointer-move move move:openHandCursor
e-resize e-resize e-resize,!right_side e_resize resizeRightCursor
ne-resize ne-resize ne-resize,!top_right_corner ne_resize resizenortheast:_windowResizeNorthEastSouthWestCursor
nw-resize nw-resize nw-resize,!top_left_corner nw_resize resizenorthwest:_windowResizeNorthWestSouthEastCursor
n-resize n-resize n-resize,!top_side n_resize resizeUpCursor
se-resize se-resize se-resize,!bottom_right_corner se_resize resizesoutheast:_windowResizeNorthWestSouthEastCursor
sw-resize sw-resize sw-resize,!bottom_left_corner sw_resize resizesouthwest:_windowResizeNorthEastSouthWestCursor
s-resize s-resize s-resize,!bottom_side s_resize resizeDownCursor
w-resize w-resize w-resize,!left_side w_resize resizeLeftCursor
ew-resize ew-resize ew-resize,!sb_h_double_arrow,split_h ew_resize resizeLeftRightCursor
ns-resize ns-resize ns-resize,!sb_v_double_arrow,split_v ns_resize resizeUpDownCursor
nesw-resize nesw-resize nesw-resize,size_bdiag,size-bdiag nesw_resize _windowResizeNorthEastSouthWestCursor
nwse-resize nwse-resize nwse-resize,size_fdiag,size-fdiag nwse_resize _windowResizeNorthWestSouthEastCursor
zoom-in zoom-in zoom-in,zoom_in zoom_in zoomin:arrowCursor
zoom-out zoom-out zoom-out,zoom_out zoom_out zoomout:arrowCursor
alias alias dnd-link alias dragLinkCursor
copy copy dnd-copy copy dragCopyCursor
not-allowed not-allowed not-allowed,forbidden,crossed_circle not_allowed operationNotAllowedCursor
no-drop no-drop no-drop,dnd-no-drop no_drop operationNotAllowedCursor
grab grab grab,openhand,!hand1 grab openHandCursor
grabbing grabbing grabbing,closedhand,dnd-none grabbing closedHandCursor
'''
def main(args: List[str]=sys.argv) -> None:
glfw_enum = []
css_names = []
glfw_xc_map = {}
glfw_xfont_map = []
kitty_to_enum_map = {}
enum_to_glfw_map = {}
enum_to_css_map = {}
glfw_cocoa_map = {}
glfw_css_map = {}
css_to_enum = {}
xc_to_enum = {}
glfw_wayland = {}
for line in cursors.splitlines():
line = line.strip()
if line:
names_, css, xc_, wayland, cocoa = line.split()
names, xc = names_.split(','), xc_.split(',')
base = css.replace('-', '_').upper()
glfw_name = 'GLFW_' + base + '_CURSOR'
enum_name = base + '_POINTER'
enum_to_glfw_map[enum_name] = glfw_name
enum_to_css_map[enum_name] = css
glfw_css_map[glfw_name] = css
css_to_enum[css] = enum_name
css_names.append(css)
glfw_wayland[glfw_name] = 'WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_' + wayland.replace('-', '_').upper()
for n in names:
kitty_to_enum_map[n] = enum_name
glfw_enum.append(glfw_name)
glfw_xc_map[glfw_name] = ', '.join(f'''"{x.replace('!', '')}"''' for x in xc)
for x in xc:
if x.startswith('!'):
glfw_xfont_map.append(f"case {glfw_name}: return set_cursor_from_font(cursor, {'XC_' + x[1:]});")
break
else:
items = tuple('"' + x.replace('!', '') + '"' for x in xc)
glfw_xfont_map.append(f'case {glfw_name}: return try_cursor_names(cursor, {len(items)}, {", ".join(items)});')
for x in xc:
x = x.lstrip('!')
xc_to_enum[x] = enum_name
parts = cocoa.split(':', 1)
if len(parts) == 1:
if parts[0].startswith('_'):
glfw_cocoa_map[glfw_name] = f'U({glfw_name}, {parts[0]});'
else:
glfw_cocoa_map[glfw_name] = f'C({glfw_name}, {parts[0]});'
else:
glfw_cocoa_map[glfw_name] = f'S({glfw_name}, {parts[0]}, {parts[1]});'
for x, v in xc_to_enum.items():
if x not in css_to_enum:
css_to_enum[x] = v
glfw_enum.append('GLFW_INVALID_CURSOR')
patch_file('glfw/glfw3.h', 'mouse cursor shapes', '\n'.join(f' {x},' for x in glfw_enum))
patch_file('glfw/wl_window.c', 'glfw to wayland mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_wayland.items()))
patch_file('glfw/wl_window.c', 'glfw to xc mapping', '\n'.join(f' C({g}, {x});' for g, x in glfw_xc_map.items()))
patch_file('glfw/x11_window.c', 'glfw to xc mapping', '\n'.join(f' {x}' for x in glfw_xfont_map))
patch_file('kitty/data-types.h', 'mouse shapes', '\n'.join(f' {x},' for x in enum_to_glfw_map))
patch_file(
'kitty/options/definition.py', 'pointer shape names', '\n'.join(f' {x!r},' for x in kitty_to_enum_map),
start_marker='# ', end_marker='',
)
patch_file('kitty/options/to-c.h', 'pointer shapes', '\n'.join(
f' else if (strcmp(name, "{k}") == 0) return {v};' for k, v in kitty_to_enum_map.items()))
patch_file('kitty/glfw.c', 'enum to glfw', '\n'.join(
f' case {k}: set_glfw_mouse_cursor(w, {v}); break;' for k, v in enum_to_glfw_map.items()))
patch_file('kitty/glfw.c', 'name to glfw', '\n'.join(
f' if (strcmp(name, "{k}") == 0) return {enum_to_glfw_map[v]};' for k, v in kitty_to_enum_map.items()))
patch_file('kitty/glfw.c', 'glfw to css', '\n'.join(
f' case {g}: return "{c}";' for g, c in glfw_css_map.items()
))
patch_file('kitty/screen.c', 'enum to css', '\n'.join(
f' case {e}: ans = "{c}"; break;' for e, c in enum_to_css_map.items()))
patch_file('kitty/screen.c', 'css to enum', '\n'.join(
f' else if (strcmp("{c}", css_name) == 0) s = {e};' for c, e in css_to_enum.items()))
patch_file('glfw/cocoa_window.m', 'glfw to cocoa', '\n'.join(f' {x}' for x in glfw_cocoa_map.values()))
patch_file('docs/pointer-shapes.rst', 'list of shape css names', '\n'.join(
f'#. {x}' if x else '' for x in [''] + sorted(css_names) + ['']), start_marker='.. ', end_marker='')
patch_file('tools/tui/loop/mouse.go', 'pointer shape enum', '\n'.join(
f'\t{x} PointerShape = {i}' for i, x in enumerate(enum_to_glfw_map)), start_marker='// ', end_marker='')
patch_file('tools/tui/loop/mouse.go', 'pointer shape tostring', '\n'.join(
f'''\tcase {x}: return "{x.lower().rpartition('_')[0].replace('_', '-')}"''' for x in enum_to_glfw_map), start_marker='// ', end_marker='')
patch_file('tools/cmd/mouse_demo/main.go', 'all pointer shapes', '\n'.join(
f'\tloop.{x},' for x in enum_to_glfw_map), start_marker='// ', end_marker='')
subprocess.check_call(['glfw/glfw.py'])
if __name__ == '__main__':
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'cursors'])

View File

@@ -49,6 +49,12 @@ from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
from kitty.remote_control import global_options_spec
from kitty.rgb import color_names
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
changed: List[str] = []
@@ -300,6 +306,7 @@ json_field_types: Dict[str, str] = {
def go_field_type(json_field_type: str) -> str:
json_field_type = json_field_type.partition('=')[0]
q = json_field_types.get(json_field_type)
if q:
return q
@@ -318,6 +325,7 @@ class JSONField:
field_def = line.split(':', 1)[0]
self.required = False
self.field, self.field_type = field_def.split('/', 1)
self.field_type, self.special_parser = self.field_type.partition('=')[::2]
if self.field.endswith('+'):
self.required = True
self.field = self.field[:-1]
@@ -364,14 +372,17 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
if oq in option_map:
o = option_map[oq]
used_options.add(oq)
optstring = f'options_{name}.{o.go_var_name}'
if field.special_parser:
optstring = f'{field.special_parser}({optstring})'
if field.field_type == 'str':
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
jc.append(f'payload.{field.struct_field_name} = escaped_string({optstring})')
elif field.field_type == 'list.str':
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings(options_{name}.{o.go_var_name})')
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings({optstring})')
elif field.field_type == 'dict.str':
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings(options_{name}.{o.go_var_name})')
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings({optstring})')
else:
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
jc.append(f'payload.{field.struct_field_name} = {optstring}')
elif field.field in handled_fields:
pass
else:
@@ -493,12 +504,12 @@ def kitten_clis() -> None:
od.append(opt.struct_declaration())
if ac is not None:
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
if not kcd:
print('specialize_command(ans)')
if has_underscore:
print("clone := root.AddClone(ans.Group, ans)")
print('clone.Hidden = false')
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
if not kcd:
print('specialize_command(ans)')
print('}')
print('type Options struct {')
print('\n'.join(od))
@@ -581,11 +592,11 @@ const VersionString string = "{kc.str_version}"
const WebsiteBaseURL string = "{kc.website_base_url}"
const FileTransferCode int = {FILE_TRANSFER_CODE}
const ImagePlaceholderChar rune = {placeholder_char}
const VCSRevision string = ""
const SSHControlMasterTemplate = "{kc.ssh_control_master_template}"
const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}"
const IsFrozenBuild bool = false
const IsStandaloneBuild bool = false
var VCSRevision string = ""
var IsFrozenBuild string = ""
var IsStandaloneBuild string = ""
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
const HintsDefaultRegex = `{DEFAULT_REGEX}`
const DefaultTermName = `{Options.term}`
@@ -822,6 +833,7 @@ def generate_ssh_kitten_data() -> None:
def normalize(t: tarfile.TarInfo) -> tarfile.TarInfo:
t.uid = t.gid = 0
t.uname = t.gname = ''
t.mtime = 0
return t
if newer(dest, *files):
@@ -833,7 +845,7 @@ def generate_ssh_kitten_data() -> None:
write_compressed_data(buf.getvalue(), d)
def main() -> None:
def main(args: List[str]=sys.argv) -> None:
with replace_if_needed('constants_generated.go') as f:
f.write(generate_constants())
with replace_if_needed('tools/utils/style/color-names_generated.go') as f:
@@ -859,4 +871,7 @@ def main() -> None:
if __name__ == '__main__':
main() # }}}
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'go-code'])
# }}}

View File

@@ -1,10 +1,18 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import os
import string
import subprocess
import sys
from pprint import pformat
from typing import Any, Dict, List, Union
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
functional_key_defs = '''# {{{
# kitty XKB macVK macU
escape Escape 0x35 -
@@ -242,6 +250,8 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
f.seek(0)
f.truncate(0)
f.write(raw)
if path.endswith('.go'):
subprocess.check_call(['go', 'fmt', path])
def serialize_dict(x: Dict[Any, Any]) -> str:
@@ -417,7 +427,7 @@ def generate_macos_mapping() -> None:
patch_file('glfw/cocoa_window.m', 'functional to macu', '\n'.join(lines))
def main() -> None:
def main(args: List[str]=sys.argv) -> None:
generate_glfw_header()
generate_xkb_mapping()
generate_functional_table()
@@ -427,4 +437,6 @@ def main() -> None:
if __name__ == '__main__':
main()
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'key-constants'])

View File

@@ -1,10 +1,16 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# vim:fileencoding=utf-8
import os
import sys
from functools import lru_cache
from typing import List
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def to_linear(a: float) -> float:
if a <= 0.04045:
@@ -41,11 +47,13 @@ def generate_srgb_gamma(declaration: str = 'static const GLfloat srgb_lut[256] =
return "\n".join(lines)
def main() -> None:
def main(args: List[str]=sys.argv) -> None:
c = generate_srgb_gamma()
with open(os.path.join('kitty', 'srgb_gamma.h'), 'w') as f:
f.write(f'{c}\n')
if __name__ == '__main__':
main()
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'srgb-lut'])

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
@@ -26,7 +26,11 @@ from typing import (
)
from urllib.request import urlopen
os.chdir(os.path.dirname(os.path.abspath(__file__)))
if __name__ == '__main__' and not __package__:
import __main__
__main__.__package__ = 'gen'
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
non_characters = frozenset(range(0xfffe, 0x10ffff, 0x10000))
non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
@@ -136,7 +140,7 @@ def parse_ucd() -> None:
# the future.
marks.add(codepoint)
with open('nerd-fonts-glyphs.txt') as f:
with open('gen/nerd-fonts-glyphs.txt') as f:
for line in f:
line = line.strip()
if not line or line.startswith('#'):
@@ -540,7 +544,7 @@ def gen_wcwidth() -> None:
def gen_rowcolumn_diacritics() -> None:
# codes of all row/column diacritics
codes = []
with open("./rowcolumn-diacritics.txt") as file:
with open("gen/rowcolumn-diacritics.txt") as file:
for line in file.readlines():
if line.startswith('#'):
continue
@@ -584,12 +588,19 @@ def gen_rowcolumn_diacritics() -> None:
subprocess.check_call(['gofmt', '-w', '-s', go_file])
parse_ucd()
parse_prop_list()
parse_emoji()
parse_eaw()
gen_ucd()
gen_wcwidth()
gen_emoji()
gen_names()
gen_rowcolumn_diacritics()
def main(args: List[str]=sys.argv) -> None:
parse_ucd()
parse_prop_list()
parse_emoji()
parse_eaw()
gen_ucd()
gen_wcwidth()
gen_emoji()
gen_names()
gen_rowcolumn_diacritics()
if __name__ == '__main__':
import runpy
m = runpy.run_path(os.path.dirname(os.path.abspath(__file__)))
m['main']([sys.executable, 'wcwidth'])

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -15,7 +15,6 @@
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <float.h>
#include <time.h>
#include <stdio.h>

View File

@@ -1020,12 +1020,14 @@ static NSLock *tick_lock = NULL;
void _glfwDispatchTickCallback(void) {
if (tick_lock && tick_callback) {
[tick_lock lock];
while(tick_callback_requested) {
tick_callback_requested = false;
tick_callback(tick_callback_data);
while(true) {
bool do_call = false;
[tick_lock lock];
if (tick_callback_requested) { do_call = true; tick_callback_requested = false; }
[tick_lock unlock];
if (do_call) tick_callback(tick_callback_data);
else break;
}
[tick_lock unlock];
}
}

View File

@@ -1259,6 +1259,15 @@ is_ascii_control_char(char x) {
}
}
static bool
is_modifier_pressed(NSUInteger flags, NSUInteger target_mask, NSUInteger other_mask, NSUInteger either_mask) {
bool target_pressed = (flags & target_mask) != 0;
bool other_pressed = (flags & other_mask) != 0;
bool either_pressed = (flags & either_mask) != 0;
if (either_pressed != (target_pressed || other_pressed)) return either_pressed;
return target_pressed;
}
- (void)flagsChanged:(NSEvent *)event
{
int action = GLFW_RELEASE;
@@ -1271,29 +1280,24 @@ is_ascii_control_char(char x) {
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, modifierFlags) != 1);
const char *mod_name = "unknown";
// Code for handling modifier key events copied form SDL_cocoakeyboard.m, with thanks. See IsModifierKeyPressedFunction()
#define action_for(modname, target_mask, other_mask, either_mask) action = is_modifier_pressed([event modifierFlags], target_mask, other_mask, either_mask) ? GLFW_PRESS : GLFW_RELEASE; mod_name = #modname; break;
switch(key) {
case GLFW_FKEY_CAPS_LOCK:
mod_name = "capslock";
action = modifierFlags & NSEventModifierFlagCapsLock ? GLFW_PRESS : GLFW_RELEASE; break;
case GLFW_FKEY_LEFT_SUPER:
case GLFW_FKEY_RIGHT_SUPER:
mod_name = "super";
action = modifierFlags & NSEventModifierFlagCommand ? GLFW_PRESS : GLFW_RELEASE; break;
case GLFW_FKEY_LEFT_CONTROL:
case GLFW_FKEY_RIGHT_CONTROL:
mod_name = "ctrl";
action = modifierFlags & NSEventModifierFlagControl ? GLFW_PRESS : GLFW_RELEASE; break;
case GLFW_FKEY_LEFT_ALT:
case GLFW_FKEY_RIGHT_ALT:
mod_name = "alt";
action = modifierFlags & NSEventModifierFlagOption ? GLFW_PRESS : GLFW_RELEASE; break;
case GLFW_FKEY_LEFT_SHIFT:
case GLFW_FKEY_RIGHT_SHIFT:
mod_name = "shift";
action = modifierFlags & NSEventModifierFlagShift ? GLFW_PRESS : GLFW_RELEASE; break;
case GLFW_FKEY_LEFT_SUPER: action_for(super, NX_DEVICELCMDKEYMASK, NX_DEVICERCMDKEYMASK, NX_COMMANDMASK);
case GLFW_FKEY_RIGHT_SUPER: action_for(super, NX_DEVICERCMDKEYMASK, NX_DEVICELCMDKEYMASK, NX_COMMANDMASK);
case GLFW_FKEY_LEFT_CONTROL: action_for(ctrl, NX_DEVICELCTLKEYMASK, NX_DEVICERCTLKEYMASK, NX_CONTROLMASK);
case GLFW_FKEY_RIGHT_CONTROL: action_for(ctrl, NX_DEVICERCTLKEYMASK, NX_DEVICELCTLKEYMASK, NX_CONTROLMASK);
case GLFW_FKEY_LEFT_ALT: action_for(alt, NX_DEVICELALTKEYMASK, NX_DEVICERALTKEYMASK, NX_ALTERNATEMASK);
case GLFW_FKEY_RIGHT_ALT: action_for(alt, NX_DEVICERALTKEYMASK, NX_DEVICELALTKEYMASK, NX_ALTERNATEMASK);
case GLFW_FKEY_LEFT_SHIFT: action_for(shift, NX_DEVICELSHIFTKEYMASK, NX_DEVICERSHIFTKEYMASK, NX_SHIFTMASK);
case GLFW_FKEY_RIGHT_SHIFT: action_for(shift, NX_DEVICERSHIFTKEYMASK, NX_DEVICELSHIFTKEYMASK, NX_SHIFTMASK);
default:
return;
}
#undef action_for
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = action, .mods = mods};
debug_key("\x1b[33mflagsChanged:\x1b[m modifier: %s native_key: 0x%x (%s) glfw_key: 0x%x %s\n",
mod_name, keycode, safe_name_for_keycode(keycode), key, format_mods(mods));
@@ -1510,7 +1514,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
(void)range; (void)actualRange;
if (_glfw.callbacks.get_ime_cursor_position) {
GLFWIMEUpdateEvent ev = { .type = GLFW_IME_UPDATE_CURSOR_POSITION };
if (_glfw.callbacks.get_ime_cursor_position((GLFWwindow*)window, &ev)) {
if (window && _glfw.callbacks.get_ime_cursor_position((GLFWwindow*)window, &ev)) {
const CGFloat left = (CGFloat)ev.cursor.left / window->ns.xscale;
const CGFloat top = (CGFloat)ev.cursor.top / window->ns.yscale;
const CGFloat cellWidth = (CGFloat)ev.cursor.width / window->ns.xscale;
@@ -1716,6 +1720,24 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
return YES;
}
static void
update_titlebar_button_visibility_after_fullscreen_transition(_GLFWwindow* w, bool traditional, bool made_fullscreen) {
// Update window button visibility
if (w->ns.titlebar_hidden) {
NSWindow *window = w->ns.object;
// The hidden buttons might be automatically reset to be visible after going full screen
// to show up in the auto-hide title bar, so they need to be set back to hidden.
BOOL button_hidden = YES;
// When title bar is configured to be hidden, it should be shown with buttons (auto-hide) after going to full screen.
if (!traditional) {
button_hidden = (BOOL) !made_fullscreen;
}
[[window standardWindowButton: NSWindowCloseButton] setHidden:button_hidden];
[[window standardWindowButton: NSWindowMiniaturizeButton] setHidden:button_hidden];
[[window standardWindowButton: NSWindowZoomButton] setHidden:button_hidden];
}
}
- (void)toggleFullScreen:(nullable id)sender
{
if (glfw_window) {
@@ -1723,6 +1745,8 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
if (glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1) return;
glfw_window->ns.in_fullscreen_transition = true;
}
NSWindowStyleMask sm = [self styleMask];
bool is_fullscreen_already = (sm & NSWindowStyleMaskFullScreen) != 0;
// When resizeIncrements is set, Cocoa cannot restore the original window size after returning from fullscreen.
const NSSize original = [self resizeIncrements];
[self setResizeIncrements:NSMakeSize(1.0, 1.0)];
@@ -1731,6 +1755,7 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
// When the window decoration is hidden, toggling fullscreen causes the style mask to be changed,
// and causes the first responder to be cleared.
if (glfw_window && !glfw_window->decorated && glfw_window->ns.view) [self makeFirstResponder:glfw_window->ns.view];
update_titlebar_button_visibility_after_fullscreen_transition(glfw_window, false, !is_fullscreen_already);
}
- (void)zoom:(id)sender
@@ -1796,9 +1821,15 @@ static bool createNativeWindow(_GLFWwindow* window,
else
{
[(NSWindow*) window->ns.object center];
_glfw.ns.cascadePoint =
NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
CGRect screen_frame = [[(NSWindow*) window->ns.object screen] frame];
if (CGRectContainsPoint(screen_frame, _glfw.ns.cascadePoint)
|| CGPointEqualToPoint(CGPointZero, _glfw.ns.cascadePoint)) {
_glfw.ns.cascadePoint =
NSPointToCGPoint([window->ns.object cascadeTopLeftFromPoint:
NSPointFromCGPoint(_glfw.ns.cascadePoint)]);
} else {
_glfw.ns.cascadePoint = CGPointZero;
}
if (wndconfig->resizable)
{
@@ -2505,27 +2536,83 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
return true;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
{
static NSCursor*
load_hidden_system_cursor(NSString *name, SEL fallback) {
// this implementation comes from SDL_cocoamouse.m
NSString *cursorPath = [@"/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors" stringByAppendingPathComponent:name];
NSDictionary *info = [NSDictionary dictionaryWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"info.plist"]];
/* we can't do animation atm. :/ */
const int frames = (int)[[info valueForKey:@"frames"] integerValue];
NSCursor *cursor;
NSImage *image = [[NSImage alloc] initWithContentsOfFile:[cursorPath stringByAppendingPathComponent:@"cursor.pdf"]];
if ((image == nil) || (image.isValid == NO)) {
return [NSCursor performSelector:fallback];
}
if (frames > 1) {
const NSCompositingOperation operation = NSCompositingOperationCopy;
const NSSize cropped_size = NSMakeSize(image.size.width, (int)(image.size.height / frames));
NSImage *cropped = [[NSImage alloc] initWithSize:cropped_size];
if (cropped == nil) {
return [NSCursor performSelector:fallback];
}
[cropped lockFocus];
{
const NSRect cropped_rect = NSMakeRect(0, 0, cropped_size.width, cropped_size.height);
[image drawInRect:cropped_rect fromRect:cropped_rect operation:operation fraction:1];
}
[cropped unlockFocus];
image = cropped;
}
cursor = [[NSCursor alloc] initWithImage:image hotSpot:NSMakePoint([[info valueForKey:@"hotx"] doubleValue], [[info valueForKey:@"hoty"] doubleValue])];
return cursor;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape) {
#define C(name, val) case name: cursor->ns.object = [NSCursor val]; break;
#define U(name, val) case name: cursor->ns.object = [[NSCursor class] performSelector:@selector(val)]; break;
#define U(name, val) case name: cursor->ns.object = [NSCursor performSelector:@selector(val)]; break;
#define S(name, val, fallback) case name: cursor->ns.object = load_hidden_system_cursor(@#val, @selector(val)); break;
switch(shape) {
C(GLFW_ARROW_CURSOR, arrowCursor);
C(GLFW_IBEAM_CURSOR, IBeamCursor);
/* start glfw to cocoa (auto generated by gen-key-constants.py do not edit) */
C(GLFW_DEFAULT_CURSOR, arrowCursor);
C(GLFW_TEXT_CURSOR, IBeamCursor);
C(GLFW_POINTER_CURSOR, pointingHandCursor);
S(GLFW_HELP_CURSOR, help, arrowCursor);
S(GLFW_WAIT_CURSOR, busybutclickable, arrowCursor);
S(GLFW_PROGRESS_CURSOR, busybutclickable, arrowCursor);
C(GLFW_CROSSHAIR_CURSOR, crosshairCursor);
C(GLFW_HAND_CURSOR, pointingHandCursor);
C(GLFW_HRESIZE_CURSOR, resizeLeftRightCursor);
C(GLFW_VRESIZE_CURSOR, resizeUpDownCursor);
U(GLFW_NW_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor);
U(GLFW_NE_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor);
U(GLFW_SW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor);
U(GLFW_SE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor);
S(GLFW_CELL_CURSOR, cell, crosshairCursor);
C(GLFW_VERTICAL_TEXT_CURSOR, IBeamCursorForVerticalLayout);
S(GLFW_MOVE_CURSOR, move, openHandCursor);
C(GLFW_E_RESIZE_CURSOR, resizeRightCursor);
S(GLFW_NE_RESIZE_CURSOR, resizenortheast, _windowResizeNorthEastSouthWestCursor);
S(GLFW_NW_RESIZE_CURSOR, resizenorthwest, _windowResizeNorthWestSouthEastCursor);
C(GLFW_N_RESIZE_CURSOR, resizeUpCursor);
S(GLFW_SE_RESIZE_CURSOR, resizesoutheast, _windowResizeNorthWestSouthEastCursor);
S(GLFW_SW_RESIZE_CURSOR, resizesouthwest, _windowResizeNorthEastSouthWestCursor);
C(GLFW_S_RESIZE_CURSOR, resizeDownCursor);
C(GLFW_W_RESIZE_CURSOR, resizeLeftCursor);
C(GLFW_EW_RESIZE_CURSOR, resizeLeftRightCursor);
C(GLFW_NS_RESIZE_CURSOR, resizeUpDownCursor);
U(GLFW_NESW_RESIZE_CURSOR, _windowResizeNorthEastSouthWestCursor);
U(GLFW_NWSE_RESIZE_CURSOR, _windowResizeNorthWestSouthEastCursor);
S(GLFW_ZOOM_IN_CURSOR, zoomin, arrowCursor);
S(GLFW_ZOOM_OUT_CURSOR, zoomout, arrowCursor);
C(GLFW_ALIAS_CURSOR, dragLinkCursor);
C(GLFW_COPY_CURSOR, dragCopyCursor);
C(GLFW_NOT_ALLOWED_CURSOR, operationNotAllowedCursor);
C(GLFW_NO_DROP_CURSOR, operationNotAllowedCursor);
C(GLFW_GRAB_CURSOR, openHandCursor);
C(GLFW_GRABBING_CURSOR, closedHandCursor);
/* end glfw to cocoa */
case GLFW_INVALID_CURSOR:
return false;
}
#undef C
#undef U
#undef S
if (!cursor->ns.object)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
@@ -2601,19 +2688,7 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
if (in_fullscreen) made_fullscreen = false;
[window toggleFullScreen: nil];
}
// Update window button visibility
if (w->ns.titlebar_hidden) {
// The hidden buttons might be automatically reset to be visible after going full screen
// to show up in the auto-hide title bar, so they need to be set back to hidden.
BOOL button_hidden = YES;
// When title bar is configured to be hidden, it should be shown with buttons (auto-hide) after going to full screen.
if (!traditional) {
button_hidden = (BOOL) !made_fullscreen;
}
[[window standardWindowButton: NSWindowCloseButton] setHidden:button_hidden];
[[window standardWindowButton: NSWindowMiniaturizeButton] setHidden:button_hidden];
[[window standardWindowButton: NSWindowZoomButton] setHidden:button_hidden];
}
update_titlebar_button_visibility_after_fullscreen_transition(w, traditional, made_fullscreen);
return made_fullscreen;
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
@@ -262,6 +262,7 @@ def generate_wrappers(glfw_header: str) -> None:
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle)
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)

38
glfw/glfw3.h vendored
View File

@@ -1099,17 +1099,39 @@ typedef enum {
* @{ */
typedef enum {
GLFW_ARROW_CURSOR,
GLFW_IBEAM_CURSOR,
/* start mouse cursor shapes (auto generated by gen-key-constants.py do not edit) */
GLFW_DEFAULT_CURSOR,
GLFW_TEXT_CURSOR,
GLFW_POINTER_CURSOR,
GLFW_HELP_CURSOR,
GLFW_WAIT_CURSOR,
GLFW_PROGRESS_CURSOR,
GLFW_CROSSHAIR_CURSOR,
GLFW_HAND_CURSOR,
GLFW_HRESIZE_CURSOR,
GLFW_VRESIZE_CURSOR,
GLFW_NW_RESIZE_CURSOR,
GLFW_CELL_CURSOR,
GLFW_VERTICAL_TEXT_CURSOR,
GLFW_MOVE_CURSOR,
GLFW_E_RESIZE_CURSOR,
GLFW_NE_RESIZE_CURSOR,
GLFW_SW_RESIZE_CURSOR,
GLFW_NW_RESIZE_CURSOR,
GLFW_N_RESIZE_CURSOR,
GLFW_SE_RESIZE_CURSOR,
GLFW_INVALID_CURSOR
GLFW_SW_RESIZE_CURSOR,
GLFW_S_RESIZE_CURSOR,
GLFW_W_RESIZE_CURSOR,
GLFW_EW_RESIZE_CURSOR,
GLFW_NS_RESIZE_CURSOR,
GLFW_NESW_RESIZE_CURSOR,
GLFW_NWSE_RESIZE_CURSOR,
GLFW_ZOOM_IN_CURSOR,
GLFW_ZOOM_OUT_CURSOR,
GLFW_ALIAS_CURSOR,
GLFW_COPY_CURSOR,
GLFW_NOT_ALLOWED_CURSOR,
GLFW_NO_DROP_CURSOR,
GLFW_GRAB_CURSOR,
GLFW_GRABBING_CURSOR,
GLFW_INVALID_CURSOR,
/* end mouse cursor shapes */
} GLFWCursorShape;
/*! @} */

View File

@@ -21,7 +21,6 @@
static char theme_name[128] = {0};
static int theme_size = -1;
static uint32_t appearance = 0;
static bool is_gnome = false;
static bool cursor_theme_changed = false;
int
@@ -102,7 +101,7 @@ HANDLER(process_desktop_settings)
dbus_message_iter_recurse(&item, &settings);
if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
process_settings_dict(&settings, process_fdo_setting);
} else if (is_gnome && strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
} else if (strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
process_settings_dict(&settings, process_gnome_setting);
}
}
@@ -192,8 +191,6 @@ setting_changed(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data U
void
glfw_initialize_desktop_settings(void) {
get_cursor_theme_from_env();
const char *desktop = getenv("XDG_CURRENT_DESKTOP");
is_gnome = desktop && strstr(desktop, "GNOME");
DBusConnection *session_bus = glfw_dbus_session_bus();
if (session_bus) {
if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running.");

60
glfw/wl_init.c vendored
View File

@@ -99,7 +99,7 @@ static void pointerHandleEnter(void* data UNUSED,
return;
}
window->wl.decorations.focus = focus;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial; _glfw.wl.pointer_enter_serial = serial;
_glfw.wl.pointerFocus = window;
window->wl.hovered = true;
@@ -177,7 +177,7 @@ static void pointerHandleMotion(void* data UNUSED,
wl_fixed_t sy)
{
_GLFWwindow* window = _glfw.wl.pointerFocus;
GLFWCursorShape cursorShape = GLFW_ARROW_CURSOR;
GLFWCursorShape cursorShape = GLFW_DEFAULT_CURSOR;
if (!window)
return;
@@ -197,21 +197,21 @@ static void pointerHandleMotion(void* data UNUSED,
return;
case TOP_DECORATION:
if (y < window->wl.decorations.metrics.width)
cursorShape = GLFW_VRESIZE_CURSOR;
cursorShape = GLFW_N_RESIZE_CURSOR;
else
cursorShape = GLFW_ARROW_CURSOR;
cursorShape = GLFW_DEFAULT_CURSOR;
break;
case LEFT_DECORATION:
if (y < window->wl.decorations.metrics.width)
cursorShape = GLFW_NW_RESIZE_CURSOR;
else
cursorShape = GLFW_HRESIZE_CURSOR;
cursorShape = GLFW_W_RESIZE_CURSOR;
break;
case RIGHT_DECORATION:
if (y < window->wl.decorations.metrics.width)
cursorShape = GLFW_NE_RESIZE_CURSOR;
else
cursorShape = GLFW_HRESIZE_CURSOR;
cursorShape = GLFW_E_RESIZE_CURSOR;
break;
case BOTTOM_DECORATION:
if (x < window->wl.decorations.metrics.width)
@@ -219,7 +219,7 @@ static void pointerHandleMotion(void* data UNUSED,
else if (x > window->wl.width + window->wl.decorations.metrics.width)
cursorShape = GLFW_SE_RESIZE_CURSOR;
else
cursorShape = GLFW_VRESIZE_CURSOR;
cursorShape = GLFW_S_RESIZE_CURSOR;
break;
default:
assert(0);
@@ -311,7 +311,7 @@ static void pointerHandleButton(void* data UNUSED,
if (window->wl.decorations.focus != CENTRAL_WINDOW)
return;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.pointer_serial = serial;
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
* codes. */
@@ -463,7 +463,7 @@ static void keyboardHandleEnter(void* data UNUSED,
return;
}
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial; _glfw.wl.keyboard_enter_serial = serial;
_glfw.wl.keyboardFocusId = window->id;
_glfwInputWindowFocus(window, true);
uint32_t* key;
@@ -572,9 +572,16 @@ static void seatHandleCapabilities(void* data UNUSED,
{
_glfw.wl.pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(_glfw.wl.pointer, &pointerListener, NULL);
if (_glfw.wl.wp_cursor_shape_manager_v1) {
if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1);
_glfw.wl.wp_cursor_shape_device_v1 = NULL;
_glfw.wl.wp_cursor_shape_device_v1 = wp_cursor_shape_manager_v1_get_pointer(_glfw.wl.wp_cursor_shape_manager_v1, _glfw.wl.pointer);
}
}
else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && _glfw.wl.pointer)
{
if (_glfw.wl.wp_cursor_shape_device_v1) wp_cursor_shape_device_v1_destroy(_glfw.wl.wp_cursor_shape_device_v1);
_glfw.wl.wp_cursor_shape_device_v1 = NULL;
wl_pointer_destroy(_glfw.wl.pointer);
_glfw.wl.pointer = NULL;
if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
@@ -622,28 +629,29 @@ static void registryHandleGlobal(void* data UNUSED,
const char* interface,
uint32_t version)
{
if (strcmp(interface, "wl_compositor") == 0)
#define is(x) strcmp(interface, #x) == 0
if (is(wl_compositor))
{
_glfw.wl.compositorVersion = min(3, version);
_glfw.wl.compositor =
wl_registry_bind(registry, name, &wl_compositor_interface,
_glfw.wl.compositorVersion);
}
else if (strcmp(interface, "wl_subcompositor") == 0)
else if (is(wl_subcompositor))
{
_glfw.wl.subcompositor =
wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
}
else if (strcmp(interface, "wl_shm") == 0)
else if (is(wl_shm))
{
_glfw.wl.shm =
wl_registry_bind(registry, name, &wl_shm_interface, 1);
}
else if (strcmp(interface, "wl_output") == 0)
else if (is(wl_output))
{
_glfwAddOutputWayland(name, version);
}
else if (strcmp(interface, "wl_seat") == 0)
else if (is(wl_seat))
{
if (!_glfw.wl.seat)
{
@@ -661,38 +669,38 @@ static void registryHandleGlobal(void* data UNUSED,
_glfwWaylandInitTextInput();
}
}
else if (strcmp(interface, "xdg_wm_base") == 0)
else if (is(xdg_wm_base))
{
_glfw.wl.wmBase =
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
}
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
else if (is(zxdg_decoration_manager_v1))
{
_glfw.wl.decorationManager =
wl_registry_bind(registry, name,
&zxdg_decoration_manager_v1_interface, 1);
}
else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0)
else if (is(zwp_relative_pointer_manager_v1))
{
_glfw.wl.relativePointerManager =
wl_registry_bind(registry, name,
&zwp_relative_pointer_manager_v1_interface,
1);
}
else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0)
else if (is(zwp_pointer_constraints_v1))
{
_glfw.wl.pointerConstraints =
wl_registry_bind(registry, name,
&zwp_pointer_constraints_v1_interface,
1);
}
else if (strcmp(interface, GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME) == 0)
else if (is(zwp_text_input_manager_v3))
{
_glfwWaylandBindTextInput(registry, name);
_glfwWaylandInitTextInput();
}
else if (strcmp(interface, "wl_data_device_manager") == 0)
else if (is(wl_data_device_manager))
{
_glfw.wl.dataDeviceManager =
wl_registry_bind(registry, name,
@@ -702,7 +710,7 @@ static void registryHandleGlobal(void* data UNUSED,
_glfwSetupWaylandDataDevice();
}
}
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0)
else if (is(zwp_primary_selection_device_manager_v1))
{
_glfw.wl.primarySelectionDeviceManager =
wl_registry_bind(registry, name,
@@ -712,10 +720,13 @@ static void registryHandleGlobal(void* data UNUSED,
_glfwSetupWaylandPrimarySelectionDevice();
}
}
else if (strstr(interface, "xdg_activation_v1") != 0) {
else if (is(xdg_activation_v1)) {
_glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1);
}
else if (is(wp_cursor_shape_manager_v1)) {
_glfw.wl.wp_cursor_shape_manager_v1 = wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, 1);
}
#undef is
}
static void registryHandleGlobalRemove(void *data UNUSED,
@@ -949,6 +960,9 @@ void _glfwPlatformTerminate(void)
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
if (_glfw.wl.xdg_activation_v1)
xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1);
if (_glfw.wl.wp_cursor_shape_manager_v1)
wp_cursor_shape_manager_v1_destroy(_glfw.wl.wp_cursor_shape_manager_v1);
if (_glfw.wl.registry)
wl_registry_destroy(_glfw.wl.registry);
if (_glfw.wl.display)

5
glfw/wl_platform.h vendored
View File

@@ -60,6 +60,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
#include "wl_text_input.h"
#include "wayland-xdg-activation-v1-client-protocol.h"
#include "wayland-cursor-shape-v1-client-protocol.h"
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define _glfw_dlclose(handle) dlclose(handle)
@@ -286,13 +287,15 @@ typedef struct _GLFWlibraryWayland
struct zwp_primary_selection_device_v1* primarySelectionDevice;
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
struct xdg_activation_v1* xdg_activation_v1;
struct wp_cursor_shape_manager_v1* wp_cursor_shape_manager_v1;
struct wp_cursor_shape_device_v1* wp_cursor_shape_device_v1;
int compositorVersion;
int seatVersion;
struct wl_surface* cursorSurface;
GLFWCursorShape cursorPreviousShape;
uint32_t serial, input_serial;
uint32_t serial, input_serial, pointer_serial, pointer_enter_serial, keyboard_enter_serial;
int32_t keyboardRepeatRate;
monotonic_t keyboardRepeatDelay;

View File

@@ -7,8 +7,6 @@
#pragma once
#include <wayland-client.h>
#define GLFW_WAYLAND_TEXT_INPUT_INTERFACE_NAME "zwp_text_input_manager_v3"
void _glfwWaylandBindTextInput(struct wl_registry* registry, uint32_t name);
void _glfwWaylandInitTextInput(void);
void _glfwWaylandDestroyTextInput(void);

138
glfw/wl_window.c vendored
View File

@@ -30,7 +30,6 @@
#include "internal.h"
#include "backend_utils.h"
#include "memfd.h"
#include "linux_notify.h"
#include "wl_client_side_decorations.h"
#include "../kitty/monotonic.h"
@@ -152,10 +151,58 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque,
return buffer;
}
static int
glfw_cursor_shape_to_wayland_cursor_shape(GLFWCursorShape g) {
#define C(g, w) case g: return w;
switch(g) {
/* start glfw to wayland mapping (auto generated by gen-key-constants.py do not edit) */
C(GLFW_DEFAULT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
C(GLFW_TEXT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT);
C(GLFW_POINTER_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER);
C(GLFW_HELP_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP);
C(GLFW_WAIT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT);
C(GLFW_PROGRESS_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS);
C(GLFW_CROSSHAIR_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR);
C(GLFW_CELL_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL);
C(GLFW_VERTICAL_TEXT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT);
C(GLFW_MOVE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE);
C(GLFW_E_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE);
C(GLFW_NE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE);
C(GLFW_NW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE);
C(GLFW_N_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE);
C(GLFW_SE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE);
C(GLFW_SW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE);
C(GLFW_S_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE);
C(GLFW_W_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE);
C(GLFW_EW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE);
C(GLFW_NS_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE);
C(GLFW_NESW_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE);
C(GLFW_NWSE_RESIZE_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE);
C(GLFW_ZOOM_IN_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN);
C(GLFW_ZOOM_OUT_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT);
C(GLFW_ALIAS_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS);
C(GLFW_COPY_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY);
C(GLFW_NOT_ALLOWED_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED);
C(GLFW_NO_DROP_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP);
C(GLFW_GRAB_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB);
C(GLFW_GRABBING_CURSOR, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING);
/* end glfw to wayland mapping */
default: return -1;
}
#undef C
}
static void
setCursorImage(_GLFWwindow* window, bool on_theme_change) {
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
_GLFWcursorWayland defaultCursor = {.shape = GLFW_DEFAULT_CURSOR};
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
if (_glfw.wl.wp_cursor_shape_device_v1) {
int which = glfw_cursor_shape_to_wayland_cursor_shape(cursorWayland->shape);
if (which > -1) {
wp_cursor_shape_device_v1_set_shape(_glfw.wl.wp_cursor_shape_device_v1, _glfw.wl.pointer_enter_serial, (uint32_t)which);
return;
}
}
struct wl_cursor_image* image = NULL;
struct wl_buffer* buffer = NULL;
struct wl_surface* surface = _glfw.wl.cursorSurface;
@@ -717,7 +764,7 @@ static void incrementCursorImage(_GLFWwindow* window)
{
if (window && window->wl.decorations.focus == CENTRAL_WINDOW && window->cursorMode != GLFW_CURSOR_HIDDEN) {
_GLFWcursor* cursor = window->wl.currentCursor;
if (cursor && cursor->wl.cursor)
if (cursor && cursor->wl.cursor && cursor->wl.cursor->image_count)
{
cursor->wl.currentImage += 1;
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
@@ -831,16 +878,38 @@ struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme*
struct wl_cursor* ans = NULL;
switch (shape)
{
C(GLFW_ARROW_CURSOR, "left_ptr", "arrow", "default")
C(GLFW_IBEAM_CURSOR, "xterm", "ibeam", "text")
C(GLFW_CROSSHAIR_CURSOR, "crosshair", "cross")
C(GLFW_HAND_CURSOR, "hand2", "grab", "grabbing", "closedhand")
C(GLFW_HRESIZE_CURSOR, "sb_h_double_arrow", "h_double_arrow", "col-resize")
C(GLFW_VRESIZE_CURSOR, "sb_v_double_arrow", "v_double_arrow", "row-resize")
C(GLFW_NW_RESIZE_CURSOR, "top_left_corner", "nw-resize")
C(GLFW_NE_RESIZE_CURSOR, "top_right_corner", "ne-resize")
C(GLFW_SW_RESIZE_CURSOR, "bottom_left_corner", "sw-resize")
C(GLFW_SE_RESIZE_CURSOR, "bottom_right_corner", "se-resize")
/* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */
C(GLFW_DEFAULT_CURSOR, "default", "left_ptr");
C(GLFW_TEXT_CURSOR, "text", "xterm", "ibeam");
C(GLFW_POINTER_CURSOR, "pointing_hand", "pointer", "hand2", "hand");
C(GLFW_HELP_CURSOR, "help", "question_arrow", "whats_this");
C(GLFW_WAIT_CURSOR, "wait", "clock", "watch");
C(GLFW_PROGRESS_CURSOR, "progress", "half-busy", "left_ptr_watch");
C(GLFW_CROSSHAIR_CURSOR, "crosshair", "tcross");
C(GLFW_CELL_CURSOR, "cell", "plus", "cross");
C(GLFW_VERTICAL_TEXT_CURSOR, "vertical-text");
C(GLFW_MOVE_CURSOR, "move", "fleur", "pointer-move");
C(GLFW_E_RESIZE_CURSOR, "e-resize", "right_side");
C(GLFW_NE_RESIZE_CURSOR, "ne-resize", "top_right_corner");
C(GLFW_NW_RESIZE_CURSOR, "nw-resize", "top_left_corner");
C(GLFW_N_RESIZE_CURSOR, "n-resize", "top_side");
C(GLFW_SE_RESIZE_CURSOR, "se-resize", "bottom_right_corner");
C(GLFW_SW_RESIZE_CURSOR, "sw-resize", "bottom_left_corner");
C(GLFW_S_RESIZE_CURSOR, "s-resize", "bottom_side");
C(GLFW_W_RESIZE_CURSOR, "w-resize", "left_side");
C(GLFW_EW_RESIZE_CURSOR, "ew-resize", "sb_h_double_arrow", "split_h");
C(GLFW_NS_RESIZE_CURSOR, "ns-resize", "sb_v_double_arrow", "split_v");
C(GLFW_NESW_RESIZE_CURSOR, "nesw-resize", "size_bdiag", "size-bdiag");
C(GLFW_NWSE_RESIZE_CURSOR, "nwse-resize", "size_fdiag", "size-fdiag");
C(GLFW_ZOOM_IN_CURSOR, "zoom-in", "zoom_in");
C(GLFW_ZOOM_OUT_CURSOR, "zoom-out", "zoom_out");
C(GLFW_ALIAS_CURSOR, "dnd-link");
C(GLFW_COPY_CURSOR, "dnd-copy");
C(GLFW_NOT_ALLOWED_CURSOR, "not-allowed", "forbidden", "crossed_circle");
C(GLFW_NO_DROP_CURSOR, "no-drop", "dnd-no-drop");
C(GLFW_GRAB_CURSOR, "grab", "openhand", "hand1");
C(GLFW_GRABBING_CURSOR, "grabbing", "closedhand", "dnd-none");
/* end glfw to xc mapping */
case GLFW_INVALID_CURSOR:
break;
}
@@ -1920,22 +1989,6 @@ static const struct zwp_primary_selection_device_v1_listener primary_selection_d
};
static void
clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) {
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
}
wl_callback_destroy(callback);
}
static void
primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) {
zwp_primary_selection_device_v1_set_selection(_glfw.wl.primarySelectionDevice, data, serial);
}
wl_callback_destroy(callback);
}
void _glfwSetupWaylandDataDevice(void) {
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
@@ -2027,13 +2080,25 @@ _glfwPlatformSetClipboard(GLFWClipboardType t) {
}
f(data_source, cd->mime_types[i]);
}
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
if (t == GLFW_CLIPBOARD) {
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
// According to some interpretations of the Wayland spec only the application that has keyboard focus can set the clipboard.
// Hurray for the Wayland nanny state!
//
// However in wl-roots based compositors, using the serial from the keyboard enter event doesn't work. No clue what
// the correct serial to use here is. Given this Wayland there probably isn't one. What a joke.
// Bug report: https://github.com/kovidgoyal/kitty/issues/6890
// Ironically one of the contributors to wl_roots claims the keyboard enter serial is the correct one to use:
// https://emersion.fr/blog/2020/wayland-clipboard-drag-and-drop/
// The Wayland spec itself says "serial number of the event that triggered this request"
// https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_data_device
// So who the fuck knows. Just use the latest received serial and ask anybody that uses Wayland
// to get their head examined.
wl_data_device_set_selection(_glfw.wl.dataDevice, _glfw.wl.dataSourceForClipboard, _glfw.wl.serial);
} else {
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
// According to the Wayland spec we can only set the primary selection in response to a pointer button event
// Hurray for the Wayland nanny state!
zwp_primary_selection_device_v1_set_selection(
_glfw.wl.primarySelectionDevice, _glfw.wl.dataSourceForPrimarySelection, _glfw.wl.pointer_serial);
}
}
@@ -2266,3 +2331,8 @@ GLFWAPI bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, boo
}
return false;
}
GLFWAPI void glfwWaylandRedrawCSDWindowTitle(GLFWwindow *handle) {
_GLFWwindow* window = (_GLFWwindow*) handle;
change_csd_title(window);
}

87
glfw/x11_window.c vendored
View File

@@ -2811,37 +2811,78 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
return true;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
{
int native = 0;
#define C(name, val) case name: native = val; break;
switch(shape) {
C(GLFW_ARROW_CURSOR, XC_left_ptr);
C(GLFW_IBEAM_CURSOR, XC_xterm);
C(GLFW_CROSSHAIR_CURSOR, XC_crosshair);
C(GLFW_HAND_CURSOR, XC_hand2);
C(GLFW_HRESIZE_CURSOR, XC_sb_h_double_arrow);
C(GLFW_VRESIZE_CURSOR, XC_sb_v_double_arrow);
C(GLFW_NW_RESIZE_CURSOR, XC_top_left_corner);
C(GLFW_NE_RESIZE_CURSOR, XC_top_right_corner);
C(GLFW_SW_RESIZE_CURSOR, XC_bottom_left_corner);
C(GLFW_SE_RESIZE_CURSOR, XC_bottom_right_corner);
case GLFW_INVALID_CURSOR:
return false;
}
#undef C
static int
set_cursor_from_font(_GLFWcursor* cursor, int native) {
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
if (!cursor->x11.handle)
{
if (!cursor->x11.handle) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to create standard cursor");
return false;
}
return true;
}
static bool
try_cursor_names(_GLFWcursor *cursor, int arg_count, ...) {
va_list ap;
va_start(ap, arg_count);
const char *first_name = "";
for (int i = 0; i < arg_count; i++) {
const char *name = va_arg(ap, const char *);
first_name = name;
cursor->x11.handle = XcursorLibraryLoadCursor(_glfw.x11.display, name);
if (cursor->x11.handle) break;
}
va_end(ap);
if (!cursor->x11.handle) {
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to load standard cursor: %s with %d aliases via Xcursor library", first_name, arg_count);
return false;
}
return true;
}
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
{
switch(shape) {
/* start glfw to xc mapping (auto generated by gen-key-constants.py do not edit) */
case GLFW_DEFAULT_CURSOR: return set_cursor_from_font(cursor, XC_left_ptr);
case GLFW_TEXT_CURSOR: return set_cursor_from_font(cursor, XC_xterm);
case GLFW_POINTER_CURSOR: return set_cursor_from_font(cursor, XC_hand2);
case GLFW_HELP_CURSOR: return set_cursor_from_font(cursor, XC_question_arrow);
case GLFW_WAIT_CURSOR: return set_cursor_from_font(cursor, XC_clock);
case GLFW_PROGRESS_CURSOR: return try_cursor_names(cursor, 3, "progress", "half-busy", "left_ptr_watch");
case GLFW_CROSSHAIR_CURSOR: return set_cursor_from_font(cursor, XC_tcross);
case GLFW_CELL_CURSOR: return set_cursor_from_font(cursor, XC_plus);
case GLFW_VERTICAL_TEXT_CURSOR: return try_cursor_names(cursor, 1, "vertical-text");
case GLFW_MOVE_CURSOR: return set_cursor_from_font(cursor, XC_fleur);
case GLFW_E_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_right_side);
case GLFW_NE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_right_corner);
case GLFW_NW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_left_corner);
case GLFW_N_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_top_side);
case GLFW_SE_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_right_corner);
case GLFW_SW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_left_corner);
case GLFW_S_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_bottom_side);
case GLFW_W_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_left_side);
case GLFW_EW_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_h_double_arrow);
case GLFW_NS_RESIZE_CURSOR: return set_cursor_from_font(cursor, XC_sb_v_double_arrow);
case GLFW_NESW_RESIZE_CURSOR: return try_cursor_names(cursor, 3, "nesw-resize", "size_bdiag", "size-bdiag");
case GLFW_NWSE_RESIZE_CURSOR: return try_cursor_names(cursor, 3, "nwse-resize", "size_fdiag", "size-fdiag");
case GLFW_ZOOM_IN_CURSOR: return try_cursor_names(cursor, 2, "zoom-in", "zoom_in");
case GLFW_ZOOM_OUT_CURSOR: return try_cursor_names(cursor, 2, "zoom-out", "zoom_out");
case GLFW_ALIAS_CURSOR: return try_cursor_names(cursor, 1, "dnd-link");
case GLFW_COPY_CURSOR: return try_cursor_names(cursor, 1, "dnd-copy");
case GLFW_NOT_ALLOWED_CURSOR: return try_cursor_names(cursor, 3, "not-allowed", "forbidden", "crossed_circle");
case GLFW_NO_DROP_CURSOR: return try_cursor_names(cursor, 2, "no-drop", "dnd-no-drop");
case GLFW_GRAB_CURSOR: return set_cursor_from_font(cursor, XC_hand1);
case GLFW_GRABBING_CURSOR: return try_cursor_names(cursor, 3, "grabbing", "closedhand", "dnd-none");
/* end glfw to xc mapping */
case GLFW_INVALID_CURSOR: return false;
}
return false;
}
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
{
if (cursor->x11.handle)

16
go.mod
View File

@@ -4,19 +4,19 @@ go 1.21
require (
github.com/ALTree/bigfloat v0.2.0
github.com/alecthomas/chroma/v2 v2.9.1
github.com/bmatcuk/doublestar/v4 v4.6.0
github.com/alecthomas/chroma/v2 v2.12.0
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/disintegration/imaging v1.6.2
github.com/dlclark/regexp2 v1.10.0
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.1
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.5.0
github.com/seancfoley/ipaddress-go v1.5.5
github.com/shirou/gopsutil/v3 v3.23.9
github.com/shirou/gopsutil/v3 v3.23.12
github.com/zeebo/xxh3 v1.0.2
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
golang.org/x/image v0.12.0
golang.org/x/sys v0.12.0
howett.net/plist v1.0.0
golang.org/x/image v0.15.0
golang.org/x/sys v0.16.0
howett.net/plist v1.0.1
)
require (

61
go.sum
View File

@@ -2,12 +2,12 @@ github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
github.com/alecthomas/chroma/v2 v2.9.1/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -18,10 +18,11 @@ github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -39,8 +40,8 @@ github.com/seancfoley/bintree v1.2.3 h1:6SPPax/9Dilcs3mDTj3CarRCWPZJV30KyP3cjcEw
github.com/seancfoley/bintree v1.2.3/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
github.com/seancfoley/ipaddress-go v1.5.5 h1:Q2isCacDQ3A46hxSbM9Q2+Gs4IopCVz1oH88L5eEgP4=
github.com/seancfoley/ipaddress-go v1.5.5/go.mod h1:C+VHKCmxTfYODkkItOj9lMemZLMigwo28E+ARlPqpk0=
github.com/shirou/gopsutil/v3 v3.23.9 h1:ZI5bWVeu2ep4/DIxB4U9okeYJ7zp/QLTO4auRb/ty/E=
github.com/shirou/gopsutil/v3 v3.23.9/go.mod h1:x/NWSb71eMcjFIO0vhyGW5nZ7oSIgVjrCnADckb85GA=
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
@@ -56,59 +57,31 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"kitty/tools/cli/markup"
"kitty/tools/tty"
"kitty/tools/tui/loop"
"kitty/tools/utils"
"kitty/tools/utils/style"
@@ -60,13 +61,16 @@ func extra_for(width, screen_width int) int {
return max(0, screen_width-width)/2 + 1
}
var debugprintln = tty.DebugPrintln
var _ = debugprintln
func GetChoices(o *Options) (response string, err error) {
response = ""
lp, err := loop.New()
if err != nil {
return "", err
}
lp.MouseTrackingMode(loop.BUTTONS_ONLY_MOUSE_TRACKING)
lp.MouseTrackingMode(loop.FULL_MOUSE_TRACKING)
prefix_style_pat := regexp.MustCompile("^(?:\x1b\\[[^m]*?m)+")
choice_order := make([]Choice, 0, len(o.Choices))
@@ -89,6 +93,9 @@ func GetChoices(o *Options) (response string, err error) {
}
letter = strings.ToLower(letter)
idx := strings.Index(strings.ToLower(text), letter)
if idx < 0 {
return "", fmt.Errorf("The choice letter %#v is not present in the choice text: %#v", letter, text)
}
idx = len([]rune(strings.ToLower(text)[:idx]))
allowed.Add(letter)
c := Choice{text: text, idx: idx, color: color, letter: letter}
@@ -123,6 +130,9 @@ func GetChoices(o *Options) (response string, err error) {
}
draw_long_text := func(screen_width int, text string, msg_lines []string) []string {
if screen_width < 3 {
return msg_lines
}
if text == "" {
msg_lines = append(msg_lines, "")
} else {
@@ -358,12 +368,15 @@ func GetChoices(o *Options) (response string, err error) {
if hidden_text != "" && message != "" {
message = message[:hidden_text_start_pos] + hidden_text + message[hidden_text_end_pos:]
hidden_text = ""
draw_screen()
_ = draw_screen()
}
}
lp.OnInitialize = func() (string, error) {
lp.SetCursorVisible(false)
if o.Title != "" {
lp.SetWindowTitle(o.Title)
}
return "", draw_screen()
}
@@ -398,16 +411,31 @@ func GetChoices(o *Options) (response string, err error) {
}
lp.OnMouseEvent = func(ev *loop.MouseEvent) error {
if ev.Event_type == loop.MOUSE_CLICK {
for letter, ranges := range clickable_ranges {
for _, r := range ranges {
if r.has_point(ev.Cell.X, ev.Cell.Y) {
response = letter
lp.Quit(0)
return nil
}
on_letter := ""
for letter, ranges := range clickable_ranges {
for _, r := range ranges {
if r.has_point(ev.Cell.X, ev.Cell.Y) {
on_letter = letter
break
}
}
}
if on_letter != "" {
if s, has_shape := lp.CurrentPointerShape(); !has_shape && s != loop.POINTER_POINTER {
lp.PushPointerShape(loop.POINTER_POINTER)
}
} else {
if _, has_shape := lp.CurrentPointerShape(); has_shape {
lp.PopPointerShape()
}
}
if ev.Event_type == loop.MOUSE_CLICK {
if on_letter != "" {
response = on_letter
lp.Quit(0)
return nil
}
if hidden_text != "" && replacement_range.has_point(ev.Cell.X, ev.Cell.Y) {
unhide()
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
@@ -30,13 +30,20 @@ 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.
--title --window-title
The title for the window in which the question is displayed. Only implemented
for yesno and choices types.
--choice -c
type=list
dest=choices
A choice for the choices type. Can be specified multiple times. Every choice has
the syntax: ``letter[;color]:text``. Where :italic:`letter` is the accelerator key
and :italic:`text` is the corresponding text. There can be an optional color
specification after the letter to indicate what color it should be.
the syntax: ``letter[;color]:text``, where :italic:`text` is the choice
text and :italic:`letter` is the selection key. :italic:`letter` is a single letter
belonging to :italic:`text`. This letter is highlighted within the choice text.
There can be an optional color specification after the letter
to indicate what color it should be.
For example: :code:`y:Yes` and :code:`n;red:No`

View File

@@ -160,3 +160,4 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['short_desc'] = 'Broadcast typed text to kitty windows'

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
@@ -115,14 +115,16 @@ controls where to display the selected error message, other options are ignored.
--regex
default={default_regex}
The regular expression to use when option :option:`--type` is set to
:code:`regex`, in python syntax. If you specify a numbered group in the regular
:code:`regex`, in Perl 5 syntax. If you specify a numbered group in the regular
expression, only the group will be matched. This allow you to match text
ignoring a prefix/suffix, as needed. The default expression matches lines. To
match text over multiple lines, you should prefix the regular expression with
:code:`(?ms)`, which turns on MULTILINE and DOTALL modes for the regex engine.
If you specify named groups and a :option:`--program`, then the program will be
passed arguments corresponding to each named group of the form
:code:`key=value`.
match text over multiple lines, things get a little tricky, as line endings
are a sequence of zero or more null bytes followed by either a carriage return
or a newline character. To have a pattern match over line endings you will need to
match the character set ``[\0\r\n]``. The newlines and null bytes are automatically
stripped from the returned text. If you specify named groups and a
:option:`--program`, then the program will be passed arguments corresponding
to each named group of the form :code:`key=value`.
--linenum-action

View File

@@ -22,6 +22,7 @@ import (
"kitty"
"kitty/tools/config"
"kitty/tools/tty"
"kitty/tools/utils"
)
@@ -221,7 +222,7 @@ func read_relevant_kitty_opts(path string) KittyOpts {
return nil
}
cp := config.ConfigParser{LineHandler: handle_line}
cp.ParseFiles(path)
_ = cp.ParseFiles(path) // ignore errors and use defaults
if ans.Url_prefixes == nil {
ans.Url_prefixes = utils.NewSetWithItems(kitty.KittyConfigDefaults.Url_prefixes...)
}
@@ -232,6 +233,9 @@ var RelevantKittyOpts = sync.OnceValue(func() KittyOpts {
return read_relevant_kitty_opts(filepath.Join(utils.ConfigDir(), "kitty.conf"))
})
var debugprintln = tty.DebugPrintln
var _ = debugprintln
func functions_for(opts *Options) (pattern string, post_processors []PostProcessorFunc, group_processors []GroupProcessorFunc) {
switch opts.Type {
case "url":

View File

@@ -43,16 +43,18 @@ func get_options_for_rg() (expecting_args map[string]bool, alias_map map[string]
if options_started {
s := strings.TrimLeft(line, " ")
indent := len(line) - len(s)
if indent < 12 && indent > 0 {
s, _, expecting_arg := strings.Cut(s, "<")
if indent < 8 && indent > 0 {
expecting_arg := strings.Contains(s, "=")
single_letter_aliases := make([]string, 0, 1)
long_option_names := make([]string, 0, 1)
for _, x := range strings.Split(s, ",") {
x = strings.TrimSpace(x)
if strings.HasPrefix(x, "--") {
long_option_names = append(long_option_names, x[2:])
lon, _, _ := strings.Cut(x[2:], "=")
long_option_names = append(long_option_names, lon)
} else if strings.HasPrefix(x, "-") {
single_letter_aliases = append(single_letter_aliases, x[1:])
son, _, _ := strings.Cut(x[1:], " ")
single_letter_aliases = append(single_letter_aliases, son)
}
}
if len(long_option_names) == 0 {
@@ -68,11 +70,15 @@ func get_options_for_rg() (expecting_args map[string]bool, alias_map map[string]
expecting_args[long_option_names[0]] = expecting_arg
}
} else {
if strings.HasPrefix(line, "OPTIONS:") {
if strings.HasSuffix(line, "OPTIONS:") {
options_started = true
}
}
}
if len(expecting_args) == 0 || len(alias_map) == 0 {
err = fmt.Errorf("Failed to parse rg help output, could not find any options")
return
}
return
}

View File

@@ -46,7 +46,7 @@ func DetectSupport(timeout time.Duration) (memory, files, direct bool, err error
lp.OnInitialize = func() (string, error) {
var iid uint32
_, _ = lp.AddTimer(timeout, false, func(loop.IdType) error {
return fmt.Errorf("Timed out waiting for a response form the terminal: %w", os.ErrDeadlineExceeded)
return fmt.Errorf("Timed out waiting for a response from the terminal: %w", os.ErrDeadlineExceeded)
})
g := func(t graphics.GRT_t, payload string) uint32 {

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
OPTIONS = '''\
@@ -68,7 +68,7 @@ option.
--detection-timeout
type=float
default=10
The amount of time (in seconds) to wait for a response form the terminal, when
The amount of time (in seconds) to wait for a response from the terminal, when
detecting image display support.

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import sys
from typing import List, Optional
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, SHIFT, SUPER
from ..tui.handler import Handler
from ..tui.loop import Loop, MouseEvent
from ..tui.operations import MouseTracking
mod_names = {
SHIFT: 'Shift',
ALT: 'Alt',
CTRL: 'Ctrl',
SUPER: 'Super',
HYPER: 'Hyper',
META: 'Meta',
NUM_LOCK: 'NumLock',
CAPS_LOCK: 'CapsLock',
}
def format_mods(mods: int) -> str:
if not mods:
return ''
lmods = []
for m, name in mod_names.items():
if mods & m:
lmods.append(name)
return '+'.join(lmods)
class Mouse(Handler):
mouse_tracking = MouseTracking.full
def __init__(self) -> None:
self.current_mouse_event: Optional[MouseEvent] = None
def initialize(self) -> None:
self.cmd.set_cursor_visible(False)
self.draw_screen()
def finalize(self) -> None:
self.cmd.set_cursor_visible(True)
def on_mouse_event(self, ev: MouseEvent) -> None:
self.current_mouse_event = ev
self.draw_screen()
@Handler.atomic_update
def draw_screen(self) -> None:
self.cmd.clear_screen()
ev = self.current_mouse_event
if ev is None:
self.print('Move the mouse or click to see mouse events')
return
self.print(f'Position: {ev.pixel_x}, {ev.pixel_y}')
self.print(f'Cell: {ev.cell_x}, {ev.cell_y}')
self.print(f'{ev.type}')
if ev.buttons:
self.print(ev.buttons)
if ev.mods:
self.print(f'Modifiers: {format_mods(ev.mods)}')
def on_interrupt(self) -> None:
self.quit_loop(0)
on_eot = on_interrupt
def main(args: List[str]) -> None:
loop = Loop()
handler = Mouse()
loop.loop(handler)
if __name__ == '__main__':
main(sys.argv)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
@@ -7,7 +7,7 @@ from typing import Any, Callable, Dict, List, Tuple
from kitty.cli import parse_args
from kitty.cli_stub import PanelCLIOptions
from kitty.constants import appname, is_macos
from kitty.constants import appname, is_macos, is_wayland
from kitty.fast_data_types import make_x11_window_a_dock_window
from kitty.os_window_size import WindowSizeData
@@ -103,15 +103,36 @@ def setup_x11_window(win_id: int) -> None:
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
from kitty.fast_data_types import glfw_primary_monitor_size
from kitty.typing import EdgeLiteral
def effective_margin(which: EdgeLiteral) -> float:
ans: float = getattr(opts.single_window_margin_width, which)
if ans < 0:
ans = getattr(opts.window_margin_width, which)
return ans
def effective_padding(which: EdgeLiteral) -> float:
ans: float = getattr(opts.single_window_padding_width, which)
if ans < 0:
ans = getattr(opts.window_padding_width, which)
return ans
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
if not is_macos and not is_wayland():
# Not sure what the deal with scaling on X11 is
xscale = yscale = 1
global window_width, window_height
monitor_width, monitor_height = glfw_primary_monitor_size()
if args.edge in {'top', 'bottom'}:
window_height = cell_height * args.lines + 1
spacing = effective_margin('top') + effective_margin('bottom')
spacing += effective_padding('top') + effective_padding('bottom')
window_height = int(cell_height * args.lines / yscale + (dpi_y / 72) * spacing + 1)
window_width = monitor_width
else:
window_width = cell_width * args.columns + 1
spacing = effective_margin('left') + effective_margin('right')
spacing += effective_padding('left') + effective_padding('right')
window_width = int(cell_width * args.columns / xscale + (dpi_x / 72) * spacing + 1)
window_height = monitor_height
return window_width, window_height
@@ -149,3 +170,4 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['short_desc'] = help_text

View File

@@ -258,3 +258,4 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = options_spec
cd['help_text'] = help_text
cd['short_desc'] = 'Query the terminal for various capabilities'

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -524,16 +524,20 @@ func get_remote_command(cd *connection_data) error {
return nil
}
var debugprintln = tty.DebugPrintln
var _ = debugprintln
func drain_potential_tty_garbage(term *tty.Term) {
err := term.ApplyOperations(tty.TCSANOW, tty.SetNoEcho)
err := term.ApplyOperations(tty.TCSANOW, tty.SetRaw)
if err != nil {
return
}
canary, err := secrets.TokenBase64()
canary, err := secrets.TokenHex()
if err != nil {
return
}
dcs, err := tui.DCSToKitty("echo", canary+"\n\r")
dcs, err := tui.DCSToKitty("echo", canary)
q := utils.UnsafeStringToBytes(canary)
if err != nil {
return
}
@@ -541,7 +545,6 @@ func drain_potential_tty_garbage(term *tty.Term) {
if err != nil {
return
}
q := utils.UnsafeStringToBytes(canary)
data := make([]byte, 0)
give_up_at := time.Now().Add(2 * time.Second)
buf := make([]byte, 0, 8192)
@@ -553,7 +556,7 @@ func drain_potential_tty_garbage(term *tty.Term) {
}
n, err := term.ReadWithTimeout(buf, timeout)
if err != nil {
return
break
}
data = append(data, buf[:n]...)
}
@@ -727,14 +730,22 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
if err != nil {
return 1, err
}
restore_escape_codes := loop.RESTORE_PRIVATE_MODE_VALUES
restore_escape_codes := loop.RESTORE_PRIVATE_MODE_VALUES + loop.HANDLE_TERMIOS_SIGNALS.EscapeCodeToReset()
if escape_codes_to_set_colors != "" {
restore_escape_codes += "\x1b[#Q"
}
defer func() {
_ = term.WriteAllString(restore_escape_codes)
term.RestoreAndClose()
}()
sigs := make(chan os.Signal, 8)
signal.Notify(sigs, unix.SIGINT, unix.SIGTERM)
cleaned_up := false
cleanup := func() {
if !cleaned_up {
_ = term.WriteAllString(restore_escape_codes)
term.RestoreAndClose()
signal.Reset()
cleaned_up = true
}
}
defer cleanup()
err = get_remote_command(&cd)
if err != nil {
return 1, err
@@ -746,8 +757,6 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
if err != nil {
return 1, err
}
sigs := make(chan os.Signal, 8)
signal.Notify(sigs, unix.SIGINT, unix.SIGTERM)
if !cd.request_data {
rq := fmt.Sprintf("id=%s:pwfile=%s:pw=%s", cd.replacements["REQUEST_ID"], cd.replacements["PASSWORD_FILENAME"], cd.replacements["DATA_PASSWORD"])
@@ -772,11 +781,11 @@ func run_ssh(ssh_args, server_args, found_extra_args []string) (rc int, err erro
}()
err = c.Wait()
drain_potential_tty_garbage(term)
signal.Reset(unix.SIGINT, unix.SIGTERM)
if err != nil {
var exit_err *exec.ExitError
if errors.As(err, &exit_err) {
if state := exit_err.ProcessState.String(); state == "signal: interrupt" {
cleanup()
_ = unix.Kill(os.Getpid(), unix.SIGINT)
// Give the signal time to be delivered
time.Sleep(20 * time.Millisecond)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys
@@ -162,18 +162,18 @@ in the .conf files/themes are ignored.
''')
opt('remote_kitty', 'if-needed', choices=('if-needed', 'no', 'yes'), long_text='''
Make :program:`kitty` available on the remote host. Useful to run kittens such
Make :program:`kitten` available on the remote host. Useful to run kittens such
as the :doc:`icat kitten </kittens/icat>` to display images or the
:doc:`transfer file kitten </kittens/transfer>` to transfer files. Only works if
the remote host has an architecture for which :link:`pre-compiled kitty binaries
<https://github.com/kovidgoyal/kitty/releases>` are available. Note that kitty
the remote host has an architecture for which :link:`pre-compiled kitten binaries
<https://github.com/kovidgoyal/kitty/releases>` are available. Note that kitten
is not actually copied to the remote host, instead a small bootstrap script is
copied which will download and run kitty when kitty is first executed on the
remote host. A value of :code:`if-needed` means kitty is installed only if not
already present in the system-wide PATH. A value of :code:`yes` means that kitty
is installed even if already present, and the installed kitty takes precedence.
Finally, :code:`no` means no kitty is installed on the remote host. The
installed kitty can be updated by running: :code:`kitty +update-kitty` on the
copied which will download and run kitten when kitten is first executed on the
remote host. A value of :code:`if-needed` means kitten is installed only if not
already present in the system-wide PATH. A value of :code:`yes` means that kitten
is installed even if already present, and the installed kitten takes precedence.
Finally, :code:`no` means no kitten is installed on the remote host. The
installed kitten can be updated by running: :code:`kitten update-self` on the
remote host.
''')
egr() # }}}

View File

@@ -28,6 +28,9 @@ func TestParseSSHArgs(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if len(ans) == 0 {
ans = []string{}
}
return ans
}
@@ -39,7 +42,7 @@ func TestParseSSHArgs(t *testing.T) {
check := func(a, b any) {
diff := cmp.Diff(a, b)
if diff != "" {
t.Fatalf("Unexpected value for args: %s\n%s", args, diff)
t.Fatalf("Unexpected value for args: %#v\n%s", args, diff)
}
}
check(split(expected_ssh_args), ssh_args)

View File

@@ -110,9 +110,9 @@ init_rsync(Rsync *ans, size_t block_size, int strong_hash_type, int checksum_typ
ans->hasher = ans->hasher_constructor();
ans->checksummer = ans->checksummer_constructor();
ans->hasher.state = ans->hasher.new();
if (ans->hasher.state == NULL) { free(ans); return "Out of memory"; }
if (ans->hasher.state == NULL) { free_rsync(ans); return "Out of memory"; }
ans->checksummer.state = ans->checksummer.new();
if (ans->checksummer.state == NULL) { free(ans); return "Out of memory"; }
if (ans->checksummer.state == NULL) { free_rsync(ans); return "Out of memory"; }
return NULL;
}

View File

@@ -128,8 +128,10 @@ def main(args: List[str]) -> None:
if __name__ == '__main__':
main(sys.argv)
elif __name__ == '__doc__':
from kitty.cli import CompletionSpec
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = option_text
cd['help_text'] = help_text
cd['short_desc'] = help_text
cd['short_desc'] = 'Transfer files easily over the TTY device'
cd['args_completion'] = CompletionSpec.from_string('type:file group:"Files"')

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import codecs

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Callable, Tuple

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import asyncio

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os

View File

@@ -50,7 +50,7 @@ func build_sets() {
}
EMOTICONS_SET = make([]rune, 0, 0x1f64f-0x1f600+1)
for i := 0x1f600; i <= 0x1f64f; i++ {
DEFAULT_SET = append(DEFAULT_SET, rune(i))
EMOTICONS_SET = append(EMOTICONS_SET, rune(i))
}
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
from typing import List, Optional

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from enum import IntFlag

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import atexit
@@ -23,6 +23,7 @@ from typing import (
Iterator,
List,
Optional,
Sequence,
Set,
Tuple,
Union,
@@ -65,7 +66,6 @@ from .fast_data_types import (
GLFW_MOD_SUPER,
GLFW_MOUSE_BUTTON_LEFT,
GLFW_PRESS,
GLFW_RELEASE,
IMPERATIVE_CLOSE_REQUESTED,
NO_CLOSE_REQUESTED,
ChildMonitor,
@@ -94,6 +94,7 @@ from .fast_data_types import (
is_modifier_key,
last_focused_os_window_id,
mark_os_window_for_close,
os_window_focus_counters,
os_window_font_size,
patch_global_colors,
redirect_mouse_handling,
@@ -104,7 +105,7 @@ from .fast_data_types import (
set_application_quit_request,
set_background_image,
set_boss,
set_in_sequence_mode,
set_ignore_os_keyboard_processing,
set_options,
set_os_window_chrome,
set_os_window_size,
@@ -116,11 +117,11 @@ from .fast_data_types import (
wrapped_kitten_names,
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches
from .keys import get_shortcut
from .layout.base import set_layout_options
from .notify import notification_activated
from .options.types import Options
from .options.utils import MINIMUM_FONT_SIZE, KeyMap, SubSequenceMap
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition, KeyMap
from .os_window_size import initial_window_size_func
from .rgb import color_from_int
from .session import Session, create_sessions, get_os_window_sizing_data
@@ -167,6 +168,7 @@ class OSWindowDict(TypedDict):
tabs: List[TabDict]
wm_class: str
wm_name: str
background_opacity: float
def listen_on(spec: str) -> Tuple[int, str]:
@@ -295,7 +297,7 @@ class VisualSelect:
set_os_window_title(self.os_window_id, '')
boss = get_boss()
redirect_mouse_handling(False)
boss.clear_pending_sequences()
boss.keyboard_mode_stack = []
for wid in self.window_ids:
w = boss.window_id_map.get(wid)
if w is not None:
@@ -332,6 +334,7 @@ class Boss:
self.clipboard = Clipboard()
self.primary_selection = Clipboard(ClipboardType.primary_selection)
self.update_check_started = False
self.peer_data_map: Dict[int, Optional[Dict[str, Sequence[str]]]] = {}
self.encryption_key = EllipticCurveKey()
self.encryption_public_key = f'{RC_ENCRYPTION_PROTOCOL_VERSION}:{base64.b85encode(self.encryption_key.public).decode("ascii")}'
self.clipboard_buffers: Dict[str, str] = {}
@@ -340,15 +343,13 @@ class Boss:
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
self.current_visual_select: Optional[VisualSelect] = None
self.startup_cursor_text_color = opts.cursor_text_color
self.pending_sequences: Optional[SubSequenceMap] = None
# A list of events received so far that are potentially part of a sequence keybinding.
self.current_sequence: List[KeyEvent] = []
self.default_pending_action: str = ''
self.cached_values = cached_values
self.os_window_map: Dict[int, TabManager] = {}
self.os_window_death_actions: Dict[int, Callable[[], None]] = {}
self.cursor_blinking = True
self.shutting_down = False
self.misc_config_errors: List[str] = []
talk_fd = getattr(single_instance, 'socket', None)
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
listen_fd = -1
@@ -361,7 +362,11 @@ class Boss:
self.allow_remote_control = 'n'
self.listening_on = ''
if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'):
listen_fd, self.listening_on = listen_on(args.listen_on)
try:
listen_fd, self.listening_on = listen_on(args.listen_on)
except Exception:
self.misc_config_errors.append(f'Invalid listen_on={args.listen_on}, ignoring')
log_error(self.misc_config_errors[-1])
self.child_monitor = ChildMonitor(
self.on_child_death,
DumpCommands(args) if args.dump_commands or args.dump_bytes else None,
@@ -370,6 +375,7 @@ class Boss:
set_boss(self)
self.args = args
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
self.keyboard_mode_stack: List[KeyboardMode] = []
self.update_keymap(global_shortcuts)
if is_macos:
from .fast_data_types import cocoa_set_notification_activated_callback
@@ -382,11 +388,13 @@ class Boss:
global_shortcuts = set_cocoa_global_shortcuts(get_options())
else:
global_shortcuts = {}
self.global_shortcuts_map: KeyMap = {v: k for k, v in global_shortcuts.items()}
self.global_shortcuts_map: KeyMap = {v: [KeyDefinition(definition=k)] for k, v in global_shortcuts.items()}
self.global_shortcuts = global_shortcuts
self.keymap = get_options().keymap.copy()
self.keyboard_modes = get_options().keyboard_modes.copy()
km = self.keyboard_modes[''].keymap
self.keyboard_modes[''].keymap = km = km.copy()
for sc in self.global_shortcuts.values():
self.keymap.pop(sc, None)
km.pop(sc, None)
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
@@ -447,6 +455,9 @@ class Boss:
for os_window_id, tm in self.os_window_map.items():
tabs = list(tm.list_tabs(self_window, tab_filter, window_filter))
if tabs:
bo = background_opacity_of(os_window_id)
if bo is None:
bo = 1
yield {
'id': os_window_id,
'platform_window_id': platform_window_id(os_window_id),
@@ -455,7 +466,8 @@ class Boss:
'last_focused': os_window_id == last_focused_os_window_id(),
'tabs': tabs,
'wm_class': tm.wm_class,
'wm_name': tm.wm_name
'wm_name': tm.wm_name,
'background_opacity': bo,
}
@property
@@ -592,14 +604,16 @@ class Boss:
self.window_id_map[window.id] = window
def _handle_remote_command(self, cmd: str, window: Optional[Window] = None, peer_id: int = 0) -> RCResponse:
from .remote_control import is_cmd_allowed, parse_cmd
from .remote_control import is_cmd_allowed, parse_cmd, remote_control_allowed
response = None
window = window or None
from_socket = peer_id > 0
is_fd_peer = from_socket and peer_id in self.peer_data_map
window_has_remote_control = bool(window and window.allow_remote_control)
if not window_has_remote_control:
if not window_has_remote_control and not is_fd_peer:
if self.allow_remote_control == 'n':
return {'ok': False, 'error': 'Remote control is disabled'}
if self.allow_remote_control == 'socket-only' and peer_id == 0:
if self.allow_remote_control == 'socket-only' and not from_socket:
return {'ok': False, 'error': 'Remote control is allowed over a socket only'}
try:
pcmd = parse_cmd(cmd, self.encryption_key)
@@ -623,13 +637,16 @@ class Boss:
extra_data: Dict[str, Any] = {}
try:
allowed_unconditionally = (
self.allow_remote_control == 'y' or (peer_id > 0 and self.allow_remote_control in ('socket-only', 'socket')) or
(window and window.remote_control_allowed(pcmd, extra_data)))
self.allow_remote_control == 'y' or
(from_socket and not is_fd_peer and self.allow_remote_control in ('socket-only', 'socket')) or
(window and window.remote_control_allowed(pcmd, extra_data)) or
(is_fd_peer and remote_control_allowed(pcmd, self.peer_data_map.get(peer_id), None, extra_data))
)
except PermissionError:
return {'ok': False, 'error': 'Remote control disallowed by window specific password'}
if allowed_unconditionally:
return self._execute_remote_command(pcmd, window, peer_id, self_window)
q = is_cmd_allowed(pcmd, window, peer_id > 0, extra_data)
q = is_cmd_allowed(pcmd, window, from_socket, extra_data)
if q is True:
return self._execute_remote_command(pcmd, window, peer_id, self_window)
if q is None:
@@ -666,7 +683,7 @@ class Boss:
), dim=True, italic=True)),
partial(self.remote_cmd_permission_received, pcmd, wid, peer_id, self_window),
'a;green:Allow request', 'p;yellow:Allow password', 'r;magenta:Deny request', 'd;red:Deny password',
window=window, default='a', hidden_text=hidden_text
window=window, default='a', hidden_text=hidden_text, title=_('Allow remote control?'),
)
if overlay_window is None:
return False
@@ -710,7 +727,7 @@ class Boss:
return response
@ac('misc', '''
Run a remote control command
Run a remote control command without needing to allow remote control
For example::
@@ -725,6 +742,22 @@ class Boss:
import shlex
self.show_error(_('remote_control mapping failed'), shlex.join(args) + '\n' + str(e))
@ac('misc', '''
Run a remote control script without needing to allow remote control
For example::
map f1 remote_control_script arg1 arg2 ...
See :ref:`rc_mapping` for details.
''')
def remote_control_script(self, path: str, *args: str) -> None:
path = which(path) or path
if not os.access(path, os.X_OK):
self.show_error('Remote control script not executable', f'The script {path} is not executable check its permissions')
return
self.run_background_process([path] + list(args), allow_remote_control=True)
def call_remote_control(self, self_window: Optional[Window], args: Tuple[str, ...]) -> 'ResponseType':
from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
from .remote_control import parse_rc_args
@@ -756,20 +789,30 @@ class Boss:
return None
raise
def peer_message_received(self, msg_bytes: bytes, peer_id: int) -> Union[bytes, bool, None]:
cmd_prefix = b'\x1bP@kitty-cmd'
terminator = b'\x1b\\'
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8')
response = self._handle_remote_command(cmd, peer_id=peer_id)
if response is None:
return None
if isinstance(response, AsyncResponse):
return True
from kitty.remote_control import encode_response_for_peer
return encode_response_for_peer(response)
def peer_message_received(self, msg_bytes: bytes, peer_id: int, is_remote_control: bool) -> Union[bytes, bool, None]:
if peer_id > 0 and msg_bytes == b'peer_death':
self.peer_data_map.pop(peer_id, None)
return False
if is_remote_control:
cmd_prefix = b'\x1bP@kitty-cmd'
terminator = b'\x1b\\'
if msg_bytes.startswith(cmd_prefix) and msg_bytes.endswith(terminator):
cmd = msg_bytes[len(cmd_prefix):-len(terminator)].decode('utf-8')
response = self._handle_remote_command(cmd, peer_id=peer_id)
if response is None:
return None
if isinstance(response, AsyncResponse):
return True
from kitty.remote_control import encode_response_for_peer
return encode_response_for_peer(response)
log_error('Malformatted remote control message received from peer, ignoring')
return None
data:SingleInstanceData = json.loads(msg_bytes.decode('utf-8'))
try:
data:SingleInstanceData = json.loads(msg_bytes.decode('utf-8'))
except Exception:
log_error('Malformed command received over single instance socket, ignoring')
return None
if isinstance(data, dict) and data.get('cmd') == 'new_instance':
from .cli_stub import CLIOptions
startup_id = data['environ'].get('DESKTOP_STARTUP_ID', '')
@@ -807,7 +850,7 @@ class Boss:
elif activation_token and is_wayland() and os_window_id:
focus_os_window(os_window_id, True, activation_token)
else:
log_error('Unknown message received from peer, ignoring')
log_error('Unknown message received over single instance socket, ignoring')
return None
def handle_remote_cmd(self, cmd: str, window: Optional[Window] = None) -> None:
@@ -912,7 +955,7 @@ class Boss:
msg = _('Are you sure you want to close this window?')
if window.has_running_program:
msg += ' ' + _('It is running a program.')
self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window)
self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window, title=_('Close window?'))
else:
self.mark_window_for_close(window)
@@ -942,6 +985,7 @@ class Boss:
window: Optional[Window] = None, # the window associated with the confirmation
confirm_on_cancel: bool = False, # on closing window
confirm_on_accept: bool = True, # on pressing enter
title: str = '' # window title
) -> Window:
result: bool = False
@@ -952,9 +996,11 @@ class Boss:
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result, *args)
cmd = ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n']
if title:
cmd += ['--title', title]
w = self.run_kitten_with_metadata(
'ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal,
'ask', cmd, window=window, custom_callback=callback_, action_on_removal=on_popup_overlay_removal,
default_data={'response': 'y' if confirm_on_cancel else 'n'})
assert isinstance(w, Window)
return w
@@ -968,6 +1014,7 @@ class Boss:
hidden_text: str = '', # text to hide in the message
hidden_text_placeholder: str = 'HIDDEN_TEXT_PLACEHOLDER', # placeholder text to insert in to message
unhide_key: str = 'u', # key to press to unhide hidden text
title: str = '' # window title
) -> Optional[Window]:
result: str = ''
@@ -987,6 +1034,8 @@ class Boss:
input_data = hidden_text
else:
input_data = None
if title:
cmd += ['--title', title]
def on_popup_overlay_removal(wid: int, boss: Boss) -> None:
callback(result)
@@ -1004,7 +1053,8 @@ class Boss:
callback: Callable[..., None], # called with the answer or empty string when aborted
window: Optional[Window] = None, # the window associated with the confirmation
prompt: str = '> ',
is_password: bool = False
is_password: bool = False,
initial_value: str = ''
) -> None:
result: str = ''
@@ -1016,6 +1066,8 @@ class Boss:
callback(result)
cmd = ['--type', 'password' if is_password else 'line', '--message', msg, '--prompt', prompt]
if initial_value:
cmd.append('--default=' + initial_value)
self.run_kitten_with_metadata(
'ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''}, action_on_removal=on_popup_overlay_removal
)
@@ -1039,7 +1091,7 @@ class Boss:
w = self.confirm(ngettext('Are you sure you want to close this tab, it has one window running?',
'Are you sure you want to close this tab, it has {} windows running?', num).format(num),
self.handle_close_tab_confirmation, tab.id,
window=tab.active_window,
window=tab.active_window, title=_('Close tab?'),
)
tab.confirm_close_window_id = w.id
@@ -1285,68 +1337,110 @@ class Boss:
t = self.active_tab
return None if t is None else t.active_window
def set_pending_sequences(self, sequences: SubSequenceMap, default_pending_action: str = '') -> None:
self.pending_sequences = sequences
self.default_pending_action = default_pending_action
set_in_sequence_mode(True)
@ac('misc', '''
End the current keyboard mode switching to the previous mode.
''')
def pop_keyboard_mode(self) -> bool:
passthrough = True
if self.keyboard_mode_stack:
self.keyboard_mode_stack.pop()
if not self.keyboard_mode_stack:
set_ignore_os_keyboard_processing(False)
passthrough = False
return passthrough
@ac('misc', '''
Switch to the specified keyboard mode, pushing it onto the stack of keyboard modes.
''')
def push_keyboard_mode(self, new_mode: str) -> None:
mode = self.keyboard_modes[new_mode]
self._push_keyboard_mode(mode)
def _push_keyboard_mode(self, mode: KeyboardMode) -> None:
self.keyboard_mode_stack.append(mode)
set_ignore_os_keyboard_processing(True)
def dispatch_possible_special_key(self, ev: KeyEvent) -> bool:
# Handles shortcuts, return True if the key was consumed
key_action = get_shortcut(self.keymap, ev)
is_root_mode = not self.keyboard_mode_stack
mode = self.keyboard_modes[''] if is_root_mode else self.keyboard_mode_stack[-1]
key_action = get_shortcut(mode.keymap, ev)
if key_action is None:
sequences = get_shortcut(get_options().sequence_map, ev)
if sequences and not isinstance(sequences, str):
self.set_pending_sequences(sequences)
self.current_sequence = [ev]
return True
if is_modifier_key(ev.key):
return False
if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev):
return True
elif isinstance(key_action, str):
return self.combine(key_action)
if not is_root_mode:
if mode.on_unknown in ('beep', 'ignore'):
if mode.on_unknown == 'beep' and get_options().enable_audio_bell:
ring_bell()
return True
if mode.on_unknown == 'passthrough':
return False
if not self.pop_keyboard_mode():
if get_options().enable_audio_bell:
ring_bell()
return True
else:
final_actions = self.matching_key_actions(key_action)
if final_actions:
mode_pos = len(self.keyboard_mode_stack) - 1
if final_actions[0].is_sequence:
if not mode.is_sequence:
sm = KeyboardMode('__sequence__')
sm.on_action = 'end'
sm.is_sequence = True
for fa in final_actions:
sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
self._push_keyboard_mode(sm)
if self.args.debug_keyboard:
print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='', flush=True)
else:
if len(final_actions) == 1:
self.pop_keyboard_mode()
return self.combine(final_actions[0].definition)
if self.args.debug_keyboard:
print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='', flush=True)
mode.keymap.clear()
for fa in final_actions:
mode.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
return True
final_action = final_actions[0]
consumed = self.combine(final_action.definition)
if consumed and not is_root_mode and mode.on_action == 'end':
if mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode:
del self.keyboard_mode_stack[mode_pos]
if not self.keyboard_mode_stack:
set_ignore_os_keyboard_processing(False)
return consumed
return False
def clear_pending_sequences(self) -> None:
self.pending_sequences = None
self.current_sequence = []
self.default_pending_action = ''
set_in_sequence_mode(False)
def process_sequence(self, ev: KeyEvent) -> bool:
# Process an event as part of a sequence. Returns whether the key
# is consumed as part of a kitty sequence keybinding.
if not self.pending_sequences:
set_in_sequence_mode(False)
return False
if self.current_sequence:
self.current_sequence.append(ev)
if ev.action == GLFW_RELEASE or is_modifier_key(ev.key):
return True
# For a press/repeat event that's not a modifier, try matching with
# kitty bindings:
remaining = {}
matched_action = None
for seq, key_action in self.pending_sequences.items():
if shortcut_matches(seq[0], ev):
seq = seq[1:]
if seq:
remaining[seq] = key_action
else:
matched_action = key_action
if remaining:
self.pending_sequences = remaining
return True
matched_action = matched_action or self.default_pending_action
if matched_action:
self.clear_pending_sequences()
self.combine(matched_action)
return True
def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> List[KeyDefinition]:
w = self.active_window
if w is not None:
w.write_to_child(b''.join(w.encoded_key(ev) for ev in self.current_sequence))
self.clear_pending_sequences()
return False
matches = []
has_sequence_match = False
for x in candidates:
if x.options.when_focus_on:
try:
if w and w in self.match_windows(x.options.when_focus_on):
matches.append(x)
if x.is_sequence:
has_sequence_match = True
except Exception:
self.show_error(_('Invalid key mapping'), _(
'The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on'))
return []
else:
if x.is_sequence:
has_sequence_match = True
matches.append(x)
if has_sequence_match:
matches = [x for x in matches if x.is_sequence]
q = matches[-1].options.when_focus_on
matches = [x for x in matches if x.options.when_focus_on == q]
else:
matches = [matches[-1]]
return matches
def cancel_current_visual_select(self) -> None:
if self.current_visual_select:
@@ -1373,27 +1467,27 @@ class Boss:
focus_os_window(tab.os_window_id, True)
self.current_visual_select = VisualSelect(tab.id, tab.os_window_id, initial_tab_id, initial_os_window_id, choose_msg, callback, reactivate_prev_tab)
if tab.current_layout.only_active_window_visible:
w = self.select_window_in_tab_using_overlay(tab, choose_msg, only_window_ids)
self.current_visual_select.window_used_for_selection_id = 0 if w is None else w.id
self.select_window_in_tab_using_overlay(tab, choose_msg, only_window_ids)
return
pending_sequences: SubSequenceMap = {}
km = KeyboardMode('__visual_select__')
km.on_action = 'end'
fmap = get_name_to_functional_number_map()
alphanumerics = get_options().visual_window_select_characters
for idx, window in tab.windows.iter_windows_with_number(only_visible=True):
if only_window_ids and window.id not in only_window_ids:
continue
ac = f'visual_window_select_action_trigger {window.id}'
ac = KeyDefinition(definition=f'visual_window_select_action_trigger {window.id}')
if idx >= len(alphanumerics):
break
ch = alphanumerics[idx]
window.screen.set_window_char(ch)
self.current_visual_select.window_ids.append(window.id)
for mods in (0, GLFW_MOD_CONTROL, GLFW_MOD_CONTROL | GLFW_MOD_SHIFT, GLFW_MOD_SUPER, GLFW_MOD_ALT, GLFW_MOD_SHIFT):
pending_sequences[(SingleKey(mods=mods, key=ord(ch.lower())),)] = ac
km.keymap[SingleKey(mods=mods, key=ord(ch.lower()))].append(ac)
if ch in string.digits:
pending_sequences[(SingleKey(mods=mods, key=fmap[f'KP_{ch}']),)] = ac
km.keymap[SingleKey(mods=mods, key=fmap[f'KP_{ch}'])].append(ac)
if len(self.current_visual_select.window_ids) > 1:
self.set_pending_sequences(pending_sequences, default_pending_action='visual_window_select_action_trigger 0')
self._push_keyboard_mode(km)
redirect_mouse_handling(True)
self.mouse_handler = self.visual_window_select_mouse_handler
else:
@@ -1428,11 +1522,16 @@ class Boss:
self.mouse_handler(ev)
def select_window_in_tab_using_overlay(self, tab: Tab, msg: str, only_window_ids: Container[int] = ()) -> Optional[Window]:
windows = tuple((None, f'Current window: {w.title}' if w is self.active_window else w.title)
if only_window_ids and w.id not in only_window_ids else (w.id, w.title)
for i, w in tab.windows.iter_windows_with_number(only_visible=False))
if len(windows) < 1:
self.visual_window_select_action_trigger(windows[0][0] if windows and windows[0][0] is not None else 0)
windows: List[Tuple[Optional[int], str]] = []
selectable_windows: List[Tuple[int, str]] = []
for i, w in tab.windows.iter_windows_with_number(only_visible=False):
if only_window_ids and w.id not in only_window_ids:
windows.append((None, f'Current window: {w.title}' if w is self.active_window else w.title))
else:
windows.append((w.id, w.title))
selectable_windows.append((w.id, w.title))
if len(selectable_windows) < 2:
self.visual_window_select_action_trigger(selectable_windows[0][0] if selectable_windows else 0)
if get_options().enable_audio_bell:
ring_bell()
return None
@@ -1510,7 +1609,8 @@ class Boss:
def report_match(f: Callable[..., Any]) -> None:
if self.args.debug_keyboard:
prefix = '\n' if dispatch_type == 'KeyPress' else ''
print(f'{prefix}\x1b[35m{dispatch_type}\x1b[m matched action:', func_name(f), flush=True)
end = ', ' if dispatch_type == 'KeyPress' else '\n'
print(f'{prefix}\x1b[35m{dispatch_type}\x1b[m matched action:', func_name(f), end=end, flush=True)
if key_action is not None:
f = getattr(self, key_action.func, None)
@@ -1536,6 +1636,10 @@ class Boss:
return True
return False
def user_menu_action(self, defn: str) -> None:
' Callback from user actions in the macOS global menu bar or other menus '
self.combine(defn)
@ac('misc', '''
Combine multiple actions and map to a single keypress
@@ -1553,8 +1657,6 @@ class Boss:
try:
actions = get_options().alias_map.resolve_aliases(action_definition, 'map' if dispatch_type == 'KeyPress' else 'mouse_map')
except Exception as e:
import traceback
traceback.print_exc()
self.show_error('Failed to parse action', f'{action_definition}\n{e}')
return True
if actions:
@@ -1564,8 +1666,6 @@ class Boss:
if len(actions) > 1:
self.drain_actions(list(actions[1:]), window_for_dispatch, dispatch_type)
except Exception as e:
import traceback
traceback.print_exc()
self.show_error('Key action failed', f'{actions[0].pretty()}\n{e}')
consumed = True
return consumed
@@ -1606,12 +1706,41 @@ class Boss:
text = '\n'.join(urls)
w.paste_text(text)
@ac('win', 'Focus the nth OS window')
@ac('win', '''
Focus the nth OS window if positive or the previously active OS windows if negative. When the number is larger
than the number of OS windows focus the last OS window. A value of zero will refocus the currently focused OS window,
this is useful if focus is not on any kitty OS window at all, however, it will only work if the window manager
allows applications to grab focus. For example::
# focus the previously active kitty OS window
map ctrl+p nth_os_window -1
# focus the current kitty OS window (grab focus)
map ctrl+0 nth_os_window 0
# focus the first kitty OS window
map ctrl+1 nth_os_window 1
# focus the last kitty OS window
map ctrl+1 nth_os_window 999
''')
def nth_os_window(self, num: int = 1) -> None:
if self.os_window_map and num > 0:
ids = list(self.os_window_map.keys())
if not self.os_window_map:
return
if num == 0:
os_window_id = current_focused_os_window_id() or last_focused_os_window_id()
focus_os_window(os_window_id, True)
elif num > 0:
ids = tuple(self.os_window_map.keys())
os_window_id = ids[min(num, len(ids)) - 1]
focus_os_window(os_window_id, True)
elif num < 0:
fc_map = os_window_focus_counters()
s = sorted(fc_map.keys(), key=fc_map.__getitem__)
if not s:
return
try:
os_window_id = s[num-1]
except IndexError:
os_window_id = s[0]
focus_os_window(os_window_id, True)
@ac('win', 'Close the currently active OS Window')
def close_os_window(self) -> None:
@@ -1639,7 +1768,7 @@ class Boss:
ngettext('Are you sure you want to close this OS window, it has one window running?',
'Are you sure you want to close this OS window, it has {} windows running', num).format(num),
self.handle_close_os_window_confirmation, os_window_id,
window=tm.active_window,
window=tm.active_window, title=_('Close OS window'),
)
tm.confirm_close_window_id = w.id
@@ -1695,7 +1824,7 @@ class Boss:
ngettext('Are you sure you want to quit kitty, it has one window running?',
'Are you sure you want to quit kitty, it has {} windows running?', num).format(num),
self.handle_quit_confirmation,
window=tm.active_window,
window=tm.active_window, title=_('Quit kitty?'),
)
self.quit_confirmation_window_id = w.id
set_application_quit_request(CLOSE_BEING_CONFIRMED)
@@ -1889,19 +2018,9 @@ class Boss:
prefilled = tab.name or tab.title
if title in ('" "', "' '"):
prefilled = ''
args = [
'--name=tab-title', '--message', _('Enter the new title for this tab below.'),
'--default', prefilled, 'do_set_tab_title', str(tab.id)]
self.run_kitten_with_metadata('ask', args)
def do_set_tab_title(self, title: str, tab_id: int) -> None:
tm = self.active_tab_manager
if tm is not None:
tab_id = int(tab_id)
for tab in tm.tabs:
if tab.id == tab_id:
tab.set_title(title)
break
self.get_line(
_('Enter the new title for this tab below. An empty title will cause the default title to be used.'),
tab.set_title, window=tab.active_window, initial_value=prefilled)
def create_special_window_for_show_error(self, title: str, msg: str, overlay_for: Optional[int] = None) -> SpecialWindowInstance:
ec = sys.exc_info()
@@ -1909,17 +2028,18 @@ class Boss:
if ec != (None, None, None):
import traceback
tb = traceback.format_exc()
cmd = [kitten_exe(), '__show_error__', kitten_exe(), '__show_error__', '--title', title]
cmd = [kitten_exe(), '__show_error__', '--title', title]
env = {}
env['KITTEN_RUNNING_AS_UI'] = '1'
env['KITTY_CONFIG_DIRECTORY'] = config_dir
return SpecialWindow(
cmd,
cmd, override_title=title,
stdin=json.dumps({'msg': msg, 'tb': tb}).encode(),
env=env,
overlay_for=overlay_for,
)
@ac('misc', 'Show an error message with the specified title and text')
def show_error(self, title: str, msg: str) -> None:
tab = self.active_tab
w = self.active_window
@@ -2223,7 +2343,9 @@ class Boss:
cwd: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
stdin: Optional[bytes] = None,
cwd_from: Optional[CwdRequest] = None
cwd_from: Optional[CwdRequest] = None,
allow_remote_control: bool = False,
remote_control_passwords: Optional[Dict[str, Sequence[str]]] = None,
) -> None:
import subprocess
env = env or None
@@ -2235,28 +2357,56 @@ class Boss:
with suppress(Exception):
cwd = cwd_from.cwd_of_child
def add_env(key: str, val: str) -> None:
nonlocal env
if env is None:
env = default_env().copy()
env[key] = val
def doit(activation_token: str = '') -> None:
nonlocal env
if activation_token:
if env is None:
env = default_env().copy()
env['XDG_ACTIVATION_TOKEN'] = activation_token
if stdin:
r, w = safe_pipe(False)
pass_fds: Tuple[int, ...] = ()
if allow_remote_control:
import socket
local, remote = socket.socketpair()
os.set_inheritable(remote.fileno(), True)
lfd = os.dup(local.fileno())
local.close()
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals)
peer_id = self.child_monitor.inject_peer(lfd)
except Exception:
os.close(w)
os.close(lfd)
remote.close()
raise
pass_fds = (remote.fileno(),)
add_env('KITTY_LISTEN_ON', f'fd:{remote.fileno()}')
self.peer_data_map[peer_id] = remote_control_passwords
if activation_token:
add_env('XDG_ACTIVATION_TOKEN', activation_token)
try:
if stdin:
r, w = safe_pipe(False)
try:
subprocess.Popen(cmd, env=env, stdin=r, cwd=cwd, preexec_fn=clear_handled_signals, pass_fds=pass_fds, close_fds=True)
except Exception:
os.close(w)
else:
thread_write(w, stdin)
finally:
os.close(r)
else:
thread_write(w, stdin)
finally:
os.close(r)
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals, pass_fds=pass_fds, close_fds=True)
finally:
if allow_remote_control:
remote.close()
try:
if is_wayland():
run_with_activation_token(doit)
else:
subprocess.Popen(cmd, env=env, cwd=cwd, preexec_fn=clear_handled_signals)
if is_wayland():
run_with_activation_token(doit)
else:
doit()
doit()
except Exception as err:
self.show_error(_('Failed to run background process'), _('Failed to run background process with error: {}').format(err))
def pipe(self, source: str, dest: str, exe: str, *args: str) -> Optional[Window]:
cmd = [exe] + list(args)
@@ -2498,7 +2648,7 @@ class Boss:
from .cli import default_config_paths
from .config import load_config
old_opts = get_options()
prev_paths = old_opts.config_paths or default_config_paths(self.args.config)
prev_paths = old_opts.all_config_paths or default_config_paths(self.args.config)
paths = paths or prev_paths
bad_lines: List[BadLine] = []
opts = load_config(*paths, overrides=old_opts.config_overrides if apply_overrides else None, accumulate_bad_lines=bad_lines)
@@ -2550,7 +2700,7 @@ class Boss:
assert isinstance(b, int)
dbus_notification_created(a, b)
def show_bad_config_lines(self, bad_lines: Iterable[BadLine]) -> None:
def show_bad_config_lines(self, bad_lines: Iterable[BadLine], misc_errors: Iterable[str] = ()) -> None:
def format_bad_line(bad_line: BadLine) -> str:
return f'{bad_line.number}:{bad_line.exception} in line: {bad_line.line}\n'
@@ -2564,7 +2714,10 @@ class Boss:
if file:
a(f'In file {file}:')
[a(format_bad_line(x)) for x in groups[file]]
if misc_errors:
a('In final effective configuration:')
for line in misc_errors:
a(line)
msg = '\n'.join(ans).rstrip()
self.show_error(_('Errors parsing configuration'), msg)

View File

@@ -47,6 +47,7 @@ typedef struct {
char *data;
size_t sz;
id_type peer_id;
bool is_remote_control_peer;
} Message;
typedef struct {
@@ -232,8 +233,53 @@ static void* io_loop(void *data);
static void* talk_loop(void *data);
static void send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz);
static void wakeup_talk_loop(bool);
static bool add_peer_to_injection_queue(int peer_fd, int pipe_fd);
static bool talk_thread_started = false;
static bool
simple_read_from_pipe(int fd, void *data, size_t sz) {
// read a small amount of data to a pipe handling only EINTR
while (true) {
ssize_t ret = read(fd, data, sz);
if (ret == -1 && errno == EINTR) continue;
return ret == (ssize_t)sz;
}
}
static PyObject*
inject_peer(PyObject *s, PyObject *a) {
#define inject_peer_doc "inject_peer(fd) -> Start communication with a peer over the specified file descriptor"
ChildMonitor *self = (ChildMonitor*)s;
if (!PyLong_Check(a)) { PyErr_SetString(PyExc_TypeError, "peer fd must be an int"); return NULL; }
long fd = PyLong_AsLong(a);
if (fd < 0) { PyErr_Format(PyExc_ValueError, "Invalid peer fd: %ld", fd); return NULL; }
if (!talk_thread_started) {
int ret;
if ((ret = pthread_create(&self->talk_thread, NULL, talk_loop, self)) != 0) {
return PyErr_Format(PyExc_OSError, "Failed to start talk thread with error: %s", strerror(ret));
}
talk_thread_started = true;
}
int fds[2] = {0};
if (!self_pipe(fds, false)) {
safe_close(fd, __FILE__, __LINE__);
return PyErr_SetFromErrno(PyExc_OSError);
}
if (!add_peer_to_injection_queue(fd, fds[1])) {
safe_close(fd, __FILE__, __LINE__);
safe_close(fds[0], __FILE__, __LINE__); safe_close(fds[1], __FILE__, __LINE__);
PyErr_SetString(PyExc_RuntimeError, "Too many peers waiting to be injected");
return NULL;
}
wakeup_talk_loop(false);
id_type peer_id = 0;
bool ok = simple_read_from_pipe(fds[0], &peer_id, sizeof(peer_id));
safe_close(fds[0], __FILE__, __LINE__);
if (!ok) { PyErr_SetString(PyExc_RuntimeError, "Failed to read peer id from self pipe"); return NULL; }
return PyLong_FromUnsignedLongLong(peer_id);
}
static PyObject *
start(PyObject *s, PyObject *a UNUSED) {
#define start_doc "start() -> Start the I/O thread"
@@ -467,7 +513,7 @@ parse_input(ChildMonitor *self) {
Message *msg = msgs + i;
PyObject *resp = NULL;
if (msg->data) {
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#K", msg->data, (int)msg->sz, msg->peer_id);
resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "y#KO", msg->data, (int)msg->sz, msg->peer_id, msg->is_remote_control_peer ? Py_True : Py_False);
free(msg->data);
if (!resp) PyErr_Print();
}
@@ -1043,6 +1089,10 @@ close_os_window(ChildMonitor *self, OSWindow *os_window) {
if (os_window->before_fullscreen.is_set && is_os_window_fullscreen(os_window)) {
w = os_window->before_fullscreen.w; h = os_window->before_fullscreen.h;
}
// On GNOME Wayland w, h are the content area size, we need to add the frame size back
int content_area_width, content_area_height;
adjust_window_size_for_csd(os_window, w, h, &content_area_width, &content_area_height);
w += w - content_area_width; h += h - content_area_height;
destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, w, h);
for (size_t t=0; t < os_window->num_tabs; t++) {
@@ -1148,6 +1198,7 @@ process_cocoa_pending_actions(void) {
if (cocoa_pending_actions_data.wd) {
if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); }
if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "sO", cocoa_pending_actions_data.wd, Py_True); }
if (cocoa_pending_actions[USER_MENU_ACTION]) { call_boss(user_menu_action, "s", cocoa_pending_actions_data.wd); }
free(cocoa_pending_actions_data.wd);
cocoa_pending_actions_data.wd = NULL;
}
@@ -1562,6 +1613,7 @@ typedef struct {
size_t capacity, used;
bool failed;
} write;
bool is_remote_control_peer;
} Peer;
static id_type peer_id_counter = 0;
@@ -1576,24 +1628,33 @@ typedef struct pollfd PollFD;
#define PEER_LIMIT 256
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
static bool
accept_peer(int listen_fd, bool shutting_down) {
int peer = accept(listen_fd, NULL, NULL);
if (UNLIKELY(peer == -1)) {
if (errno == EINTR) return true;
if (!shutting_down) perror("accept() on talk socket failed!");
return false;
}
static id_type
add_peer(int peer, bool is_remote_control_peer) {
id_type ans = 0;
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;
ans = p->id;
p->is_remote_control_peer = is_remote_control_peer;
} else {
log_error("Too many peers want to talk, ignoring one.");
nuke_socket(peer);
}
return ans;
}
static bool
accept_peer(int listen_fd, bool shutting_down, bool is_remote_control_peer) {
int peer = accept(listen_fd, NULL, NULL);
if (UNLIKELY(peer == -1)) {
if (errno == EINTR) return true;
if (!shutting_down) perror("accept() on talk socket failed!");
return false;
}
add_peer(peer, is_remote_control_peer);
return true;
}
@@ -1620,11 +1681,23 @@ queue_peer_message(ChildMonitor *self, Peer *peer) {
}
}
m->peer_id = peer->id;
m->is_remote_control_peer = peer->is_remote_control_peer;
peer->num_of_unresponded_messages_sent_to_main_thread++;
talk_mutex(unlock);
wakeup_main_loop();
}
static void
notify_on_peer_removal(ChildMonitor *self, const Peer *p) {
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
Message *m = self->messages + self->messages_count++;
memset(m, 0, sizeof(Message));
m->data = strdup("peer_death");
if (m->data) m->sz = strlen("peer_death");
m->peer_id = p->id;
m->is_remote_control_peer = p->id;
}
static bool
has_complete_peer_command(Peer *peer) {
peer->read.command_end = 0;
@@ -1701,17 +1774,51 @@ wakeup_talk_loop(bool in_signal_handler) {
}
static void
prune_peers(void) {
static bool
prune_peers(ChildMonitor *self) {
bool pruned = false;
for (size_t idx = talk_data.num_peers; idx-- > 0;) {
Peer *p = talk_data.peers + idx;
if (p->read.finished && !p->num_of_unresponded_messages_sent_to_main_thread && !p->write.used) {
notify_on_peer_removal(self, p);
free_peer(p);
remove_i_from_array(talk_data.peers, idx, talk_data.num_peers);
pruned = true;
}
}
return pruned;
}
static struct {
size_t num;
struct { int peer_fd, pipe_fd; } fds[16];
} peers_to_inject = {0};
static bool
add_peer_to_injection_queue(int peer_fd, int pipe_fd) {
bool added = false;
talk_mutex(lock);
if (peers_to_inject.num < arraysz(peers_to_inject.fds)) {
peers_to_inject.fds[peers_to_inject.num].peer_fd = peer_fd;
peers_to_inject.fds[peers_to_inject.num].pipe_fd = pipe_fd;
peers_to_inject.num++;
added = true;
}
talk_mutex(unlock);
return added;
}
static void
simple_write_to_pipe(int fd, void *data, size_t sz) {
// write a small amount of data to a pipe handling only EINTR
while (true) {
ssize_t ret = write(fd, data, sz);
if (ret == -1 && errno == EINTR) continue;
break;
}
}
static void*
talk_loop(void *data) {
// The talk thread loop
@@ -1730,9 +1837,18 @@ talk_loop(void *data) {
while (LIKELY(!self->shutting_down)) {
num_peer_fds = 0;
bool need_to_wakup_main_loop = false;
talk_mutex(lock);
if (peers_to_inject.num) {
for (size_t i = 0; i < peers_to_inject.num; i++) {
id_type added_peer_id = add_peer(peers_to_inject.fds[i].peer_fd, true);
simple_write_to_pipe(peers_to_inject.fds[i].pipe_fd, &added_peer_id, sizeof(id_type));
safe_close(peers_to_inject.fds[i].pipe_fd, __FILE__, __LINE__);
}
peers_to_inject.num = 0;
}
if (talk_data.num_peers > 0) {
talk_mutex(lock);
prune_peers();
if (prune_peers(self)) need_to_wakup_main_loop = true;
for (size_t i = 0; i < talk_data.num_peers; i++) {
Peer *p = talk_data.peers + i;
if (!p->read.finished || p->write.used) {
@@ -1745,14 +1861,15 @@ talk_loop(void *data) {
fds[p->fd_array_idx].events = flags;
} else p->fd_array_idx = 0;
}
talk_mutex(unlock);
}
talk_mutex(unlock);
if (need_to_wakup_main_loop) wakeup_main_loop();
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) {
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 (!accept_peer(fds[i].fd, self->shutting_down, fds[i].fd == self->listen_fd)) goto end;
}
}
if (fds[num_listen_fds - 1].revents & POLLIN) {
@@ -1813,6 +1930,7 @@ send_response_to_peer(id_type peer_id, const char *msg, size_t msg_sz) {
// Boilerplate {{{
static PyMethodDef methods[] = {
METHOD(add_child, METH_VARARGS)
METHOD(inject_peer, METH_O)
METHOD(needs_write, METH_VARARGS)
METHOD(start, METH_NOARGS)
METHOD(wakeup, METH_NOARGS)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
@@ -11,7 +11,7 @@ import kitty.fast_data_types as fast_data_types
from .constants import handled_signals, is_freebsd, is_macos, kitten_exe, kitty_base_dir, shell_path, terminfo_dir
from .types import run_once
from .utils import log_error, which
from .utils import cmdline_for_hold, log_error, which
try:
from typing import TypedDict
@@ -200,6 +200,7 @@ class Child:
cwd_from: Optional['CwdRequest'] = None,
is_clone_launch: str = '',
add_listen_on_env_var: bool = True,
hold: bool = False,
):
self.is_clone_launch = is_clone_launch
self.add_listen_on_env_var = add_listen_on_env_var
@@ -214,10 +215,12 @@ class Child:
self.cwd = os.path.abspath(cwd)
self.stdin = stdin
self.env = env or {}
self.final_env:Dict[str, str] = {}
self.is_default_shell = bool(self.argv and self.argv[0] == shell_path)
self.should_run_via_run_shell_kitten = is_macos and self.is_default_shell
self.hold = hold
def final_env(self) -> Dict[str, str]:
def get_final_env(self) -> Dict[str, str]:
from kitty.options.utils import DELETE_ENV_VAR
env = default_env().copy()
opts = fast_data_types.get_options()
@@ -273,9 +276,9 @@ class Child:
os.set_inheritable(stdin_read_fd, True)
else:
stdin_read_fd = stdin_write_fd = -1
final_env = self.final_env()
env = tuple(f'{k}={v}' for k, v in final_env.items())
self.final_env = self.get_final_env()
argv = list(self.argv)
cwd = self.cwd
if self.should_run_via_run_shell_kitten:
# bash will only source ~/.bash_profile if it detects it is a login
# shell (see the invocation section of the bash man page), which it
@@ -299,14 +302,23 @@ class Child:
if is_macos:
# In addition for getlogin() to work we need to run the shell
# via the /usr/bin/login wrapper, sigh.
# And login on macOS looks for .hushlogin in CWD instead of
# HOME, bloody idiotic so we cant cwd when running it.
# https://github.com/kovidgoyal/kitty/issues/6511
import pwd
user = pwd.getpwuid(os.geteuid()).pw_name
if cwd:
argv.append('--cwd=' + cwd)
cwd = os.path.expanduser('~')
argv = ['/usr/bin/login', '-f', '-l', '-p', user] + argv
self.final_exe = which(argv[0]) or argv[0]
self.final_exe = final_exe = which(argv[0]) or argv[0]
self.final_argv0 = argv[0]
if self.hold:
argv = cmdline_for_hold(argv)
final_exe = argv[0]
env = tuple(f'{k}={v}' for k, v in self.final_env.items())
pid = fast_data_types.spawn(
self.final_exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
final_exe, cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd,
ready_read_fd, ready_write_fd, tuple(handled_signals), kitten_exe(), opts.forward_stdio)
os.close(slave)
self.pid = pid
@@ -379,9 +391,9 @@ class Child:
def environ(self) -> Dict[str, str]:
try:
assert self.pid is not None
return environ_of_process(self.pid)
return environ_of_process(self.pid) or self.final_env.copy()
except Exception:
return {}
return self.final_env.copy()
@property
def current_cwd(self) -> Optional[str]:

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import shlex
import sys
from collections import deque
from dataclasses import dataclass
@@ -17,6 +16,7 @@ from .fast_data_types import wcswidth
from .options.types import Options as KittyOpts
from .types import run_once
from .typing import BadLineType, TypedDict
from .utils import shlex_split
class CompletionType(Enum):
@@ -45,7 +45,7 @@ class CompletionSpec:
@staticmethod
def from_string(raw: str) -> 'CompletionSpec':
self = CompletionSpec()
for x in shlex.split(raw):
for x in shlex_split(raw):
ck, vv = x.split(':', 1)
if ck == 'type':
self.type = getattr(CompletionType, vv)
@@ -889,7 +889,8 @@ Change to the specified directory when launching.
--detach
type=bool-set
condition=not is_macos
Detach from the controlling terminal, if any. Not available on macOS.
Detach from the controlling terminal, if any. Not available on macOS. On macOS
use :code:`open -a kitty.app -n` instead.
--session
@@ -943,10 +944,10 @@ Listen on the specified socket address for control messages. For example,
UNIX sockets, not associated with a file, like this: :option:`{appname}
--listen-on`=unix:@mykitty. Environment variables are expanded and relative
paths are resolved with respect to the temporary directory. To control kitty,
you can send commands to it with :italic:`{appname} @` using the
:option:`{appname} @ --to` option to specify this address. Note that if you run
:italic:`{appname} @` within a kitty window, there is no need to specify the
:option:`{appname} @ --to` option as it will automatically read from the
you can send commands to it with :italic:`kitten @` using the
:option:`kitten @ --to` option to specify this address. Note that if you run
:italic:`kitten @` within a kitty window, there is no need to specify the
:option:`kitten @ --to` option as it will automatically read from the
environment. Note that this will be ignored unless :opt:`allow_remote_control`
is set to either: :code:`yes`, :code:`socket` or :code:`socket-only`. This can
also be specified in :file:`kitty.conf`.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
# Replay the log from --dump-commands. To use first run

View File

@@ -212,6 +212,16 @@ find_app_name(void) {
@end
// }}}
@interface UserMenuItem : NSMenuItem
@property (nonatomic) size_t action_index;
@end
@implementation UserMenuItem {
}
@end
@interface GlobalMenuTarget : NSObject
+ (GlobalMenuTarget *) shared_instance;
@end
@@ -220,6 +230,13 @@ find_app_name(void) {
@implementation GlobalMenuTarget
- (void)user_menu_action:(id)sender {
UserMenuItem *m = sender;
if (m.action_index < OPT(global_menu).count && OPT(global_menu.entries)) {
set_cocoa_pending_action(USER_MENU_ACTION, OPT(global_menu).entries[m.action_index].definition);
}
}
PENDING(edit_config_file, PREFERENCES_WINDOW)
PENDING(new_os_window, NEW_OS_WINDOW)
PENDING(detach_tab, DETACH_TAB)
@@ -559,6 +576,35 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
// global menu {{{
static void
add_user_global_menu_entry(struct MenuItem *e, NSMenu *bar, size_t action_index) {
NSMenu *parent = bar;
UserMenuItem *final_item = nil;
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
for (size_t i = 0; i < e->location_count; i++) {
NSMenuItem *item = [parent itemWithTitle:@(e->location[i])];
if (!item) {
final_item = [[UserMenuItem alloc] initWithTitle:@(e->location[i]) action:@selector(user_menu_action:) keyEquivalent:@""];
final_item.target = global_menu_target;
[parent addItem:final_item];
item = final_item;
[final_item release];
}
if (i + 1 < e->location_count) {
if (![item hasSubmenu]) {
NSMenu* sub_menu = [[NSMenu alloc] initWithTitle:item.title];
[item setSubmenu:sub_menu];
[sub_menu release];
}
parent = [item submenu];
if (!parent) return;
}
}
if (final_item != nil) {
final_item.action_index = action_index;
}
}
void
cocoa_create_global_menu(void) {
NSString* app_name = find_app_name();
@@ -665,8 +711,17 @@ cocoa_create_global_menu(void) {
[NSApp setHelpMenu:helpMenu];
[helpMenu release];
if (OPT(global_menu.entries)) {
for (size_t i = 0; i < OPT(global_menu.count); i++) {
struct MenuItem *e = OPT(global_menu.entries) + i;
if (e->definition && e->location && e->location_count > 1) {
add_user_global_menu_entry(e, bar, i);
}
}
}
[bar release];
class_addMethod(
object_getClass([NSApp delegate]),
@selector(applicationDockMenu:),
@@ -675,6 +730,7 @@ cocoa_create_global_menu(void) {
[NSApp setServicesProvider:[[[ServiceProvider alloc] init] autorelease]];
#undef MENU_ITEM
}

View File

@@ -70,6 +70,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
is_mutiple_vars = {}
option_names = set()
color_table = list(map(str, range(256)))
choice_dedup: Dict[str, str] = {}
choice_parser_dedup: Dict[str, str] = {}
def parser_function_declaration(option_name: str) -> None:
t('')
@@ -100,6 +102,10 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
if option.choices:
typ = 'typing.Literal[{}]'.format(', '.join(repr(x) for x in option.choices))
ename = f'choices_for_{option.name}'
if typ in choice_dedup:
typ = choice_dedup[typ]
else:
choice_dedup[typ] = ename
choices[ename] = typ
typ = ename
func = str
@@ -134,12 +140,18 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
imports.add(('kitty.constants', 'is_macos'))
a(f' {option.name}: {typ} = {defval}')
if option.choices:
ecname = f'choices_for_{option.name}'
crepr = f'frozenset({option.choices!r})'
if crepr in choice_parser_dedup:
crepr = choice_parser_dedup[crepr]
else:
choice_parser_dedup[crepr] = ecname
t(' val = val.lower()')
t(f' if val not in self.choices_for_{option.name}:')
t(f' raise ValueError(f"The value {{val}} is not a valid choice for {option.name}")')
t(f' ans["{option.name}"] = val')
t('')
t(f' choices_for_{option.name} = frozenset({option.choices!r})')
t(f' {ecname} = {crepr}')
for option_name, (typ, mval) in is_mutiple_vars.items():
a(f' {option_name}: {typ} = ' '{}')
@@ -187,6 +199,7 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
a(' ))')
a(' config_paths: typing.Tuple[str, ...] = ()')
a(' all_config_paths: typing.Tuple[str, ...] = ()')
a(' config_overrides: typing.Tuple[str, ...] = ()')
a('')
a(' def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:')
@@ -368,12 +381,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
output_imports(imports)
a('')
if choices:
a('if typing.TYPE_CHECKING:')
for name, cdefn in choices.items():
a(f' {name} = {cdefn}')
a('else:')
for name in choices:
a(f' {name} = str')
a(f'{name} = {cdefn}')
a('')
a('option_names = ( # {{''{')

View File

@@ -1,9 +1,8 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import shlex
import sys
from contextlib import contextmanager
from typing import (
@@ -29,7 +28,7 @@ from ..fast_data_types import Color
from ..rgb import to_color as as_color
from ..types import ConvertibleToNumbers, ParsedShortcut, run_once
from ..typing import Protocol
from ..utils import expandvars, log_error
from ..utils import expandvars, log_error, shlex_split
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
ItemParser = Callable[[str, str, Dict[str, Any]], bool]
@@ -94,7 +93,6 @@ class ToCmdline:
return self
def __call__(self, x: str, expand: bool = True) -> List[str]:
ans = shlex.split(x)
if expand:
ans = list(
map(
@@ -103,9 +101,11 @@ class ToCmdline:
os.environ if self.override_env is None else self.override_env,
fallback_to_os_env=False
),
ans
shlex_split(x)
)
)
else:
ans = list(shlex_split(x))
return ans

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import json
@@ -11,7 +11,7 @@ from .conf.utils import BadLine, parse_config_base
from .conf.utils import load_config as _load_config
from .constants import cache_dir, defconf
from .options.types import Options, defaults, option_names
from .options.utils import KeyDefinition, KeyMap, MouseMap, MouseMapping, SequenceMap, build_action_aliases
from .options.utils import KeyboardMode, KeyboardModeMap, KeyDefinition, MouseMap, MouseMapping, build_action_aliases
from .typing import TypedDict
from .utils import log_error
@@ -100,28 +100,26 @@ def finalize_keys(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] =
else:
accumulate_bad_lines.append(BadLine(d.definition_location.number, d.definition_location.line, err, d.definition_location.file))
keymap: KeyMap = {}
sequence_map: SequenceMap = {}
modes: KeyboardModeMap = {'': KeyboardMode()}
for defn in defns:
is_no_op = defn.is_no_op
if defn.is_sequence:
keymap.pop(defn.trigger, None)
s = sequence_map.setdefault(defn.trigger, {})
if is_no_op:
s.pop(defn.rest, None)
if not s:
del sequence_map[defn.trigger]
if defn.options.new_mode:
modes[defn.options.new_mode] = nm = KeyboardMode(defn.options.new_mode)
nm.on_unknown = defn.options.on_unknown
nm.on_action = defn.options.on_action
defn.definition = f'push_keyboard_mode {defn.options.new_mode}'
try:
m = modes[defn.options.mode]
except KeyError:
kerr = f'The keyboard mode {defn.options.mode} is unknown, ignoring the mapping'
if accumulate_bad_lines is None:
log_error(kerr)
else:
s[defn.rest] = defn.definition
else:
sequence_map.pop(defn.trigger, None)
if is_no_op:
keymap.pop(defn.trigger, None)
else:
keymap[defn.trigger] = defn.definition
opts.keymap = keymap
opts.sequence_map = sequence_map
dl = defn.definition_location
accumulate_bad_lines.append(BadLine(dl.number, dl.line, KeyError(kerr), dl.file))
continue
m.keymap[defn.trigger].append(defn)
opts.keyboard_modes = modes
def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] = None) -> None:
@@ -140,11 +138,10 @@ def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[List[B
mousemap: MouseMap = {}
for defn in defns:
is_no_op = defn.is_no_op
if is_no_op:
mousemap.pop(defn.trigger, None)
else:
if defn.definition:
mousemap[defn.trigger] = defn.definition
else:
mousemap.pop(defn.trigger, None)
opts.mousemap = mousemap
@@ -164,7 +161,8 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula
from .options.parse import merge_result_dicts
overrides = tuple(overrides) if overrides is not None else ()
opts_dict, paths = _load_config(defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines), merge_result_dicts, *paths, overrides=overrides)
opts_dict, found_paths = _load_config(
defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines), merge_result_dicts, *paths, overrides=overrides)
opts = Options(opts_dict)
opts.alias_map = build_action_aliases(opts.kitten_alias, 'kitten')
@@ -179,7 +177,8 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula
if opts.background_opacity < 1.0 and opts.macos_titlebar_color > 0:
log_error('Cannot use both macos_titlebar_color and background_opacity')
opts.macos_titlebar_color = 0
opts.config_paths = paths
opts.config_paths = found_paths
opts.all_config_paths = paths
opts.config_overrides = overrides
return opts

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import errno
@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 30, 1)
version: Version = Version(0, 32, 0)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -240,7 +240,7 @@ find_substitute_face(CFStringRef str, CTFontRef old_font, CPUCell *cpu_cell) {
new_font = manually_search_fallback_fonts(old_font, cpu_cell);
if (new_font) return new_font;
}
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
PyErr_Format(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font for: %s", [(NSString *)str UTF8String]);
return NULL;
}
return new_font;
@@ -270,7 +270,22 @@ create_fallback_face(PyObject *base_face, CPUCell* cell, bool UNUSED bold, bool
}
else { search_for_fallback(); }
if (new_font == NULL) return NULL;
return (PyObject*)ct_face(new_font, fg);
NSURL *url = (NSURL*)CTFontCopyAttribute(new_font, kCTFontURLAttribute);
const char *font_path = [[url path] UTF8String];
ssize_t idx = -1;
PyObject *q, *ans = NULL;
while ((q = iter_fallback_faces(fg, &idx))) {
CTFace *qf = (CTFace*)q;
const char *qpath;
if (qf->path && (qpath = PyUnicode_AsUTF8(qf->path)) && strcmp(qpath, font_path) == 0) {
ans = PyLong_FromSsize_t(idx);
break;
}
}
[url release];
if (ans == NULL) return (PyObject*)ct_face(new_font, fg);
CFRelease(new_font);
return ans;
}
unsigned int
@@ -319,6 +334,9 @@ harfbuzz_font_for_face(PyObject* s) {
if (!self->hb_font) {
self->hb_font = hb_coretext_font_create(self->ct_font);
if (!self->hb_font) fatal("Failed to create hb_font");
// dunno if we need this, harfbuzz docs say it is used by CoreText
// for optical sizing which changes the look of glyphs at small and large sizes
hb_font_set_ptem(self->hb_font, self->scaled_point_sz);
hb_ot_font_set_funcs(self->hb_font);
}
return self->hb_font;
@@ -421,13 +439,12 @@ struct RenderBuffers {
CGGlyph *glyphs;
CGRect *boxes;
CGPoint *positions;
CGSize *advances;
};
static struct RenderBuffers buffers = {0};
static void
finalize(void) {
free(buffers.render_buf); free(buffers.glyphs); free(buffers.boxes); free(buffers.positions); free(buffers.advances);
free(buffers.render_buf); free(buffers.glyphs); free(buffers.boxes); free(buffers.positions);
memset(&buffers, 0, sizeof(struct RenderBuffers));
if (all_fonts_collection_data) CFRelease(all_fonts_collection_data);
if (window_title_font) CFRelease(window_title_font);
@@ -471,11 +488,10 @@ ensure_render_space(size_t width, size_t height, size_t num_glyphs) {
}
if (buffers.sz < num_glyphs) {
buffers.sz = MAX(128, num_glyphs * 2);
buffers.advances = calloc(sizeof(buffers.advances[0]), buffers.sz);
buffers.boxes = calloc(sizeof(buffers.boxes[0]), buffers.sz);
buffers.glyphs = calloc(sizeof(buffers.glyphs[0]), buffers.sz);
buffers.positions = calloc(sizeof(buffers.positions[0]), buffers.sz);
if (!buffers.advances || !buffers.boxes || !buffers.glyphs || !buffers.positions) fatal("Out of memory");
if (!buffers.boxes || !buffers.glyphs || !buffers.positions) fatal("Out of memory");
}
}
@@ -612,7 +628,7 @@ render_single_ascii_char_as_mask(const char ch, size_t *result_width, size_t *re
static bool
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
unsigned int canvas_width = cell_width * num_cells;
ensure_render_space(canvas_width, cell_height, num_glyphs);
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, buffers.glyphs, buffers.boxes, num_glyphs);
@@ -626,17 +642,20 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
CGFloat sz = CTFontGetSize(ct_font);
sz *= canvas_width / right;
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
bool ret = do_render(new_font, CTFontGetUnitsPerEm(new_font), bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
CFRelease(new_font);
return ret;
}
}
CGFloat x = 0, y = 0;
CTFontGetAdvancesForGlyphs(ct_font, kCTFontOrientationDefault, buffers.glyphs, buffers.advances, num_glyphs);
CGFloat scale = CTFontGetSize(ct_font) / units_per_em;
for (unsigned i=0; i < num_glyphs; i++) {
buffers.positions[i].x = x; buffers.positions[i].y = y;
if (debug_rendering) printf("x=%f origin=%f width=%f advance=%f\n", x, buffers.boxes[i].origin.x, buffers.boxes[i].size.width, buffers.advances[i].width);
x += buffers.advances[i].width; y += buffers.advances[i].height;
buffers.positions[i].x = x + hb_positions[i].x_offset * scale; buffers.positions[i].y = y + hb_positions[i].y_offset * scale;
if (debug_rendering) printf("x=%f y=%f origin=%f width=%f x_advance=%f x_offset=%f y_advance=%f y_offset=%f\n",
buffers.positions[i].x, buffers.positions[i].y, buffers.boxes[i].origin.x, buffers.boxes[i].size.width,
hb_positions[i].x_advance * scale, hb_positions[i].x_offset * scale,
hb_positions[i].y_advance * scale, hb_positions[i].y_offset * scale);
x += hb_positions[i].x_advance * scale; y += hb_positions[i].y_advance * scale;
}
if (*was_colored) {
render_color_glyph(ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline);
@@ -661,7 +680,7 @@ render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *inf
CTFace *self = (CTFace*)s;
ensure_render_space(128, 128, num_glyphs);
for (unsigned i=0; i < num_glyphs; i++) buffers.glyphs[i] = info[i].codepoint;
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
return do_render(self->ct_font, self->units_per_em, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
}
@@ -691,8 +710,8 @@ postscript_name_for_face(const PyObject *face_) {
static PyObject *
repr(CTFace *self) {
char buf[1024] = {0};
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
(self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
(self->ascent), (self->descent), (self->leading), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
return PyUnicode_FromFormat(
"Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)",
self->family_name, self->full_name, self->postscript_name, self->path, self->units_per_em, buf
@@ -708,7 +727,6 @@ static PyMethodDef module_methods[] = {
static PyMemberDef members[] = {
#define MEM(name, type) {#name, type, offsetof(CTFace, name), READONLY, #name}
MEM(units_per_em, T_UINT),
MEM(point_sz, T_FLOAT),
MEM(scaled_point_sz, T_FLOAT),
MEM(ascent, T_FLOAT),
MEM(descent, T_FLOAT),

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