Compare commits

...

82 Commits

Author SHA1 Message Date
Kovid Goyal
827b6598b2 version 0.23.0 2021-08-16 07:38:02 +05:30
Kovid Goyal
59bafea06d Add some docs for non-interactive theme changing 2021-08-16 07:37:59 +05:30
Kovid Goyal
d61aca40b8 Make link to template.conf a download link 2021-08-16 07:14:49 +05:30
Kovid Goyal
bf704d35a2 Add a link to kitty-themes in the changelog 2021-08-16 07:11:05 +05:30
Kovid Goyal
fbc8881a96 Merge branch 'master' of github.com:kovidgoyal/kitty 2021-08-15 08:28:00 +05:30
Kovid Goyal
8cfb1efb01 hints kitten: Make the --program option work with --linenum-action=self
Fixes #3931
2021-08-15 08:27:25 +05:30
Kovid Goyal
f399b8466c Merge branch 'patch-1' of https://github.com/con-f-use/kitty 2021-08-14 19:42:19 +05:30
con-f-use
437ba69049 docs: typo in hint.rst, adaption to new layout 2021-08-14 15:19:27 +02:00
Kovid Goyal
dbc7e8e85d When dragging word or line selections, ensure the initially selected item is never deselected
This matches behavior in most other programs
Fixes #3930
2021-08-13 20:23:43 +05:30
Kovid Goyal
f3333ce941 Fix getting connection data from ssh command line when -- is used in the command line
This broke the remote file kitten when using the ssh kitten as the later
was changed to use -- recently.

Fixes #3929
2021-08-13 17:14:43 +05:30
Kovid Goyal
97a9261096 ... 2021-08-13 17:04:49 +05:30
Kovid Goyal
5a76dab3c6 ControlMaster must not suppress exceptions 2021-08-13 17:02:27 +05:30
Kovid Goyal
5a92d3f312 Fix a regression that broke the edit action in the remote file kitten 2021-08-13 16:59:40 +05:30
Kovid Goyal
b173dd1c39 Fix error getting editor in remote file kitten not being reported 2021-08-13 16:57:01 +05:30
Kovid Goyal
473e1a6f22 Fix #3926
Apparently nvim assumes cnorm doesn't affect cursor shape. It's
ambiguous whether cnorm should affect it or not. So to prevent
regressions in case other programs assume that as well, have it only
make the cursor visible and blinking, without changing its shape.
2021-08-13 09:31:18 +05:30
Kovid Goyal
9c4a890688 Adjust attenuation based on color 2021-08-11 12:41:09 +05:30
Kovid Goyal
a803c8bcc5 Make the visual bell flash more gentle, especially on dark themes
Fixes #2937
2021-08-11 12:09:25 +05:30
Kovid Goyal
5c7ce18379 Move visual bell code into shaders.c 2021-08-11 12:04:52 +05:30
Andrew Mayorov
aff6fdfa84 Make visual bell flash much more gentle
Right now visual bell makes background flash sharply with bright white
(when configured with darkish color theme). This causes eye strain,
especially prominent in unlit environments.

This change makes background bounce smoothly between regular bg color
and highlight (selection) bg color for the configured visual bell
duration. Intensity is animated with cubic easing functions. It
currently peaks at 20% of the duration, this is hardcoded.

Mark functions computing intensity and easing as inline

Do smarter blending of visual bell flash

Blend highlight color with pegtop's softlight mode over both background
and half as much over foreground. This should help with flash visibility
in light themed and inverted colors contexts.

Blend flash in BACKGROUND pass also

Revert "Do smarter blending of visual bell flash"

This reverts commit 9a269d55c7d04334b1c4891c42914423517dfad6.

Revert "Blend flash in BACKGROUND pass also"

This reverts commit 756332cb9d7576b08db78406e37349aa6d1df156.

Revert "Make visual bell flash much more gentle"

This reverts commit cbfe5d59ada2da690e14c7532a7468b0c307c462.

Render flash overlay in a separate drawcall

Trigger frequent repaints to make for smooth animation

Attenuate flash a bit more
2021-08-11 11:54:43 +05:30
Kovid Goyal
477a652b44 Function to update a theme file 2021-08-11 09:09:07 +05:30
Kovid Goyal
15e0b42f04 Make the regex for commenting settings more robust 2021-08-11 08:46:42 +05:30
Kovid Goyal
b25ea9c863 Fix #3913 2021-08-09 08:37:53 +05:30
Kovid Goyal
c838cb03c9 Do not allow reloading the config to affect allow_remote_control 2021-08-08 15:34:34 +05:30
Kovid Goyal
ba9adc127e Use realpath when creating conf.bak as well 2021-08-08 10:49:41 +05:30
Kovid Goyal
b7d603c4de Atomic save should preserve stat if the file exists 2021-08-08 10:41:46 +05:30
Kovid Goyal
71f1f3aa64 Atomic save should use the target of symlinks 2021-08-08 10:35:38 +05:30
Kovid Goyal
7d1c26202a Add completion for options that take choices 2021-08-08 10:08:15 +05:30
Kovid Goyal
80db2f6558 Completion for theme names 2021-08-08 09:57:57 +05:30
Kovid Goyal
e376c79dda Show the current theme name in kitty.conf as well 2021-08-08 09:42:07 +05:30
Kovid Goyal
5a47e0d2e4 Make reload_conf_in_all_kitties re-useable 2021-08-08 09:38:29 +05:30
Kovid Goyal
ca9143bebc Allow reloading conf in all kitty instances as well 2021-08-08 09:33:36 +05:30
Kovid Goyal
a49f6799de Print usage when no args provided to +runpy 2021-08-08 09:10:47 +05:30
Kovid Goyal
d916ecc4f3 Add a non-interactive mode to the themes kitten 2021-08-08 07:57:14 +05:30
Kovid Goyal
e1ed9aca10 clear_all_mouse_shortcuts -> clear_all_mouse_actions 2021-08-07 21:34:16 +05:30
Kovid Goyal
0e2f1b9405 Merge branch 'fork' of https://github.com/bdeshi/kitty 2021-08-07 21:30:57 +05:30
bdeshi
db5a2d2141 add clear_all_mouse_shortcuts option to clear mouse_maps 2021-08-07 21:33:56 +06:00
Kovid Goyal
a9771dccba Change list position in kittens intro 2021-08-07 18:40:36 +05:30
Kovid Goyal
4849e07c26 Document the themes kitten 2021-08-07 18:38:33 +05:30
Kovid Goyal
4645e78fa7 Reload config in parent kitty when running the themes kitten 2021-08-07 16:54:47 +05:30
Kovid Goyal
9e2590eb15 Set the KITTY_PID env var when running child processes 2021-08-07 16:48:34 +05:30
Kovid Goyal
94575a5cf6 Implement modifying of kitty.conf
An include for the current theme is added and
all existing color related settings are commented out
so that the theme overlays on kitty's default settings.
2021-08-07 16:34:41 +05:30
Kovid Goyal
a597a8d86b Make the recent list functional 2021-08-07 15:31:03 +05:30
Kovid Goyal
3287798efe terminfo: Fix "cnorm" the property for setting the cursor to normal using a solid block rather than a blinking block cursor
cnorm should match the default cursor in kitty, which is a blinking
block. Fixes #3906
2021-08-07 15:15:59 +05:30
Kovid Goyal
d01ac17334 Implement placing of selected theme in kitty config dir 2021-08-07 15:04:32 +05:30
Kovid Goyal
2c96727c45 Implement searching in the themes kitten 2021-08-07 11:17:32 +05:30
Kovid Goyal
ca1b2454bd Fix position output in matcher 2021-08-07 10:45:50 +05:30
Kovid Goyal
064fc17ce3 More work on themes kitten 2021-08-05 21:38:39 +05:30
Kovid Goyal
43ccf9cb41 Draw theme demo 2021-08-05 13:17:16 +05:30
Kovid Goyal
96857a197c Allow default theme to have no settings 2021-08-05 11:20:01 +05:30
Kovid Goyal
079ff7785c ... 2021-08-05 07:47:32 +05:30
Kovid Goyal
d6a6cbe153 Migrate import in test 2021-08-05 07:05:19 +05:30
Kovid Goyal
2486cfd45d Move match() into a more re-useable location 2021-08-05 06:59:24 +05:30
Kovid Goyal
30cb7286db Fix building on macOS
macOS defines newlocale() in xlocale.h
2021-08-05 06:47:45 +05:30
Kovid Goyal
9e7253c179 macOS: Set LANG to en_US if cocoa reports language as en_* that is not a valid locale
Fixes #3899
2021-08-05 06:29:21 +05:30
Kovid Goyal
276a82d1f7 Make get_all_actions() work in binary builds 2021-08-05 06:10:41 +05:30
Kovid Goyal
57ced9bc83 Dont send press event for shift+middle when grabbed
Fixes #3902
2021-08-05 05:03:22 +05:30
Kovid Goyal
3c3662b032 Limit display length for theme names 2021-08-04 23:18:57 +05:30
Kovid Goyal
f490b9a8bd Better error message for get_all_actions() when docstrings are missing 2021-08-04 22:58:05 +05:30
Kovid Goyal
c7ccedae95 ... 2021-08-04 22:27:48 +05:30
Kovid Goyal
03517459db Type check set_mode and reset_mode 2021-08-04 22:25:27 +05:30
Kovid Goyal
96326280e5 Use a pending update when drawing the screen 2021-08-04 22:06:05 +05:30
Kovid Goyal
9b7899780b Implement drawing the list of themes 2021-08-04 21:56:33 +05:30
Kovid Goyal
e01cd057e8 Fix timestamp not being updated in downloaded zip file comment
Also clean up tab bar formatting
2021-08-04 20:34:00 +05:30
Kovid Goyal
ba85ca1991 Finish the tab bar for the themes kitten 2021-08-04 19:06:07 +05:30
Kovid Goyal
92a9b71f21 More work on the themes kitten 2021-08-04 18:43:26 +05:30
Kovid Goyal
e50c26d1b9 Allow using a non-toal dict to init Options objects 2021-08-04 17:06:50 +05:30
Kovid Goyal
7090c24321 More work on the themes kitten 2021-08-04 14:13:09 +05:30
Kovid Goyal
55319cd6d6 FreeType: Round glyph advances
The old code would simply add advances and store as a float with
individual glyph positioning using floor() based on the advance +
offset. This breaks rendering of infinite length ligatures at some font
sizes as they either under or overflow. Rounding the advances seems to
fix it. Dont know if it will have other ill-effects for non infinite
length ligatures. If it does we can test total ligature length and
use rounding only for long ligatures.

Fixes #3896
2021-08-04 08:40:49 +05:30
Kovid Goyal
8a6b51441c More work on themes kitten 2021-08-04 07:36:09 +05:30
Kovid Goyal
36670b49a0 Make creation of kitty-themes.zip atomic 2021-08-03 17:47:47 +05:30
Kovid Goyal
a402d848d2 Work on themes kitten UI 2021-08-03 17:32:15 +05:30
Kovid Goyal
37c563802c typo 2021-08-03 12:58:38 +05:30
Kovid Goyal
2d7032973c Change the semantics of --title slightly
Now it no longer overrides the titles of windows/tabs created in the
session. This allows windows/tabs to have their own titles while fixing
the OS Window's title.
2021-08-03 09:51:53 +05:30
Kovid Goyal
70b5f5bce3 Fix kitty --title not overriding tab titles
It must set the OS Window title permanently.

Fixes #3893
2021-08-03 09:34:13 +05:30
Kovid Goyal
be34af4555 Remove inline from .c files
It is ignored by compilers and hides unused static functions
2021-08-03 09:11:17 +05:30
Kovid Goyal
815539a933 Clean up the docs for tab_bar_style 2021-08-03 08:52:19 +05:30
Kovid Goyal
ab889e2945 A new "slant" style for the tab bar 2021-08-03 08:45:55 +05:30
Kovid Goyal
b3231c8003 Fix a link in the conf docs 2021-08-02 21:58:39 +05:30
Kovid Goyal
5f6cb34f77 placeholder 2021-08-02 21:54:06 +05:30
Kovid Goyal
a99a080c50 More work on theme loading 2021-08-02 21:46:33 +05:30
Kovid Goyal
4a1ca8d582 Move theme parser into kitty code 2021-08-02 18:54:48 +05:30
Kovid Goyal
050eb5660d Start work on themes kitten 2021-08-02 17:56:31 +05:30
93 changed files with 2245 additions and 739 deletions

View File

@@ -24,6 +24,8 @@ def remote_control(args: List[str]) -> None:
def runpy(args: List[str]) -> None: def runpy(args: List[str]) -> None:
if len(args) < 2:
raise SystemExit('Usage: kitty +runpy "some python code"')
sys.argv = ['kitty'] + args[2:] sys.argv = ['kitty'] + args[2:]
exec(args[1]) exec(args[1])

View File

@@ -4,6 +4,52 @@ Changelog
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal. |kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
To update |kitty|, :doc:`follow the instructions <binary>`. To update |kitty|, :doc:`follow the instructions <binary>`.
0.23.0 [2021-08-16]
----------------------
- A new :doc:`themes kitten </kittens/themes>` to easily change kitty themes.
Choose from almost two hundred themes in the `kitty themes repository
<https://github.com/kovidgoyal/kitty-themes>`_
- A new style for the tab bar that makes tabs looks like the tabs in a physical
tabbed file, see :opt:`tab_bar_style`
- Make the visual bell flash more gentle, especially on dark themes
(:pull:`2937`)
- Fix :option:`kitty --title` not overriding the OS Window title when multiple
tabs are present. Also this option is no longer used as the default title for
windows, allowing individual tabs/windows to have their own titles, even when
the OS Window has a fixed overall title (:iss:`3893`)
- Linux: Fix some very long ligatures being rendered incorrectly at some font
sizes (:iss:`3896`)
- Fix shift+middle click to paste sending a mouse press event but no release
event which breaks some applications that grab the mouse but cant handle
mouse events (:iss:`3902`)
- macOS: When the language is set to English and the country to one for which
an English locale does not exist, set :envvar:`LANG` to ``en_US.UTF-8``
(:iss:`3899`)
- terminfo: Fix "cnorm" the property for setting the cursor to normal using a
solid block rather than a blinking block cursor (:iss:`3906`)
- Add :opt:`clear_all_mouse_actions` to clear all mouse actions defined to
that point (:iss:`3907`)
- Fix the remote file kitten not working when using -- with ssh. The ssh kitten
was recently changed to do this (:iss:`3929`)
- When dragging word or line selections, ensure the initially selected item is
never deselected. This matches behavior in most other programs (:iss:`3930`)
- hints kitten: Make copy/paste with the :option:`kitty +kitten hints
--program` option work when using the ``self``
:option:`kitty +kitten hints --linenum-action` (:iss:`3931`)
0.22.2 [2021-08-02] 0.22.2 [2021-08-02]
---------------------- ----------------------

View File

@@ -117,7 +117,14 @@ explicitly set a UTF-8 locale, like::
How do I change the colors in a running kitty instance? How do I change the colors in a running kitty instance?
------------------------------------------------------------ ------------------------------------------------------------
You can either use the The easiest way to do it is to use the :doc:`themes kitten </kittens/themes>`,
to choose a new color theme. Simply run::
kitty +kitten themes
And choose your theme from the list.
Additionally, You can use the
`OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands>`_ `OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands>`_
to set colors or you can define keyboard shortcuts to set colors, for example:: to set colors or you can define keyboard shortcuts to set colors, for example::

View File

@@ -66,12 +66,25 @@ Variables that influence kitty behavior
Variables that kitty sets when running child programs Variables that kitty sets when running child programs
.. envvar:: LANG
This is set only on macOS, and only if the country and language from the
macOS user settings form a valid locale.
.. envvar:: KITTY_WINDOW_ID .. envvar:: KITTY_WINDOW_ID
An integer that is the id for the kitty :term:`window` the program is running in. An integer that is the id for the kitty :term:`window` the program is running in.
Can be used with the :doc:`kitty remote control facility <remote-control>`. Can be used with the :doc:`kitty remote control facility <remote-control>`.
.. envvar:: KITTY_PID
An integer that is the process id for the kitty process in which the program
is running. Allows programs to tell kitty to reload its config by sending it
the SIGUSR1 signal.
.. envvar:: WINDOWID .. envvar:: WINDOWID
The id for the :term:`OS Window <os_window>` the program is running in. Only available The id for the :term:`OS Window <os_window>` the program is running in. Only available

View File

@@ -92,10 +92,8 @@ look it up in the Google dictionary.
.. note:: .. note::
To avoid having to specify the same command line options on ever invocation, To avoid having to specify the same command line options on every invocation,
you can use the :opt:`kitten_alias` option in :file:`kitty.conf` to create aliases you can use the :opt:`kitten_alias` option in :file:`kitty.conf`, creating aliases
that have common sets of options. For example:: that have common sets of options. For example::
kitten_alias myhints hints --alphabet qfjdkslaureitywovmcxzpq1234567890 kitten_alias myhints hints --alphabet qfjdkslaureitywovmcxzpq1234567890
Documentation for the full set of options is below.

66
docs/kittens/themes.rst Normal file
View File

@@ -0,0 +1,66 @@
Changing kitty colors
========================
The themes kitten allows you to easily change color themes, from a collection
of almost two hundred pre-built themes available at `kitty-themes
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
kitty +kitten themes
The kitten allows you to pick a theme, with live previews of the colors. You
can choose between light and dark themes and search by theme name by just
typing a few characters from the name.
The kitten maintains a list of recently used themes to allow quick switching.
If you want to restore the colors to default, you can do so by choosing the
``Default`` theme.
.. versionadded:: 0.23.0
The themes kitten
How it works
----------------
A theme in kitty is just a :file:`.conf` file containing kitty settings.
When you select a theme, the kitten simply copies the :file:`.conf` file
to :file:`~/.config/kitty/current-theme.conf` and adds an include for
:file:`current-theme.conf` to :file:`kitty.conf`. It also comments out
any existing color settings in :file:`kitty.conf` so they do not interfere.
Once that's done, the kitten sends kitty a signal to make it reload its config.
Using your own themes
-----------------------
You can also create your own themes as :file:`.conf` files. Put them in the
:file:`themes` sub-directory of the kitty config directory, usually,
:file:`~/.config/kitty/themes` and the kitten will automatically add them to
the list of themes. You can use this to modify the builtin themes, by giving
the conf file the name :file:`Some theme name.conf` to override the builtin
theme of that name. Note that after doing so you have to run the kitten and
choose that theme once for your changes to be applied.
Contributing new themes
-------------------------
If you wish to contribute a new theme to the kitty theme repository, simply
go to `kitty-themes <https://github.com/kovidgoyal/kitty-themes>`_ and open a pull request
asking to add your contributions to the repository. Use the file
:download:`template.conf <https://github.com/kovidgoyal/kitty-themes/raw/master/template.conf>` as
a template when creating your theme.
Changing the theme non-interactively
---------------------------------------
You can specify the theme name as an argument when invoking the kitten
to have it change to that theme instantly. For example::
kitty +kitten theme --reload-in=all Dimmed Monokai
Will change the theme to ``Dimmed Monokai`` in all running kitty
instances. See below for more details on non-interactive operation.
.. include:: ../generated/cli-kitten-themes.rst

View File

@@ -10,6 +10,7 @@ Extend with kittens
kittens/icat kittens/icat
kittens/diff kittens/diff
kittens/unicode-input kittens/unicode-input
kittens/themes
kittens/hints kittens/hints
kittens/remote_file kittens/remote_file
kittens/hyperlinked_grep kittens/hyperlinked_grep

View File

@@ -116,7 +116,7 @@ endless. See the :doc:`tutorial <remote-control>` to get started.
Startup Sessions Startup Sessions
------------------ ------------------
You can control the :term:`tabs <tab>`, `:term:`kitty window <window>` layout, You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
working directory, startup programs, working directory, startup programs,
etc. by creating a "session" file and using the :option:`kitty --session` etc. by creating a "session" file and using the :option:`kitty --session`
command line flag or the :opt:`startup_session` option in :file:`kitty.conf`. command line flag or the :opt:`startup_session` option in :file:`kitty.conf`.

View File

@@ -91,7 +91,7 @@ compare_timers(const void *a_, const void *b_) {
return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0; return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0;
} }
static inline void static void
update_timers(EventLoopData *eld) { update_timers(EventLoopData *eld) {
if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers); if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers);
} }
@@ -164,7 +164,7 @@ prepareForPoll(EventLoopData *eld, monotonic_t timeout) {
return timeout; return timeout;
} }
static inline struct timespec static struct timespec
calc_time(monotonic_t nsec) { calc_time(monotonic_t nsec) {
struct timespec result; struct timespec result;
result.tv_sec = nsec / (1000LL * 1000LL * 1000LL); result.tv_sec = nsec / (1000LL * 1000LL * 1000LL);
@@ -271,7 +271,7 @@ wakeupEventLoop(EventLoopData *eld) {
} }
#ifndef HAS_EVENT_FD #ifndef HAS_EVENT_FD
static inline void static void
closeFds(int *fds, size_t count) { closeFds(int *fds, size_t count) {
while(count--) { while(count--) {
if (*fds > 0) { if (*fds > 0) {

View File

@@ -453,7 +453,7 @@ void* _glfwLoadLocalVulkanLoaderNS(void)
////// GLFW platform API ////// ////// GLFW platform API //////
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
static inline bool static bool
is_ctrl_tab(NSEvent *event, NSEventModifierFlags modifierFlags) { is_ctrl_tab(NSEvent *event, NSEventModifierFlags modifierFlags) {
if ( if (
(modifierFlags == NSEventModifierFlagControl && (modifierFlags == NSEventModifierFlagControl &&
@@ -464,7 +464,7 @@ is_ctrl_tab(NSEvent *event, NSEventModifierFlags modifierFlags) {
return false; return false;
} }
static inline bool static bool
is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) { is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) {
if (modifierFlags != NSEventModifierFlagCommand) return false; if (modifierFlags != NSEventModifierFlagCommand) return false;
if ([event.charactersIgnoringModifiers isEqualToString:@"."]) return true; if ([event.charactersIgnoringModifiers isEqualToString:@"."]) return true;
@@ -703,7 +703,7 @@ typedef struct {
static Timer timers[128] = {{0}}; static Timer timers[128] = {{0}};
static size_t num_timers = 0; static size_t num_timers = 0;
static inline void static void
remove_timer_at(size_t idx) { remove_timer_at(size_t idx) {
if (idx < num_timers) { if (idx < num_timers) {
Timer *t = timers + idx; Timer *t = timers + idx;

View File

@@ -233,10 +233,10 @@ mac_ucode_to_functional(uint32_t key_code) { // {{{
} }
} // }}} } // }}}
static inline bool static bool
is_surrogate(UniChar uc) { return (uc - 0xd800u) < 2048u; } is_surrogate(UniChar uc) { return (uc - 0xd800u) < 2048u; }
static inline uint32_t static uint32_t
get_first_codepoint(UniChar *utf16, UniCharCount num) { get_first_codepoint(UniChar *utf16, UniCharCount num) {
if (!num) return 0; if (!num) return 0;
if (!is_surrogate(*utf16)) return *utf16; if (!is_surrogate(*utf16)) return *utf16;
@@ -244,7 +244,7 @@ get_first_codepoint(UniChar *utf16, UniCharCount num) {
return 0; return 0;
} }
static inline bool static bool
is_pua_char(uint32_t ch) { is_pua_char(uint32_t ch) {
return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF); return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
} }
@@ -321,7 +321,7 @@ _glfwShutdownCVDisplayLink(unsigned long long timer_id UNUSED, void *user_data U
} }
} }
static inline void static void
requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) { requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
if (!callback) { if (!callback) {
w->ns.renderFrameRequested = false; w->ns.renderFrameRequested = false;
@@ -505,7 +505,7 @@ translateFlags(NSUInteger flags)
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); } #define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
static inline const char* static const char*
format_mods(int mods) { format_mods(int mods) {
static char buf[128]; static char buf[128];
char *p = buf, *s; char *p = buf, *s;
@@ -525,7 +525,7 @@ format_mods(int mods) {
return buf; return buf;
} }
static inline const char* static const char*
format_text(const char *src) { format_text(const char *src) {
static char buf[256]; static char buf[256];
char *p = buf; char *p = buf;
@@ -1144,7 +1144,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
[super updateTrackingAreas]; [super updateTrackingAreas];
} }
static inline UInt32 static UInt32
convert_cocoa_to_carbon_modifiers(NSUInteger flags) { convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
UInt32 mods = 0; UInt32 mods = 0;
if (flags & NSEventModifierFlagShift) if (flags & NSEventModifierFlagShift)
@@ -1161,7 +1161,7 @@ convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
return (mods >> 8) & 0xFF; return (mods >> 8) & 0xFF;
} }
static inline void static void
convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) { convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) {
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
src, src,
@@ -1174,12 +1174,12 @@ convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t
CFRelease(string); CFRelease(string);
} }
static inline bool static bool
alternate_key_is_ok(uint32_t key, uint32_t akey) { alternate_key_is_ok(uint32_t key, uint32_t akey) {
return akey > 31 && akey != key && !is_pua_char(akey); return akey > 31 && akey != key && !is_pua_char(akey);
} }
static inline void static void
add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) { add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) {
ev->alternate_key = translateKey(ev->native_key, false); ev->alternate_key = translateKey(ev->native_key, false);
if (!alternate_key_is_ok(ev->key, ev->alternate_key)) ev->alternate_key = 0; if (!alternate_key_is_ok(ev->key, ev->alternate_key)) ev->alternate_key = 0;
@@ -1198,7 +1198,7 @@ add_alternate_keys(GLFWkeyevent *ev, NSEvent *event) {
} }
} }
static inline bool static bool
is_ascii_control_char(char x) { is_ascii_control_char(char x) {
return x == 0 || (1 <= x && x <= 31) || x == 127; return x == 0 || (1 <= x && x <= 31) || x == 127;
} }

4
glfw/dbus_glfw.c vendored
View File

@@ -31,7 +31,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
static inline void static void
report_error(DBusError *err, const char *fmt, ...) { report_error(DBusError *err, const char *fmt, ...) {
static char buf[1024]; static char buf[1024];
va_list args; va_list args;
@@ -64,7 +64,7 @@ on_dbus_watch_ready(int fd UNUSED, int events, void *data) {
dbus_watch_handle(watch, flags); dbus_watch_handle(watch, flags);
} }
static inline int static int
events_for_watch(DBusWatch *watch) { events_for_watch(DBusWatch *watch) {
int events = 0; int events = 0;
unsigned int flags = dbus_watch_get_flags(watch); unsigned int flags = dbus_watch_get_flags(watch);

12
glfw/ibus_glfw.c vendored
View File

@@ -50,13 +50,13 @@ enum Capabilities {
}; };
static inline bool static bool
test_env_var(const char *name, const char *val) { test_env_var(const char *name, const char *val) {
const char *q = getenv(name); const char *q = getenv(name);
return (q && strcmp(q, val) == 0) ? true : false; return (q && strcmp(q, val) == 0) ? true : false;
} }
static inline size_t static size_t
GLFW_MIN(size_t a, size_t b) { GLFW_MIN(size_t a, size_t b) {
return a < b ? a : b; return a < b ? a : b;
} }
@@ -106,7 +106,7 @@ get_ibus_text_from_message(DBusMessage *msg) {
return text; return text;
} }
static inline void static void
send_text(const char *text, GLFWIMEState ime_state) { send_text(const char *text, GLFWIMEState ime_state) {
_GLFWwindow *w = _glfwFocusedWindow(); _GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) { if (w && w->callbacks.keyboard) {
@@ -147,7 +147,7 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data)
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
} }
static inline const char* static const char*
get_ibus_address_file_name(void) { get_ibus_address_file_name(void) {
const char *addr; const char *addr;
static char ans[PATH_MAX]; static char ans[PATH_MAX];
@@ -196,7 +196,7 @@ get_ibus_address_file_name(void) {
} }
static inline bool static bool
read_ibus_address(_GLFWIBUSData *ibus) { read_ibus_address(_GLFWIBUSData *ibus) {
static char buf[1024]; static char buf[1024];
struct stat s; struct stat s;
@@ -379,7 +379,7 @@ typedef enum
} IBusModifierType; } IBusModifierType;
static inline uint32_t static uint32_t
ibus_key_state(unsigned int glfw_modifiers, int action) { ibus_key_state(unsigned int glfw_modifiers, int action) {
uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0; uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0;
#define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i #define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i

2
glfw/wl_init.c vendored
View File

@@ -54,7 +54,7 @@
#endif #endif
static inline int min(int n1, int n2) static int min(int n1, int n2)
{ {
return n1 < n2 ? n1 : n2; return n1 < n2 ? n1 : n2;
} }

View File

@@ -43,7 +43,7 @@ text_input_leave(void *data UNUSED, struct zwp_text_input_v3 *text_input UNUSED,
} }
} }
static inline void static void
send_text(const char *text, GLFWIMEState ime_state) { send_text(const char *text, GLFWIMEState ime_state) {
_GLFWwindow *w = _glfwFocusedWindow(); _GLFWwindow *w = _glfwFocusedWindow();
if (w && w->callbacks.keyboard) { if (w && w->callbacks.keyboard) {

2
glfw/wl_window.c vendored
View File

@@ -1762,7 +1762,7 @@ void _glfwSetupWaylandPrimarySelectionDevice() {
if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL); if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL);
} }
static inline bool _glfwEnsureDataDevice(void) { static bool _glfwEnsureDataDevice(void) {
if (!_glfw.wl.dataDeviceManager) if (!_glfw.wl.dataDeviceManager)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,

6
glfw/x11_window.c vendored
View File

@@ -281,7 +281,7 @@ updateNormalHints(_GLFWwindow* window, int width, int height)
XFree(hints); XFree(hints);
} }
static inline bool static bool
is_window_fullscreen(_GLFWwindow* window) is_window_fullscreen(_GLFWwindow* window)
{ {
Atom* states; Atom* states;
@@ -310,7 +310,7 @@ is_window_fullscreen(_GLFWwindow* window)
return ans; return ans;
} }
static inline void static void
set_fullscreen(_GLFWwindow *window, bool on) { set_fullscreen(_GLFWwindow *window, bool on) {
if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) { if (_glfw.x11.NET_WM_STATE && _glfw.x11.NET_WM_STATE_FULLSCREEN) {
sendEventToWM(window, sendEventToWM(window,
@@ -2646,7 +2646,7 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
PropModeReplace, (unsigned char*) &value, 1); PropModeReplace, (unsigned char*) &value, 1);
} }
static inline unsigned static unsigned
dispatch_x11_queued_events(int num_events) { dispatch_x11_queued_events(int num_events) {
unsigned dispatched = num_events > 0 ? num_events : 0; unsigned dispatched = num_events > 0 ? num_events : 0;
while (num_events-- > 0) { while (num_events-- > 0) {

8
glfw/xkb_glfw.c vendored
View File

@@ -490,7 +490,7 @@ load_compose_tables(_GLFWXKBData *xkb) {
xkb_compose_table_unref(compose_table); xkb_compose_table_unref(compose_table);
} }
static inline xkb_mod_mask_t static xkb_mod_mask_t
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) { active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
size_t i = 0; size_t i = 0;
xkb_mod_mask_t ans = 0; xkb_mod_mask_t ans = 0;
@@ -577,7 +577,7 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t keycode) {
} }
static inline xkb_keysym_t static xkb_keysym_t
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed, char *key_text, int n) { compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed, char *key_text, int n) {
*compose_completed = 0; *compose_completed = 0;
if (sym == XKB_KEY_NoSymbol || !composeState) return sym; if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
@@ -610,7 +610,7 @@ glfw_xkb_keysym_from_name(const char *name, bool case_sensitive) {
return (int)xkb_keysym_from_name(name, case_sensitive ? XKB_KEYSYM_NO_FLAGS : XKB_KEYSYM_CASE_INSENSITIVE); return (int)xkb_keysym_from_name(name, case_sensitive ? XKB_KEYSYM_NO_FLAGS : XKB_KEYSYM_CASE_INSENSITIVE);
} }
static inline const char* static const char*
format_mods(unsigned int mods) { format_mods(unsigned int mods) {
static char buf[128]; static char buf[128];
char *p = buf, *s; char *p = buf, *s;
@@ -632,7 +632,7 @@ format_mods(unsigned int mods) {
return buf; return buf;
} }
static inline const char* static const char*
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) { format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
static char buf[512]; static char buf[512];
char *p = buf, *s; char *p = buf, *s;

View File

@@ -3,47 +3,14 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import sys import sys
from typing import Iterable, List, Union from typing import List
from kitty.key_encoding import KeyEvent from kitty.key_encoding import KeyEvent
from . import subseq_matcher
from ..tui.handler import Handler from ..tui.handler import Handler
from ..tui.loop import Loop from ..tui.loop import Loop
def match(
input_data: Union[str, bytes, Iterable[Union[str, bytes]]],
query: str,
threads: int = 0,
positions: bool = False,
level1: str = '/',
level2: str = '-_0123456789',
level3: str = '.',
limit: int = 0,
mark_before: str = '',
mark_after: str = '',
delimiter: str = '\n'
) -> List[str]:
if isinstance(input_data, str):
idata = [x.encode('utf-8') for x in input_data.split(delimiter)]
elif isinstance(input_data, bytes):
idata = input_data.split(delimiter.encode('utf-8'))
else:
idata = [x.encode('utf-8') if isinstance(x, str) else x for x in input_data]
query = query.lower()
level1 = level1.lower()
level2 = level2.lower()
level3 = level3.lower()
data = subseq_matcher.match(
idata, (level1, level2, level3), query,
positions, limit, threads,
mark_before, mark_after, delimiter)
if data is None:
return []
return list(filter(None, data.split(delimiter or '\n')))
class ChooseHandler(Handler): class ChooseHandler(Handler):
def initialize(self) -> None: def initialize(self) -> None:

39
kittens/choose/match.py Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Iterable, List, Union
from . import subseq_matcher
def match(
input_data: Union[str, bytes, Iterable[Union[str, bytes]]],
query: str,
threads: int = 0,
positions: bool = False,
level1: str = '/',
level2: str = '-_0123456789',
level3: str = '.',
limit: int = 0,
mark_before: str = '',
mark_after: str = '',
delimiter: str = '\n'
) -> List[str]:
if isinstance(input_data, str):
idata = [x.encode('utf-8') for x in input_data.split(delimiter)]
elif isinstance(input_data, bytes):
idata = input_data.split(delimiter.encode('utf-8'))
else:
idata = [x.encode('utf-8') if isinstance(x, str) else x for x in input_data]
query = query.lower()
level1 = level1.lower()
level2 = level2.lower()
level3 = level3.lower()
data = subseq_matcher.match(
idata, (level1, level2, level3), query,
positions, limit, threads,
mark_before, mark_after, delimiter)
if data is None:
return []
return list(filter(None, data.split(delimiter or '\n')))

View File

@@ -14,7 +14,7 @@
#ifdef ISWINDOWS #ifdef ISWINDOWS
#include <io.h> #include <io.h>
#define STDOUT_FILENO 1 #define STDOUT_FILENO 1
static inline ssize_t ms_write(int fd, const void* buf, size_t count) { return _write(fd, buf, (unsigned int)count); } static ssize_t ms_write(int fd, const void* buf, size_t count) { return _write(fd, buf, (unsigned int)count); }
#define write ms_write #define write ms_write
#else #else
#include <unistd.h> #include <unistd.h>
@@ -24,7 +24,7 @@ static inline ssize_t ms_write(int fd, const void* buf, size_t count) { return _
#define FIELD(x, which) (((Candidate*)(x))->which) #define FIELD(x, which) (((Candidate*)(x))->which)
static inline bool static bool
ensure_space(GlobalData *global, size_t sz) { ensure_space(GlobalData *global, size_t sz) {
if (global->output_sz < sz + global->output_pos || !global->output) { if (global->output_sz < sz + global->output_pos || !global->output) {
size_t before = global->output_sz; size_t before = global->output_sz;
@@ -38,7 +38,7 @@ ensure_space(GlobalData *global, size_t sz) {
return true; return true;
} }
static inline void static void
output_text(GlobalData *global, const text_t *data, size_t sz) { output_text(GlobalData *global, const text_t *data, size_t sz) {
if (ensure_space(global, sz)) { if (ensure_space(global, sz)) {
memcpy(global->output + global->output_pos, data, sizeof(text_t) * sz); memcpy(global->output + global->output_pos, data, sizeof(text_t) * sz);
@@ -66,10 +66,10 @@ static void
output_positions(GlobalData *global, len_t *positions, len_t num) { output_positions(GlobalData *global, len_t *positions, len_t num) {
wchar_t buf[128]; wchar_t buf[128];
for (len_t i = 0; i < num; i++) { for (len_t i = 0; i < num; i++) {
int num = swprintf(buf, sizeof(buf)/sizeof(buf[0]), L"%u", positions[i]); int pnum = swprintf(buf, arraysz(buf), L"%u", positions[i]);
if (num > 0 && ensure_space(global, num + 1)) { if (pnum > 0 && ensure_space(global, pnum + 1)) {
for (int i = 0; i < num; i++) global->output[global->output_pos++] = buf[i]; for (int k = 0; k < pnum; k++) global->output[global->output_pos++] = buf[k];
global->output[global->output_pos++] = (i == num - 1) ? ',' : ':'; global->output[global->output_pos++] = (i == num - 1) ? ':' : ',';
} }
} }
} }

View File

@@ -59,7 +59,7 @@ free_workspace(void *v) {
return NULL; return NULL;
} }
static inline bool static bool
has_char(text_t *text, len_t sz, text_t ch) { has_char(text_t *text, len_t sz, text_t ch) {
for(len_t i = 0; i < sz; i++) { for(len_t i = 0; i < sz; i++) {
if(text[i] == ch) return true; if(text[i] == ch) return true;
@@ -67,7 +67,7 @@ has_char(text_t *text, len_t sz, text_t ch) {
return false; return false;
} }
static inline uint8_t static uint8_t
level_factor_for(text_t current, text_t last, WorkSpace *w) { level_factor_for(text_t current, text_t last, WorkSpace *w) {
text_t lch = LOWERCASE(last); text_t lch = LOWERCASE(last);
if (has_char(w->level1, w->level1_len, lch)) return 90; if (has_char(w->level1, w->level1_len, lch)) return 90;
@@ -101,7 +101,7 @@ init_workspace(WorkSpace *w, text_t *haystack, len_t haystack_len) {
} }
static inline bool static bool
has_atleast_one_match(WorkSpace *w) { has_atleast_one_match(WorkSpace *w) {
int p = -1; int p = -1;
bool found; bool found;
@@ -118,7 +118,7 @@ has_atleast_one_match(WorkSpace *w) {
#define POSITION(x) w->positions[x][w->address[x]] #define POSITION(x) w->positions[x][w->address[x]]
static inline bool static bool
increment_address(WorkSpace *w) { increment_address(WorkSpace *w) {
len_t pos = w->needle_len - 1; len_t pos = w->needle_len - 1;
while(true) { while(true) {
@@ -130,7 +130,7 @@ increment_address(WorkSpace *w) {
return false; return false;
} }
static inline bool static bool
address_is_monotonic(WorkSpace *w) { address_is_monotonic(WorkSpace *w) {
// Check if the character positions pointed to by the current address are monotonic // Check if the character positions pointed to by the current address are monotonic
for (len_t i = 1; i < w->needle_len; i++) { for (len_t i = 1; i < w->needle_len; i++) {
@@ -139,7 +139,7 @@ address_is_monotonic(WorkSpace *w) {
return true; return true;
} }
static inline double static double
calc_score(WorkSpace *w) { calc_score(WorkSpace *w) {
double ans = 0; double ans = 0;
len_t distance, pos; len_t distance, pos;

View File

@@ -72,8 +72,11 @@ class Options:
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None: def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
if options_dict is not None: if options_dict is not None:
null = object()
for key in option_names: for key in option_names:
setattr(self, key, options_dict[key]) val = options_dict.get(key, null)
if val is not null:
setattr(self, key, val)
@property @property
def _fields(self) -> typing.Tuple[str, ...]: def _fields(self) -> typing.Tuple[str, ...]:

View File

@@ -41,7 +41,7 @@ typedef struct {
static const Segment EMPTY_SEGMENT = { .current_pos = UINT_MAX }; static const Segment EMPTY_SEGMENT = { .current_pos = UINT_MAX };
static inline bool static bool
convert_segment(PyObject *highlight, Segment *dest) { convert_segment(PyObject *highlight, Segment *dest) {
PyObject *val = NULL; PyObject *val = NULL;
#define I #define I
@@ -62,7 +62,7 @@ convert_segment(PyObject *highlight, Segment *dest) {
return true; return true;
} }
static inline bool static bool
next_segment(SegmentPointer *s, PyObject *highlights) { next_segment(SegmentPointer *s, PyObject *highlights) {
if (s->pos < s->num) { if (s->pos < s->num) {
if (!convert_segment(PyList_GET_ITEM(highlights, s->pos), &s->sg)) return false; if (!convert_segment(PyList_GET_ITEM(highlights, s->pos), &s->sg)) return false;
@@ -89,7 +89,7 @@ ensure_space(LineBuffer *b, size_t num) {
return true; return true;
} }
static inline bool static bool
insert_code(PyObject *code, LineBuffer *b) { insert_code(PyObject *code, LineBuffer *b) {
unsigned int csz = PyUnicode_GET_LENGTH(code); unsigned int csz = PyUnicode_GET_LENGTH(code);
if (!ensure_space(b, csz)) return false; if (!ensure_space(b, csz)) return false;
@@ -97,7 +97,7 @@ insert_code(PyObject *code, LineBuffer *b) {
return true; return true;
} }
static inline bool static bool
add_line(Segment *bg_segment, Segment *fg_segment, LineBuffer *b, PyObject *ans) { add_line(Segment *bg_segment, Segment *fg_segment, LineBuffer *b, PyObject *ans) {
bool bg_is_active = bg_segment->current_pos == bg_segment->end_pos, fg_is_active = fg_segment->current_pos == fg_segment->end_pos; bool bg_is_active = bg_segment->current_pos == bg_segment->end_pos, fg_is_active = fg_segment->current_pos == fg_segment->end_pos;
if (bg_is_active) { if(!insert_code(bg_segment->end_code, b)) return false; } if (bg_is_active) { if(!insert_code(bg_segment->end_code, b)) return false; }

View File

@@ -553,10 +553,12 @@ choices=self,window,tab,os_window,background
Where to perform the action on matched errors. :code:`self` means the current Where to perform the action on matched errors. :code:`self` means the current
window, :code:`window` a new kitty window, :code:`tab` a new tab, window, :code:`window` a new kitty window, :code:`tab` a new tab,
:code:`os_window` a new OS window and :code:`background` run in the background. :code:`os_window` a new OS window and :code:`background` run in the background.
The action to perform on the matched errors. The actual action is whatever The actual action is whatever arguments are provided to the kitten, for
arguments are provided to the kitten, for example: :code:`kitty + kitten hints example:
--type=linenum --linenum-action=tab vim +{line} {path}` will open the matched :code:`kitty + kitten hints --type=linenum --linenum-action=tab vim +{line} {path}`
path at the matched line number in vim in a new kitty tab. will open the matched path at the matched line number in vim in
a new kitty tab. Note that only when using :code:`self` are the special values for
:option:`kitty +kitten hints --program` to copy/paste the text respected.
--url-prefixes --url-prefixes
@@ -705,9 +707,19 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
if action == 'self': if action == 'self':
if w is not None: if w is not None:
import shlex is_copy_action = cmd[0] in ('-', '@', '*')
text = ' '.join(shlex.quote(arg) for arg in cmd) if is_copy_action:
w.paste_bytes(text + '\r') text = ' '.join(cmd[1:])
if cmd[0] == '-':
w.paste_bytes(text)
elif cmd[0] == '@':
set_clipboard_string(text)
elif cmd[0] == '*':
set_primary_selection(text)
else:
import shlex
text = ' '.join(shlex.quote(arg) for arg in cmd)
w.paste_bytes(text + '\r')
elif action == 'background': elif action == 'background':
import subprocess import subprocess
subprocess.Popen(cmd, cwd=data['cwd']) subprocess.Popen(cmd, cwd=data['cwd'])

View File

@@ -138,7 +138,6 @@ class ControlMaster:
).wait() ).wait()
if self.tdir: if self.tdir:
shutil.rmtree(self.tdir) shutil.rmtree(self.tdir)
return True
@property @property
def is_alive(self) -> bool: def is_alive(self) -> bool:
@@ -286,6 +285,7 @@ def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result:
show_error('Failed to copy file from remote machine') show_error('Failed to copy file from remote machine')
elif action == 'edit': elif action == 'edit':
print('Editing', cli_opts.path, 'from', cli_opts.hostname) print('Editing', cli_opts.path, 'from', cli_opts.hostname)
editor = get_editor()
with ControlMaster(conn_data, remote_path, cli_opts) as master: with ControlMaster(conn_data, remote_path, cli_opts) as master:
if not master.check_hostname_matches(): if not master.check_hostname_matches():
return None return None
@@ -294,7 +294,6 @@ def handle_action(action: str, cli_opts: RemoteFileCLIOptions) -> Result:
return None return None
mtime = os.path.getmtime(master.dest) mtime = os.path.getmtime(master.dest)
print(reset_terminal(), end='', flush=True) print(reset_terminal(), end='', flush=True)
editor = get_editor()
editor_process = subprocess.Popen(editor + [master.dest]) editor_process = subprocess.Popen(editor + [master.dest])
while editor_process.poll() is None: while editor_process.poll() is None:
time.sleep(0.1) time.sleep(0.1)

View File

@@ -87,14 +87,14 @@ def launch(args: List[str]) -> None:
del args[:2] del args[:2]
args = [kitten] + args args = [kitten] + args
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
from kittens.tui.operations import clear_screen, reset_mode from kittens.tui.operations import clear_screen, reset_mode, Mode
set_debug(kitten) set_debug(kitten)
m = import_kitten_main_module(config_dir, kitten) m = import_kitten_main_module(config_dir, kitten)
try: try:
result = m['start'](args) result = m['start'](args)
finally: finally:
sys.stdin = sys.__stdin__ sys.stdin = sys.__stdin__
print(reset_mode('ALTERNATE_SCREEN') + clear_screen(), end='') print(reset_mode(Mode.ALTERNATE_SCREEN) + clear_screen(), end='')
if result is not None: if result is not None:
import json import json
data = json.dumps(result) data = json.dumps(result)

View File

@@ -146,15 +146,20 @@ def get_connection_data(args: List[str]) -> Optional[SSHConnectionData]:
port: Optional[int] = None port: Optional[int] = None
expecting_port = False expecting_port = False
expecting_option_val = False expecting_option_val = False
expecting_hostname = False
for i, arg in enumerate(args): for i, arg in enumerate(args):
if not found_ssh: if not found_ssh:
if os.path.basename(arg).lower() in ('ssh', 'ssh.exe'): if os.path.basename(arg).lower() in ('ssh', 'ssh.exe'):
found_ssh = arg found_ssh = arg
continue continue
if expecting_hostname:
return SSHConnectionData(found_ssh, arg, port)
if arg.startswith('-') and not expecting_option_val: if arg.startswith('-') and not expecting_option_val:
if arg in boolean_ssh_args: if arg in boolean_ssh_args:
continue continue
if arg == '--':
expecting_hostname = True
if arg.startswith('-p'): if arg.startswith('-p'):
if arg[2:].isdigit(): if arg[2:].isdigit():
with suppress(Exception): with suppress(Exception):

View File

View File

@@ -0,0 +1,387 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import datetime
import http
import json
import os
import re
import shutil
import signal
import tempfile
import zipfile
from contextlib import suppress
from typing import Any, Callable, Dict, Iterator, Match, Optional, Tuple, Union, Type
from urllib.error import HTTPError
from urllib.request import Request, urlopen
from kitty.config import atomic_save, parse_config
from kitty.constants import cache_dir, config_dir
from kitty.options.types import Options as KittyOptions
from kitty.rgb import Color
from kitty.utils import reload_conf_in_all_kitties
from ..choose.match import match
MARK_BEFORE = '\033[33m'
MARK_AFTER = '\033[39m'
def patch_conf(raw: str, theme_name: str) -> str:
addition = f'# BEGIN_KITTY_THEME\n# {theme_name}\ninclude current-theme.conf\n# END_KITTY_THEME'
nraw, num = re.subn(r'^# BEGIN_KITTY_THEME.+?# END_KITTY_THEME', addition, raw, flags=re.MULTILINE | re.DOTALL)
if not num:
if raw:
raw += '\n\n'
nraw = raw + addition
# comment out all existing color definitions
color_conf_items = [f'color{i}' for i in range(256)] + list(filter(None, '''
foreground
background
selection_foreground
selection_background
cursor
cursor_text_color
url_color
active_border_color
inactive_border_color
bell_border_color
wayland_titlebar_color
macos_titlebar_color
active_tab_foreground
active_tab_background
inactive_tab_foreground
inactive_tab_background
tab_bar_background
mark1_foreground
mark1_background
mark2_foreground
mark2_background
mark3_foreground
mark3_background
'''.splitlines()))
pat = fr'^\s*({"|".join(color_conf_items)})\b'
return re.sub(pat, r'# \1', nraw, flags=re.MULTILINE)
def set_comment_in_zip_file(path: str, data: str) -> None:
with zipfile.ZipFile(path, 'a') as zf:
zf.comment = data.encode('utf-8')
class NoCacheFound(ValueError):
pass
def fetch_themes(
name: str = 'kitty-themes',
url: str = 'https://codeload.github.com/kovidgoyal/kitty-themes/zip/master',
cache_age: float = 1,
) -> str:
now = datetime.datetime.now(datetime.timezone.utc)
cache_age_delta = datetime.timedelta(days=cache_age)
class Metadata:
def __init__(self) -> None:
self.etag = ''
self.timestamp = now
def __str__(self) -> str:
return json.dumps({'etag': self.etag, 'timestamp': self.timestamp.isoformat()})
dest_path = os.path.join(cache_dir(), f'{name}.zip')
m = Metadata()
with suppress(Exception), zipfile.ZipFile(dest_path, 'r') as zf:
q = json.loads(zf.comment)
m.etag = str(q.get('etag') or '')
m.timestamp = datetime.datetime.fromisoformat(q['timestamp'])
if cache_age < 0 or (now - m.timestamp) < cache_age_delta:
return dest_path
if cache_age < 0:
raise NoCacheFound('No local themes cache found and negative cache age specified, aborting')
rq = Request(url)
m.timestamp = now
if m.etag:
rq.add_header('If-None-Match', m.etag)
try:
res = urlopen(rq, timeout=30)
except HTTPError as e:
if m.etag and e.code == http.HTTPStatus.NOT_MODIFIED:
set_comment_in_zip_file(dest_path, str(m))
return dest_path
raise
m.etag = res.headers.get('etag') or ''
needs_delete = False
try:
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(dest_path), dir=os.path.dirname(dest_path), delete=False) as f:
needs_delete = True
shutil.copyfileobj(res, f)
f.flush()
set_comment_in_zip_file(f.name, str(m))
os.replace(f.name, dest_path)
needs_delete = False
finally:
if needs_delete:
os.unlink(f.name)
return dest_path
def zip_file_loader(path_to_zip: str, theme_file_name: str, file_name: str) -> Callable[[], str]:
name = os.path.join(os.path.dirname(theme_file_name), file_name)
def zip_loader() -> str:
with zipfile.ZipFile(path_to_zip, 'r') as zf, zf.open(name) as f:
return f.read().decode('utf-8')
return zip_loader
def theme_name_from_file_name(fname: str) -> str:
ans = fname.rsplit('.', 1)[0]
ans = ans.replace('_', ' ')
def camel_case(m: Match) -> str:
return str(m.group(1) + ' ' + m.group(2))
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
return ' '.join(x.capitalize() for x in filter(None, ans.split()))
class LineParser:
def __init__(self) -> None:
self.in_metadata = False
self.in_blurb = False
self.keep_going = True
def __call__(self, line: str, ans: Dict[str, Any]) -> None:
is_block = line.startswith('## ')
if self.in_metadata and not is_block:
self.keep_going = False
return
if not self.in_metadata and is_block:
self.in_metadata = True
if not self.in_metadata:
return
line = line[3:]
if self.in_blurb:
ans['blurb'] += ' ' + line
return
try:
key, val = line.split(':', 1)
except Exception:
self.keep_going = False
return
key = key.strip().lower()
val = val.strip()
if val:
ans[key] = val
if key == 'blurb':
self.in_blurb = True
def parse_theme(fname: str, raw: str, exc_class: Type[BaseException] = SystemExit) -> Dict[str, Any]:
lines = raw.splitlines()
conf = parse_config(lines)
bg = conf.get('background', Color())
is_dark = max(bg) < 115
ans: Dict[str, Any] = {'name': theme_name_from_file_name(fname)}
parser = LineParser()
for i, line in enumerate(raw.splitlines()):
line = line.strip()
if not line:
continue
try:
parser(line, ans)
except Exception as e:
raise exc_class(
f'Failed to parse {fname} line {i+1} with error: {e}')
if not parser.keep_going:
break
if is_dark:
ans['is_dark'] = True
ans['num_settings'] = len(conf) - len(parse_config(()))
if ans['num_settings'] < 1 and fname != 'default.conf':
raise exc_class(f'The theme {fname} has no settings')
return ans
def update_theme_file(path: str) -> bool:
with open(path) as f:
raw = f.read()
td = parse_theme(os.path.basename(path), raw, exc_class=ValueError)
if 'upstream' not in td:
return False
nraw = urlopen(td['upstream']).read().decode('utf-8')
if raw == nraw:
return False
atomic_save(nraw.encode('utf-8'), path)
return True
class Theme:
name: str = ''
author: str = ''
license: str = ''
is_dark: bool = False
blurb: str = ''
num_settings: int = 0
def apply_dict(self, d: Dict[str, Any]) -> None:
self.name = str(d['name'])
for x in ('author', 'license', 'blurb'):
a = d.get(x)
if isinstance(a, str):
setattr(self, x, a)
for x in ('is_dark', 'num_settings'):
a = d.get(x)
if isinstance(a, int):
setattr(self, x, a)
def __init__(self, loader: Callable[[], str]):
self._loader = loader
self._raw: Optional[str] = None
self._opts: Optional[KittyOptions] = None
@property
def raw(self) -> str:
if self._raw is None:
self._raw = self._loader()
return self._raw
@property
def kitty_opts(self) -> KittyOptions:
if self._opts is None:
self._opts = KittyOptions(options_dict=parse_config(self.raw.splitlines()))
return self._opts
def save_in_dir(self, dirpath: str) -> None:
atomic_save(self.raw.encode('utf-8'), os.path.join(dirpath, f'{self.name}.conf'))
def save_in_conf(self, confdir: str, reload_in: str) -> None:
atomic_save(self.raw.encode('utf-8'), os.path.join(confdir, 'current-theme.conf'))
confpath = os.path.realpath(os.path.join(confdir, 'kitty.conf'))
try:
with open(confpath) as f:
raw = f.read()
except FileNotFoundError:
raw = ''
nraw = patch_conf(raw, self.name)
if raw:
with open(confpath + '.bak', 'w') as f:
f.write(raw)
atomic_save(nraw.encode('utf-8'), confpath)
if reload_in == 'parent':
if 'KITTY_PID' in os.environ:
os.kill(int(os.environ['KITTY_PID']), signal.SIGUSR1)
elif reload_in == 'all':
reload_conf_in_all_kitties()
class Themes:
def __init__(self) -> None:
self.themes: Dict[str, Theme] = {}
self.index_map: Tuple[str, ...] = ()
def __len__(self) -> int:
return len(self.themes)
def __iter__(self) -> Iterator[Theme]:
return iter(self.themes.values())
def __getitem__(self, key: Union[int, str]) -> Theme:
if isinstance(key, str):
return self.themes[key]
if key < 0:
key += len(self.index_map)
return self.themes[self.index_map[key]]
def load_from_zip(self, path_to_zip: str) -> None:
with zipfile.ZipFile(path_to_zip, 'r') as zf:
for name in zf.namelist():
if os.path.basename(name) == 'themes.json':
theme_file_name = name
with zf.open(theme_file_name) as f:
items = json.loads(f.read())
break
else:
raise ValueError(f'No themes.json found in {path_to_zip}')
for item in items:
t = Theme(zip_file_loader(path_to_zip, theme_file_name, item['file']))
t.apply_dict(item)
if t.name:
self.themes[t.name] = t
def load_from_dir(self, path: str) -> None:
if not os.path.isdir(path):
return
for name in os.listdir(path):
if name.endswith('.conf'):
with open(os.path.join(path, name), 'rb') as f:
raw = f.read().decode()
try:
d = parse_theme(name, raw)
except (Exception, SystemExit):
continue
t = Theme(lambda: raw)
t.apply_dict(d)
if t.name:
self.themes[t.name] = t
def filtered(self, is_ok: Callable[[Theme], bool]) -> 'Themes':
ans = Themes()
def sort_key(k: Tuple[str, Theme]) -> str:
return k[1].name.lower()
ans.themes = {k: v for k, v in sorted(self.themes.items(), key=sort_key) if is_ok(v)}
ans.index_map = tuple(ans.themes)
return ans
def copy(self) -> 'Themes':
ans = Themes()
ans.themes = self.themes.copy()
ans.index_map = self.index_map
return ans
def apply_search(
self, expression: str, mark_before: str = MARK_BEFORE, mark_after: str = MARK_AFTER
) -> Iterator[str]:
raw = '\n'.join(self.themes)
results = match(raw, expression, positions=True, level1=' ')
themes: Dict[str, Theme] = {}
for r in results:
pos, k = r.split(':', 1)
positions = tuple(map(int, pos.split(',')))
text = k
for p in reversed(positions):
text = text[:p] + mark_before + text[p] + mark_after + text[p+1:]
themes[k] = self.themes[k]
yield text
self.themes = themes
self.index_map = tuple(self.themes)
def load_themes(cache_age: float = 1., ignore_no_cache: bool = False) -> Themes:
ans = Themes()
try:
fetched = fetch_themes(cache_age=cache_age)
except NoCacheFound:
if not ignore_no_cache:
raise
ans.load_from_zip(fetched)
ans.load_from_dir(os.path.join(config_dir, 'themes'))
ans.index_map = tuple(ans.themes)
return ans

594
kittens/themes/main.py Normal file
View File

@@ -0,0 +1,594 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
import sys
import traceback
from enum import Enum, auto
from gettext import gettext as _
from typing import (
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
)
from kitty.cli import create_default_opts, parse_args
from kitty.cli_stub import ThemesCLIOptions
from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.rgb import color_as_sharp, color_from_int
from kitty.typing import KeyEventType
from kitty.utils import ScreenSize
from ..tui.handler import Handler
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop
from ..tui.operations import color_code, styled
from .collection import MARK_AFTER, NoCacheFound, Theme, Themes, load_themes
separator = ''
def format_traceback(msg: str) -> str:
return traceback.format_exc() + '\n\n' + styled(msg, fg='red')
def limit_length(text: str, limit: int = 32) -> str:
x = truncate_point_for_length(text, limit - 1)
if x >= len(text):
return text
return text[:x] + ''
class State(Enum):
fetching = auto()
browsing = auto()
searching = auto()
accepting = auto()
def dark_filter(q: Theme) -> bool:
return q.is_dark
def light_filter(q: Theme) -> bool:
return not q.is_dark
def all_filter(q: Theme) -> bool:
return True
def create_recent_filter(names: Iterable[str]) -> Callable[[Theme], bool]:
allowed = frozenset(names)
def recent_filter(q: Theme) -> bool:
return q.name in allowed
return recent_filter
def mark_shortcut(text: str, acc: str) -> str:
acc_idx = text.lower().index(acc.lower())
return text[:acc_idx] + styled(text[acc_idx], underline='straight', bold=True, fg_intense=True) + text[acc_idx+1:]
class ThemesList:
def __init__(self) -> None:
self.themes = Themes()
self.current_search: str = ''
self.display_strings: Tuple[str, ...] = ()
self.widths: Tuple[int, ...] = ()
self.max_width = 0
self.current_idx = 0
def __bool__(self) -> bool:
return bool(self.display_strings)
def __len__(self) -> int:
return len(self.themes)
def next(self, delta: int = 1, allow_wrapping: bool = True) -> bool:
if not self:
return False
idx = self.current_idx + delta
if not allow_wrapping and (idx < 0 or idx >= len(self)):
return False
while idx < 0:
idx += len(self)
self.current_idx = idx % len(self)
return True
def update_themes(self, themes: Themes) -> None:
self.themes = self.all_themes = themes
if self.current_search:
self.themes = self.all_themes.copy()
self.display_strings = tuple(map(limit_length, self.themes.apply_search(self.current_search)))
else:
self.display_strings = tuple(map(limit_length, (t.name for t in self.themes)))
self.widths = tuple(map(wcswidth, self.display_strings))
self.max_width = max(self.widths) if self.widths else 0
self.current_idx = 0
def update_search(self, search: str = '') -> bool:
if search == self.current_search:
return False
self.current_search = search
self.update_themes(self.all_themes)
return True
def lines(self, num_rows: int) -> Iterator[Tuple[str, int, bool]]:
if num_rows < 1:
return
before_num = min(self.current_idx, num_rows - 1)
start = self.current_idx - before_num
for i in range(start, min(start + num_rows, len(self.display_strings))):
line = self.display_strings[i]
yield line, self.widths[i], i == self.current_idx
@property
def current_theme(self) -> Theme:
return self.themes[self.current_idx]
class ThemesHandler(Handler):
def __init__(self, cached_values: Dict[str, Any], cli_opts: ThemesCLIOptions) -> None:
self.cached_values = cached_values
self.cli_opts = cli_opts
self.state = State.fetching
self.report_traceback_on_exit: Optional[str] = None
self.filter_map: Dict[str, Callable[[Theme], bool]] = {
'dark': dark_filter, 'light': light_filter, 'all': all_filter,
'recent': create_recent_filter(self.cached_values.get('recent', ()))
}
self.themes_list = ThemesList()
self.colors_set_once = False
self.line_edit = LineEdit()
self.tabs = tuple('all dark light recent'.split())
def update_recent(self) -> None:
r = list(self.cached_values.get('recent', ()))
if self.themes_list:
name = self.themes_list.current_theme.name
r = [name] + [x for x in r if x != name]
self.cached_values['recent'] = r[:20]
def enforce_cursor_state(self) -> None:
self.cmd.set_cursor_visible(self.state == State.fetching)
def init_terminal_state(self) -> None:
self.cmd.save_colors()
self.cmd.set_line_wrapping(False)
self.cmd.set_window_title('Choose a theme for kitty')
self.cmd.set_cursor_shape('bar')
def initialize(self) -> None:
self.init_terminal_state()
self.draw_screen()
self.fetch_themes()
def finalize(self) -> None:
self.cmd.restore_colors()
self.cmd.set_cursor_visible(True)
@property
def current_category(self) -> str:
cat: str = self.cached_values.get('category', 'all')
if cat not in self.filter_map:
cat = 'all'
return cat
@current_category.setter
def current_category(self, cat: str) -> None:
if cat not in self.filter_map:
cat = 'all'
self.cached_values['category'] = cat
def set_colors_to_current_theme(self) -> bool:
if not self.themes_list and self.colors_set_once:
return False
self.colors_set_once = True
if self.themes_list:
o = self.themes_list.current_theme.kitty_opts
else:
o = create_default_opts()
self.cmd.set_default_colors(
fg=o.foreground, bg=o.background, cursor=o.cursor, select_bg=o.selection_background, select_fg=o.selection_foreground
)
self.current_opts = o
cmds = []
for i in range(256):
col = color_as_sharp(color_from_int(o.color_table[i]))
cmds.append(f'{i};{col}')
self.print(end='\033]4;' + ';'.join(cmds) + '\033\\')
def redraw_after_category_change(self) -> None:
self.themes_list.update_themes(self.all_themes.filtered(self.filter_map[self.current_category]))
self.set_colors_to_current_theme()
self.draw_screen()
# Theme fetching {{{
def fetch_themes(self) -> None:
def fetching_done(themes_or_exception: Union[Themes, str]) -> None:
if isinstance(themes_or_exception, str):
self.report_traceback_on_exit = themes_or_exception
self.quit_loop(1)
return
self.all_themes: Themes = themes_or_exception
self.state = State.browsing
self.redraw_after_category_change()
def fetch() -> None:
try:
themes: Union[Themes, str] = load_themes(self.cli_opts.cache_age)
except Exception:
themes = format_traceback('Failed to download themes')
self.asyncio_loop.call_soon_threadsafe(fetching_done, themes)
self.asyncio_loop.run_in_executor(None, fetch)
self.draw_screen()
def draw_fetching_screen(self) -> None:
self.print('Downloading themes from repository, please wait...')
def on_fetching_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('esc'):
self.quit_loop(0)
# }}}
# Theme browsing {{{
def draw_tab_bar(self) -> None:
self.print(styled(' ' * self.screen_size.cols, reverse=True), end='\r')
def draw_tab(text: str, name: str, acc: str) -> None:
is_active = name == self.current_category
if is_active:
text = styled(f'{text} #{len(self.themes_list)}', italic=True)
else:
text = mark_shortcut(text, acc)
self.cmd.styled(f' {text} ', reverse=not is_active)
for t in self.tabs:
draw_tab(t.capitalize(), t, t[0])
self.cmd.sgr('0')
self.print()
def draw_bottom_bar(self) -> None:
self.cmd.set_cursor_position(0, self.screen_size.rows)
self.print(styled(' ' * self.screen_size.cols, reverse=True), end='\r')
for (t, sc) in (('search (/)', 's'), ('accept (⏎)', 'c')):
text = mark_shortcut(t.capitalize(), sc)
self.cmd.styled(f' {text} ', reverse=True)
self.cmd.sgr('0')
def draw_search_bar(self) -> None:
self.cmd.set_cursor_position(0, self.screen_size.rows)
self.cmd.clear_to_eol()
self.line_edit.write(self.write)
def draw_theme_demo(self) -> None:
theme = self.themes_list.current_theme
xstart = self.themes_list.max_width + 3
sz = self.screen_size.cols - xstart
if sz < 20:
return
sz -= 1
y = 0
colors = 'black red green yellow blue magenta cyan white'.split()
trunc = sz // 8 - 1
def next_line() -> None:
nonlocal y
self.write('\r')
y += 1
self.cmd.set_cursor_position(xstart - 1, y)
self.write(separator + ' ')
def write_para(text: str) -> None:
text = re.sub(r'\s+', ' ', text)
while text:
sp = truncate_point_for_length(text, sz)
self.write(text[:sp])
next_line()
text = text[sp:]
def write_colors(bg: Optional[str] = None) -> None:
for intense in (False, True):
buf = []
for c in colors:
buf.append(styled(c[:trunc], fg=c, fg_intense=intense))
self.cmd.styled(' '.join(buf), bg=bg, bg_intense=intense)
next_line()
next_line()
def write_line(text: str) -> None:
self.write(text)
next_line()
self.cmd.set_cursor_position()
next_line()
self.cmd.styled(theme.name.center(sz), bold=True, fg='green')
next_line()
if theme.author:
self.cmd.styled(theme.author.center(sz), italic=True)
next_line()
if theme.blurb:
next_line()
write_para(theme.blurb)
next_line()
write_colors()
for bg in colors:
write_colors(bg)
def draw_browsing_screen(self) -> None:
self.draw_tab_bar()
num_rows = self.screen_size.rows - 2
mw = self.themes_list.max_width + 1
for line, width, is_current in self.themes_list.lines(num_rows):
num_rows -= 1
if is_current:
line = line.replace(MARK_AFTER, '\033[' + color_code('green') + 'm')
self.cmd.styled('>' if is_current else ' ', fg='green')
self.cmd.styled(line, bold=is_current, fg='green' if is_current else None)
self.cmd.move_cursor_by(mw - width, 'right')
self.print(separator)
if self.themes_list:
self.draw_theme_demo()
self.draw_bottom_bar() if self.state is State.browsing else self.draw_search_bar()
def on_searching_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('enter'):
self.state = State.browsing
self.draw_bottom_bar()
return
if key_event.matches('esc'):
self.state = State.browsing
self.themes_list.update_search('')
self.set_colors_to_current_theme()
return self.draw_screen()
if key_event.text:
self.line_edit.on_text(key_event.text, in_bracketed_paste)
else:
if not self.line_edit.on_key(key_event):
if key_event.matches('left') or key_event.matches('shift+tab'):
return self.next_category(-1)
if key_event.matches('right') or key_event.matches('tab'):
return self.next_category(1)
if key_event.matches('down'):
return self.next(delta=1)
if key_event.matches('up'):
return self.next(delta=-1)
if key_event.matches('page_down'):
return self.next(delta=self.screen_size.rows - 3, allow_wrapping=False)
if key_event.matches('page_up'):
return self.next(delta=3 - self.screen_size.rows, allow_wrapping=False)
return
if self.line_edit.current_input:
q = self.line_edit.current_input[1:]
if self.themes_list.update_search(q):
self.set_colors_to_current_theme()
self.draw_screen()
else:
self.draw_search_bar()
else:
self.state = State.browsing
self.draw_bottom_bar()
def on_browsing_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('esc') or key_event.matches('q'):
return self.quit_loop(0)
for cat in 'all dark light recent'.split():
if key_event.matches(cat[0]) or key_event.matches(f'alt+{cat[0]}'):
if cat != self.current_category:
self.current_category = cat
self.redraw_after_category_change()
return
if key_event.matches('left') or key_event.matches('shift+tab'):
return self.next_category(-1)
if key_event.matches('right') or key_event.matches('tab'):
return self.next_category(1)
if key_event.matches('j') or key_event.matches('down'):
return self.next(delta=1)
if key_event.matches('k') or key_event.matches('up'):
return self.next(delta=-1)
if key_event.matches('page_down'):
return self.next(delta=self.screen_size.rows - 3, allow_wrapping=False)
if key_event.matches('page_up'):
return self.next(delta=3 - self.screen_size.rows, allow_wrapping=False)
if key_event.matches('s') or key_event.matches('/'):
return self.start_search()
if key_event.matches('c') or key_event.matches('enter'):
if not self.themes_list:
self.cmd.beep()
return
self.state = State.accepting
return self.draw_screen()
def start_search(self) -> None:
self.line_edit.clear()
self.line_edit.add_text('/' + self.themes_list.current_search)
self.state = State.searching
self.draw_screen()
def next_category(self, delta: int = 1) -> None:
idx = self.tabs.index(self.current_category) + delta + len(self.tabs)
self.current_category = self.tabs[idx % len(self.tabs)]
self.redraw_after_category_change()
def next(self, delta: int = 1, allow_wrapping: bool = True) -> None:
if self.themes_list.next(delta, allow_wrapping):
self.set_colors_to_current_theme()
self.draw_screen()
else:
self.cmd.bell()
# }}}
# Accepting {{{
def draw_accepting_screen(self) -> None:
name = self.themes_list.current_theme.name
name = styled(name, bold=True, fg="green")
kc = styled('kitty.conf', italic=True)
def ac(x: str) -> str:
return styled(x, fg='red')
self.cmd.set_line_wrapping(True)
self.print(f'You have chosen the {name} theme')
self.print()
self.print('What would you like to do?')
self.print()
self.print(' ', f'{ac("M")}odify {kc} to load', styled(name, bold=True, fg="green"))
self.print()
self.print(' ', f'{ac("P")}lace the theme file in {config_dir} but do not modify {kc}')
self.print()
self.print(' ', f'{ac("A")}bort and return to list of themes')
self.print()
self.print(' ', f'{ac("Q")}uit')
def on_accepting_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.matches('q') or key_event.matches('esc'):
self.quit_loop(0)
return
if key_event.matches('a'):
self.state = State.browsing
self.draw_screen()
return
if key_event.matches('p'):
self.themes_list.current_theme.save_in_dir(config_dir)
self.update_recent()
self.quit_loop(0)
return
if key_event.matches('m'):
self.themes_list.current_theme.save_in_conf(config_dir, self.cli_opts.reload_in)
self.update_recent()
self.quit_loop(0)
return
# }}}
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if self.state is State.fetching:
self.on_fetching_key_event(key_event, in_bracketed_paste)
elif self.state is State.browsing:
self.on_browsing_key_event(key_event, in_bracketed_paste)
elif self.state is State.searching:
self.on_searching_key_event(key_event, in_bracketed_paste)
elif self.state is State.accepting:
self.on_accepting_key_event(key_event, in_bracketed_paste)
def draw_screen(self) -> None:
with self.pending_update():
self.cmd.clear_screen()
self.enforce_cursor_state()
self.cmd.set_line_wrapping(False)
if self.state is State.fetching:
self.draw_fetching_screen()
elif self.state in (State.browsing, State.searching):
self.draw_browsing_screen()
elif self.state is State.accepting:
self.draw_accepting_screen()
def on_resize(self, screen_size: ScreenSize) -> None:
self.screen_size = screen_size
self.draw_screen()
def on_interrupt(self) -> None:
self.quit_loop(1)
def on_eot(self) -> None:
self.quit_loop(1)
help_text = (
'Change the kitty theme. If no theme name is supplied, run interactively, otherwise'
' change the current theme to the specified theme name.'
)
usage = '[theme name to switch to]'
OPTIONS = '''
--cache-age
type=float
default=1
Check for new themes only after the specified number of days. A value of
zero will always check for new themes. A negative value will never check
for new themes, instead raising an error if a local copy of the themes
is not available.
--reload-in
default=parent
choices=none,parent,all
By default, this kitten will signal only the parent kitty instance it is
running in to reload its config, after making changes. Use this option
to instead either not reload the config at all or in all running
kitty instances.
--dump-theme
type=bool-set
default=false
When running non-interactively, dump the specified theme to STDOUT
instead of changing kitty.conf.
'''.format
def parse_themes_args(args: List[str]) -> Tuple[ThemesCLIOptions, List[str]]:
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten themes', result_class=ThemesCLIOptions)
def non_interactive(cli_opts: ThemesCLIOptions, theme_name: str) -> None:
try:
themes = load_themes(cli_opts.cache_age)
except NoCacheFound as e:
raise SystemExit(str(e))
try:
theme = themes[theme_name]
except KeyError:
raise SystemExit(f'No theme named: {theme_name}')
if cli_opts.dump_theme:
print(theme.raw)
return
theme.save_in_conf(config_dir, cli_opts.reload_in)
def main(args: List[str]) -> None:
try:
cli_opts, items = parse_themes_args(args[1:])
except SystemExit as e:
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return None
if len(items) > 1:
items = [' '.join(items)]
if len(items) == 1:
return non_interactive(cli_opts, items[0])
loop = Loop()
with cached_values_for('themes-kitten') as cached_values:
handler = ThemesHandler(cached_values, cli_opts)
loop.loop(handler)
if loop.return_code != 0:
if handler.report_traceback_on_exit:
print(handler.report_traceback_on_exit, file=sys.stderr)
input('Press Enter to quit.')
if handler.state is State.fetching:
# asycio uses non-daemonic threads in its ThreadPoolExecutor
# so we will hang here till the download completes without
# os._exit
os._exit(loop.return_code)
raise SystemExit(loop.return_code)
if __name__ == '__main__':
main(sys.argv)
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text

View File

@@ -10,10 +10,12 @@ from typing import (
from kitty.types import ParsedShortcut from kitty.types import ParsedShortcut
from kitty.typing import ( from kitty.typing import (
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType, AbstractEventLoop, BossType, Debug, ImageManagerType, KeyActionType,
KeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType KeyEventType, LoopType, MouseEvent, ScreenSize, TermManagerType
) )
from .operations import pending_update
class Handler: class Handler:
@@ -134,6 +136,9 @@ class Handler:
def suspend(self) -> ContextManager[TermManagerType]: def suspend(self) -> ContextManager[TermManagerType]:
return self._term_manager.suspend() return self._term_manager.suspend()
def pending_update(self) -> ContextManager[None]:
return pending_update(self.write)
class HandleResult: class HandleResult:

View File

@@ -5,6 +5,7 @@
import sys import sys
from contextlib import contextmanager from contextlib import contextmanager
from functools import wraps from functools import wraps
from enum import Enum
from typing import ( from typing import (
IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union IO, Any, Callable, Dict, Generator, Optional, Tuple, TypeVar, Union
) )
@@ -22,44 +23,45 @@ SAVE_PRIVATE_MODE_VALUES = '\033[?s'
RESTORE_PRIVATE_MODE_VALUES = '\033[?r' RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
SAVE_COLORS = '\033[#P' SAVE_COLORS = '\033[#P'
RESTORE_COLORS = '\033[#Q' RESTORE_COLORS = '\033[#Q'
MODES = dict(
LNM=(20, ''),
IRM=(4, ''),
DECKM=(1, '?'),
DECSCNM=(5, '?'),
DECOM=(6, '?'),
DECAWM=(7, '?'),
DECARM=(8, '?'),
DECTCEM=(25, '?'),
MOUSE_BUTTON_TRACKING=(1000, '?'),
MOUSE_MOTION_TRACKING=(1002, '?'),
MOUSE_MOVE_TRACKING=(1003, '?'),
FOCUS_TRACKING=(1004, '?'),
MOUSE_UTF8_MODE=(1005, '?'),
MOUSE_SGR_MODE=(1006, '?'),
MOUSE_URXVT_MODE=(1015, '?'),
ALTERNATE_SCREEN=(1049, '?'),
BRACKETED_PASTE=(2004, '?'),
)
F = TypeVar('F') F = TypeVar('F')
all_cmds: Dict[str, Callable] = {} all_cmds: Dict[str, Callable] = {}
class Mode(Enum):
LNM = 20, ''
IRM = 4, ''
DECKM = 1, '?'
DECSCNM = 5, '?'
DECOM = 6, '?'
DECAWM = 7, '?'
DECARM = 8, '?'
DECTCEM = 25, '?'
MOUSE_BUTTON_TRACKING = 1000, '?'
MOUSE_MOTION_TRACKING = 1002, '?'
MOUSE_MOVE_TRACKING = 1003, '?'
FOCUS_TRACKING = 1004, '?'
MOUSE_UTF8_MODE = 1005, '?'
MOUSE_SGR_MODE = 1006, '?'
MOUSE_URXVT_MODE = 1015, '?'
ALTERNATE_SCREEN = 1049, '?'
BRACKETED_PASTE = 2004, '?'
PENDING_UPDATE = 2026, '?'
def cmd(f: F) -> F: def cmd(f: F) -> F:
all_cmds[f.__name__] = f # type: ignore all_cmds[f.__name__] = f # type: ignore
return f return f
@cmd @cmd
def set_mode(which: str, private: bool = True) -> str: def set_mode(which: Mode) -> str:
num, private_ = MODES[which] num, private = which.value
return '\033[{}{}h'.format(private_, num) return '\033[{}{}h'.format(private, num)
@cmd @cmd
def reset_mode(which: str) -> str: def reset_mode(which: Mode) -> str:
num, private = MODES[which] num, private = which.value
return '\033[{}{}l'.format(private, num) return '\033[{}{}l'.format(private, num)
@@ -100,16 +102,16 @@ def set_window_title(value: str) -> str:
@cmd @cmd
def set_line_wrapping(yes_or_no: bool) -> str: def set_line_wrapping(yes_or_no: bool) -> str:
return set_mode('DECAWM') if yes_or_no else reset_mode('DECAWM') return set_mode(Mode.DECAWM) if yes_or_no else reset_mode(Mode.DECAWM)
@cmd @cmd
def set_cursor_visible(yes_or_no: bool) -> str: def set_cursor_visible(yes_or_no: bool) -> str:
return set_mode('DECTCEM') if yes_or_no else reset_mode('DECTCEM') return set_mode(Mode.DECTCEM) if yes_or_no else reset_mode(Mode.DECTCEM)
@cmd @cmd
def set_cursor_position(x: int, y: int) -> str: # (0, 0) is top left def set_cursor_position(x: int = 0, y: int = 0) -> str: # (0, 0) is top left
return '\033[{};{}H'.format(y + 1, x + 1) return '\033[{};{}H'.format(y + 1, x + 1)
@@ -262,18 +264,18 @@ def clear_images_on_screen(delete_data: bool = False) -> str:
def init_state(alternate_screen: bool = True) -> str: def init_state(alternate_screen: bool = True) -> str:
ans = ( ans = (
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') + S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode(Mode.LNM) +
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') + reset_mode(Mode.IRM) + reset_mode(Mode.DECKM) + reset_mode(Mode.DECSCNM) +
set_mode('DECARM') + set_mode('DECAWM') + set_mode(Mode.DECARM) + set_mode(Mode.DECAWM) +
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') + set_mode(Mode.DECTCEM) + reset_mode(Mode.MOUSE_BUTTON_TRACKING) +
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') + reset_mode(Mode.MOUSE_MOTION_TRACKING) + reset_mode(Mode.MOUSE_MOVE_TRACKING) +
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') + reset_mode(Mode.FOCUS_TRACKING) + reset_mode(Mode.MOUSE_UTF8_MODE) +
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') + reset_mode(Mode.MOUSE_SGR_MODE) + reset_mode(Mode.MOUSE_UTF8_MODE) +
set_mode('BRACKETED_PASTE') + SAVE_COLORS + set_mode(Mode.BRACKETED_PASTE) + SAVE_COLORS +
'\033[*x' # reset DECSACE to default region select '\033[*x' # reset DECSACE to default region select
) )
if alternate_screen: if alternate_screen:
ans += set_mode('ALTERNATE_SCREEN') + reset_mode('DECOM') ans += set_mode(Mode.ALTERNATE_SCREEN) + reset_mode(Mode.DECOM)
ans += clear_screen() ans += clear_screen()
ans += '\033[>31u' # extended keyboard mode ans += '\033[>31u' # extended keyboard mode
return ans return ans
@@ -283,13 +285,20 @@ def reset_state(normal_screen: bool = True) -> str:
ans = '' ans = ''
ans += '\033[<u' # restore keyboard mode ans += '\033[<u' # restore keyboard mode
if normal_screen: if normal_screen:
ans += reset_mode('ALTERNATE_SCREEN') ans += reset_mode(Mode.ALTERNATE_SCREEN)
ans += RESTORE_PRIVATE_MODE_VALUES ans += RESTORE_PRIVATE_MODE_VALUES
ans += RESTORE_CURSOR ans += RESTORE_CURSOR
ans += RESTORE_COLORS ans += RESTORE_COLORS
return ans return ans
@contextmanager
def pending_update(write: Callable[[str], None]) -> Generator[None, None, None]:
write(set_mode(Mode.PENDING_UPDATE))
yield
write(reset_mode(Mode.PENDING_UPDATE))
@contextmanager @contextmanager
def cursor(write: Callable[[str], None]) -> Generator[None, None, None]: def cursor(write: Callable[[str], None]) -> Generator[None, None, None]:
write(SAVE_CURSOR) write(SAVE_CURSOR)
@@ -300,9 +309,9 @@ def cursor(write: Callable[[str], None]) -> Generator[None, None, None]:
@contextmanager @contextmanager
def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]: def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]:
f = f or sys.stdout f = f or sys.stdout
print(set_mode('ALTERNATE_SCREEN'), end='', file=f) print(set_mode(Mode.ALTERNATE_SCREEN), end='', file=f)
yield yield
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f) print(reset_mode(Mode.ALTERNATE_SCREEN), end='', file=f)
@contextmanager @contextmanager
@@ -350,6 +359,16 @@ def set_default_colors(
return ans return ans
@cmd
def save_colors() -> str:
return '\x1b[#P'
@cmd
def restore_colors() -> str:
return '\x1b[#Q'
@cmd @cmd
def write_to_clipboard(data: Union[str, bytes], use_primary: bool = False) -> str: def write_to_clipboard(data: Union[str, bytes], use_primary: bool = False) -> str:
from base64 import standard_b64encode from base64 import standard_b64encode
@@ -396,6 +415,7 @@ def as_type_stub() -> str:
'from kitty.typing import GraphicsCommandType, ScreenSize', 'from kitty.typing import GraphicsCommandType, ScreenSize',
'from kitty.rgb import Color', 'from kitty.rgb import Color',
'import kitty.rgb', 'import kitty.rgb',
'import kittens.tui.operations',
] ]
methods = [] methods = []
for name, func in all_cmds.items(): for name, func in all_cmds.items():

View File

@@ -7,7 +7,7 @@ from typing import Dict, List, NamedTuple
from .boss import Boss from .boss import Boss
from .tabs import Tab from .tabs import Tab
from .types import run_once from .types import run_once, ActionGroup, ActionSpec
from .window import Window from .window import Window
@@ -18,7 +18,7 @@ class Action(NamedTuple):
long_help: str long_help: str
groups = { groups: Dict[ActionGroup, str] = {
'cp': 'Copy/paste', 'cp': 'Copy/paste',
'sc': 'Scrolling', 'sc': 'Scrolling',
'win': 'Window management', 'win': 'Window management',
@@ -33,22 +33,21 @@ group_title = groups.__getitem__
@run_once @run_once
def get_all_actions() -> Dict[str, List[Action]]: def get_all_actions() -> Dict[str, List[Action]]:
' test docstring '
ans: Dict[str, List[Action]] = {} ans: Dict[str, List[Action]] = {}
def is_action(x: object) -> bool: def is_action(x: object) -> bool:
doc = getattr(x, '__doc__', '') return isinstance(getattr(x, 'action_spec', None), ActionSpec)
return bool(doc and doc.strip().startswith('@ac:'))
def as_action(x: object) -> Action: def as_action(x: object) -> Action:
doc = inspect.cleandoc(x.__doc__ or '') spec: ActionSpec = getattr(x, 'action_spec')
doc = inspect.cleandoc(spec.doc)
lines = doc.splitlines() lines = doc.splitlines()
first = lines.pop(0) first = lines.pop(0)
parts = first.split(':', 2) short_help = first
grp = parts[1].strip()
short_help = parts[2].strip()
long_help = '\n'.join(lines).strip() long_help = '\n'.join(lines).strip()
return Action(getattr(x, '__name__'), grp, short_help, long_help) return Action(getattr(x, '__name__'), spec.group, short_help, long_help)
seen = set() seen = set()
for cls in (Window, Tab, Boss): for cls in (Window, Tab, Boss):

View File

@@ -50,7 +50,7 @@ from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import ( from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
) )
from .types import SingleKey from .types import SingleKey, ac
from .typing import PopenType, TypedDict from .typing import PopenType, TypedDict
from .utils import ( from .utils import (
func_name, get_editor, get_new_os_window_size, get_primary_selection, func_name, get_editor, get_new_os_window_size, get_primary_selection,
@@ -162,7 +162,10 @@ class Boss:
talk_fd = getattr(single_instance, 'socket', None) talk_fd = getattr(single_instance, 'socket', None)
talk_fd = -1 if talk_fd is None else talk_fd.fileno() talk_fd = -1 if talk_fd is None else talk_fd.fileno()
listen_fd = -1 listen_fd = -1
if args.listen_on and (opts.allow_remote_control in ('y', 'socket-only')): # we dont allow reloading the config file to change
# allow_remote_control
self.allow_remote_control = opts.allow_remote_control
if args.listen_on and (self.allow_remote_control in ('y', 'socket-only')):
listen_fd = listen_on(args.listen_on) listen_fd = listen_on(args.listen_on)
self.child_monitor = ChildMonitor( self.child_monitor = ChildMonitor(
self.on_child_death, self.on_child_death,
@@ -213,7 +216,7 @@ class Boss:
os_window_id = create_os_window( os_window_id = create_os_window(
initial_window_size_func(size_data, self.cached_values), initial_window_size_func(size_data, self.cached_values),
pre_show_callback, pre_show_callback,
self.args.title or appname, wname, wclass) self.args.title or appname, wname, wclass, disallow_override_title=bool(self.args.title))
else: else:
wname = self.args.name or self.args.cls or appname wname = self.args.name or self.args.cls or appname
wclass = self.args.cls or appname wclass = self.args.cls or appname
@@ -333,8 +336,8 @@ class Boss:
startup_session = next(create_sessions(get_options(), special_window=sw, cwd_from=cwd_from)) startup_session = next(create_sessions(get_options(), special_window=sw, cwd_from=cwd_from))
return self.add_os_window(startup_session) return self.add_os_window(startup_session)
@ac('win', 'New OS Window')
def new_os_window(self, *args: str) -> None: def new_os_window(self, *args: str) -> None:
'@ac:win: New OS Window'
self._new_os_window(args) self._new_os_window(args)
@property @property
@@ -343,8 +346,8 @@ class Boss:
if t is not None: if t is not None:
return t.active_window_for_cwd return t.active_window_for_cwd
@ac('win', 'New OS Window with the same working directory as the currently active window')
def new_os_window_with_cwd(self, *args: str) -> None: def new_os_window_with_cwd(self, *args: str) -> None:
'@ac:win: New OS Window with the same working directory as the currently active window'
w = self.active_window_for_cwd w = self.active_window_for_cwd
cwd_from = w.child.pid_for_cwd if w is not None else None cwd_from = w.child.pid_for_cwd if w is not None else None
self._new_os_window(args, cwd_from) self._new_os_window(args, cwd_from)
@@ -362,7 +365,7 @@ class Boss:
from .remote_control import handle_cmd from .remote_control import handle_cmd
response = None response = None
window = window or None window = window or None
if get_options().allow_remote_control == 'y' or from_peer or getattr(window, 'allow_remote_control', False): if self.allow_remote_control == 'y' or from_peer or getattr(window, 'allow_remote_control', False):
try: try:
response = handle_cmd(self, window, cmd) response = handle_cmd(self, window, cmd)
except Exception as err: except Exception as err:
@@ -380,16 +383,16 @@ class Boss:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'} response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control to your kitty.conf'}
return response return response
def remote_control(self, *args: str) -> None: @ac('misc', '''
''' Run a remote control command
@ac:misc: Run a remote control command
For example:: For example::
map F1 remote_control set-spacing margin=30 map F1 remote_control set-spacing margin=30
See :ref:`rc_mapping` for details. See :ref:`rc_mapping` for details.
''' ''')
def remote_control(self, *args: str) -> None:
from .rc.base import ( from .rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli PayloadGetter, command_for_name, parse_subcommand_cli
) )
@@ -501,8 +504,8 @@ class Boss:
if window: if window:
self.child_monitor.mark_for_close(window.id) self.child_monitor.mark_for_close(window.id)
@ac('tab', 'Close the current tab')
def close_tab(self, tab: Optional[Tab] = None) -> None: def close_tab(self, tab: Optional[Tab] = None) -> None:
'@ac:tab: Close the current tab'
tab = tab or self.active_tab tab = tab or self.active_tab
if tab: if tab:
self.confirm_tab_close(tab) self.confirm_tab_close(tab)
@@ -534,12 +537,12 @@ class Boss:
for window in tab: for window in tab:
self.close_window(window) self.close_window(window)
@ac('win', 'Toggle the fullscreen status of the active OS Window')
def toggle_fullscreen(self, os_window_id: int = 0) -> None: def toggle_fullscreen(self, os_window_id: int = 0) -> None:
'@ac:win: Toggle the fullscreen status of the active OS Window'
toggle_fullscreen(os_window_id) toggle_fullscreen(os_window_id)
@ac('win', 'Toggle the maximized status of the active OS Window')
def toggle_maximized(self, os_window_id: int = 0) -> None: def toggle_maximized(self, os_window_id: int = 0) -> None:
'@ac:win: Toggle the maximized status of the active OS Window'
toggle_maximized(os_window_id) toggle_maximized(os_window_id)
def start(self, first_os_window_id: int) -> None: def start(self, first_os_window_id: int) -> None:
@@ -566,9 +569,8 @@ class Boss:
if tm is not None: if tm is not None:
tm.resize() tm.resize()
def clear_terminal(self, action: str, only_active: bool) -> None: @ac('misc', '''
''' Clear the terminal
@ac:misc: Clear the terminal
See :sc:`reset_terminal` for details. For example:: See :sc:`reset_terminal` for details. For example::
@@ -581,7 +583,8 @@ class Boss:
# Scroll the contents of the screen into the scrollback # Scroll the contents of the screen into the scrollback
map kitty_mod+f12 clear_terminal scroll active map kitty_mod+f12 clear_terminal scroll active
''' ''')
def clear_terminal(self, action: str, only_active: bool) -> None:
if only_active: if only_active:
windows = [] windows = []
w = self.active_window w = self.active_window
@@ -615,12 +618,12 @@ class Boss:
def set_font_size(self, new_size: float) -> None: # legacy def set_font_size(self, new_size: float) -> None: # legacy
self.change_font_size(True, None, new_size) self.change_font_size(True, None, new_size)
def change_font_size(self, all_windows: bool, increment_operation: Optional[str], amt: float) -> None: @ac('win', '''
''' Change the font size for the current or all OS Windows
@ac:win: Change the font size for the current or all OS Windows
See :ref:`conf-kitty-shortcuts.fonts` for details. See :ref:`conf-kitty-shortcuts.fonts` for details.
''' ''')
def change_font_size(self, all_windows: bool, increment_operation: Optional[str], amt: float) -> None:
def calc_new_size(old_size: float) -> float: def calc_new_size(old_size: float) -> float:
new_size = old_size new_size = old_size
if amt == 0: if amt == 0:
@@ -676,16 +679,16 @@ class Boss:
def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None: def _set_os_window_background_opacity(self, os_window_id: int, opacity: float) -> None:
change_background_opacity(os_window_id, max(0.1, min(opacity, 1.0))) change_background_opacity(os_window_id, max(0.1, min(opacity, 1.0)))
def set_background_opacity(self, opacity: str) -> None: @ac('win', '''
''' Set the background opacity for the active OS Window
@ac:win: Set the background opacity for the active OS Window
For example:: For example::
map f1 set_background_opacity +0.1 map f1 set_background_opacity +0.1
map f2 set_background_opacity -0.1 map f2 set_background_opacity -0.1
map f3 set_background_opacity 0.5 map f3 set_background_opacity 0.5
''' ''')
def set_background_opacity(self, opacity: str) -> None:
window = self.active_window window = self.active_window
if window is None or not opacity: if window is None or not opacity:
return return
@@ -761,12 +764,12 @@ class Boss:
if matched_action is not None: if matched_action is not None:
self.dispatch_action(matched_action) self.dispatch_action(matched_action)
def start_resizing_window(self) -> None: @ac('win', '''
''' Resize the active window interactively
@ac:win: Resize the active window interactively
See :ref:`window_resizing` for details. See :ref:`window_resizing` for details.
''' ''')
def start_resizing_window(self) -> None:
w = self.active_window w = self.active_window
if w is None: if w is None:
return return
@@ -843,9 +846,8 @@ class Boss:
return True return True
return False return False
def combine(self, *actions: KeyAction) -> None: @ac('misc', '''
''' Combine multiple actions and map to a single keypress
@ac:misc: Combine multiple actions and map to a single keypress
The syntax is:: The syntax is::
@@ -854,7 +856,8 @@ class Boss:
For example:: For example::
map kitty_mod+e combine : new_window : next_layout map kitty_mod+e combine : new_window : next_layout
''' ''')
def combine(self, *actions: KeyAction) -> None:
for key_action in actions: for key_action in actions:
self.dispatch_action(key_action) self.dispatch_action(key_action)
@@ -889,8 +892,8 @@ class Boss:
text = '\n'.join(parse_uri_list(text)) text = '\n'.join(parse_uri_list(text))
w.paste(text) w.paste(text)
@ac('win', 'Close the currently active OS Window')
def close_os_window(self) -> None: def close_os_window(self) -> None:
'@ac:win: Close the currently active OS Window'
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
self.confirm_os_window_close(tm.os_window_id) self.confirm_os_window_close(tm.os_window_id)
@@ -929,8 +932,8 @@ class Boss:
if action is not None: if action is not None:
action() action()
@ac('win', 'Quit, closing all windows')
def quit(self, *args: Any) -> None: def quit(self, *args: Any) -> None:
'@ac:win: Quit, closing all windows'
tm = self.active_tab tm = self.active_tab
num = 0 num = 0
for q in self.os_window_map.values(): for q in self.os_window_map.values():
@@ -990,8 +993,8 @@ class Boss:
copy_colors_from=self.active_window copy_colors_from=self.active_window
) )
@ac('misc', 'Edit the kitty.conf config file in your favorite text editor')
def edit_config_file(self, *a: Any) -> None: def edit_config_file(self, *a: Any) -> None:
'@ac:misc: Edit the kitty.conf config file in your favorite text editor'
confpath = prepare_config_file_for_editing() confpath = prepare_config_file_for_editing()
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a # On macOS vim fails to handle SIGWINCH if it occurs early, so add a
# small delay. # small delay.
@@ -1081,8 +1084,8 @@ class Boss:
overlay_window.action_on_removal = callback_wrapper overlay_window.action_on_removal = callback_wrapper
return overlay_window return overlay_window
@ac('misc', 'Run the specified kitten. See :doc:`/kittens/custom` for details')
def kitten(self, kitten: str, *args: str) -> None: def kitten(self, kitten: str, *args: str) -> None:
'@ac:misc: Run the specified kitten. See :doc:`/kittens/custom` for details'
import shlex import shlex
cmdline = args[0] if args else '' cmdline = args[0] if args else ''
kargs = shlex.split(cmdline) if cmdline else [] kargs = shlex.split(cmdline) if cmdline else []
@@ -1098,12 +1101,12 @@ class Boss:
if data is not None: if data is not None:
end_kitten(data, target_window_id, self) end_kitten(data, target_window_id, self)
@ac('misc', 'Input an arbitrary unicode character. See :doc:`/kittens/unicode-input` for details.')
def input_unicode_character(self) -> None: def input_unicode_character(self) -> None:
'@ac:misc: Input an arbitrary unicode character. See :doc:`/kittens/unicode-input` for details.'
self._run_kitten('unicode_input') self._run_kitten('unicode_input')
@ac('tab', 'Change the title of the active tab')
def set_tab_title(self) -> None: def set_tab_title(self) -> None:
'@ac:tab: Change the title of the active tab'
tab = self.active_tab tab = self.active_tab
if tab: if tab:
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)] args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
@@ -1121,8 +1124,8 @@ class Boss:
def show_error(self, title: str, msg: str) -> None: def show_error(self, title: str, msg: str) -> None:
self._run_kitten('show_error', args=['--title', title], input_data=msg) self._run_kitten('show_error', args=['--title', title], input_data=msg)
@ac('mk', 'Create a new marker')
def create_marker(self) -> None: def create_marker(self) -> None:
'@ac:mk: Create a new marker'
w = self.active_window w = self.active_window
if w: if w:
spec = None spec = None
@@ -1145,8 +1148,8 @@ class Boss:
], ],
custom_callback=done, action_on_removal=done2) custom_callback=done, action_on_removal=done2)
@ac('misc', 'Run the kitty shell to control kitty with commands')
def kitty_shell(self, window_type: str = 'window') -> None: def kitty_shell(self, window_type: str = 'window') -> None:
'@ac:misc: Run the kitty shell to control kitty with commands'
kw: Dict[str, Any] = {} kw: Dict[str, Any] = {}
cmd = [kitty_exe(), '@'] cmd = [kitty_exe(), '@']
aw = self.active_window aw = self.active_window
@@ -1194,8 +1197,8 @@ class Boss:
if not found_action: if not found_action:
open_url(url, program or get_options().open_url_with, cwd=cwd) open_url(url, program or get_options().open_url_with, cwd=cwd)
@ac('misc', 'Click a URL using the keyboard')
def open_url_with_hints(self) -> None: def open_url_with_hints(self) -> None:
'@ac:misc: Click a URL using the keyboard'
self._run_kitten('hints') self._run_kitten('hints')
def drain_actions(self, actions: List) -> None: def drain_actions(self, actions: List) -> None:
@@ -1223,8 +1226,8 @@ class Boss:
if w is not None: if w is not None:
w.paste(text) w.paste(text)
@ac('cp', 'Paste from the clipboard to the active window')
def paste_from_clipboard(self) -> None: def paste_from_clipboard(self) -> None:
'@ac:cp: Paste from the clipboard to the active window'
text = get_clipboard_string() text = get_clipboard_string()
self.paste_to_active_window(text) self.paste_to_active_window(text)
@@ -1234,8 +1237,8 @@ class Boss:
def current_primary_selection_or_clipboard(self) -> str: def current_primary_selection_or_clipboard(self) -> str:
return get_primary_selection() if supports_primary_selection else get_clipboard_string() return get_primary_selection() if supports_primary_selection else get_clipboard_string()
@ac('cp', 'Paste from the clipboard to the active window')
def paste_from_selection(self) -> None: def paste_from_selection(self) -> None:
'@ac:cp: Paste from the clipboard to the active window'
text = self.current_primary_selection_or_clipboard() text = self.current_primary_selection_or_clipboard()
self.paste_to_active_window(text) self.paste_to_active_window(text)
@@ -1248,12 +1251,12 @@ class Boss:
if get_options().copy_on_select: if get_options().copy_on_select:
self.copy_to_buffer(get_options().copy_on_select) self.copy_to_buffer(get_options().copy_on_select)
def copy_to_buffer(self, buffer_name: str) -> None: @ac('cp', '''
''' Copy the selection from the active window to the specified buffer
@ac:cp: Copy the selection from the active window to the specified buffer
See :ref:`cpbuf` for details. See :ref:`cpbuf` for details.
''' ''')
def copy_to_buffer(self, buffer_name: str) -> None:
w = self.active_window w = self.active_window
if w is not None and not w.destroyed: if w is not None and not w.destroyed:
text = w.text_for_selection() text = w.text_for_selection()
@@ -1265,12 +1268,12 @@ class Boss:
else: else:
self.clipboard_buffers[buffer_name] = text self.clipboard_buffers[buffer_name] = text
def paste_from_buffer(self, buffer_name: str) -> None: @ac('cp', '''
''' Paste from the specified buffer to the active window
@ac:cp: Paste from the specified buffer to the active window
See :ref:`cpbuf` for details. See :ref:`cpbuf` for details.
''' ''')
def paste_from_buffer(self, buffer_name: str) -> None:
if buffer_name == 'clipboard': if buffer_name == 'clipboard':
text: Optional[str] = get_clipboard_string() text: Optional[str] = get_clipboard_string()
elif buffer_name == 'primary': elif buffer_name == 'primary':
@@ -1280,12 +1283,12 @@ class Boss:
if text: if text:
self.paste_to_active_window(text) self.paste_to_active_window(text)
def goto_tab(self, tab_num: int) -> None: @ac('tab', '''
''' Go to the specified tab, by number, starting with 1
@ac:tab: Go to the specified tab, by number, starting with 1
Zero and negative numbers go to previously active tabs Zero and negative numbers go to previously active tabs
''' ''')
def goto_tab(self, tab_num: int) -> None:
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.goto_tab(tab_num - 1) tm.goto_tab(tab_num - 1)
@@ -1296,14 +1299,14 @@ class Boss:
return tm.set_active_tab(tab) return tm.set_active_tab(tab)
return False return False
@ac('tab', 'Make the next tab active')
def next_tab(self) -> None: def next_tab(self) -> None:
'@ac:tab: Make the next tab active'
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.next_tab() tm.next_tab()
@ac('tab', 'Make the previous tab active')
def previous_tab(self) -> None: def previous_tab(self) -> None:
'@ac:tab: Make the previous tab active'
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.next_tab(-1) tm.next_tab(-1)
@@ -1462,12 +1465,12 @@ class Boss:
args = args[1:] args = args[1:]
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from) self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
@ac('tab', 'Create a new tab')
def new_tab(self, *args: str) -> None: def new_tab(self, *args: str) -> None:
'@ac:tab: Create a new tab'
self._create_tab(list(args)) self._create_tab(list(args))
@ac('tab', 'Create a new tab with working directory for the window in it set to the same as the active window')
def new_tab_with_cwd(self, *args: str) -> None: def new_tab_with_cwd(self, *args: str) -> None:
'@ac:tab: Create a new tab with working directory for the window in it set to the same as the active window'
w = self.active_window_for_cwd w = self.active_window_for_cwd
cwd_from = w.child.pid_for_cwd if w is not None else None cwd_from = w.child.pid_for_cwd if w is not None else None
self._create_tab(list(args), cwd_from=cwd_from) self._create_tab(list(args), cwd_from=cwd_from)
@@ -1494,46 +1497,46 @@ class Boss:
else: else:
return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control) return tab.new_window(cwd_from=cwd_from, location=location, allow_remote_control=allow_remote_control)
@ac('win', 'Create a new window')
def new_window(self, *args: str) -> None: def new_window(self, *args: str) -> None:
'@ac:win: Create a new window'
self._new_window(list(args)) self._new_window(list(args))
@ac('win', 'Create a new window with working directory same as that of the active window')
def new_window_with_cwd(self, *args: str) -> None: def new_window_with_cwd(self, *args: str) -> None:
'@ac:win: Create a new window with working directory same as that of the active window'
w = self.active_window_for_cwd w = self.active_window_for_cwd
if w is None: if w is None:
return self.new_window(*args) return self.new_window(*args)
cwd_from = w.child.pid_for_cwd cwd_from = w.child.pid_for_cwd
self._new_window(list(args), cwd_from=cwd_from) self._new_window(list(args), cwd_from=cwd_from)
def launch(self, *args: str) -> None: @ac('misc', '''
''' Launch the specified program in a new window/tab/etc.
@ac:misc: Launch the specified program in a new window/tab/etc.
See :doc:`launch` for details See :doc:`launch` for details
''' ''')
def launch(self, *args: str) -> None:
from kitty.launch import launch, parse_launch_args from kitty.launch import launch, parse_launch_args
opts, args_ = parse_launch_args(args) opts, args_ = parse_launch_args(args)
launch(self, opts, args_) launch(self, opts, args_)
@ac('tab', 'Move the active tab forward')
def move_tab_forward(self) -> None: def move_tab_forward(self) -> None:
'@ac:tab: Move the active tab forward'
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.move_tab(1) tm.move_tab(1)
@ac('tab', 'Move the active tab backward')
def move_tab_backward(self) -> None: def move_tab_backward(self) -> None:
'@ac:tab: Move the active tab backward'
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.move_tab(-1) tm.move_tab(-1)
def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None: @ac('misc', '''
''' Turn on/off ligatures in the specified window
@ac:misc: Turn on/off ligatures in the specified window
See :opt:`disable_ligatures` for details See :opt:`disable_ligatures` for details
''' ''')
def disable_ligatures_in(self, where: Union[str, Iterable[Window]], strategy: int) -> None:
if isinstance(where, str): if isinstance(where, str):
windows: List[Window] = [] windows: List[Window] = []
if where == 'active': if where == 'active':
@@ -1590,16 +1593,16 @@ class Boss:
self.default_bg_changed_for(w.id) self.default_bg_changed_for(w.id)
w.refresh() w.refresh()
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None: @ac('misc', '''
''' Reload the config file
@ac:misc: Reload the config file
If mapped without arguments reloads the default config file, otherwise loads If mapped without arguments reloads the default config file, otherwise loads
the specified config files, in order. Loading a config file *replaces* all the specified config files, in order. Loading a config file *replaces* all
config options. For example:: config options. For example::
map f5 load_config_file /path/to/some/kitty.conf map f5 load_config_file /path/to/some/kitty.conf
''' ''')
def load_config_file(self, *paths: str, apply_overrides: bool = True) -> None:
from .config import load_config from .config import load_config
old_opts = get_options() old_opts = get_options()
paths = paths or old_opts.config_paths paths = paths or old_opts.config_paths
@@ -1656,14 +1659,14 @@ class Boss:
msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip() msg = '\n'.join(map(format_bad_line, bad_lines)).rstrip()
self.show_error(_('Errors in kitty.conf'), msg) self.show_error(_('Errors in kitty.conf'), msg)
def set_colors(self, *args: str) -> None: @ac('misc', '''
''' Change colors in the specified windows
@ac:misc: Change colors in the specified windows
For details, see :ref:`at_set-colors`. For example:: For details, see :ref:`at_set-colors`. For example::
map f5 set_colors --configured /path/to/some/config/file/colors.conf map f5 set_colors --configured /path/to/some/config/file/colors.conf
''' ''')
def set_colors(self, *args: str) -> None:
from kitty.rc.base import ( from kitty.rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli PayloadGetter, command_for_name, parse_subcommand_cli
) )
@@ -1728,8 +1731,8 @@ class Boss:
self._cleanup_tab_after_window_removal(tab) self._cleanup_tab_after_window_removal(tab)
target_tab.make_active() target_tab.make_active()
@ac('tab', 'Interactively select a tab to switch to')
def select_tab(self) -> None: def select_tab(self) -> None:
'@ac:tab: Interactively select a tab to switch to'
title = 'Choose a tab to switch to' title = 'Choose a tab to switch to'
lines = [title, ''] lines = [title, '']
fmt = ': {1}' fmt = ': {1}'
@@ -1761,12 +1764,12 @@ class Boss:
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2 ), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
) )
def detach_window(self, *args: str) -> None: @ac('win', '''
''' Detach a window, moving it to another tab or OS Window
@ac:win: Detach a window, moving it to another tab or OS Window
See :ref:`detaching windows <detach_window>` for details. See :ref:`detaching windows <detach_window>` for details.
''' ''')
def detach_window(self, *args: str) -> None:
if not args or args[0] == 'new': if not args or args[0] == 'new':
return self._move_window_to(target_os_window_id='new') return self._move_window_to(target_os_window_id='new')
if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right'): if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right'):
@@ -1817,12 +1820,12 @@ class Boss:
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2 ), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
) )
def detach_tab(self, *args: str) -> None: @ac('tab', '''
''' Detach a tab, moving it to another OS Window
@ac:tab: Detach a tab, moving it to another OS Window
See :ref:`detaching windows <detach_window>` for details. See :ref:`detaching windows <detach_window>` for details.
''' ''')
def detach_tab(self, *args: str) -> None:
if not args or args[0] == 'new': if not args or args[0] == 'new':
return self._move_tab_to() return self._move_tab_to()
@@ -1894,8 +1897,8 @@ class Boss:
if report: if report:
w.report_notification_activated(identifier) w.report_notification_activated(identifier)
@ac('misc', 'Show the environment variables that the kitty process sees')
def show_kitty_env_vars(self) -> None: def show_kitty_env_vars(self) -> None:
'@ac:misc: Show the environment variables that the kitty process sees'
w = self.active_window w = self.active_window
if w: if w:
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items()) output = '\n'.join(f'{k}={v}' for k, v in os.environ.items())
@@ -1919,8 +1922,8 @@ class Boss:
if w is not None: if w is not None:
tab.remove_window(w) tab.remove_window(w)
@ac('misc', 'Show the effective configuration kitty is running with')
def debug_config(self) -> None: def debug_config(self) -> None:
'@ac:misc: Show the effective configuration kitty is running with'
from .debug_config import debug_config from .debug_config import debug_config
w = self.active_window w = self.active_window
if w is not None: if w is not None:
@@ -1929,7 +1932,7 @@ class Boss:
output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m' output += '\n\x1b[35mThis debug output has been copied to the clipboard\x1b[m'
self.display_scrollback(w, output, title=_('Current kitty options')) self.display_scrollback(w, output, title=_('Current kitty options'))
@ac('misc', 'Discard this event completely ignoring it')
def discard_event(self) -> None: def discard_event(self) -> None:
'@ac:misc: Discard this event completely ignoring it'
pass pass
mouse_discard_event = discard_event mouse_discard_event = discard_event

View File

@@ -111,7 +111,7 @@ static size_t reaped_pids_count = 0;
// before ticking over the main loop. Negative values mean wait forever. // before ticking over the main loop. Negative values mean wait forever.
static monotonic_t maximum_wait = -1; static monotonic_t maximum_wait = -1;
static inline void static void
set_maximum_wait(monotonic_t val) { set_maximum_wait(monotonic_t val) {
if (val >= 0 && (val < maximum_wait || maximum_wait < 0)) maximum_wait = val; if (val >= 0 && (val < maximum_wait || maximum_wait < 0)) maximum_wait = val;
} }
@@ -307,7 +307,7 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline bool static bool
do_parse(ChildMonitor *self, Screen *screen, monotonic_t now) { do_parse(ChildMonitor *self, Screen *screen, monotonic_t now) {
bool input_read = false; bool input_read = false;
screen_mutex(lock, read); screen_mutex(lock, read);
@@ -414,7 +414,7 @@ parse_input(ChildMonitor *self) {
return input_read; return input_read;
} }
static inline void static void
mark_child_for_close(ChildMonitor *self, id_type window_id) { mark_child_for_close(ChildMonitor *self, id_type window_id) {
children_mutex(lock); children_mutex(lock);
for (size_t i = 0; i < self->count; i++) { for (size_t i = 0; i < self->count; i++) {
@@ -437,7 +437,7 @@ mark_for_close(ChildMonitor *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline bool static bool
pty_resize(int fd, struct winsize *dim) { pty_resize(int fd, struct winsize *dim) {
while(true) { while(true) {
if (ioctl(fd, TIOCSWINSZ, dim) == -1) { if (ioctl(fd, TIOCSWINSZ, dim) == -1) {
@@ -515,7 +515,7 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
extern void cocoa_update_menu_bar_title(PyObject*); extern void cocoa_update_menu_bar_title(PyObject*);
static inline void static void
collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) { collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow *os_window) {
ScreenRenderData *rd = &w->render_data; ScreenRenderData *rd = &w->render_data;
Cursor *cursor = rd->screen->cursor; Cursor *cursor = rd->screen->cursor;
@@ -542,7 +542,7 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, monotonic_t now, OSWindow
ans->is_focused = os_window->is_focused; ans->is_focused = os_window->is_focused;
} }
static inline void static void
change_menubar_title(PyObject *title UNUSED) { change_menubar_title(PyObject *title UNUSED) {
#ifdef __APPLE__ #ifdef __APPLE__
static PyObject *current_title = NULL; static PyObject *current_title = NULL;
@@ -553,7 +553,7 @@ change_menubar_title(PyObject *title UNUSED) {
#endif #endif
} }
static inline bool static bool
prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) { prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows, bool *all_windows_have_same_bg, bool scan_for_animated_images) {
#define TD os_window->tab_bar_render_data #define TD os_window->tab_bar_render_data
bool needs_render = os_window->needs_render; bool needs_render = os_window->needs_render;
@@ -614,8 +614,8 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int *
return needs_render; return needs_render;
} }
static inline void static void
render_os_window(OSWindow *os_window, monotonic_t now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) { render_os_window(OSWindow *os_window, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) {
// ensure all pixels are cleared to background color at least once in every buffer // ensure all pixels are cleared to background color at least once in every buffer
if (os_window->clear_count++ < 3) blank_os_window(os_window); if (os_window->clear_count++ < 3) blank_os_window(os_window);
Tab *tab = os_window->tabs + os_window->active_tab; Tab *tab = os_window->tabs + os_window->active_tab;
@@ -637,8 +637,7 @@ render_os_window(OSWindow *os_window, monotonic_t now, unsigned int active_windo
bool is_active_window = i == tab->active_window; bool is_active_window = i == tab->active_window;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx * x_ratio, WD.dy * y_ratio, WD.screen, os_window, is_active_window, true); draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx * x_ratio, WD.dy * y_ratio, WD.screen, os_window, is_active_window, true);
if (WD.screen->start_visual_bell_at != 0) { if (WD.screen->start_visual_bell_at != 0) {
monotonic_t bell_left = OPT(visual_bell_duration) - (now - WD.screen->start_visual_bell_at); set_maximum_wait(OPT(repaint_delay));
set_maximum_wait(bell_left);
} }
w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_x = WD.screen->cursor_render_info.x; w->last_cursor_y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape; w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_x = WD.screen->cursor_render_info.x; w->last_cursor_y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape;
} }
@@ -664,7 +663,7 @@ draw_resizing_text(OSWindow *w) {
} }
} }
static inline bool static bool
no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_wait) { no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_wait) {
bool ans = now - w->last_render_frame_received_at > max_wait; bool ans = now - w->last_render_frame_received_at > max_wait;
if (ans && global_state.debug_rendering) { if (ans && global_state.debug_rendering) {
@@ -677,7 +676,7 @@ no_render_frame_received_recently(OSWindow *w, monotonic_t now, monotonic_t max_
return ans; return ans;
} }
static inline void static void
render(monotonic_t now, bool input_read) { render(monotonic_t now, bool input_read) {
EVDBG("input_read: %d, check_for_active_animated_images: %d", input_read, global_state.check_for_active_animated_images); EVDBG("input_read: %d, check_for_active_animated_images: %d", input_read, global_state.check_for_active_animated_images);
static monotonic_t last_render_at = MONOTONIC_T_MIN; static monotonic_t last_render_at = MONOTONIC_T_MIN;
@@ -726,7 +725,7 @@ render(monotonic_t now, bool input_read) {
if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg, scan_for_animated_images)) needs_render = true; if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows, &all_windows_have_same_bg, scan_for_animated_images)) needs_render = true;
if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true; if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true;
if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true; if (w->render_calls < 3 && w->bgimage && w->bgimage->texture_id) needs_render = true;
if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg); if (needs_render) render_os_window(w, active_window_id, active_window_bg, num_visible_windows, all_windows_have_same_bg);
if (w->is_focused) change_menubar_title(w->window_title); if (w->is_focused) change_menubar_title(w->window_title);
} }
last_render_at = now; last_render_at = now;
@@ -736,7 +735,7 @@ render(monotonic_t now, bool input_read) {
typedef struct { int fd; uint8_t *buf; size_t sz; } ThreadWriteData; typedef struct { int fd; uint8_t *buf; size_t sz; } ThreadWriteData;
static inline ThreadWriteData* static ThreadWriteData*
alloc_twd(size_t sz) { alloc_twd(size_t sz) {
ThreadWriteData *data = calloc(1, sizeof(ThreadWriteData)); ThreadWriteData *data = calloc(1, sizeof(ThreadWriteData));
if (data != NULL) { if (data != NULL) {
@@ -747,7 +746,7 @@ alloc_twd(size_t sz) {
return data; return data;
} }
static inline void static void
free_twd(ThreadWriteData *x) { free_twd(ThreadWriteData *x) {
if (x != NULL) free(x->buf); if (x != NULL) free(x->buf);
free(x); free(x);
@@ -770,7 +769,7 @@ monitor_pid(PyObject *self UNUSED, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline void static void
report_reaped_pids(void) { report_reaped_pids(void) {
children_mutex(lock); children_mutex(lock);
if (reaped_pids_count) { if (reaped_pids_count) {
@@ -860,7 +859,7 @@ remove_python_timer(PyObject *self UNUSED, PyObject *args) {
} }
static inline void static void
process_pending_resizes(monotonic_t now) { process_pending_resizes(monotonic_t now) {
global_state.has_pending_resizes = false; global_state.has_pending_resizes = false;
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
@@ -890,7 +889,7 @@ process_pending_resizes(monotonic_t now) {
} }
} }
static inline void static void
close_os_window(ChildMonitor *self, OSWindow *os_window) { close_os_window(ChildMonitor *self, OSWindow *os_window) {
destroy_os_window(os_window); destroy_os_window(os_window);
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height); call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
@@ -901,7 +900,7 @@ close_os_window(ChildMonitor *self, OSWindow *os_window) {
remove_os_window(os_window->id); remove_os_window(os_window->id);
} }
static inline bool static bool
process_pending_closes(ChildMonitor *self) { process_pending_closes(ChildMonitor *self) {
if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) { if (global_state.quit_request == CONFIRMABLE_CLOSE_REQUESTED) {
call_boss(quit, ""); call_boss(quit, "");
@@ -1075,7 +1074,7 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
// I/O thread functions {{{ // I/O thread functions {{{
static inline void static void
add_children(ChildMonitor *self) { add_children(ChildMonitor *self) {
for (; add_queue_count > 0 && self->count < MAX_CHILDREN;) { for (; add_queue_count > 0 && self->count < MAX_CHILDREN;) {
add_queue_count--; add_queue_count--;
@@ -1088,7 +1087,7 @@ add_children(ChildMonitor *self) {
} }
static inline void static void
hangup(pid_t pid) { hangup(pid_t pid) {
errno = 0; errno = 0;
pid_t pgid = getpgid(pid); pid_t pgid = getpgid(pid);
@@ -1100,14 +1099,14 @@ hangup(pid_t pid) {
} }
static inline void static void
cleanup_child(ssize_t i) { cleanup_child(ssize_t i) {
safe_close(children[i].fd, __FILE__, __LINE__); safe_close(children[i].fd, __FILE__, __LINE__);
hangup(children[i].pid); hangup(children[i].pid);
} }
static inline void static void
remove_children(ChildMonitor *self) { remove_children(ChildMonitor *self) {
if (self->count > 0) { if (self->count > 0) {
size_t count = 0; size_t count = 0;
@@ -1186,7 +1185,7 @@ handle_signal(int signum, void *data) {
} }
} }
static inline void static void
mark_child_for_removal(ChildMonitor *self, pid_t pid) { mark_child_for_removal(ChildMonitor *self, pid_t pid) {
children_mutex(lock); children_mutex(lock);
for (size_t i = 0; i < self->count; i++) { for (size_t i = 0; i < self->count; i++) {
@@ -1198,7 +1197,7 @@ mark_child_for_removal(ChildMonitor *self, pid_t pid) {
children_mutex(unlock); children_mutex(unlock);
} }
static inline void static void
mark_monitored_pids(pid_t pid, int status) { mark_monitored_pids(pid_t pid, int status) {
children_mutex(lock); children_mutex(lock);
for (ssize_t i = monitored_pids_count - 1; i >= 0; i--) { for (ssize_t i = monitored_pids_count - 1; i >= 0; i--) {
@@ -1213,7 +1212,7 @@ mark_monitored_pids(pid_t pid, int status) {
children_mutex(unlock); children_mutex(unlock);
} }
static inline void static void
reap_children(ChildMonitor *self, bool enable_close_on_child_death) { reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
int status; int status;
pid_t pid; pid_t pid;
@@ -1229,7 +1228,7 @@ reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
} }
} }
static inline void static void
write_to_child(int fd, Screen *screen) { write_to_child(int fd, Screen *screen) {
size_t written = 0; size_t written = 0;
ssize_t ret = 0; ssize_t ret = 0;
@@ -1387,7 +1386,7 @@ typedef struct pollfd PollFD;
#define PEER_LIMIT 256 #define PEER_LIMIT 256
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); } #define nuke_socket(s) { shutdown(s, SHUT_RDWR); safe_close(s, __FILE__, __LINE__); }
static inline bool static bool
accept_peer(int listen_fd, bool shutting_down) { accept_peer(int listen_fd, bool shutting_down) {
int peer = accept(listen_fd, NULL, NULL); int peer = accept(listen_fd, NULL, NULL);
if (UNLIKELY(peer == -1)) { if (UNLIKELY(peer == -1)) {
@@ -1408,7 +1407,7 @@ accept_peer(int listen_fd, bool shutting_down) {
return true; return true;
} }
static inline void static void
free_peer(Peer *peer) { free_peer(Peer *peer) {
free(peer->read.data); peer->read.data = NULL; free(peer->read.data); peer->read.data = NULL;
free(peer->write.data); peer->write.data = NULL; free(peer->write.data); peer->write.data = NULL;
@@ -1417,7 +1416,7 @@ free_peer(Peer *peer) {
#define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{" #define KITTY_CMD_PREFIX "\x1bP@kitty-cmd{"
static inline void static void
queue_peer_message(ChildMonitor *self, Peer *peer) { queue_peer_message(ChildMonitor *self, Peer *peer) {
talk_mutex(lock); talk_mutex(lock);
ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true); ensure_space_for(self, messages, Message, self->messages_count + 16, messages_capacity, 16, true);
@@ -1436,7 +1435,7 @@ queue_peer_message(ChildMonitor *self, Peer *peer) {
wakeup_main_loop(); wakeup_main_loop();
} }
static inline bool static bool
has_complete_peer_command(Peer *peer) { has_complete_peer_command(Peer *peer) {
peer->read.command_end = 0; peer->read.command_end = 0;
if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) { if (peer->read.used > sizeof(KITTY_CMD_PREFIX) && memcmp(peer->read.data, KITTY_CMD_PREFIX, sizeof(KITTY_CMD_PREFIX)-1) == 0) {
@@ -1451,7 +1450,7 @@ has_complete_peer_command(Peer *peer) {
} }
static inline void static void
dispatch_peer_command(ChildMonitor *self, Peer *peer) { dispatch_peer_command(ChildMonitor *self, Peer *peer) {
if (peer->read.command_end) { if (peer->read.command_end) {
size_t used = peer->read.used; size_t used = peer->read.used;
@@ -1466,7 +1465,7 @@ dispatch_peer_command(ChildMonitor *self, Peer *peer) {
} }
} }
static inline void static void
read_from_peer(ChildMonitor *self, Peer *peer) { read_from_peer(ChildMonitor *self, Peer *peer) {
#define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; } #define failed(msg) { log_error("Reading from peer failed: %s", msg); shutdown(peer->fd, SHUT_RD); peer->read.finished = true; return; }
if (peer->read.used >= peer->read.capacity) { if (peer->read.used >= peer->read.capacity) {
@@ -1492,7 +1491,7 @@ read_from_peer(ChildMonitor *self, Peer *peer) {
#undef failed #undef failed
} }
static inline void static void
write_to_peer(Peer *peer) { write_to_peer(Peer *peer) {
talk_mutex(lock); talk_mutex(lock);
ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL); ssize_t n = send(peer->fd, peer->write.data, peer->write.used, MSG_NOSIGNAL);
@@ -1512,7 +1511,7 @@ wakeup_talk_loop(bool in_signal_handler) {
} }
static inline void static void
prune_peers(void) { prune_peers(void) {
for (size_t i = 0; i < talk_data.num_peers; i++) { for (size_t i = 0; i < talk_data.num_peers; i++) {
size_t idx = talk_data.num_peers - 1 - i; size_t idx = talk_data.num_peers - 1 - i;

View File

@@ -16,7 +16,7 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <termios.h> #include <termios.h>
static inline char** static char**
serialize_string_tuple(PyObject *src) { serialize_string_tuple(PyObject *src) {
Py_ssize_t sz = PyTuple_GET_SIZE(src); Py_ssize_t sz = PyTuple_GET_SIZE(src);
@@ -32,7 +32,7 @@ serialize_string_tuple(PyObject *src) {
return ans; return ans;
} }
static inline void static void
free_string_tuple(char** data) { free_string_tuple(char** data) {
size_t i = 0; size_t i = 0;
while(data[i]) free(data[i++]); while(data[i]) free(data[i++]);
@@ -41,7 +41,7 @@ free_string_tuple(char** data) {
extern char **environ; extern char **environ;
static inline void static void
write_to_stderr(const char *text) { write_to_stderr(const char *text) {
size_t sz = strlen(text); size_t sz = strlen(text);
size_t written = 0; size_t written = 0;
@@ -58,7 +58,7 @@ write_to_stderr(const char *text) {
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); } #define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
static inline void static void
wait_for_terminal_ready(int fd) { wait_for_terminal_ready(int fd) {
char data; char data;
while(1) { while(1) {

View File

@@ -14,6 +14,7 @@ from typing import (
import kitty.fast_data_types as fast_data_types import kitty.fast_data_types as fast_data_types
from .constants import is_macos, shell_path, terminfo_dir from .constants import is_macos, shell_path, terminfo_dir
from .types import run_once
try: try:
from typing import TypedDict from typing import TypedDict
@@ -23,8 +24,9 @@ except ImportError:
if is_macos: if is_macos:
from kitty.fast_data_types import ( from kitty.fast_data_types import (
cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process, cmdline_of_process, cwd_of_process as _cwd,
process_group_map as _process_group_map environ_of_process as _environ_of_process,
process_group_map as _process_group_map
) )
def cwd_of_process(pid: int) -> str: def cwd_of_process(pid: int) -> str:
@@ -170,6 +172,11 @@ def openpty() -> Tuple[int, int]:
return master, slave return master, slave
@run_once
def getpid() -> str:
return str(os.getpid())
class ProcessDesc(TypedDict): class ProcessDesc(TypedDict):
cwd: Optional[str] cwd: Optional[str]
pid: int pid: int
@@ -216,6 +223,7 @@ class Child:
env.update(self.env) env.update(self.env)
env['TERM'] = fast_data_types.get_options().term env['TERM'] = fast_data_types.get_options().term
env['COLORTERM'] = 'truecolor' env['COLORTERM'] = 'truecolor'
env['KITTY_PID'] = getpid()
if self.cwd: if self.cwd:
# needed in case cwd is a symlink, in which case shells # needed in case cwd is a symlink, in which case shells
# can use it to display the current directory name rather # can use it to display the current directory name rather

View File

@@ -596,10 +596,8 @@ Set the name part of the :italic:`WM_CLASS` property (defaults to using the valu
--title -T --title -T
Set the window title. This will override any title set by the program running inside kitty. So Set the OS window title. This will override any title set by the program running inside kitty, permanently
only use this if you are running a program that does not set titles. If combined fixing the OS Window's title. So only use this if you are running a program that does not set titles.
with :option:`{appname} --session` the title will be used for all windows created by the
session, that do not set their own titles.
--config -c --config -c

View File

@@ -14,6 +14,7 @@ LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOpt
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions QueryTerminalCLIOptions = BroadcastCLIOptions = ShowKeyCLIOptions = CLIOptions
ThemesCLIOptions = CLIOptions
def generate_stub() -> None: def generate_stub() -> None:
@@ -72,6 +73,9 @@ def generate_stub() -> None:
from kittens.unicode_input.main import OPTIONS from kittens.unicode_input.main import OPTIONS
do(OPTIONS(), 'UnicodeCLIOptions') do(OPTIONS(), 'UnicodeCLIOptions')
from kittens.themes.main import OPTIONS
do(OPTIONS(), 'ThemesCLIOptions')
from kitty.rc.base import all_command_names, command_for_name from kitty.rc.base import all_command_names, command_for_name
for cmd_name in all_command_names(): for cmd_name in all_command_names():
cmd = command_for_name(cmd_name) cmd = command_for_name(cmd_name)

View File

@@ -18,7 +18,6 @@
// Needed for _NSGetProgname // Needed for _NSGetProgname
#include <crt_externs.h> #include <crt_externs.h>
#include <objc/runtime.h> #include <objc/runtime.h>
#include <xlocale.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200) #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask #define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -574,12 +573,7 @@ cocoa_get_lang(PyObject UNUSED *self) {
locale = [[NSLocale currentLocale] localeIdentifier]; locale = [[NSLocale currentLocale] localeIdentifier];
} }
if (!locale) { Py_RETURN_NONE; } if (!locale) { Py_RETURN_NONE; }
// Make sure the locale value is valid, that is it can be used
// to construct an actual locale
const char* locale_utf8 = [locale UTF8String]; const char* locale_utf8 = [locale UTF8String];
locale_t test_locale = newlocale(LC_ALL_MASK, locale_utf8, NULL);
if (!test_locale) { Py_RETURN_NONE; }
freelocale(test_locale);
return Py_BuildValue("s", locale_utf8); return Py_BuildValue("s", locale_utf8);
} // autoreleasepool } // autoreleasepool

View File

@@ -30,7 +30,7 @@ static uint32_t FG_BG_256[256] = {
0xffffff, // 15 0xffffff, // 15
}; };
static inline void static void
init_FG_BG_table(void) { init_FG_BG_table(void) {
if (UNLIKELY(FG_BG_256[255] == 0)) { if (UNLIKELY(FG_BG_256[255] == 0)) {
// colors 16..232: the 6x6x6 color cube // colors 16..232: the 6x6x6 color cube
@@ -111,7 +111,7 @@ copy_color_profile(ColorProfile *dest, ColorProfile *src) {
dest->dirty = true; dest->dirty = true;
} }
static inline void static void
patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t which, int change_configured) { patch_color_table(const char *key, PyObject *profiles, PyObject *spec, size_t which, int change_configured) {
PyObject *v = PyDict_GetItemString(spec, key); PyObject *v = PyDict_GetItemString(spec, key);
if (v) { if (v) {

View File

@@ -338,15 +338,14 @@ def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix:
complete_files_and_dirs(ans, prefix, files_group_name='Watcher files') complete_files_and_dirs(ans, prefix, files_group_name='Watcher files')
elif dest == 'directory': elif dest == 'directory':
complete_files_and_dirs(ans, prefix, files_group_name='Directories', predicate=os.path.isdir) complete_files_and_dirs(ans, prefix, files_group_name='Directories', predicate=os.path.isdir)
elif dest == 'start_as':
k = 'Start as'
ans.add_match_group(k, {x: x for x in 'normal,fullscreen,maximized,minimized'.split(',') if x.startswith(prefix)}, trailing_space=False)
elif dest == 'listen_on': elif dest == 'listen_on':
if ':' not in prefix: if ':' not in prefix:
k = 'Address type' k = 'Address type'
ans.add_match_group(k, {x: x for x in ('unix:', 'tcp:') if x.startswith(prefix)}, trailing_space=False) ans.add_match_group(k, {x: x for x in ('unix:', 'tcp:') if x.startswith(prefix)}, trailing_space=False)
elif prefix.startswith('unix:') and not prefix.startswith('@'): elif prefix.startswith('unix:') and not prefix.startswith('@'):
complete_files_and_dirs(ans, prefix[len('unix:'):], files_group_name='UNIX sockets', add_prefix='unix:') complete_files_and_dirs(ans, prefix[len('unix:'):], files_group_name='UNIX sockets', add_prefix='unix:')
else:
complete_basic_option_args(ans, opt, prefix)
CompleteArgsFunc = Callable[[Completions, Optional[OptionDict], str, Delegate], None] CompleteArgsFunc = Callable[[Completions, Optional[OptionDict], str, Delegate], None]
@@ -474,6 +473,11 @@ def complete_files_and_dirs(
ans.add_match_group(files_group_name, files, is_files=True) ans.add_match_group(files_group_name, files, is_files=True)
def complete_basic_option_args(ans: Completions, opt: OptionDict, prefix: str) -> None:
if opt['choices']:
ans.add_match_group(f'Choices for {opt["dest"]}', tuple(k for k in opt['choices'] if k.startswith(prefix)))
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None: def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
from .guess_mime_type import guess_type from .guess_mime_type import guess_type
@@ -485,6 +489,18 @@ def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str,
if opt is None: if opt is None:
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate) complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
else:
complete_basic_option_args(ans, opt, prefix)
def complete_themes_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
if opt is None:
from kittens.themes.collection import load_themes
themes = load_themes(cache_age=-1, ignore_no_cache=True)
names = tuple(t.name for t in themes if t.name.startswith(prefix))
ans.add_match_group('Themes', names)
else:
complete_basic_option_args(ans, opt, prefix)
def remote_files_completer(name: str, matchers: Tuple[str, ...]) -> CompleteArgsFunc: def remote_files_completer(name: str, matchers: Tuple[str, ...]) -> CompleteArgsFunc:
@@ -500,6 +516,8 @@ def remote_files_completer(name: str, matchers: Tuple[str, ...]) -> CompleteArgs
if opt is None: if opt is None:
complete_files_and_dirs(ans, prefix, name, predicate) complete_files_and_dirs(ans, prefix, name, predicate)
else:
complete_basic_option_args(ans, opt, prefix)
return complete_files_map return complete_files_map
@@ -509,6 +527,8 @@ def remote_args_completer(title: str, words: Iterable[str]) -> CompleteArgsFunc:
def complete_names_for_arg(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None: def complete_names_for_arg(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
if opt is None: if opt is None:
ans.add_match_group(title, {c: '' for c in items if c.startswith(prefix)}) ans.add_match_group(title, {c: '' for c in items if c.startswith(prefix)})
else:
complete_basic_option_args(ans, opt, prefix)
return complete_names_for_arg return complete_names_for_arg
@@ -522,6 +542,8 @@ def complete_diff_args(ans: Completions, opt: Optional[OptionDict], prefix: str,
complete_files_and_dirs(ans, prefix, 'Files') complete_files_and_dirs(ans, prefix, 'Files')
elif opt['dest'] == 'config': elif opt['dest'] == 'config':
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate) complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
else:
complete_basic_option_args(ans, opt, prefix)
def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_word: bool) -> None: def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_word: bool) -> None:
@@ -548,6 +570,7 @@ def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_wor
complete_alias_map(ans, words, new_word, option_map, { complete_alias_map(ans, words, new_word, option_map, {
'icat': complete_icat_args, 'icat': complete_icat_args,
'diff': complete_diff_args, 'diff': complete_diff_args,
'themes': complete_themes_args,
}.get(kitten)) }.get(kitten))

View File

@@ -185,8 +185,11 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
if defn.has_color_table: if defn.has_color_table:
a(' self.color_table = array(self.color_table.typecode, self.color_table)') a(' self.color_table = array(self.color_table.typecode, self.color_table)')
a(' if options_dict is not None:') a(' if options_dict is not None:')
a(' null = object()')
a(' for key in option_names:') a(' for key in option_names:')
a(' setattr(self, key, options_dict[key])') a(' val = options_dict.get(key, null)')
a(' if val is not null:')
a(' setattr(self, key, val)')
a('') a('')
a(' @property') a(' @property')

View File

@@ -44,8 +44,9 @@ def expand_opt_references(conf_name: str, text: str) -> str:
def remove_markup(text: str) -> str: def remove_markup(text: str) -> str:
ref_map = { ref_map = {
'layouts': f'{website_url("overview")}#layouts', 'layouts': f'{website_url("overview")}#layouts',
'sessions': f'{website_url("overview")}#layouts', 'sessions': f'{website_url("overview")}#startup-sessions',
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions', 'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
'action-select_tab': f'{website_url("actions")}#select-tab',
} }
def sub(m: Match) -> str: def sub(m: Match) -> str:

View File

@@ -35,12 +35,17 @@ def build_ansi_color_table(opts: Optional[Options] = None) -> int:
def atomic_save(data: bytes, path: str) -> None: def atomic_save(data: bytes, path: str) -> None:
import shutil
import tempfile import tempfile
path = os.path.realpath(path)
fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp') fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
try: try:
with os.fdopen(fd, 'wb') as f: with os.fdopen(fd, 'wb') as f:
f.write(data) f.write(data)
os.rename(p, path) with suppress(FileNotFoundError):
shutil.copystat(path, p)
os.utime(p)
os.replace(p, path)
finally: finally:
try: try:
os.remove(p) os.remove(p)
@@ -123,9 +128,12 @@ def finalize_keys(opts: Options) -> None:
def finalize_mouse_mappings(opts: Options) -> None: def finalize_mouse_mappings(opts: Options) -> None:
defns: List[MouseMapping] = [] defns: List[MouseMapping] = []
for d in opts.mouse_map: for d in opts.mouse_map:
defns.append(d.resolve_and_copy(opts.kitty_mod, opts.kitten_alias)) if d is None: # clear_all_mouse_actions
defns = [] # type: ignore
else:
defns.append(d.resolve_and_copy(opts.kitty_mod, opts.kitten_alias))
mousemap: MouseMap = {} mousemap: MouseMap = {}
for defn in defns: for defn in defns:
is_no_op = defn.action.func in no_op_actions is_no_op = defn.action.func in no_op_actions
if is_no_op: if is_no_op:

View File

@@ -23,7 +23,7 @@ class Version(NamedTuple):
appname: str = 'kitty' appname: str = 'kitty'
kitty_face = '🐱' kitty_face = '🐱'
version: Version = Version(0, 22, 2) version: Version = Version(0, 23, 0)
str_version: str = '.'.join(map(str, version)) str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat is_macos: bool = 'darwin' in _plat

View File

@@ -34,7 +34,7 @@ typedef struct {
} CTFace; } CTFace;
PyTypeObject CTFace_Type; PyTypeObject CTFace_Type;
static inline char* static char*
convert_cfstring(CFStringRef src, int free_src) { convert_cfstring(CFStringRef src, int free_src) {
#define SZ 4094 #define SZ 4094
static char buf[SZ+2] = {0}; static char buf[SZ+2] = {0};
@@ -46,7 +46,7 @@ convert_cfstring(CFStringRef src, int free_src) {
#undef SZ #undef SZ
} }
static inline void static void
init_face(CTFace *self, CTFontRef font, FONTS_DATA_HANDLE fg UNUSED) { init_face(CTFace *self, CTFontRef font, FONTS_DATA_HANDLE fg UNUSED) {
if (self->hb_font) hb_font_destroy(self->hb_font); if (self->hb_font) hb_font_destroy(self->hb_font);
self->hb_font = NULL; self->hb_font = NULL;
@@ -61,7 +61,7 @@ init_face(CTFace *self, CTFontRef font, FONTS_DATA_HANDLE fg UNUSED) {
self->scaled_point_sz = CTFontGetSize(self->ct_font); self->scaled_point_sz = CTFontGetSize(self->ct_font);
} }
static inline CTFace* static CTFace*
ct_face(CTFontRef font, FONTS_DATA_HANDLE fg) { ct_face(CTFontRef font, FONTS_DATA_HANDLE fg) {
CTFace *self = (CTFace *)CTFace_Type.tp_alloc(&CTFace_Type, 0); CTFace *self = (CTFace *)CTFace_Type.tp_alloc(&CTFace_Type, 0);
if (self) { if (self) {
@@ -174,7 +174,7 @@ coretext_all_fonts(PyObject UNUSED *_self) {
return ans; return ans;
} }
static inline unsigned int static unsigned int
glyph_id_for_codepoint_ctfont(CTFontRef ct_font, char_type ch) { glyph_id_for_codepoint_ctfont(CTFontRef ct_font, char_type ch) {
unichar chars[2] = {0}; unichar chars[2] = {0};
CGGlyph glyphs[2] = {0}; CGGlyph glyphs[2] = {0};
@@ -183,7 +183,7 @@ glyph_id_for_codepoint_ctfont(CTFontRef ct_font, char_type ch) {
return glyphs[0]; return glyphs[0];
} }
static inline bool static bool
is_last_resort_font(CTFontRef new_font) { is_last_resort_font(CTFontRef new_font) {
CFStringRef name = CTFontCopyPostScriptName(new_font); CFStringRef name = CTFontCopyPostScriptName(new_font);
CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0); CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0);
@@ -191,7 +191,7 @@ is_last_resort_font(CTFontRef new_font) {
return cr == kCFCompareEqualTo; return cr == kCFCompareEqualTo;
} }
static inline CTFontRef static CTFontRef
manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) { manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
CFArrayRef fonts = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection()); CFArrayRef fonts = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
CTFontRef ans = NULL; CTFontRef ans = NULL;
@@ -220,7 +220,7 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
return ans; return ans;
} }
static inline CTFontRef static CTFontRef
find_substitute_face(CFStringRef str, CTFontRef old_font, CPUCell *cpu_cell) { find_substitute_face(CFStringRef str, CTFontRef old_font, CPUCell *cpu_cell) {
// CTFontCreateForString returns the original font when there are combining // CTFontCreateForString returns the original font when there are combining
// diacritics in the font and the base character is in the original font, // diacritics in the font and the base character is in the original font,
@@ -296,7 +296,7 @@ get_glyph_width(PyObject *s, glyph_index g) {
return (int)ceil(bounds.size.width); return (int)ceil(bounds.size.width);
} }
static inline float static float
scaled_point_sz(FONTS_DATA_HANDLE fg) { scaled_point_sz(FONTS_DATA_HANDLE fg) {
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts; return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
} }
@@ -448,7 +448,7 @@ finalize(void) {
} }
static inline void static void
render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int width, unsigned int height, unsigned int baseline) { render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int width, unsigned int height, unsigned int baseline) {
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
if (color_space == NULL) fatal("Out of memory"); if (color_space == NULL) fatal("Out of memory");
@@ -492,7 +492,7 @@ ensure_render_space(size_t width, size_t height, size_t num_glyphs) {
} }
} }
static inline void static void
render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned int baseline, unsigned int num_glyphs) { render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned int baseline, unsigned int num_glyphs) {
memset(buffers.render_buf, 0, width * height); memset(buffers.render_buf, 0, width * height);
CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray(); CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray();
@@ -538,7 +538,7 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) {
} }
static inline bool 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, 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; unsigned int canvas_width = cell_width * num_cells;
ensure_render_space(canvas_width, cell_height, num_glyphs); ensure_render_space(canvas_width, cell_height, num_glyphs);

View File

@@ -46,7 +46,7 @@ cursor_reset_display_attrs(Cursor *self) {
} }
static inline void static void
parse_color(int *params, unsigned int *i, unsigned int count, uint32_t *result) { parse_color(int *params, unsigned int *i, unsigned int count, uint32_t *result) {
unsigned int attr; unsigned int attr;
uint8_t r, g, b; uint8_t r, g, b;

View File

@@ -11,6 +11,7 @@
#include <unistd.h> #include <unistd.h>
#undef _DARWIN_C_SOURCE #undef _DARWIN_C_SOURCE
#endif #endif
#include "data-types.h" #include "data-types.h"
#include "cleanup.h" #include "cleanup.h"
#include "safe-wrappers.h" #include "safe-wrappers.h"
@@ -23,13 +24,17 @@
#include <signal.h> #include <signal.h>
#include <fcntl.h> #include <fcntl.h>
#include <stdio.h> #include <stdio.h>
#include <locale.h>
#ifdef WITH_PROFILER #ifdef WITH_PROFILER
#include <gperftools/profiler.h> #include <gperftools/profiler.h>
#endif #endif
#include "monotonic.h" #include "monotonic.h"
#ifdef __APPLE__ #ifdef __APPLE__
#include <libproc.h> #include <libproc.h>
#include <xlocale.h>
static PyObject* static PyObject*
user_cache_dir() { user_cache_dir() {
@@ -91,7 +96,7 @@ stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
} }
#endif #endif
static inline bool static bool
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) { put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) {
struct termios raw_termios = *termios_p; struct termios raw_termios = *termios_p;
cfmakeraw(&raw_termios); cfmakeraw(&raw_termios);
@@ -156,6 +161,15 @@ wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr))); return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
} }
static PyObject*
locale_is_valid(PyObject *self UNUSED, PyObject *args) {
char *name;
if (!PyArg_ParseTuple(args, "s", &name)) return NULL;
locale_t test_locale = newlocale(LC_ALL_MASK, name, NULL);
if (!test_locale) { Py_RETURN_FALSE; }
freelocale(test_locale);
Py_RETURN_TRUE;
}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
@@ -169,6 +183,7 @@ static PyMethodDef module_methods[] = {
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""}, {"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
{"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""},
#ifdef __APPLE__ #ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS), METHODB(user_cache_dir, METH_NOARGS),
METHODB(process_group_map, METH_NOARGS), METHODB(process_group_map, METH_NOARGS),

View File

@@ -84,7 +84,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
static int static int
open_cache_file_without_tmpfile(const char *cache_path) { open_cache_file_without_tmpfile(const char *cache_path) {
int fd = -1; int fd = -1;
static const char *template = "%s/disk-cache-XXXXXXXXXXXX"; static const char template[] = "%s/disk-cache-XXXXXXXXXXXX";
const size_t sz = strlen(cache_path) + sizeof(template) + 4; const size_t sz = strlen(cache_path) + sizeof(template) + 4;
FREE_AFTER_FUNCTION char *buf = calloc(1, sz); FREE_AFTER_FUNCTION char *buf = calloc(1, sz);
if (!buf) { errno = ENOMEM; return -1; } if (!buf) { errno = ENOMEM; return -1; }
@@ -158,7 +158,7 @@ copy_between_files(int infd, int outfd, off_t in_pos, size_t len, uint8_t *buf,
return true; return true;
} }
static inline off_t static off_t
size_of_cache_file(DiskCache *self) { size_of_cache_file(DiskCache *self) {
return lseek(self->cache_file_fd, 0, SEEK_END); return lseek(self->cache_file_fd, 0, SEEK_END);
} }
@@ -255,7 +255,7 @@ cmp_pos_in_cache_file(void *a_, void *b_) {
return a->pos_in_cache_file - b->pos_in_cache_file; return a->pos_in_cache_file - b->pos_in_cache_file;
} }
static inline void static void
find_hole(DiskCache *self) { find_hole(DiskCache *self) {
off_t required_size = self->currently_writing.data_sz, prev = -100; off_t required_size = self->currently_writing.data_sz, prev = -100;
HASH_SORT(self->entries, cmp_pos_in_cache_file); HASH_SORT(self->entries, cmp_pos_in_cache_file);
@@ -271,7 +271,7 @@ find_hole(DiskCache *self) {
} }
} }
static inline bool static bool
find_cache_entry_to_write(DiskCache *self) { find_cache_entry_to_write(DiskCache *self) {
CacheEntry *tmp, *s; CacheEntry *tmp, *s;
off_t size_on_disk = size_of_cache_file(self); off_t size_on_disk = size_of_cache_file(self);
@@ -298,7 +298,7 @@ find_cache_entry_to_write(DiskCache *self) {
return false; return false;
} }
static inline bool static bool
write_dirty_entry(DiskCache *self) { write_dirty_entry(DiskCache *self) {
size_t left = self->currently_writing.data_sz; size_t left = self->currently_writing.data_sz;
uint8_t *p = self->currently_writing.data; uint8_t *p = self->currently_writing.data;
@@ -330,7 +330,7 @@ write_dirty_entry(DiskCache *self) {
return true; return true;
} }
static inline void static void
retire_currently_writing(DiskCache *self) { retire_currently_writing(DiskCache *self) {
CacheEntry *s = NULL; CacheEntry *s = NULL;
HASH_FIND(hh, self->entries, self->currently_writing.hash_key, self->currently_writing.hash_keylen, s); HASH_FIND(hh, self->entries, self->currently_writing.hash_key, self->currently_writing.hash_keylen, s);
@@ -477,7 +477,7 @@ dealloc(DiskCache* self) {
Py_TYPE(self)->tp_free((PyObject*)self); Py_TYPE(self)->tp_free((PyObject*)self);
} }
static inline CacheEntry* static CacheEntry*
create_cache_entry(const void *key, const size_t key_sz) { create_cache_entry(const void *key, const size_t key_sz) {
CacheEntry *s = calloc(1, sizeof(CacheEntry)); CacheEntry *s = calloc(1, sizeof(CacheEntry));
if (!s) return (CacheEntry*)PyErr_NoMemory(); if (!s) return (CacheEntry*)PyErr_NoMemory();

View File

@@ -494,7 +494,8 @@ def create_os_window(
wm_class_class: str, wm_class_class: str,
load_programs: Optional[Callable[[bool], None]] = None, load_programs: Optional[Callable[[bool], None]] = None,
x: int = -1, x: int = -1,
y: int = -1 y: int = -1,
disallow_override_title: bool = False,
) -> int: ) -> int:
pass pass
@@ -671,6 +672,10 @@ def cocoa_get_lang() -> Optional[str]:
pass pass
def locale_is_valid(name: str) -> bool:
pass
def mark_os_window_for_close(os_window_id: int, cr_type: int = 2) -> bool: def mark_os_window_for_close(os_window_id: int, cr_type: int = 2) -> bool:
pass pass
@@ -1208,3 +1213,7 @@ class OSWindowSize(TypedDict):
def get_os_window_size(os_window_id: int) -> Optional[OSWindowSize]: def get_os_window_size(os_window_id: int) -> Optional[OSWindowSize]:
pass pass
def get_all_processes() -> Tuple[int, ...]:
pass

View File

@@ -35,10 +35,10 @@ finalize(void) {
} }
} }
static inline PyObject* static PyObject*
pybool(FcBool x) { PyObject *ans = x ? Py_True: Py_False; Py_INCREF(ans); return ans; } pybool(FcBool x) { PyObject *ans = x ? Py_True: Py_False; Py_INCREF(ans); return ans; }
static inline PyObject* static PyObject*
pyspacing(int val) { pyspacing(int val) {
#define S(x) case FC_##x: return PyUnicode_FromString(#x) #define S(x) case FC_##x: return PyUnicode_FromString(#x)
switch(val) { S(PROPORTIONAL); S(DUAL); S(MONO); S(CHARCELL); default: return PyUnicode_FromString("UNKNOWN"); } switch(val) { S(PROPORTIONAL); S(DUAL); S(MONO); S(CHARCELL); default: return PyUnicode_FromString("UNKNOWN"); }
@@ -46,7 +46,7 @@ pyspacing(int val) {
} }
static inline PyObject* static PyObject*
pattern_as_dict(FcPattern *pat) { pattern_as_dict(FcPattern *pat) {
PyObject *ans = PyDict_New(), *p = NULL, *list = NULL; PyObject *ans = PyDict_New(), *p = NULL, *list = NULL;
if (ans == NULL) return NULL; if (ans == NULL) return NULL;
@@ -112,7 +112,7 @@ exit:
#undef LS #undef LS
} }
static inline PyObject* static PyObject*
font_set(FcFontSet *fs) { font_set(FcFontSet *fs) {
PyObject *ans = PyTuple_New(fs->nfont); PyObject *ans = PyTuple_New(fs->nfont);
if (ans == NULL) return NULL; if (ans == NULL) return NULL;
@@ -154,7 +154,7 @@ end:
return ans; return ans;
} }
static inline PyObject* static PyObject*
_fc_match(FcPattern *pat) { _fc_match(FcPattern *pat) {
FcPattern *match = NULL; FcPattern *match = NULL;
PyObject *ans = NULL; PyObject *ans = NULL;
@@ -172,7 +172,7 @@ end:
static char_type char_buf[1024]; static char_type char_buf[1024];
static inline void static void
add_charset(FcPattern *pat, size_t num) { add_charset(FcPattern *pat, size_t num) {
FcCharSet *charset = NULL; FcCharSet *charset = NULL;
if (num) { if (num) {
@@ -190,7 +190,7 @@ end:
if (charset != NULL) FcCharSetDestroy(charset); if (charset != NULL) FcCharSetDestroy(charset);
} }
static inline bool static bool
_native_fc_match(FcPattern *pat, FontConfigFace *ans) { _native_fc_match(FcPattern *pat, FontConfigFace *ans) {
bool ok = false; bool ok = false;
FcPattern *match = NULL; FcPattern *match = NULL;

View File

@@ -101,7 +101,7 @@ ensure_canvas_can_fit(FontGroup *fg, unsigned cells) {
} }
static inline void static void
save_window_font_groups(void) { save_window_font_groups(void) {
for (size_t o = 0; o < global_state.num_os_windows; o++) { for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *w = global_state.os_windows + o; OSWindow *w = global_state.os_windows + o;
@@ -109,7 +109,7 @@ save_window_font_groups(void) {
} }
} }
static inline void static void
restore_window_font_groups(void) { restore_window_font_groups(void) {
for (size_t o = 0; o < global_state.num_os_windows; o++) { for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *w = global_state.os_windows + o; OSWindow *w = global_state.os_windows + o;
@@ -123,7 +123,7 @@ restore_window_font_groups(void) {
} }
} }
static inline bool static bool
font_group_is_unused(FontGroup *fg) { font_group_is_unused(FontGroup *fg) {
for (size_t o = 0; o < global_state.num_os_windows; o++) { for (size_t o = 0; o < global_state.num_os_windows; o++) {
OSWindow *w = global_state.os_windows + o; OSWindow *w = global_state.os_windows + o;
@@ -140,7 +140,7 @@ free_maps(Font *font) {
font->glyph_properties_hash_table = NULL; font->glyph_properties_hash_table = NULL;
} }
static inline void static void
del_font(Font *f) { del_font(Font *f) {
Py_CLEAR(f->face); Py_CLEAR(f->face);
free(f->ffs_hb_features); f->ffs_hb_features = NULL; free(f->ffs_hb_features); f->ffs_hb_features = NULL;
@@ -148,7 +148,7 @@ del_font(Font *f) {
f->bold = false; f->italic = false; f->bold = false; f->italic = false;
} }
static inline void static void
del_font_group(FontGroup *fg) { del_font_group(FontGroup *fg) {
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0}; free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
fg->sprite_map = free_sprite_map(fg->sprite_map); fg->sprite_map = free_sprite_map(fg->sprite_map);
@@ -156,7 +156,7 @@ del_font_group(FontGroup *fg) {
free(fg->fonts); fg->fonts = NULL; free(fg->fonts); fg->fonts = NULL;
} }
static inline void static void
trim_unused_font_groups(void) { trim_unused_font_groups(void) {
save_window_font_groups(); save_window_font_groups();
size_t i = 0; size_t i = 0;
@@ -171,7 +171,7 @@ trim_unused_font_groups(void) {
restore_window_font_groups(); restore_window_font_groups();
} }
static inline void static void
add_font_group(void) { add_font_group(void) {
if (num_font_groups) trim_unused_font_groups(); if (num_font_groups) trim_unused_font_groups();
if (num_font_groups >= font_groups_capacity) { if (num_font_groups >= font_groups_capacity) {
@@ -184,7 +184,7 @@ add_font_group(void) {
num_font_groups++; num_font_groups++;
} }
static inline FontGroup* static FontGroup*
font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y) { font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y) {
for (size_t i = 0; i < num_font_groups; i++) { for (size_t i = 0; i < num_font_groups; i++) {
FontGroup *fg = font_groups + i; FontGroup *fg = font_groups + i;
@@ -205,7 +205,7 @@ font_group_for(double font_sz_in_pts, double logical_dpi_x, double logical_dpi_y
// Sprites {{{ // Sprites {{{
static inline void static void
sprite_map_set_error(int error) { sprite_map_set_error(int error) {
switch(error) { switch(error) {
case 1: case 1:
@@ -223,7 +223,7 @@ sprite_tracker_set_limits(size_t max_texture_size_, size_t max_array_len_) {
max_array_len = MIN(0xfffu, max_array_len_); max_array_len = MIN(0xfffu, max_array_len_);
} }
static inline void static void
do_increment(FontGroup *fg, int *error) { do_increment(FontGroup *fg, int *error) {
fg->sprite_tracker.x++; fg->sprite_tracker.x++;
if (fg->sprite_tracker.x >= fg->sprite_tracker.xnum) { if (fg->sprite_tracker.x >= fg->sprite_tracker.xnum) {
@@ -265,7 +265,7 @@ sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_wi
} }
// }}} // }}}
static inline PyObject* static PyObject*
desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) { desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
PyObject *d = specialize_font_descriptor(desc, fg); PyObject *d = specialize_font_descriptor(desc, fg);
if (d == NULL) return NULL; if (d == NULL) return NULL;
@@ -274,7 +274,7 @@ desc_to_face(PyObject *desc, FONTS_DATA_HANDLE fg) {
return ans; return ans;
} }
static inline bool static bool
init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) { init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentation) {
f->face = face; Py_INCREF(f->face); f->face = face; Py_INCREF(f->face);
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation; f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
@@ -311,7 +311,7 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati
return true; return true;
} }
static inline void static void
free_font_groups(void) { free_font_groups(void) {
if (font_groups) { if (font_groups) {
for (size_t i = 0; i < num_font_groups; i++) del_font_group(font_groups + i); for (size_t i = 0; i < num_font_groups; i++) del_font_group(font_groups + i);
@@ -332,7 +332,7 @@ python_send_to_gpu(FONTS_DATA_HANDLE fg, unsigned int x, unsigned int y, unsigne
} }
static inline void static void
calc_cell_metrics(FontGroup *fg) { calc_cell_metrics(FontGroup *fg) {
unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; unsigned int cell_height, cell_width, baseline, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness, &strikethrough_position, &strikethrough_thickness); cell_metrics(fg->fonts[fg->medium_font_idx].face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness, &strikethrough_position, &strikethrough_thickness);
@@ -371,17 +371,17 @@ calc_cell_metrics(FontGroup *fg) {
ensure_canvas_can_fit(fg, 8); ensure_canvas_can_fit(fg, 8);
} }
static inline bool static bool
face_has_codepoint(PyObject* face, char_type cp) { face_has_codepoint(PyObject* face, char_type cp) {
return glyph_id_for_codepoint(face, cp) > 0; return glyph_id_for_codepoint(face, cp) > 0;
} }
static inline bool static bool
has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) { has_emoji_presentation(CPUCell *cpu_cell, GPUCell *gpu_cell) {
return (gpu_cell->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15; return (gpu_cell->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cell->ch) && cpu_cell->cc_idx[0] != VS15;
} }
static inline bool static bool
has_cell_text(Font *self, CPUCell *cell) { has_cell_text(Font *self, CPUCell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false; if (!face_has_codepoint(self->face, cell->ch)) return false;
char_type combining_chars[arraysz(cell->cc_idx)]; char_type combining_chars[arraysz(cell->cc_idx)];
@@ -403,7 +403,7 @@ has_cell_text(Font *self, CPUCell *cell) {
return true; return true;
} }
static inline void static void
output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) { output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) {
printf("U+%x ", cell->ch); printf("U+%x ", cell->ch);
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) { for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
@@ -417,7 +417,7 @@ output_cell_fallback_data(CPUCell *cell, bool bold, bool italic, bool emoji_pres
printf("\n"); printf("\n");
} }
static inline ssize_t static ssize_t
load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) { load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool emoji_presentation) {
if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; } if (fg->fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
ssize_t f; ssize_t f;
@@ -456,7 +456,7 @@ load_fallback_font(FontGroup *fg, CPUCell *cell, bool bold, bool italic, bool em
return ans; return ans;
} }
static inline ssize_t static ssize_t
fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) { fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
bool bold = (gpu_cell->attrs >> BOLD_SHIFT) & 1; bool bold = (gpu_cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (gpu_cell->attrs >> ITALIC_SHIFT) & 1; bool italic = (gpu_cell->attrs >> ITALIC_SHIFT) & 1;
@@ -474,7 +474,7 @@ fallback_font(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
return load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation); return load_fallback_font(fg, cpu_cell, bold, italic, emoji_presentation);
} }
static inline ssize_t static ssize_t
in_symbol_maps(FontGroup *fg, char_type ch) { in_symbol_maps(FontGroup *fg, char_type ch) {
for (size_t i = 0; i < num_symbol_maps; i++) { for (size_t i = 0; i < num_symbol_maps; i++) {
if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return fg->first_symbol_font_idx + symbol_maps[i].font_idx; if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return fg->first_symbol_font_idx + symbol_maps[i].font_idx;
@@ -491,7 +491,7 @@ in_symbol_maps(FontGroup *fg, char_type ch) {
// - BLANK_FONT // - BLANK_FONT
// - BOX_FONT // - BOX_FONT
// - an index in the fonts list // - an index in the fonts list
static inline ssize_t static ssize_t
font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_fallback_font, bool *is_emoji_presentation) { font_for_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell, bool *is_fallback_font, bool *is_emoji_presentation) {
*is_fallback_font = false; *is_fallback_font = false;
*is_emoji_presentation = false; *is_emoji_presentation = false;
@@ -531,13 +531,13 @@ START_ALLOW_CASE_RANGE
END_ALLOW_CASE_RANGE END_ALLOW_CASE_RANGE
} }
static inline void static void
set_sprite(GPUCell *cell, sprite_index x, sprite_index y, sprite_index z) { set_sprite(GPUCell *cell, sprite_index x, sprite_index y, sprite_index z) {
cell->sprite_x = x; cell->sprite_y = y; cell->sprite_z = z; cell->sprite_x = x; cell->sprite_y = y; cell->sprite_z = z;
} }
// Gives a unique (arbitrary) id to a box glyph // Gives a unique (arbitrary) id to a box glyph
static inline glyph_index static glyph_index
box_glyph_id(char_type ch) { box_glyph_id(char_type ch) {
START_ALLOW_CASE_RANGE START_ALLOW_CASE_RANGE
switch(ch) { switch(ch) {
@@ -596,7 +596,7 @@ render_box_cell(FontGroup *fg, CPUCell *cpu_cell, GPUCell *gpu_cell) {
Py_DECREF(ret); Py_DECREF(ret);
} }
static inline void static void
load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells) { load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells) {
index_type num; index_type num;
hb_buffer_clear_contents(harfbuzz_buffer); hb_buffer_clear_contents(harfbuzz_buffer);
@@ -617,13 +617,13 @@ load_hb_buffer(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_
} }
static inline void static void
set_cell_sprite(GPUCell *cell, const SpritePosition *sp) { set_cell_sprite(GPUCell *cell, const SpritePosition *sp) {
cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z; cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z;
if (sp->colored) cell->sprite_z |= 0x4000; if (sp->colored) cell->sprite_z |= 0x4000;
} }
static inline pixel* static pixel*
extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) { extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) {
pixel *ans = fg->canvas.buf + (fg->cell_width * fg->cell_height * (fg->canvas.current_cells - 1)), *dest = ans, *src = fg->canvas.buf + (i * fg->cell_width); pixel *ans = fg->canvas.buf + (fg->cell_width * fg->cell_height * (fg->canvas.current_cells - 1)), *dest = ans, *src = fg->canvas.buf + (i * fg->cell_width);
unsigned int stride = fg->cell_width * num_cells; unsigned int stride = fg->cell_width * num_cells;
@@ -638,7 +638,7 @@ typedef struct GlyphRenderScratch {
} GlyphRenderScratch; } GlyphRenderScratch;
static GlyphRenderScratch global_glyph_render_scratch = {0}; static GlyphRenderScratch global_glyph_render_scratch = {0};
static inline void static void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph) { render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index *glyphs, unsigned glyph_count, bool center_glyph) {
#define sp global_glyph_render_scratch.sprite_positions #define sp global_glyph_render_scratch.sprite_positions
int error = 0; int error = 0;
@@ -703,14 +703,14 @@ typedef struct {
static GroupState group_state = {0}; static GroupState group_state = {0};
static inline unsigned int static unsigned int
num_codepoints_in_cell(CPUCell *cell) { num_codepoints_in_cell(CPUCell *cell) {
unsigned int ans = 1; unsigned int ans = 1;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++; for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
return ans; return ans;
} }
static inline void static void
shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature) { shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature) {
if (group_state.groups_capacity <= 2 * num_cells) { if (group_state.groups_capacity <= 2 * num_cells) {
group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs
@@ -747,7 +747,7 @@ shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb
else group_state.num_glyphs = MIN(info_length, positions_length); else group_state.num_glyphs = MIN(info_length, positions_length);
} }
static inline bool static bool
is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) { is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
// A glyph is special if the codepoint it corresponds to matches a // A glyph is special if the codepoint it corresponds to matches a
// different glyph in the font // different glyph in the font
@@ -764,7 +764,7 @@ is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
return s->data & SPECIAL_VALUE_MASK; return s->data & SPECIAL_VALUE_MASK;
} }
static inline bool static bool
is_empty_glyph(glyph_index glyph_id, Font *font) { is_empty_glyph(glyph_index glyph_id, Font *font) {
// A glyph is empty if its metrics have a width of zero // A glyph is empty if its metrics have a width of zero
GlyphProperties *s = find_or_create_glyph_properties(&font->glyph_properties_hash_table, glyph_id); GlyphProperties *s = find_or_create_glyph_properties(&font->glyph_properties_hash_table, glyph_id);
@@ -776,7 +776,7 @@ is_empty_glyph(glyph_index glyph_id, Font *font) {
return s->data & EMPTY_VALUE_MASK; return s->data & EMPTY_VALUE_MASK;
} }
static inline unsigned int static unsigned int
check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) { check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
cell_data->codepoints_consumed++; cell_data->codepoints_consumed++;
if (cell_data->codepoints_consumed >= cell_data->num_codepoints) { if (cell_data->codepoints_consumed >= cell_data->num_codepoints) {
@@ -806,7 +806,7 @@ check_cell_consumed(CellData *cell_data, CPUCell *last_cpu_cell) {
return 0; return 0;
} }
static inline LigatureType static LigatureType
ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) { ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) {
const char *p, *m, *s, *e; const char *p, *m, *s, *e;
if (strategy == SPACERS_IOSEVKA) { if (strategy == SPACERS_IOSEVKA) {
@@ -826,7 +826,7 @@ ligature_type_from_glyph_name(const char *glyph_name, SpacerStrategy strategy) {
#define G(x) (group_state.x) #define G(x) (group_state.x)
static inline void static void
detect_spacer_strategy(hb_font_t *hbf, Font *font) { detect_spacer_strategy(hb_font_t *hbf, Font *font) {
CPUCell cpu_cells[3] = {{.ch = '='}, {.ch = '='}, {.ch = '='}}; CPUCell cpu_cells[3] = {{.ch = '='}, {.ch = '='}, {.ch = '='}};
GPUCell gpu_cells[3] = {{.attrs = 1}, {.attrs = 1}, {.attrs = 1}}; GPUCell gpu_cells[3] = {{.attrs = 1}, {.attrs = 1}, {.attrs = 1}};
@@ -1071,7 +1071,7 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
#endif #endif
} }
static inline void static void
merge_groups_for_pua_space_ligature(void) { merge_groups_for_pua_space_ligature(void) {
while (G(group_idx) > 0) { while (G(group_idx) > 0) {
Group *g = G(groups), *g1 = G(groups) + 1; Group *g = G(groups), *g1 = G(groups) + 1;
@@ -1084,14 +1084,14 @@ merge_groups_for_pua_space_ligature(void) {
#undef MOVE_GLYPH_TO_NEXT_GROUP #undef MOVE_GLYPH_TO_NEXT_GROUP
static inline bool static bool
is_group_calt_ligature(const Group *group) { is_group_calt_ligature(const Group *group) {
GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx; GPUCell *first_cell = G(first_gpu_cell) + group->first_cell_idx;
return group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1; return group->num_cells > 1 && group->has_special_glyph && (first_cell->attrs & WIDTH_MASK) == 1;
} }
static inline void static void
split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right) { split_run_at_offset(index_type cursor_offset, index_type *left, index_type *right) {
*left = 0; *right = 0; *left = 0; *right = 0;
for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) { for (unsigned idx = 0; idx < G(group_idx) + 1; idx++) {
@@ -1107,7 +1107,7 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ
} }
static inline void static void
render_groups(FontGroup *fg, Font *font, bool center_glyph) { render_groups(FontGroup *fg, Font *font, bool center_glyph) {
unsigned idx = 0; unsigned idx = 0;
while (idx <= G(group_idx)) { while (idx <= G(group_idx)) {
@@ -1174,7 +1174,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
} }
#undef G #undef G
static inline void static void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy) { render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy) {
switch(font_idx) { switch(font_idx) {
default: default:
@@ -1211,7 +1211,7 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
} }
} }
static inline bool static bool
is_non_emoji_dingbat(char_type ch) { is_non_emoji_dingbat(char_type ch) {
return 0x2700 <= ch && ch <= 0x27bf && !is_emoji(ch); return 0x2700 <= ch && ch <= 0x27bf && !is_emoji(ch);
} }
@@ -1295,7 +1295,7 @@ render_simple_text(FONTS_DATA_HANDLE fg_, const char *text) {
return ans; return ans;
} }
static inline void static void
clear_symbol_maps(void) { clear_symbol_maps(void) {
if (symbol_maps) { free(symbol_maps); symbol_maps = NULL; num_symbol_maps = 0; } if (symbol_maps) { free(symbol_maps); symbol_maps = NULL; num_symbol_maps = 0; }
} }
@@ -1329,7 +1329,7 @@ set_font_data(PyObject UNUSED *m, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline void static void
send_prerendered_sprites(FontGroup *fg) { send_prerendered_sprites(FontGroup *fg) {
int error = 0; int error = 0;
sprite_index x = 0, y = 0, z = 0; sprite_index x = 0, y = 0, z = 0;
@@ -1354,7 +1354,7 @@ send_prerendered_sprites(FontGroup *fg) {
Py_CLEAR(args); Py_CLEAR(args);
} }
static inline size_t static size_t
initialize_font(FontGroup *fg, unsigned int desc_idx, const char *ftype) { initialize_font(FontGroup *fg, unsigned int desc_idx, const char *ftype) {
PyObject *d = PyObject_CallFunction(descriptor_for_idx, "I", desc_idx); PyObject *d = PyObject_CallFunction(descriptor_for_idx, "I", desc_idx);
if (d == NULL) { PyErr_Print(); fatal("Failed for %s font", ftype); } if (d == NULL) { PyErr_Print(); fatal("Failed for %s font", ftype); }

View File

@@ -76,18 +76,18 @@ static FT_Library library;
FT_Library FT_Library
freetype_library(void) { return library; } freetype_library(void) { return library; }
static inline int static int
font_units_to_pixels_y(Face *self, int x) { font_units_to_pixels_y(Face *self, int x) {
return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0); return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.y_scale) / 64.0);
} }
static inline int static int
font_units_to_pixels_x(Face *self, int x) { font_units_to_pixels_x(Face *self, int x) {
return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0); return (int)ceil((double)FT_MulFix(x, self->face->size->metrics.x_scale) / 64.0);
} }
static inline int static int
get_load_flags(int hinting, int hintstyle, int base) { get_load_flags(int hinting, int hintstyle, int base) {
int flags = base; int flags = base;
if (hinting) { if (hinting) {
@@ -98,7 +98,7 @@ get_load_flags(int hinting, int hintstyle, int base) {
} }
static inline bool static bool
load_glyph(Face *self, int glyph_index, int load_type) { load_glyph(Face *self, int glyph_index, int load_type) {
int flags = get_load_flags(self->hinting, self->hintstyle, load_type); int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
int error = FT_Load_Glyph(self->face, glyph_index, flags); int error = FT_Load_Glyph(self->face, glyph_index, flags);
@@ -110,7 +110,7 @@ load_glyph(Face *self, int glyph_index, int load_type) {
return true; return true;
} }
static inline unsigned int static unsigned int
get_height_for_char(Face *self, char ch) { get_height_for_char(Face *self, char ch) {
unsigned int ans = 0; unsigned int ans = 0;
int glyph_index = FT_Get_Char_Index(self->face, ch); int glyph_index = FT_Get_Char_Index(self->face, ch);
@@ -125,7 +125,7 @@ get_height_for_char(Face *self, char ch) {
return ans; return ans;
} }
static inline unsigned int static unsigned int
calc_cell_height(Face *self, bool for_metrics) { calc_cell_height(Face *self, bool for_metrics) {
unsigned int ans = font_units_to_pixels_y(self, self->height); unsigned int ans = font_units_to_pixels_y(self, self->height);
if (for_metrics) { if (for_metrics) {
@@ -139,7 +139,7 @@ calc_cell_height(Face *self, bool for_metrics) {
return ans; return ans;
} }
static inline bool static bool
set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt xdpi, FT_UInt ydpi, unsigned int desired_height, unsigned int cell_height) { set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt xdpi, FT_UInt ydpi, unsigned int desired_height, unsigned int cell_height) {
int error = FT_Set_Char_Size(self->face, 0, char_height, xdpi, ydpi); int error = FT_Set_Char_Size(self->face, 0, char_height, xdpi, ydpi);
if (!error) { if (!error) {
@@ -189,7 +189,7 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA
return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height); return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->cell_height);
} }
static inline bool static bool
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) { init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) {
#define CPY(n) self->n = self->face->n; #define CPY(n) self->n = self->face->n;
CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness); CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness);
@@ -290,7 +290,7 @@ postscript_name_for_face(const PyObject *face_) {
return ps_name ? ps_name : ""; return ps_name ? ps_name : "";
} }
static inline unsigned int static unsigned int
calc_cell_width(Face *self) { calc_cell_width(Face *self) {
unsigned int ans = 0; unsigned int ans = 0;
for (char_type i = 32; i < 128; i++) { for (char_type i = 32; i < 128; i++) {
@@ -382,7 +382,7 @@ typedef struct {
int bitmap_left, bitmap_top; int bitmap_left, bitmap_top;
} ProcessedBitmap; } ProcessedBitmap;
static inline void static void
free_processed_bitmap(ProcessedBitmap *bm) { free_processed_bitmap(ProcessedBitmap *bm) {
if (bm->needs_free) { if (bm->needs_free) {
bm->needs_free = false; bm->needs_free = false;
@@ -390,7 +390,7 @@ free_processed_bitmap(ProcessedBitmap *bm) {
} }
} }
static inline void static void
trim_borders(ProcessedBitmap *ans, size_t extra) { trim_borders(ProcessedBitmap *ans, size_t extra) {
bool column_has_text = false; bool column_has_text = false;
@@ -407,7 +407,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
ans->width -= extra; ans->width -= extra;
} }
static inline void static void
populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) { populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) {
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch; ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
ans->rows = bitmap->rows; ans->rows = bitmap->rows;
@@ -438,7 +438,7 @@ freetype_convert_mono_bitmap(FT_Bitmap *src, FT_Bitmap *dest) {
return true; return true;
} }
static inline bool static bool
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) { render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) {
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false; if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false;
unsigned int max_width = cell_width * num_cells; unsigned int max_width = cell_width * num_cells;
@@ -508,7 +508,7 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
bm->buf = dest; bm->needs_free = true; bm->stride = 4 * width; bm->width = width; bm->rows = cell_height; bm->buf = dest; bm->needs_free = true; bm->stride = 4 * width; bm->width = width; bm->rows = cell_height;
} }
static inline void static void
detect_right_edge(ProcessedBitmap *ans) { detect_right_edge(ProcessedBitmap *ans) {
ans->right_edge = 0; ans->right_edge = 0;
for (ssize_t x = ans->width - 1; !ans->right_edge && x > -1; x--) { for (ssize_t x = ans->width - 1; !ans->right_edge && x > -1; x--) {
@@ -519,7 +519,7 @@ detect_right_edge(ProcessedBitmap *ans) {
} }
} }
static inline bool static bool
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline UNUSED) { render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline UNUSED) {
unsigned short best = 0, diff = USHRT_MAX; unsigned short best = 0, diff = USHRT_MAX;
const short limit = self->face->num_fixed_sizes; const short limit = self->face->num_fixed_sizes;
@@ -550,7 +550,7 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int
} }
static inline void static void
copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) {
for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) { for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) {
pixel *d = dest + dest_stride * dr; pixel *d = dest + dest_stride * dr;
@@ -566,7 +566,9 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect
} }
} }
static inline void static const bool debug_placement = false;
static void
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline, unsigned int glyph_num) { place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline, unsigned int glyph_num) {
// We want the glyph to be positioned inside the cell based on the bearingX // We want the glyph to be positioned inside the cell based on the bearingX
// and bearingY values, making sure that it does not overflow the cell. // and bearingY values, making sure that it does not overflow the cell.
@@ -575,6 +577,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
// Calculate column bounds // Calculate column bounds
int32_t xoff = (int32_t)(x_offset + bm->bitmap_left); int32_t xoff = (int32_t)(x_offset + bm->bitmap_left);
if (debug_placement) printf(" bitmap_left: %d xoff: %d", bm->bitmap_left, xoff);
if (xoff < 0) src.left += -xoff; if (xoff < 0) src.left += -xoff;
else dest.left = xoff; else dest.left = xoff;
// Move the dest start column back if the width overflows because of it, but only if we are not in a very long/infinite ligature // Move the dest start column back if the width overflows because of it, but only if we are not in a very long/infinite ligature
@@ -629,10 +632,17 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
} }
x_offset = x + (float)positions[i].x_offset / 64.0f; x_offset = x + (float)positions[i].x_offset / 64.0f;
y = (float)positions[i].y_offset / 64.0f; y = (float)positions[i].y_offset / 64.0f;
if (debug_placement) printf("%d: x=%f canvas: %u", i, x_offset, canvas_width);
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) { if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i); place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline, i);
} }
x += (float)positions[i].x_advance / 64.0f; if (debug_placement) printf(" adv: %f\n", (float)positions[i].x_advance / 64.0f);
// the roundf() below is needed for infinite length ligatures, for a test case
// use: kitty --config None -o 'font_family Fira Code' -o 'font_size 4.5' sh -c
// "echo '|---|--------|-------|-------------|-------------|HH'; read"
// if this causes issues with non-infinite ligatures, we could choose this behavior
// based on num_glyphs and/or num_cells
x += roundf((float)positions[i].x_advance / 64.0f);
free_processed_bitmap(&bm); free_processed_bitmap(&bm);
} }

View File

@@ -226,7 +226,7 @@ unbind_buffer(ssize_t buf_idx) {
glBindBuffer(buffers[buf_idx].usage, 0); glBindBuffer(buffers[buf_idx].usage, 0);
} }
static inline void static void
alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) { alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) {
Buffer *b = buffers + idx; Buffer *b = buffers + idx;
if (b->size == size) return; if (b->size == size) return;
@@ -234,13 +234,13 @@ alloc_buffer(ssize_t idx, GLsizeiptr size, GLenum usage) {
glBufferData(b->usage, size, NULL, usage); glBufferData(b->usage, size, NULL, usage);
} }
static inline void* static void*
map_buffer(ssize_t idx, GLenum access) { map_buffer(ssize_t idx, GLenum access) {
void *ans = glMapBuffer(buffers[idx].usage, access); void *ans = glMapBuffer(buffers[idx].usage, access);
return ans; return ans;
} }
static inline void static void
unmap_buffer(ssize_t idx) { unmap_buffer(ssize_t idx) {
glUnmapBuffer(buffers[idx].usage); glUnmapBuffer(buffers[idx].usage);
} }

View File

@@ -49,7 +49,7 @@ request_tick_callback(void) {
glfwPostEmptyEvent(); glfwPostEmptyEvent();
} }
static inline void static void
min_size_for_os_window(OSWindow *window, int *min_width, int *min_height) { min_size_for_os_window(OSWindow *window, int *min_width, int *min_height) {
*min_width = MAX(8u, window->fonts_data->cell_width + 1); *min_width = MAX(8u, window->fonts_data->cell_width + 1);
*min_height = MAX(8u, window->fonts_data->cell_height + 1); *min_height = MAX(8u, window->fonts_data->cell_height + 1);
@@ -124,7 +124,7 @@ update_os_window_references() {
} }
} }
static inline bool static bool
set_callback_window(GLFWwindow *w) { set_callback_window(GLFWwindow *w) {
global_state.callback_os_window = glfwGetWindowUserPointer(w); global_state.callback_os_window = glfwGetWindowUserPointer(w);
if (global_state.callback_os_window) return true; if (global_state.callback_os_window) return true;
@@ -137,7 +137,7 @@ set_callback_window(GLFWwindow *w) {
return false; return false;
} }
static inline bool static bool
is_window_ready_for_callbacks(void) { is_window_ready_for_callbacks(void) {
OSWindow *w = global_state.callback_os_window; OSWindow *w = global_state.callback_os_window;
if (w->num_tabs == 0) return false; if (w->num_tabs == 0) return false;
@@ -148,7 +148,7 @@ is_window_ready_for_callbacks(void) {
#define WINDOW_CALLBACK(name, fmt, ...) call_boss(name, "K" fmt, global_state.callback_os_window->id, __VA_ARGS__) #define WINDOW_CALLBACK(name, fmt, ...) call_boss(name, "K" fmt, global_state.callback_os_window->id, __VA_ARGS__)
static inline void static void
show_mouse_cursor(GLFWwindow *w) { show_mouse_cursor(GLFWwindow *w) {
glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
} }
@@ -250,7 +250,7 @@ refresh_callback(GLFWwindow *w) {
static int mods_at_last_key_or_button_event = 0; static int mods_at_last_key_or_button_event = 0;
static inline int static int
key_to_modifier(uint32_t key) { key_to_modifier(uint32_t key) {
switch(key) { switch(key) {
case GLFW_FKEY_LEFT_SHIFT: case GLFW_FKEY_LEFT_SHIFT:
@@ -403,7 +403,7 @@ application_close_requested_callback(int flags) {
} }
} }
static inline void get_window_dpi(GLFWwindow *w, double *x, double *y); static void get_window_dpi(GLFWwindow *w, double *x, double *y);
#ifdef __APPLE__ #ifdef __APPLE__
static bool static bool
@@ -492,7 +492,7 @@ set_os_window_size(OSWindow *os_window, int x, int y) {
glfwSetWindowSize(os_window->handle, x, y); glfwSetWindowSize(os_window->handle, x, y);
} }
static inline void static void
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) { get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
*xscale = 1; *yscale = 1; *xscale = 1; *yscale = 1;
if (w) glfwGetWindowContentScale(w, xscale, yscale); if (w) glfwGetWindowContentScale(w, xscale, yscale);
@@ -611,7 +611,7 @@ set_titlebar_color(OSWindow *w, color_type color, bool use_system_color) {
} }
} }
static inline PyObject* static PyObject*
native_window_handle(GLFWwindow *w) { native_window_handle(GLFWwindow *w) {
#ifdef __APPLE__ #ifdef __APPLE__
void *ans = glfwGetCocoaWindow(w); void *ans = glfwGetCocoaWindow(w);
@@ -623,12 +623,12 @@ native_window_handle(GLFWwindow *w) {
static PyObject* static PyObject*
create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
int x = -1, y = -1; int x = -1, y = -1, disallow_override_title = 0;
char *title, *wm_class_class, *wm_class_name; char *title, *wm_class_class, *wm_class_name;
PyObject *load_programs = NULL, *get_window_size, *pre_show_callback; PyObject *load_programs = NULL, *get_window_size, *pre_show_callback;
static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "load_programs", "x", "y", NULL}; static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "load_programs", "x", "y", "disallow_override_title", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|Oii", (char**)kwlist, if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|Oiip", (char**)kwlist,
&get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL; &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y, &disallow_override_title)) return NULL;
static bool is_first_window = true; static bool is_first_window = true;
if (is_first_window) { if (is_first_window) {
@@ -742,6 +742,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
} }
OSWindow *w = add_os_window(); OSWindow *w = add_os_window();
w->handle = glfw_window; w->handle = glfw_window;
w->disallow_title_changes = disallow_override_title;
update_os_window_references(); update_os_window_references();
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
// On some platforms (macOS) newly created windows don't get the initial focus in event // On some platforms (macOS) newly created windows don't get the initial focus in event
@@ -815,7 +816,7 @@ os_window_update_size_increments(OSWindow *window) {
} }
#ifdef __APPLE__ #ifdef __APPLE__
static inline bool static bool
window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) { window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source_workspace_count) {
static size_t workspaces[64]; static size_t workspaces[64];
size_t workspace_count = cocoa_get_workspace_ids(w, workspaces, arraysz(workspaces)); size_t workspace_count = cocoa_get_workspace_ids(w, workspaces, arraysz(workspaces));
@@ -827,7 +828,7 @@ window_in_same_cocoa_workspace(void *w, size_t *source_workspaces, size_t source
return false; return false;
} }
static inline void static void
cocoa_focus_last_window(id_type source_window_id, size_t *source_workspaces, size_t source_workspace_count) { cocoa_focus_last_window(id_type source_window_id, size_t *source_workspaces, size_t source_workspace_count) {
id_type highest_focus_number = 0; id_type highest_focus_number = 0;
OSWindow *window_to_focus = NULL; OSWindow *window_to_focus = NULL;

View File

@@ -29,31 +29,31 @@ PyTypeObject GraphicsManager_Type;
// caching {{{ // caching {{{
#define CACHE_KEY_BUFFER_SIZE 32 #define CACHE_KEY_BUFFER_SIZE 32
static inline size_t static size_t
cache_key(const ImageAndFrame x, char *key) { cache_key(const ImageAndFrame x, char *key) {
return snprintf(key, CACHE_KEY_BUFFER_SIZE, "%llx:%x", x.image_id, x.frame_id); return snprintf(key, CACHE_KEY_BUFFER_SIZE, "%llx:%x", x.image_id, x.frame_id);
} }
#define CK(x) key, cache_key(x, key) #define CK(x) key, cache_key(x, key)
static inline bool static bool
add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) { add_to_cache(GraphicsManager *self, const ImageAndFrame x, const void *data, const size_t sz) {
char key[CACHE_KEY_BUFFER_SIZE]; char key[CACHE_KEY_BUFFER_SIZE];
return add_to_disk_cache(self->disk_cache, CK(x), data, sz); return add_to_disk_cache(self->disk_cache, CK(x), data, sz);
} }
static inline bool static bool
remove_from_cache(GraphicsManager *self, const ImageAndFrame x) { remove_from_cache(GraphicsManager *self, const ImageAndFrame x) {
char key[CACHE_KEY_BUFFER_SIZE]; char key[CACHE_KEY_BUFFER_SIZE];
return remove_from_disk_cache(self->disk_cache, CK(x)); return remove_from_disk_cache(self->disk_cache, CK(x));
} }
static inline bool static bool
read_from_cache(const GraphicsManager *self, const ImageAndFrame x, void **data, size_t *sz) { read_from_cache(const GraphicsManager *self, const ImageAndFrame x, void **data, size_t *sz) {
char key[CACHE_KEY_BUFFER_SIZE]; char key[CACHE_KEY_BUFFER_SIZE];
return read_from_disk_cache_simple(self->disk_cache, CK(x), data, sz, false); return read_from_disk_cache_simple(self->disk_cache, CK(x), data, sz, false);
} }
static inline size_t static size_t
cache_size(const GraphicsManager *self) { return disk_cache_total_size(self->disk_cache); } cache_size(const GraphicsManager *self) { return disk_cache_total_size(self->disk_cache); }
#undef CK #undef CK
// }}} // }}}
@@ -75,13 +75,13 @@ grman_alloc() {
return self; return self;
} }
static inline void static void
free_refs_data(Image *img) { free_refs_data(Image *img) {
free(img->refs); img->refs = NULL; free(img->refs); img->refs = NULL;
img->refcnt = 0; img->refcap = 0; img->refcnt = 0; img->refcap = 0;
} }
static inline void static void
free_load_data(LoadData *ld) { free_load_data(LoadData *ld) {
free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0; ld->buf = NULL; free(ld->buf); ld->buf_used = 0; ld->buf_capacity = 0; ld->buf = NULL;
if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz); if (ld->mapped_file) munmap(ld->mapped_file, ld->mapped_file_sz);
@@ -89,7 +89,7 @@ free_load_data(LoadData *ld) {
ld->loading_for = (const ImageAndFrame){0}; ld->loading_for = (const ImageAndFrame){0};
} }
static inline void static void
free_image(GraphicsManager *self, Image *img) { free_image(GraphicsManager *self, Image *img) {
if (img->texture_id) free_texture(&img->texture_id); if (img->texture_id) free_texture(&img->texture_id);
ImageAndFrame key = { .image_id=img->internal_id, .frame_id = img->root_frame.id }; ImageAndFrame key = { .image_id=img->internal_id, .frame_id = img->root_frame.id };
@@ -121,7 +121,7 @@ dealloc(GraphicsManager* self) {
static id_type internal_id_counter = 1; static id_type internal_id_counter = 1;
static inline Image* static Image*
img_by_internal_id(GraphicsManager *self, id_type id) { img_by_internal_id(GraphicsManager *self, id_type id) {
for (size_t i = 0; i < self->image_count; i++) { for (size_t i = 0; i < self->image_count; i++) {
if (self->images[i].internal_id == id) return self->images + i; if (self->images[i].internal_id == id) return self->images + i;
@@ -129,7 +129,7 @@ img_by_internal_id(GraphicsManager *self, id_type id) {
return NULL; return NULL;
} }
static inline Image* static Image*
img_by_client_id(GraphicsManager *self, uint32_t id) { img_by_client_id(GraphicsManager *self, uint32_t id) {
for (size_t i = 0; i < self->image_count; i++) { for (size_t i = 0; i < self->image_count; i++) {
if (self->images[i].client_id == id) return self->images + i; if (self->images[i].client_id == id) return self->images + i;
@@ -137,7 +137,7 @@ img_by_client_id(GraphicsManager *self, uint32_t id) {
return NULL; return NULL;
} }
static inline Image* static Image*
img_by_client_number(GraphicsManager *self, uint32_t number) { img_by_client_number(GraphicsManager *self, uint32_t number) {
// get the newest image with the specified number // get the newest image with the specified number
for (size_t i = self->image_count; i-- > 0; ) { for (size_t i = self->image_count; i-- > 0; ) {
@@ -147,14 +147,14 @@ img_by_client_number(GraphicsManager *self, uint32_t number) {
} }
static inline void static void
remove_image(GraphicsManager *self, size_t idx) { remove_image(GraphicsManager *self, size_t idx) {
free_image(self, self->images + idx); free_image(self, self->images + idx);
remove_i_from_array(self->images, idx, self->image_count); remove_i_from_array(self->images, idx, self->image_count);
self->layers_dirty = true; self->layers_dirty = true;
} }
static inline void static void
remove_images(GraphicsManager *self, bool(*predicate)(Image*), id_type skip_image_internal_id) { remove_images(GraphicsManager *self, bool(*predicate)(Image*), id_type skip_image_internal_id) {
for (size_t i = self->image_count; i-- > 0;) { for (size_t i = self->image_count; i-- > 0;) {
Image *img = self->images + i; Image *img = self->images + i;
@@ -173,7 +173,7 @@ trim_predicate(Image *img) {
} }
static inline void static void
apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type currently_added_image_internal_id) { apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type currently_added_image_internal_id) {
// First remove unreferenced images, even if they have an id // First remove unreferenced images, even if they have an id
remove_images(self, trim_predicate, currently_added_image_internal_id); remove_images(self, trim_predicate, currently_added_image_internal_id);
@@ -190,7 +190,7 @@ apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type current
static char command_response[512] = {0}; static char command_response[512] = {0};
static inline void static void
set_command_failed_response(const char *code, const char *fmt, ...) { set_command_failed_response(const char *code, const char *fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
@@ -203,7 +203,7 @@ set_command_failed_response(const char *code, const char *fmt, ...) {
// Decode formats {{{ // Decode formats {{{
#define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; } #define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; }
static inline bool static bool
mmap_img_file(GraphicsManager *self, int fd, size_t sz, off_t offset) { mmap_img_file(GraphicsManager *self, int fd, size_t sz, off_t offset) {
if (!sz) { if (!sz) {
struct stat s; struct stat s;
@@ -220,7 +220,7 @@ err:
} }
static inline const char* static const char*
zlib_strerror(int ret) { zlib_strerror(int ret) {
#define Z(x) case x: return #x; #define Z(x) case x: return #x;
static char buf[128]; static char buf[128];
@@ -239,7 +239,7 @@ zlib_strerror(int ret) {
#undef Z #undef Z
} }
static inline bool static bool
inflate_zlib(LoadData *load_data, uint8_t *buf, size_t bufsz) { inflate_zlib(LoadData *load_data, uint8_t *buf, size_t bufsz) {
bool ok = false; bool ok = false;
z_stream z; z_stream z;
@@ -272,7 +272,7 @@ png_error_handler(const char *code, const char *msg) {
set_command_failed_response(code, "%s", msg); set_command_failed_response(code, "%s", msg);
} }
static inline bool static bool
inflate_png(LoadData *load_data, uint8_t *buf, size_t bufsz) { inflate_png(LoadData *load_data, uint8_t *buf, size_t bufsz) {
png_read_data d = {.err_handler=png_error_handler}; png_read_data d = {.err_handler=png_error_handler};
inflate_png_inner(&d, buf, bufsz); inflate_png_inner(&d, buf, bufsz);
@@ -342,7 +342,7 @@ png_path_to_bitmap(const char* path, uint8_t** data, unsigned int* width, unsign
} }
static inline Image* static Image*
find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) { find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
if (id) { if (id) {
for (size_t i = 0; i < self->image_count; i++) { for (size_t i = 0; i < self->image_count; i++) {
@@ -359,7 +359,7 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
return ans; return ans;
} }
static inline uint32_t static uint32_t
get_free_client_id(const GraphicsManager *self) { get_free_client_id(const GraphicsManager *self) {
if (!self->image_count) return 1; if (!self->image_count) return 1;
uint32_t *client_ids = malloc(sizeof(uint32_t) * self->image_count); uint32_t *client_ids = malloc(sizeof(uint32_t) * self->image_count);
@@ -608,7 +608,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
#undef MAX_DATA_SZ #undef MAX_DATA_SZ
} }
static inline const char* static const char*
finish_command_response(const GraphicsCommand *g, bool data_loaded) { finish_command_response(const GraphicsCommand *g, bool data_loaded) {
static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128]; static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
bool is_ok_response = !command_response[0]; bool is_ok_response = !command_response[0];
@@ -638,7 +638,7 @@ finish_command_response(const GraphicsCommand *g, bool data_loaded) {
// Displaying images {{{ // Displaying images {{{
static inline void static void
update_src_rect(ImageRef *ref, Image *img) { update_src_rect(ImageRef *ref, Image *img) {
// The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image // The src rect in OpenGL co-ords [0, 1] with origin at top-left corner of image
ref->src_rect.left = (float)ref->src_x / (float)img->width; ref->src_rect.left = (float)ref->src_x / (float)img->width;
@@ -647,7 +647,7 @@ update_src_rect(ImageRef *ref, Image *img) {
ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height; ref->src_rect.bottom = (float)(ref->src_y + ref->src_height) / (float)img->height;
} }
static inline void static void
update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) { update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelSize cell) {
uint32_t t; uint32_t t;
if (num_cols == 0) { if (num_cols == 0) {
@@ -708,7 +708,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
return img->client_id; return img->client_id;
} }
static inline void static void
set_vertex_data(ImageRenderData *rd, const ImageRef *ref, const ImageRect *dest_rect) { set_vertex_data(ImageRenderData *rd, const ImageRef *ref, const ImageRect *dest_rect) {
#define R(n, a, b) rd->vertices[n*4] = ref->src_rect.a; rd->vertices[n*4 + 1] = ref->src_rect.b; rd->vertices[n*4 + 2] = dest_rect->a; rd->vertices[n*4 + 3] = dest_rect->b; #define R(n, a, b) rd->vertices[n*4] = ref->src_rect.a; rd->vertices[n*4 + 1] = ref->src_rect.b; rd->vertices[n*4 + 2] = dest_rect->a; rd->vertices[n*4 + 3] = dest_rect->b;
R(0, right, top); R(1, right, bottom); R(2, left, bottom); R(3, left, top); R(0, right, top); R(1, right, bottom); R(2, left, bottom); R(3, left, top);
@@ -804,13 +804,13 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
// Animation {{{ // Animation {{{
#define DEFAULT_GAP 40 #define DEFAULT_GAP 40
static inline Frame* static Frame*
current_frame(Image *img) { current_frame(Image *img) {
if (img->current_frame_index > img->extra_framecnt) return NULL; if (img->current_frame_index > img->extra_framecnt) return NULL;
return img->current_frame_index ? img->extra_frames + img->current_frame_index - 1 : &img->root_frame; return img->current_frame_index ? img->extra_frames + img->current_frame_index - 1 : &img->root_frame;
} }
static inline Frame* static Frame*
frame_for_id(Image *img, const uint32_t frame_id) { frame_for_id(Image *img, const uint32_t frame_id) {
if (img->root_frame.id == frame_id) return &img->root_frame; if (img->root_frame.id == frame_id) return &img->root_frame;
for (unsigned i = 0; i < img->extra_framecnt; i++) { for (unsigned i = 0; i < img->extra_framecnt; i++) {
@@ -832,7 +832,7 @@ frame_for_number(Image *img, const uint32_t frame_number) {
} }
} }
static inline void static void
change_gap(Image *img, Frame *f, int32_t gap) { change_gap(Image *img, Frame *f, int32_t gap) {
uint32_t prev_gap = f->gap; uint32_t prev_gap = f->gap;
f->gap = MAX(0, gap); f->gap = MAX(0, gap);
@@ -845,14 +845,14 @@ typedef struct {
bool is_4byte_aligned, is_opaque; bool is_4byte_aligned, is_opaque;
} CoalescedFrameData; } CoalescedFrameData;
static inline void static void
blend_on_opaque(uint8_t *under_px, const uint8_t *over_px) { blend_on_opaque(uint8_t *under_px, const uint8_t *over_px) {
const float alpha = (float)over_px[3] / 255.f; const float alpha = (float)over_px[3] / 255.f;
const float alpha_op = 1.f - alpha; const float alpha_op = 1.f - alpha;
for (unsigned i = 0; i < 3; i++) under_px[i] = (uint8_t)(over_px[i] * alpha + under_px[i] * alpha_op); for (unsigned i = 0; i < 3; i++) under_px[i] = (uint8_t)(over_px[i] * alpha + under_px[i] * alpha_op);
} }
static inline void static void
alpha_blend(uint8_t *dest_px, const uint8_t *src_px) { alpha_blend(uint8_t *dest_px, const uint8_t *src_px) {
if (src_px[3]) { if (src_px[3]) {
const float dest_a = (float)dest_px[3] / 255.f, src_a = (float)src_px[3] / 255.f; const float dest_a = (float)dest_px[3] / 255.f, src_a = (float)src_px[3] / 255.f;
@@ -1255,7 +1255,7 @@ handle_animation_control_command(GraphicsManager *self, bool *is_dirty, const Gr
} }
} }
static inline bool static bool
image_is_animatable(const Image *img) { image_is_animatable(const Image *img) {
return img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->is_drawn && img->animation_duration && ( return img->animation_state != ANIMATION_STOPPED && img->extra_framecnt && img->is_drawn && img->animation_duration && (
!img->max_loops || img->current_loop < img->max_loops); !img->max_loops || img->current_loop < img->max_loops);
@@ -1368,7 +1368,7 @@ handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsComm
// Image lifetime/scrolling {{{ // Image lifetime/scrolling {{{
static inline void static void
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image) { filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image) {
bool matched = false; bool matched = false;
for (size_t i = self->image_count; i-- > 0;) { for (size_t i = self->image_count; i-- > 0;) {
@@ -1403,24 +1403,24 @@ modify_refs(GraphicsManager *self, const void* data, bool (*filter_func)(ImageRe
} }
static inline bool static bool
scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { scroll_filter_func(ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
ScrollData *d = (ScrollData*)data; ScrollData *d = (ScrollData*)data;
ref->start_row += d->amt; ref->start_row += d->amt;
return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit; return ref->start_row + (int32_t)ref->effective_num_rows <= d->limit;
} }
static inline bool static bool
ref_within_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) { ref_within_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom; return ref->start_row >= (int32_t)margin_top && ref->start_row + ref->effective_num_rows <= margin_bottom;
} }
static inline bool static bool
ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) { ref_outside_region(const ImageRef *ref, index_type margin_top, index_type margin_bottom) {
return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom; return ref->start_row + ref->effective_num_rows <= margin_top || ref->start_row > (int32_t)margin_bottom;
} }
static inline bool static bool
scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) { scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixelSize cell) {
ScrollData *d = (ScrollData*)data; ScrollData *d = (ScrollData*)data;
if (ref_within_region(ref, d->margin_top, d->margin_bottom)) { if (ref_within_region(ref, d->margin_top, d->margin_bottom)) {
@@ -1459,12 +1459,12 @@ grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize
} }
} }
static inline bool static bool
clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { clear_filter_func(const ImageRef *ref, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
return ref->start_row + (int32_t)ref->effective_num_rows > 0; return ref->start_row + (int32_t)ref->effective_num_rows > 0;
} }
static inline bool static bool
clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) { clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data, CellPixelSize cell UNUSED) {
return true; return true;
} }
@@ -1474,14 +1474,14 @@ grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false); filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false);
} }
static inline bool static bool
id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) { id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data; const GraphicsCommand *g = data;
if (g->id && img->client_id == g->id) return !g->placement_id || ref->client_id == g->placement_id; if (g->id && img->client_id == g->id) return !g->placement_id || ref->client_id == g->placement_id;
return false; return false;
} }
static inline bool static bool
number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) { number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data; const GraphicsCommand *g = data;
if (g->image_number && img->client_number == g->image_number) return !g->placement_id || ref->client_id == g->placement_id; if (g->image_number && img->client_number == g->image_number) return !g->placement_id || ref->client_id == g->placement_id;
@@ -1489,31 +1489,31 @@ number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelS
} }
static inline bool static bool
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data; const GraphicsCommand *g = data;
return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols)); return ref->start_column <= (int32_t)g->x_offset - 1 && ((int32_t)g->x_offset - 1) < ((int32_t)(ref->start_column + ref->effective_num_cols));
} }
static inline bool static bool
y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { y_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data; const GraphicsCommand *g = data;
return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows)); return ref->start_row <= (int32_t)g->y_offset - 1 && ((int32_t)(g->y_offset - 1 < ref->start_row + ref->effective_num_rows));
} }
static inline bool static bool
z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) { z_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
const GraphicsCommand *g = data; const GraphicsCommand *g = data;
return ref->z_index == g->z_index; return ref->z_index == g->z_index;
} }
static inline bool static bool
point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) { point_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell); return x_filter_func(ref, img, data, cell) && y_filter_func(ref, img, data, cell);
} }
static inline bool static bool
point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) { point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell) {
return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell); return z_filter_func(ref, img, data, cell) && point_filter_func(ref, img, data, cell);
} }
@@ -1682,7 +1682,7 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
return ans; return ans;
} }
static inline PyObject* static PyObject*
image_as_dict(GraphicsManager *self, Image *img) { image_as_dict(GraphicsManager *self, Image *img) {
#define U(x) #x, (unsigned int)(img->x) #define U(x) #x, (unsigned int)(img->x)
#define B(x) #x, img->x ? Py_True : Py_False #define B(x) #x, img->x ? Py_True : Py_False

View File

@@ -14,7 +14,7 @@
extern PyTypeObject Line_Type; extern PyTypeObject Line_Type;
#define SEGMENT_SIZE 2048 #define SEGMENT_SIZE 2048
static inline void static void
add_segment(HistoryBuf *self) { add_segment(HistoryBuf *self) {
self->num_segments += 1; self->num_segments += 1;
self->segments = realloc(self->segments, sizeof(HistoryBufSegment) * self->num_segments); self->segments = realloc(self->segments, sizeof(HistoryBufSegment) * self->num_segments);
@@ -28,7 +28,7 @@ add_segment(HistoryBuf *self) {
s->line_attrs = (line_attrs_type*)(((uint8_t*)s->gpu_cells) + gpu_cells_size); s->line_attrs = (line_attrs_type*)(((uint8_t*)s->gpu_cells) + gpu_cells_size);
} }
static inline index_type static index_type
segment_for(HistoryBuf *self, index_type y) { segment_for(HistoryBuf *self, index_type y) {
index_type seg_num = y / SEGMENT_SIZE; index_type seg_num = y / SEGMENT_SIZE;
while (UNLIKELY(seg_num >= self->num_segments && SEGMENT_SIZE * self->num_segments < self->ynum)) add_segment(self); while (UNLIKELY(seg_num >= self->num_segments && SEGMENT_SIZE * self->num_segments < self->ynum)) add_segment(self);
@@ -42,23 +42,23 @@ segment_for(HistoryBuf *self, index_type y) {
return self->segments[seg_num].which + y * stride; \ return self->segments[seg_num].which + y * stride; \
} }
static inline CPUCell* static CPUCell*
cpu_lineptr(HistoryBuf *self, index_type y) { cpu_lineptr(HistoryBuf *self, index_type y) {
seg_ptr(cpu_cells, self->xnum); seg_ptr(cpu_cells, self->xnum);
} }
static inline GPUCell* static GPUCell*
gpu_lineptr(HistoryBuf *self, index_type y) { gpu_lineptr(HistoryBuf *self, index_type y) {
seg_ptr(gpu_cells, self->xnum); seg_ptr(gpu_cells, self->xnum);
} }
static inline line_attrs_type* static line_attrs_type*
attrptr(HistoryBuf *self, index_type y) { attrptr(HistoryBuf *self, index_type y) {
seg_ptr(line_attrs, 1); seg_ptr(line_attrs, 1);
} }
static inline PagerHistoryBuf* static PagerHistoryBuf*
alloc_pagerhist(size_t pagerhist_sz) { alloc_pagerhist(size_t pagerhist_sz) {
PagerHistoryBuf *ph; PagerHistoryBuf *ph;
if (!pagerhist_sz) return NULL; if (!pagerhist_sz) return NULL;
@@ -71,14 +71,14 @@ alloc_pagerhist(size_t pagerhist_sz) {
return ph; return ph;
} }
static inline void static void
free_pagerhist(HistoryBuf *self) { free_pagerhist(HistoryBuf *self) {
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf); if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf);
free(self->pagerhist); free(self->pagerhist);
self->pagerhist = NULL; self->pagerhist = NULL;
} }
static inline bool static bool
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) { pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
size_t buffer_size = ringbuf_capacity(ph->ringbuf); size_t buffer_size = ringbuf_capacity(ph->ringbuf);
if (buffer_size >= ph->maximum_size) return false; if (buffer_size >= ph->maximum_size) return false;
@@ -92,7 +92,7 @@ pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
return true; return true;
} }
static inline void static void
pagerhist_clear(HistoryBuf *self) { pagerhist_clear(HistoryBuf *self) {
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf); if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf);
} }
@@ -133,7 +133,7 @@ dealloc(HistoryBuf* self) {
Py_TYPE(self)->tp_free((PyObject*)self); Py_TYPE(self)->tp_free((PyObject*)self);
} }
static inline index_type static index_type
index_of(HistoryBuf *self, index_type lnum) { index_of(HistoryBuf *self, index_type lnum) {
// The index (buffer position) of the line with line number lnum // The index (buffer position) of the line with line number lnum
// This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer. // This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer.
@@ -142,7 +142,7 @@ index_of(HistoryBuf *self, index_type lnum) {
return (self->start_of_data + idx) % self->ynum; return (self->start_of_data + idx) % self->ynum;
} }
static inline void static void
init_line(HistoryBuf *self, index_type num, Line *l) { init_line(HistoryBuf *self, index_type num, Line *l) {
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num // Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
l->cpu_cells = cpu_lineptr(self, num); l->cpu_cells = cpu_lineptr(self, num);
@@ -180,7 +180,7 @@ historybuf_clear(HistoryBuf *self) {
self->start_of_data = 0; self->start_of_data = 0;
} }
static inline bool static bool
pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) { pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
if (sz > ph->maximum_size) return false; if (sz > ph->maximum_size) return false;
if (!sz) return true; if (!sz) return true;
@@ -190,7 +190,7 @@ pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
return true; return true;
} }
static inline bool static bool
pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) { pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
uint8_t scratch[8]; uint8_t scratch[8];
size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch)); size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch));
@@ -210,7 +210,7 @@ pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
return false; return false;
} }
static inline bool static bool
pagerhist_write_ucs4(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) { pagerhist_write_ucs4(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) {
uint8_t scratch[4]; uint8_t scratch[4];
for (size_t i = 0; i < sz; i++) { for (size_t i = 0; i < sz; i++) {
@@ -220,7 +220,7 @@ pagerhist_write_ucs4(PagerHistoryBuf *ph, const Py_UCS4 *buf, size_t sz) {
return true; return true;
} }
static inline void static void
pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) { pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
PagerHistoryBuf *ph = self->pagerhist; PagerHistoryBuf *ph = self->pagerhist;
if (!ph) return; if (!ph) return;
@@ -233,7 +233,7 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1); if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1);
} }
static inline index_type static index_type
historybuf_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) { historybuf_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
index_type idx = (self->start_of_data + self->count) % self->ynum; index_type idx = (self->start_of_data + self->count) % self->ynum;
init_line(self, idx, self->line); init_line(self, idx, self->line);
@@ -327,10 +327,10 @@ end:
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline Line* static Line*
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; } get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
static inline char_type static char_type
pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) { pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) {
uint32_t codep; UTF8State state = UTF8_ACCEPT; uint32_t codep; UTF8State state = UTF8_ACCEPT;
*count = 0; *count = 0;

View File

@@ -46,7 +46,7 @@ is_modifier_key(const uint32_t key) {
END_ALLOW_CASE_RANGE END_ALLOW_CASE_RANGE
} }
static inline void static void
convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) { convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) {
if (!key_encoding_flags) mods &= ~GLFW_LOCK_MASK; if (!key_encoding_flags) mods &= ~GLFW_LOCK_MASK;
ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0, ev->mods.hyper = (mods & GLFW_MOD_HYPER) > 0, ev->mods.meta = (mods & GLFW_MOD_META) > 0; ev->mods.alt = (mods & GLFW_MOD_ALT) > 0, ev->mods.ctrl = (mods & GLFW_MOD_CONTROL) > 0, ev->mods.shift = (mods & GLFW_MOD_SHIFT) > 0, ev->mods.super = (mods & GLFW_MOD_SUPER) > 0, ev->mods.hyper = (mods & GLFW_MOD_HYPER) > 0, ev->mods.meta = (mods & GLFW_MOD_META) > 0;
@@ -63,7 +63,7 @@ convert_glfw_mods(int mods, KeyEvent *ev, const unsigned key_encoding_flags) {
} }
static inline void static void
init_encoding_data(EncodingData *ans, const KeyEvent *ev) { init_encoding_data(EncodingData *ans, const KeyEvent *ev) {
ans->add_actions = ev->report_all_event_types && ev->action != PRESS; ans->add_actions = ev->report_all_event_types && ev->action != PRESS;
ans->has_mods = ev->mods.encoded[0] && ( ev->mods.encoded[0] != '1' || ev->mods.encoded[1] ); ans->has_mods = ev->mods.encoded[0] && ( ev->mods.encoded[0] != '1' || ev->mods.encoded[1] );
@@ -76,7 +76,7 @@ init_encoding_data(EncodingData *ans, const KeyEvent *ev) {
memcpy(ans->encoded_mods, ev->mods.encoded, sizeof(ans->encoded_mods)); memcpy(ans->encoded_mods, ev->mods.encoded, sizeof(ans->encoded_mods));
} }
static inline int static int
serialize(const EncodingData *data, char *output, const char csi_trailer) { serialize(const EncodingData *data, char *output, const char csi_trailer) {
int pos = 0; int pos = 0;
bool second_field_not_empty = data->has_mods || data->add_actions; bool second_field_not_empty = data->has_mods || data->add_actions;
@@ -113,7 +113,7 @@ serialize(const EncodingData *data, char *output, const char csi_trailer) {
return pos; return pos;
} }
static inline uint32_t static uint32_t
convert_kp_key_to_normal_key(uint32_t key_number) { convert_kp_key_to_normal_key(uint32_t key_number) {
switch(key_number) { switch(key_number) {
#define S(x) case GLFW_FKEY_KP_##x: key_number = GLFW_FKEY_##x; break; #define S(x) case GLFW_FKEY_KP_##x: key_number = GLFW_FKEY_##x; break;
@@ -329,7 +329,7 @@ encode_printable_ascii_key_legacy(const KeyEvent *ev, char *output) {
return 0; return 0;
} }
static inline bool static bool
is_legacy_ascii_key(uint32_t key) { is_legacy_ascii_key(uint32_t key) {
START_ALLOW_CASE_RANGE START_ALLOW_CASE_RANGE
switch (key) { switch (key) {
@@ -408,7 +408,7 @@ encode_key(const KeyEvent *ev, char *output) {
return serialize(&ed, output, 'u'); return serialize(&ed, output, 'u');
} }
static inline bool static bool
startswith_ascii_control_char(const char *p) { startswith_ascii_control_char(const char *p) {
if (!p || !*p) return true; if (!p || !*p) return true;
uint32_t codep; UTF8State state = UTF8_ACCEPT; uint32_t codep; UTF8State state = UTF8_ACCEPT;

View File

@@ -20,7 +20,7 @@ typedef struct {
PyObject *text; PyObject *text;
} PyKeyEvent; } PyKeyEvent;
static inline PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev); static PyObject* convert_glfw_key_event_to_python(const GLFWkeyevent *ev);
static PyObject* static PyObject*
new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) { new(PyTypeObject *type UNUSED, PyObject *args, PyObject *kw) {
@@ -58,7 +58,7 @@ PyTypeObject PyKeyEvent_Type = {
.tp_new = new, .tp_new = new,
}; };
static inline PyObject* static PyObject*
convert_glfw_key_event_to_python(const GLFWkeyevent *ev) { convert_glfw_key_event_to_python(const GLFWkeyevent *ev) {
PyKeyEvent *self = (PyKeyEvent*)PyKeyEvent_Type.tp_alloc(&PyKeyEvent_Type, 0); PyKeyEvent *self = (PyKeyEvent*)PyKeyEvent_Type.tp_alloc(&PyKeyEvent_Type, 0);
if (!self) return NULL; if (!self) return NULL;
@@ -71,7 +71,7 @@ convert_glfw_key_event_to_python(const GLFWkeyevent *ev) {
} }
// }}} // }}}
static inline Window* static Window*
active_window(void) { active_window(void) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
Window *w = t->windows + t->active_window; Window *w = t->windows + t->active_window;
@@ -79,7 +79,7 @@ active_window(void) {
return NULL; return NULL;
} }
static inline void static void
update_ime_position(OSWindow *os_window, Window* w, Screen *screen) { update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height; unsigned int cell_width = os_window->fonts_data->cell_width, cell_height = os_window->fonts_data->cell_height;
unsigned int left = w->geometry.left, top = w->geometry.top; unsigned int left = w->geometry.left, top = w->geometry.top;

View File

@@ -11,7 +11,7 @@
#define CMD_BUF_SZ 2048 #define CMD_BUF_SZ 2048
static inline bool static bool
append_buf(char buf[CMD_BUF_SZ], size_t *pos, PyObject *ans) { append_buf(char buf[CMD_BUF_SZ], size_t *pos, PyObject *ans) {
if (*pos) { if (*pos) {
PyObject *bytes = PyBytes_FromStringAndSize(buf, *pos); PyObject *bytes = PyBytes_FromStringAndSize(buf, *pos);
@@ -24,7 +24,7 @@ append_buf(char buf[CMD_BUF_SZ], size_t *pos, PyObject *ans) {
return true; return true;
} }
static inline bool static bool
add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) { add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) {
if (*pos >= CMD_BUF_SZ) { if (*pos >= CMD_BUF_SZ) {
if (!append_buf(buf, pos, ans)) return false; if (!append_buf(buf, pos, ans)) return false;
@@ -34,7 +34,7 @@ add_char(char buf[CMD_BUF_SZ], size_t *pos, char ch, PyObject *ans) {
return true; return true;
} }
static inline bool static bool
read_response(int fd, monotonic_t timeout, PyObject *ans) { read_response(int fd, monotonic_t timeout, PyObject *ans) {
static char buf[CMD_BUF_SZ]; static char buf[CMD_BUF_SZ];
size_t pos = 0; size_t pos = 0;

View File

@@ -21,7 +21,7 @@ from .window import Watchers, Window
try: try:
from typing import TypedDict from typing import TypedDict
except ImportError: except ImportError:
TypedDict = Dict[str, Any] TypedDict = dict
class LaunchSpec(NamedTuple): class LaunchSpec(NamedTuple):

View File

@@ -12,17 +12,17 @@
extern PyTypeObject Line_Type; extern PyTypeObject Line_Type;
extern PyTypeObject HistoryBuf_Type; extern PyTypeObject HistoryBuf_Type;
static inline CPUCell* static CPUCell*
cpu_lineptr(LineBuf *linebuf, index_type y) { cpu_lineptr(LineBuf *linebuf, index_type y) {
return linebuf->cpu_cell_buf + y * linebuf->xnum; return linebuf->cpu_cell_buf + y * linebuf->xnum;
} }
static inline GPUCell* static GPUCell*
gpu_lineptr(LineBuf *linebuf, index_type y) { gpu_lineptr(LineBuf *linebuf, index_type y) {
return linebuf->gpu_cell_buf + y * linebuf->xnum; return linebuf->gpu_cell_buf + y * linebuf->xnum;
} }
static inline void static void
clear_chars_to(LineBuf* linebuf, index_type y, char_type ch) { clear_chars_to(LineBuf* linebuf, index_type y, char_type ch) {
clear_chars_in_line(cpu_lineptr(linebuf, y), gpu_lineptr(linebuf, y), linebuf->xnum, ch); clear_chars_in_line(cpu_lineptr(linebuf, y), gpu_lineptr(linebuf, y), linebuf->xnum, ch);
} }
@@ -118,7 +118,7 @@ dealloc(LineBuf* self) {
Py_TYPE(self)->tp_free((PyObject*)self); Py_TYPE(self)->tp_free((PyObject*)self);
} }
static inline void static void
init_line(LineBuf *lb, Line *l, index_type ynum) { init_line(LineBuf *lb, Line *l, index_type ynum) {
l->cpu_cells = cpu_lineptr(lb, ynum); l->cpu_cells = cpu_lineptr(lb, ynum);
l->gpu_cells = gpu_lineptr(lb, ynum); l->gpu_cells = gpu_lineptr(lb, ynum);
@@ -193,7 +193,7 @@ dirty_lines(LineBuf *self, PyObject *a UNUSED) {
return ans; return ans;
} }
static inline bool static bool
allocate_line_storage(Line *line, bool initialize) { allocate_line_storage(Line *line, bool initialize) {
if (initialize) { if (initialize) {
line->cpu_cells = PyMem_Calloc(line->xnum, sizeof(CPUCell)); line->cpu_cells = PyMem_Calloc(line->xnum, sizeof(CPUCell));
@@ -209,7 +209,7 @@ allocate_line_storage(Line *line, bool initialize) {
return true; return true;
} }
static inline PyObject* static PyObject*
create_line_copy_inner(LineBuf* self, index_type y) { create_line_copy_inner(LineBuf* self, index_type y) {
Line src, *line; Line src, *line;
line = alloc_line(); line = alloc_line();
@@ -247,7 +247,7 @@ copy_line_to(LineBuf *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline void static void
clear_line_(Line *l, index_type xnum) { clear_line_(Line *l, index_type xnum) {
zero_at_ptr_count(l->cpu_cells, xnum); zero_at_ptr_count(l->cpu_cells, xnum);
zero_at_ptr_count(l->gpu_cells, xnum); zero_at_ptr_count(l->gpu_cells, xnum);
@@ -439,7 +439,7 @@ end:
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline Line* static Line*
get_line(void *x, int y) { get_line(void *x, int y) {
LineBuf *self = (LineBuf*)x; LineBuf *self = (LineBuf*)x;
linebuf_init_line(self, MAX(0, y)); linebuf_init_line(self, MAX(0, y));

View File

@@ -49,7 +49,7 @@ cell_text(CPUCell *cell) {
// URL detection {{{ // URL detection {{{
static inline index_type static index_type
find_colon_slash(Line *self, index_type x, index_type limit) { find_colon_slash(Line *self, index_type x, index_type limit) {
// Find :// at or before x // Find :// at or before x
index_type pos = MIN(x, self->xnum - 1); index_type pos = MIN(x, self->xnum - 1);
@@ -84,7 +84,7 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
return 0; return 0;
} }
static inline bool static bool
prefix_matches(Line *self, index_type at, const char_type* prefix, index_type prefix_len) { prefix_matches(Line *self, index_type at, const char_type* prefix, index_type prefix_len) {
if (prefix_len > at) return false; if (prefix_len > at) return false;
index_type p, i; index_type p, i;
@@ -94,7 +94,7 @@ prefix_matches(Line *self, index_type at, const char_type* prefix, index_type pr
return i == prefix_len; return i == prefix_len;
} }
static inline bool static bool
has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_type *ans) { has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_type *ans) {
for (size_t i = 0; i < OPT(url_prefixes.num); i++) { for (size_t i = 0; i < OPT(url_prefixes.num); i++) {
index_type prefix_len = OPT(url_prefixes.values[i].len); index_type prefix_len = OPT(url_prefixes.values[i].len);
@@ -106,7 +106,7 @@ has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_ty
#define MIN_URL_LEN 5 #define MIN_URL_LEN 5
static inline bool static bool
has_url_beyond(Line *self, index_type x) { has_url_beyond(Line *self, index_type x) {
for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) { for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) {
if (!is_url_char(self->cpu_cells[i].ch)) return false; if (!is_url_char(self->cpu_cells[i].ch)) return false;
@@ -268,7 +268,7 @@ sprite_at(Line* self, PyObject *x) {
return Py_BuildValue("HHH", c->sprite_x, c->sprite_y, c->sprite_z); return Py_BuildValue("HHH", c->sprite_x, c->sprite_y, c->sprite_z);
} }
static inline void static void
write_sgr(const char *val, ANSIBuf *output) { write_sgr(const char *val, ANSIBuf *output) {
#define W(c) output->buf[output->len++] = c #define W(c) output->buf[output->len++] = c
W(0x1b); W('['); W(0x1b); W('[');
@@ -277,7 +277,7 @@ write_sgr(const char *val, ANSIBuf *output) {
#undef W #undef W
} }
static inline void static void
write_hyperlink(hyperlink_id_type hid, ANSIBuf *output) { write_hyperlink(hyperlink_id_type hid, ANSIBuf *output) {
#define W(c) output->buf[output->len++] = c #define W(c) output->buf[output->len++] = c
const char *key = hid ? get_hyperlink_for_id(output->hyperlink_pool, hid, false) : NULL; const char *key = hid ? get_hyperlink_for_id(output->hyperlink_pool, hid, false) : NULL;
@@ -636,7 +636,7 @@ set_attribute(Line *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline int static int
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) { color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
switch(val & 0xff) { switch(val & 0xff) {
case 1: case 1:
@@ -652,7 +652,7 @@ color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsi
} }
} }
static inline const char* static const char*
decoration_as_sgr(uint8_t decoration) { decoration_as_sgr(uint8_t decoration) {
switch(decoration) { switch(decoration) {
case 1: return "4;"; case 1: return "4;";
@@ -722,7 +722,7 @@ line_has_mark(Line *line, attrs_type mark) {
return false; return false;
} }
static inline void static void
report_marker_error(PyObject *marker) { report_marker_error(PyObject *marker) {
if (!PyObject_HasAttrString(marker, "error_reported")) { if (!PyObject_HasAttrString(marker, "error_reported")) {
PyErr_Print(); PyErr_Print();
@@ -730,7 +730,7 @@ report_marker_error(PyObject *marker) {
} else PyErr_Clear(); } else PyErr_Clear();
} }
static inline void static void
apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int *match_pos) { apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int *match_pos) {
#define MARK { line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK; line->gpu_cells[x].attrs |= mark; } #define MARK { line->gpu_cells[x].attrs &= ATTRS_MASK_WITHOUT_MARK; line->gpu_cells[x].attrs |= mark; }
index_type x = *cell_pos; index_type x = *cell_pos;
@@ -757,7 +757,7 @@ apply_mark(Line *line, const attrs_type mark, index_type *cell_pos, unsigned int
#undef MARK #undef MARK
} }
static inline void static void
apply_marker(PyObject *marker, Line *line, const PyObject *text) { apply_marker(PyObject *marker, Line *line, const PyObject *text) {
unsigned int l=0, r=0, col=0, match_pos=0; unsigned int l=0, r=0, col=0, match_pos=0;
PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col); PyObject *pl = PyLong_FromVoidPtr(&l), *pr = PyLong_FromVoidPtr(&r), *pcol = PyLong_FromVoidPtr(&col);

View File

@@ -23,7 +23,7 @@ cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
} }
// Read the maximum argument size for processes // Read the maximum argument size for processes
static inline int static int
get_argmax() { get_argmax() {
int argmax; int argmax;
int mib[] = { CTL_KERN, KERN_ARGMAX }; int mib[] = { CTL_KERN, KERN_ARGMAX };
@@ -34,6 +34,25 @@ get_argmax() {
return 0; return 0;
} }
static PyObject*
get_all_processes(PyObject *self UNUSED, PyObject *args UNUSED) {
pid_t num = proc_listallpids(NULL, 0);
if (num <= 0) return PyTuple_New(0);
size_t sz = sizeof(pid_t) * num * 2;
pid_t *buf = malloc(sz);
if (!buf) return PyErr_NoMemory();
num = proc_listallpids(buf, sz);
if (num <= 0) { free(buf); return PyTuple_New(0); }
PyObject *ans = PyTuple_New(num);
if (!ans) { free(buf); return NULL; }
for (pid_t i = 0; i < num; i++) {
long long pid = buf[i];
PyObject *t = PyLong_FromLongLong(pid);
if (!t) { free(buf); Py_CLEAR(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, t);
}
return ans;
}
static PyObject* static PyObject*
cmdline_of_process(PyObject *self UNUSED, PyObject *pid_) { cmdline_of_process(PyObject *self UNUSED, PyObject *pid_) {
@@ -255,6 +274,7 @@ static PyMethodDef module_methods[] = {
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""}, {"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{"cmdline_of_process", (PyCFunction)cmdline_of_process, METH_O, ""}, {"cmdline_of_process", (PyCFunction)cmdline_of_process, METH_O, ""},
{"environ_of_process", (PyCFunction)environ_of_process, METH_O, ""}, {"environ_of_process", (PyCFunction)environ_of_process, METH_O, ""},
{"get_all_processes", (PyCFunction)get_all_processes, METH_NOARGS, ""},
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@@ -157,7 +157,7 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ())
run_app.initial_window_size_func(get_os_window_sizing_data(opts), cached_values), run_app.initial_window_size_func(get_os_window_sizing_data(opts), cached_values),
pre_show_callback, pre_show_callback,
args.title or appname, args.name or args.cls or appname, args.title or appname, args.name or args.cls or appname,
args.cls or appname, load_all_shaders) args.cls or appname, load_all_shaders, disallow_override_title=bool(args.title))
boss = Boss(opts, args, cached_values, global_shortcuts) boss = Boss(opts, args, cached_values, global_shortcuts)
boss.start(window_id) boss.start(window_id)
if bad_lines: if bad_lines:
@@ -195,10 +195,15 @@ run_app = AppRunner()
def ensure_macos_locale() -> None: def ensure_macos_locale() -> None:
# Ensure the LANG env var is set. See # Ensure the LANG env var is set. See
# https://github.com/kovidgoyal/kitty/issues/90 # https://github.com/kovidgoyal/kitty/issues/90
from .fast_data_types import cocoa_get_lang from .fast_data_types import cocoa_get_lang, locale_is_valid
if 'LANG' not in os.environ: if 'LANG' not in os.environ:
lang = cocoa_get_lang() lang = cocoa_get_lang()
if lang is not None: if lang is not None:
if not locale_is_valid(lang):
if lang.startswith('en_'):
lang = 'en_US'
else:
log_error(f'Could not set LANG Cocoa returns language as: {lang}')
os.environ['LANG'] = lang + '.UTF-8' os.environ['LANG'] = lang + '.UTF-8'

View File

@@ -30,7 +30,7 @@ typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
#define EXTRA_BUTTON_INDICATOR (1 << 7) #define EXTRA_BUTTON_INDICATOR (1 << 7)
static inline unsigned int static unsigned int
button_map(int button) { button_map(int button) {
switch(button) { switch(button) {
case GLFW_MOUSE_BUTTON_LEFT: case GLFW_MOUSE_BUTTON_LEFT:
@@ -50,7 +50,7 @@ button_map(int button) {
} }
} }
static inline unsigned int static unsigned int
encode_button(unsigned int button) { encode_button(unsigned int button) {
if (button >= 8 && button <= 11) { if (button >= 8 && button <= 11) {
return (button - 8) | EXTRA_BUTTON_INDICATOR; return (button - 8) | EXTRA_BUTTON_INDICATOR;
@@ -65,7 +65,7 @@ encode_button(unsigned int button) {
static char mouse_event_buf[64]; static char mouse_event_buf[64];
static inline int static int
encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_protocol, int button, MouseAction action, int mods) { encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_protocol, int button, MouseAction action, int mods) {
unsigned int cb = 0; unsigned int cb = 0;
if (action == MOVE) { if (action == MOVE) {
@@ -161,33 +161,33 @@ dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabb
return handled; return handled;
} }
static inline unsigned int static unsigned int
window_left(Window *w) { window_left(Window *w) {
return w->geometry.left - w->padding.left; return w->geometry.left - w->padding.left;
} }
static inline unsigned int static unsigned int
window_right(Window *w) { window_right(Window *w) {
return w->geometry.right + w->padding.right; return w->geometry.right + w->padding.right;
} }
static inline unsigned int static unsigned int
window_top(Window *w) { window_top(Window *w) {
return w->geometry.top - w->padding.top; return w->geometry.top - w->padding.top;
} }
static inline unsigned int static unsigned int
window_bottom(Window *w) { window_bottom(Window *w) {
return w->geometry.bottom + w->padding.bottom; return w->geometry.bottom + w->padding.bottom;
} }
static inline bool static bool
contains_mouse(Window *w) { contains_mouse(Window *w) {
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w)); return (w->visible && window_left(w) <= x && x <= window_right(w) && window_top(w) <= y && y <= window_bottom(w));
} }
static inline double static double
distance_to_window(Window *w) { distance_to_window(Window *w) {
double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y; double x = global_state.callback_os_window->mouse_x, y = global_state.callback_os_window->mouse_y;
double cx = (window_left(w) + window_right(w)) / 2.0; double cx = (window_left(w) + window_right(w)) / 2.0;
@@ -197,7 +197,7 @@ distance_to_window(Window *w) {
static bool clamp_to_window = false; static bool clamp_to_window = false;
static inline bool static bool
cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_cell, OSWindow *os_window) { cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_cell, OSWindow *os_window) {
WindowGeometry *g = &w->geometry; WindowGeometry *g = &w->geometry;
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
@@ -232,9 +232,9 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
return false; return false;
} }
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx) #define HANDLER(name) static void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
static inline void static void
set_mouse_cursor_when_dragging(void) { set_mouse_cursor_when_dragging(void) {
if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) { if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) {
mouse_cursor_shape = OPT(pointer_shape_when_dragging); mouse_cursor_shape = OPT(pointer_shape_when_dragging);
@@ -242,7 +242,7 @@ set_mouse_cursor_when_dragging(void) {
} }
} }
static inline void static void
update_drag(Window *w) { update_drag(Window *w) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen && screen->selections.in_progress) { if (screen && screen->selections.in_progress) {
@@ -251,7 +251,7 @@ update_drag(Window *w) {
set_mouse_cursor_when_dragging(); set_mouse_cursor_when_dragging();
} }
static inline bool static bool
do_drag_scroll(Window *w, bool upwards) { do_drag_scroll(Window *w, bool upwards) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen->linebuf == screen->main_linebuf) { if (screen->linebuf == screen->main_linebuf) {
@@ -280,7 +280,7 @@ drag_scroll(Window *w, OSWindow *frame) {
return false; return false;
} }
static inline void static void
extend_selection(Window *w, bool ended, bool extend_nearest) { extend_selection(Window *w, bool ended, bool extend_nearest) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen_has_selection(screen)) { if (screen_has_selection(screen)) {
@@ -289,12 +289,12 @@ extend_selection(Window *w, bool ended, bool extend_nearest) {
} }
} }
static inline void static void
set_mouse_cursor_for_screen(Screen *screen) { set_mouse_cursor_for_screen(Screen *screen) {
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed); mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed);
} }
static inline void static void
handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) { handle_mouse_movement_in_kitty(Window *w, int button, bool mouse_cell_changed) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (screen->selections.in_progress && (button == global_state.active_drag_button)) { if (screen->selections.in_progress && (button == global_state.active_drag_button)) {
@@ -343,12 +343,12 @@ HANDLER(handle_move_event) {
} }
} }
static inline double static double
distance(double x1, double y1, double x2, double y2) { distance(double x1, double y1, double x2, double y2) {
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
} }
static inline void static void
clear_click_queue(Window *w, int button) { clear_click_queue(Window *w, int button) {
if (0 <= button && button <= (ssize_t)arraysz(w->click_queues)) w->click_queues[button].length = 0; if (0 <= button && button <= (ssize_t)arraysz(w->click_queues)) w->click_queues[button].length = 0;
} }
@@ -467,7 +467,7 @@ HANDLER(handle_button_event) {
else add_press(w, button, modifiers); else add_press(w, button, modifiers);
} }
static inline int static int
currently_pressed_button(void) { currently_pressed_button(void) {
for (int i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++) { for (int i = 0; i <= GLFW_MOUSE_BUTTON_LAST; i++) {
if (global_state.callback_os_window->mouse_button_pressed[i]) return i; if (global_state.callback_os_window->mouse_button_pressed[i]) return i;
@@ -485,7 +485,7 @@ HANDLER(handle_event) {
} }
} }
static inline void static void
handle_tab_bar_mouse(int button, int UNUSED modifiers) { handle_tab_bar_mouse(int button, int UNUSED modifiers) {
static monotonic_t last_click_at = 0; static monotonic_t last_click_at = 0;
if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return; if (button != GLFW_MOUSE_BUTTON_LEFT || !global_state.callback_os_window->mouse_button_pressed[button]) return;
@@ -495,7 +495,7 @@ handle_tab_bar_mouse(int button, int UNUSED modifiers) {
call_boss(activate_tab_at, "KdO", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, is_double ? Py_True : Py_False); call_boss(activate_tab_at, "KdO", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x, is_double ? Py_True : Py_False);
} }
static inline bool static bool
mouse_in_region(Region *r) { mouse_in_region(Region *r) {
if (r->left == r->right) return false; if (r->left == r->right) return false;
if (global_state.callback_os_window->mouse_y < r->top || global_state.callback_os_window->mouse_y > r->bottom) return false; if (global_state.callback_os_window->mouse_y < r->top || global_state.callback_os_window->mouse_y > r->bottom) return false;
@@ -503,7 +503,7 @@ mouse_in_region(Region *r) {
return true; return true;
} }
static inline Window* static Window*
window_for_id(id_type window_id) { window_for_id(id_type window_id) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) { for (unsigned int i = 0; i < t->num_windows; i++) {
@@ -513,7 +513,7 @@ window_for_id(id_type window_id) {
return NULL; return NULL;
} }
static inline Window* static Window*
window_for_event(unsigned int *window_idx, bool *in_tab_bar) { window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
Region central, tab_bar; Region central, tab_bar;
os_window_regions(global_state.callback_os_window, &central, &tab_bar); os_window_regions(global_state.callback_os_window, &central, &tab_bar);
@@ -530,7 +530,7 @@ window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
return NULL; return NULL;
} }
static inline Window* static Window*
closest_window_for_event(unsigned int *window_idx) { closest_window_for_event(unsigned int *window_idx) {
Window *ans = NULL; Window *ans = NULL;
double closest_distance = UINT_MAX; double closest_distance = UINT_MAX;

View File

@@ -499,6 +499,14 @@ of URLs with a plain click::
automatically end it and no release event will be dispatched. automatically end it and no release event will be dispatched.
''') ''')
opt('clear_all_mouse_actions', 'no',
option_type='clear_all_mouse_actions',
long_text='''
You can have kitty remove all mouse actions seen up to this point.
Useful, for instance, to remove the default mouse actions.
'''
)
mma('Click the link under the mouse cursor when no selection is created', mma('Click the link under the mouse cursor when no selection is created',
'click_url_or_select left click ungrabbed mouse_click_url_or_select', 'click_url_or_select left click ungrabbed mouse_click_url_or_select',
) )
@@ -555,6 +563,10 @@ mma('Paste from the primary selection even when grabbed',
'paste_selection_grabbed shift+middle release ungrabbed,grabbed paste_selection', 'paste_selection_grabbed shift+middle release ungrabbed,grabbed paste_selection',
) )
mma('Discard press event for middle click paste',
'paste_selection_grabbed shift+middle press grabbed discard_event',
)
mma('Start selecting text even when grabbed', mma('Start selecting text even when grabbed',
'start_simple_selection_grabbed shift+left press ungrabbed,grabbed mouse_selection normal', 'start_simple_selection_grabbed shift+left press ungrabbed,grabbed mouse_selection normal',
) )
@@ -872,15 +884,22 @@ tab.
opt('tab_bar_style', 'fade', opt('tab_bar_style', 'fade',
choices=('fade', 'hidden', 'powerline', 'separator'), ctype='!tab_bar_style', choices=('fade', 'hidden', 'powerline', 'separator', 'slant'), ctype='!tab_bar_style',
long_text=''' long_text='''
The tab bar style, can be one of: :code:`fade`, :code:`separator`, The tab bar style, can be one of:
:code:`powerline`, or :code:`hidden`. In the fade style, each tab's edges fade
into the background color, in the separator style, tabs are separated by a :code:`fade`
configurable separator, and the powerline shows the tabs as a continuous line. Each tab's edges fade into the background color (see :opt:`tab_fade`)
If you use the hidden style, you might want to create a mapping for the :code:`slant`
:code:`select_tab` action which presents you with a list of tabs and allows for Tabs look like the tabs in a physical file
easy switching to a tab. :code:`separator`
Tabs are separated by a configurable separator (see :opt:`tab_separator`)
:code:`powerline`
Tabs are shown as a continuous line with "fancy" separators (see :opt:`tab_powerline_style`)
:code:`hidden`
The tab bar is hidden. If you use this, you might want to create a mapping
for the :ref:`action-select_tab` action which presents you with a list
of tabs and allows for easy switching to a tab.
''' '''
) )
@@ -2451,8 +2470,7 @@ running within kitty to control it, with :code:`yes` or only programs that
connect to the socket specified with the :option:`kitty --listen-on` command connect to the socket specified with the :option:`kitty --listen-on` command
line option, if you use the value :code:`socket-only`. The latter is useful if line option, if you use the value :code:`socket-only`. The latter is useful if
you want to prevent programs running on a remote computer over ssh from you want to prevent programs running on a remote computer over ssh from
controlling kitty. Changing this option by reloading the config will only affect controlling kitty. Reloading the config will not affect this setting.
newly created windows.
''' '''
) )

View File

@@ -8,8 +8,8 @@ from kitty.conf.utils import (
) )
from kitty.options.utils import ( from kitty.options.utils import (
active_tab_title_template, adjust_baseline, adjust_line_height, allow_hyperlinks, active_tab_title_template, adjust_baseline, adjust_line_height, allow_hyperlinks,
allow_remote_control, box_drawing_scale, clear_all_shortcuts, clipboard_control, allow_remote_control, box_drawing_scale, clear_all_mouse_actions, clear_all_shortcuts,
config_or_absolute_path, copy_on_select, cursor_text_color, clipboard_control, config_or_absolute_path, copy_on_select, cursor_text_color,
deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias, deprecated_hide_window_decorations_aliases, deprecated_macos_show_window_title_in_menubar_alias,
deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations, deprecated_send_text, disable_ligatures, edge_width, env, font_features, hide_window_decorations,
kitten_alias, macos_option_as_alt, macos_titlebar_color, optional_edge_width, parse_map, kitten_alias, macos_option_as_alt, macos_titlebar_color, optional_edge_width, parse_map,
@@ -90,6 +90,9 @@ class Parser:
def box_drawing_scale(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def box_drawing_scale(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['box_drawing_scale'] = box_drawing_scale(val) ans['box_drawing_scale'] = box_drawing_scale(val)
def clear_all_mouse_actions(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
clear_all_mouse_actions(val, ans)
def clear_all_shortcuts(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def clear_all_shortcuts(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
clear_all_shortcuts(val, ans) clear_all_shortcuts(val, ans)
@@ -1167,7 +1170,7 @@ class Parser:
raise ValueError(f"The value {val} is not a valid choice for tab_bar_style") raise ValueError(f"The value {val} is not a valid choice for tab_bar_style")
ans["tab_bar_style"] = val ans["tab_bar_style"] = val
choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator')) choices_for_tab_bar_style = frozenset(('fade', 'hidden', 'powerline', 'separator', 'slant'))
def tab_fade(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: def tab_fade(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['tab_fade'] = tab_fade(val) ans['tab_fade'] = tab_fade(val)

11
kitty/options/types.py generated
View File

@@ -23,7 +23,7 @@ if typing.TYPE_CHECKING:
choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand'] choices_for_pointer_shape_when_dragging = typing.Literal['arrow', 'beam', 'hand']
choices_for_pointer_shape_when_grabbed = typing.Literal['arrow', 'beam', 'hand'] choices_for_pointer_shape_when_grabbed = typing.Literal['arrow', 'beam', 'hand']
choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart'] choices_for_strip_trailing_spaces = typing.Literal['always', 'never', 'smart']
choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator'] choices_for_tab_bar_style = typing.Literal['fade', 'hidden', 'powerline', 'separator', 'slant']
choices_for_tab_powerline_style = typing.Literal['angled', 'round', 'slanted'] choices_for_tab_powerline_style = typing.Literal['angled', 'round', 'slanted']
choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right'] choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'right']
else: else:
@@ -61,6 +61,7 @@ option_names = ( # {{{
'bold_font', 'bold_font',
'bold_italic_font', 'bold_italic_font',
'box_drawing_scale', 'box_drawing_scale',
'clear_all_mouse_actions',
'clear_all_shortcuts', 'clear_all_shortcuts',
'click_interval', 'click_interval',
'clipboard_control', 'clipboard_control',
@@ -453,6 +454,7 @@ class Options:
bold_font: str = 'auto' bold_font: str = 'auto'
bold_italic_font: str = 'auto' bold_italic_font: str = 'auto'
box_drawing_scale: typing.Tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0) box_drawing_scale: typing.Tuple[float, float, float, float] = (0.001, 1.0, 1.5, 2.0)
clear_all_mouse_actions: bool = False
clear_all_shortcuts: bool = False clear_all_shortcuts: bool = False
click_interval: float = -1.0 click_interval: float = -1.0
clipboard_control: typing.Tuple[str, ...] = ('write-clipboard', 'write-primary') clipboard_control: typing.Tuple[str, ...] = ('write-clipboard', 'write-primary')
@@ -608,8 +610,11 @@ class Options:
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None: def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
self.color_table = array(self.color_table.typecode, self.color_table) self.color_table = array(self.color_table.typecode, self.color_table)
if options_dict is not None: if options_dict is not None:
null = object()
for key in option_names: for key in option_names:
setattr(self, key, options_dict[key]) val = options_dict.get(key, null)
if val is not null:
setattr(self, key, val)
@property @property
def _fields(self) -> typing.Tuple[str, ...]: def _fields(self) -> typing.Tuple[str, ...]:
@@ -886,6 +891,8 @@ defaults.mouse_map = [
MouseMapping(2, 1, -1, True, KeyAction('paste_selection')), MouseMapping(2, 1, -1, True, KeyAction('paste_selection')),
# paste_selection_grabbed # paste_selection_grabbed
MouseMapping(2, 1, -1, False, KeyAction('paste_selection')), MouseMapping(2, 1, -1, False, KeyAction('paste_selection')),
# paste_selection_grabbed
MouseMapping(2, 1, 1, True, KeyAction('discard_event')),
# start_simple_selection_grabbed # start_simple_selection_grabbed
MouseMapping(0, 1, 1, True, KeyAction('mouse_selection', (0,))), MouseMapping(0, 1, 1, True, KeyAction('mouse_selection', (0,))),
# start_simple_selection_grabbed # start_simple_selection_grabbed

View File

@@ -696,6 +696,13 @@ def tab_bar_margin_height(x: str) -> TabBarMarginHeight:
return TabBarMarginHeight(next(ans), next(ans)) return TabBarMarginHeight(next(ans), next(ans))
def clear_all_mouse_actions(val: str, dict_with_parse_results: Optional[Dict[str, Any]] = None) -> bool:
ans = to_bool(val)
if ans and dict_with_parse_results is not None:
dict_with_parse_results['mouse_map'] = [None]
return ans
def clear_all_shortcuts(val: str, dict_with_parse_results: Optional[Dict[str, Any]] = None) -> bool: def clear_all_shortcuts(val: str, dict_with_parse_results: Optional[Dict[str, Any]] = None) -> bool:
ans = to_bool(val) ans = to_bool(val)
if ans and dict_with_parse_results is not None: if ans and dict_with_parse_results is not None:

View File

@@ -23,7 +23,7 @@ static const uint64_t pow_10_array[] = {
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000
}; };
static inline int64_t static int64_t
utoi(const uint32_t *buf, unsigned int sz) { utoi(const uint32_t *buf, unsigned int sz) {
int64_t ans = 0; int64_t ans = 0;
const uint32_t *p = buf; const uint32_t *p = buf;
@@ -45,7 +45,7 @@ utoi(const uint32_t *buf, unsigned int sz) {
} }
static inline const char* static const char*
utf8(char_type codepoint) { utf8(char_type codepoint) {
if (!codepoint) return ""; if (!codepoint) return "";
static char buf[8]; static char buf[8];
@@ -313,7 +313,7 @@ dispatch_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_c
// OSC mode {{{ // OSC mode {{{
static inline bool static bool
parse_osc_8(char *buf, char **id, char **url) { parse_osc_8(char *buf, char **id, char **url) {
char *boundary = strstr(buf, ";"); char *boundary = strstr(buf, ";");
if (boundary == NULL) return false; if (boundary == NULL) return false;
@@ -331,7 +331,7 @@ parse_osc_8(char *buf, char **id, char **url) {
return true; return true;
} }
static inline void static void
dispatch_hyperlink(Screen *screen, size_t pos, size_t size, PyObject DUMP_UNUSED *dump_callback) { dispatch_hyperlink(Screen *screen, size_t pos, size_t size, PyObject DUMP_UNUSED *dump_callback) {
// since the spec says only ASCII printable chars are allowed in OSC 8, we // since the spec says only ASCII printable chars are allowed in OSC 8, we
// can just convert to char* directly // can just convert to char* directly
@@ -481,14 +481,14 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case '/': case '/':
static inline void static void
screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, false, -1); } screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, false, -1); }
static inline void static void
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); } screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
static inline void static void
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1u, count); i++) screen_tab(s); } screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1u, count); i++) screen_tab(s); }
static inline const char* static const char*
repr_csi_params(int *params, unsigned int num_params) { repr_csi_params(int *params, unsigned int num_params) {
if (!num_params) return ""; if (!num_params) return "";
static char buf[256]; static char buf[256];
@@ -633,7 +633,7 @@ parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, int *params, PyObject
#undef SEND_SGR #undef SEND_SGR
} }
static inline unsigned int static unsigned int
parse_region(Region *r, uint32_t *buf, unsigned int num) { parse_region(Region *r, uint32_t *buf, unsigned int num) {
unsigned int i, start, num_params = 0; unsigned int i, start, num_params = 0;
int params[8] = {0}; int params[8] = {0};
@@ -668,7 +668,7 @@ parse_region(Region *r, uint32_t *buf, unsigned int num) {
return i; return i;
} }
static inline const char* static const char*
csi_letter(unsigned code) { csi_letter(unsigned code) {
static char buf[8]; static char buf[8];
if (33 <= code && code <= 126) snprintf(buf, sizeof(buf), "%c", code); if (33 <= code && code <= 126) snprintf(buf, sizeof(buf), "%c", code);
@@ -676,7 +676,7 @@ csi_letter(unsigned code) {
return buf; return buf;
} }
static inline void static void
dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#define AT_MOST_ONE_PARAMETER { \ #define AT_MOST_ONE_PARAMETER { \
if (num_params > 1) { \ if (num_params > 1) { \
@@ -996,7 +996,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
// DCS mode {{{ // DCS mode {{{
static inline bool static bool
startswith(const uint32_t *string, size_t sz, const char *prefix) { startswith(const uint32_t *string, size_t sz, const char *prefix) {
size_t l = strlen(prefix); size_t l = strlen(prefix);
if (sz < l) return false; if (sz < l) return false;
@@ -1008,7 +1008,7 @@ startswith(const uint32_t *string, size_t sz, const char *prefix) {
#define PENDING_MODE_CHAR '=' #define PENDING_MODE_CHAR '='
static inline void static void
dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return; if (screen->parser_buf_pos < 2) return;
switch(screen->parser_buf[0]) { switch(screen->parser_buf[0]) {
@@ -1077,7 +1077,7 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#include "parse-graphics-command.h" #include "parse-graphics-command.h"
static inline void static void
dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return; if (screen->parser_buf_pos < 2) return;
switch(screen->parser_buf[0]) { switch(screen->parser_buf[0]) {
@@ -1093,7 +1093,7 @@ dispatch_apc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
// }}} // }}}
// PM mode {{{ // PM mode {{{
static inline void static void
dispatch_pm(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { dispatch_pm(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return; if (screen->parser_buf_pos < 2) return;
switch(screen->parser_buf[0]) { switch(screen->parser_buf[0]) {
@@ -1144,7 +1144,7 @@ accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback,
return false; return false;
} }
static inline bool static bool
accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { accumulate_dcs(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
switch(ch) { switch(ch) {
case ST: case ST:
@@ -1174,7 +1174,7 @@ END_ALLOW_CASE_RANGE
} }
static inline bool static bool
accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
switch(ch) { switch(ch) {
case ST: case ST:
@@ -1200,7 +1200,7 @@ accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
} }
static inline bool static bool
accumulate_csi(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) { accumulate_csi(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback) {
#define ENSURE_SPACE \ #define ENSURE_SPACE \
if (screen->parser_buf_pos > PARSER_BUF_SZ - 1) { \ if (screen->parser_buf_pos > PARSER_BUF_SZ - 1) { \
@@ -1329,14 +1329,14 @@ extern uint32_t *latin1_charset;
} \ } \
} }
static inline void static void
_parse_bytes(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) { _parse_bytes(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
unsigned int i; unsigned int i;
decode_loop(dispatch, ;); decode_loop(dispatch, ;);
FLUSH_DRAW; FLUSH_DRAW;
} }
static inline size_t static size_t
_parse_bytes_watching_for_pending(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) { _parse_bytes_watching_for_pending(Screen *screen, const uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
unsigned int i; unsigned int i;
decode_loop(dispatch, if (screen->pending_mode.activated_at) goto end); decode_loop(dispatch, if (screen->pending_mode.activated_at) goto end);

12
kitty/rgb.py generated
View File

@@ -4,7 +4,7 @@
import re import re
from contextlib import suppress from contextlib import suppress
from typing import Optional, NamedTuple from typing import NamedTuple, Optional, Tuple
class Color(NamedTuple): class Color(NamedTuple):
@@ -12,6 +12,12 @@ class Color(NamedTuple):
green: int = 0 green: int = 0
blue: int = 0 blue: int = 0
def __truediv__(self, denom: float) -> Tuple[float, float, float]:
return self.red / denom, self.green / denom, self.blue / denom
def as_sgr(self) -> str:
return ':2:{}:{}:{}'.format(*self)
def luminance(self) -> float: def luminance(self) -> float:
return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
@@ -77,7 +83,7 @@ def color_as_sharp(x: Color) -> str:
def color_as_sgr(x: Color) -> str: def color_as_sgr(x: Color) -> str:
return ':2:{}:{}:{}'.format(*x) return x.as_sgr()
def to_color(raw: str, validate: bool = False) -> Optional[Color]: def to_color(raw: str, validate: bool = False) -> Optional[Color]:
@@ -856,8 +862,8 @@ color_names = {
if __name__ == '__main__': if __name__ == '__main__':
# Read RGB color table from specified rgb.txt file # Read RGB color table from specified rgb.txt file
import sys
import pprint import pprint
import sys
data = {} data = {}
with open(sys.argv[-1]) as f: with open(sys.argv[-1]) as f:
for line in f: for line in f:

View File

@@ -26,7 +26,7 @@
#include <sys/param.h> #include <sys/param.h>
#include <assert.h> #include <assert.h>
static inline size_t static size_t
size_t_min(size_t x, size_t y) { size_t_min(size_t x, size_t y) {
return x > y ? y : x; return x > y ? y : x;
} }

View File

@@ -32,14 +32,14 @@ static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECA
// Constructor/destructor {{{ // Constructor/destructor {{{
static inline void static void
clear_selection(Selections *selections) { clear_selection(Selections *selections) {
selections->in_progress = false; selections->in_progress = false;
selections->extend_mode = EXTEND_CELL; selections->extend_mode = EXTEND_CELL;
selections->count = 0; selections->count = 0;
} }
static inline void static void
init_tabstops(bool *tabstops, index_type count) { init_tabstops(bool *tabstops, index_type count) {
// In terminfo we specify the number of initial tabstops (it) as 8 // In terminfo we specify the number of initial tabstops (it) as 8
for (unsigned int t=0; t < count; t++) { for (unsigned int t=0; t < count; t++) {
@@ -47,7 +47,7 @@ init_tabstops(bool *tabstops, index_type count) {
} }
} }
static inline bool static bool
init_overlay_line(Screen *self, index_type columns) { init_overlay_line(Screen *self, index_type columns) {
PyMem_Free(self->overlay_line.cpu_cells); PyMem_Free(self->overlay_line.cpu_cells);
PyMem_Free(self->overlay_line.gpu_cells); PyMem_Free(self->overlay_line.gpu_cells);
@@ -146,7 +146,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
} }
static void deactivate_overlay_line(Screen *self); static void deactivate_overlay_line(Screen *self);
static inline Line* range_line_(Screen *self, int y); static Line* range_line_(Screen *self, int y);
void void
screen_reset(Screen *self) { screen_reset(Screen *self) {
@@ -195,7 +195,7 @@ screen_dirty_sprite_positions(Screen *self) {
for (index_type i = 0; i < self->historybuf->count; i++) historybuf_mark_line_dirty(self->historybuf, i); for (index_type i = 0; i < self->historybuf->count; i++) historybuf_mark_line_dirty(self->historybuf, i);
} }
static inline HistoryBuf* static HistoryBuf*
realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns, ANSIBuf *as_ansi_buf) { realloc_hb(HistoryBuf *old, unsigned int lines, unsigned int columns, ANSIBuf *as_ansi_buf) {
HistoryBuf *ans = alloc_historybuf(lines, columns, 0); HistoryBuf *ans = alloc_historybuf(lines, columns, 0);
if (ans == NULL) { PyErr_NoMemory(); return NULL; } if (ans == NULL) { PyErr_NoMemory(); return NULL; }
@@ -213,7 +213,7 @@ typedef struct CursorTrack {
struct { index_type x, y; } temp; struct { index_type x, y; } temp;
} CursorTrack; } CursorTrack;
static inline LineBuf* static LineBuf*
realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, CursorTrack *a, CursorTrack *b, ANSIBuf *as_ansi_buf) { realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *nclb, index_type *ncla, HistoryBuf *hb, CursorTrack *a, CursorTrack *b, ANSIBuf *as_ansi_buf) {
LineBuf *ans = alloc_linebuf(lines, columns); LineBuf *ans = alloc_linebuf(lines, columns);
if (ans == NULL) { PyErr_NoMemory(); return NULL; } if (ans == NULL) { PyErr_NoMemory(); return NULL; }
@@ -223,13 +223,13 @@ realloc_lb(LineBuf *old, unsigned int lines, unsigned int columns, index_type *n
return ans; return ans;
} }
static inline bool static bool
is_selection_empty(const Selection *s) { is_selection_empty(const Selection *s) {
int start_y = (int)s->start.y - (int)s->start_scrolled_by, end_y = (int)s->end.y - (int)s->end_scrolled_by; int start_y = (int)s->start.y - (int)s->start_scrolled_by, end_y = (int)s->end.y - (int)s->end_scrolled_by;
return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && start_y == end_y; return s->start.x == s->end.x && s->start.in_left_half_of_cell == s->end.in_left_half_of_cell && start_y == end_y;
} }
static inline void static void
index_selection(const Screen *self, Selections *selections, bool up) { index_selection(const Screen *self, Selections *selections, bool up) {
for (size_t i = 0; i < selections->count; i++) { for (size_t i = 0; i < selections->count; i++) {
Selection *s = selections->items + i; Selection *s = selections->items + i;
@@ -239,6 +239,8 @@ index_selection(const Screen *self, Selections *selections, bool up) {
s->start.y--; s->start.y--;
if (s->input_start.y) s->input_start.y--; if (s->input_start.y) s->input_start.y--;
if (s->input_current.y) s->input_current.y--; if (s->input_current.y) s->input_current.y--;
if (s->initial_extent.start.y) s->initial_extent.start.y--;
if (s->initial_extent.end.y) s->initial_extent.end.y--;
} }
if (s->end.y == 0) s->end_scrolled_by += 1; if (s->end.y == 0) s->end_scrolled_by += 1;
else s->end.y--; else s->end.y--;
@@ -423,7 +425,7 @@ screen_designate_charset(Screen *self, uint32_t which, uint32_t as) {
} }
} }
static inline void static void
move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) { move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type xpos, index_type ypos) {
self->cursor->x = xpos; self->cursor->y = ypos; self->cursor->x = xpos; self->cursor->y = ypos;
CPUCell src_cpu = *cpu_cell, *dest_cpu; CPUCell src_cpu = *cpu_cell, *dest_cpu;
@@ -448,7 +450,7 @@ move_widened_char(Screen *self, CPUCell* cpu_cell, GPUCell *gpu_cell, index_type
*dest_gpu = src_gpu; *dest_gpu = src_gpu;
} }
static inline bool static bool
selection_has_screen_line(const Selections *selections, const int y) { selection_has_screen_line(const Selections *selections, const int y) {
for (size_t i = 0; i < selections->count; i++) { for (size_t i = 0; i < selections->count; i++) {
const Selection *s = selections->items + i; const Selection *s = selections->items + i;
@@ -498,11 +500,11 @@ remap_hyperlink_ids(Screen *self, hyperlink_id_type *map) {
} }
static inline bool is_flag_pair(char_type a, char_type b) { static bool is_flag_pair(char_type a, char_type b) {
return is_flag_codepoint(a) && is_flag_codepoint(b); return is_flag_codepoint(a) && is_flag_codepoint(b);
} }
static inline bool static bool
draw_second_flag_codepoint(Screen *self, char_type ch) { draw_second_flag_codepoint(Screen *self, char_type ch) {
index_type xpos = 0, ypos = 0; index_type xpos = 0, ypos = 0;
if (self->cursor->x > 1) { if (self->cursor->x > 1) {
@@ -523,7 +525,7 @@ draw_second_flag_codepoint(Screen *self, char_type ch) {
return true; return true;
} }
static inline void static void
draw_combining_char(Screen *self, char_type ch) { draw_combining_char(Screen *self, char_type ch) {
bool has_prev_char = false; bool has_prev_char = false;
index_type xpos = 0, ypos = 0; index_type xpos = 0, ypos = 0;
@@ -715,12 +717,12 @@ select_graphic_rendition(Screen *self, int *params, unsigned int count, Region *
} else cursor_from_sgr(self->cursor, params, count); } else cursor_from_sgr(self->cursor, params, count);
} }
static inline void static void
write_to_test_child(Screen *self, const char *data, size_t sz) { write_to_test_child(Screen *self, const char *data, size_t sz) {
PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r); PyObject *r = PyObject_CallMethod(self->test_child, "write", "y#", data, sz); if (r == NULL) PyErr_Print(); Py_CLEAR(r);
} }
static inline void static void
write_to_child(Screen *self, const char *data, size_t sz) { write_to_child(Screen *self, const char *data, size_t sz) {
if (self->window_id) schedule_write_to_child(self->window_id, 1, data, sz); if (self->window_id) schedule_write_to_child(self->window_id, 1, data, sz);
if (self->test_child != Py_None) { write_to_test_child(self, data, sz); } if (self->test_child != Py_None) { write_to_test_child(self, data, sz); }
@@ -762,7 +764,7 @@ write_escape_code_to_child(Screen *self, unsigned char which, const char *data)
} }
} }
static inline bool static bool
cursor_within_margins(Screen *self) { cursor_within_margins(Screen *self) {
return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom; return self->margin_top <= self->cursor->y && self->cursor->y <= self->margin_bottom;
} }
@@ -1512,11 +1514,7 @@ screen_use_latin1(Screen *self, bool on) {
bool bool
screen_invert_colors(Screen *self) { screen_invert_colors(Screen *self) {
bool inverted = false; bool inverted = false;
if (self->start_visual_bell_at > 0) { if (self->modes.mDECSCNM) inverted = true;
if (monotonic() - self->start_visual_bell_at <= OPT(visual_bell_duration)) inverted = true;
else self->start_visual_bell_at = 0;
}
if (self->modes.mDECSCNM) inverted = inverted ? false : true;
return inverted; return inverted;
} }
@@ -1796,20 +1794,20 @@ screen_request_capabilities(Screen *self, char c, PyObject *q) {
// }}} // }}}
// Rendering {{{ // Rendering {{{
static inline void static void
update_line_data(Line *line, unsigned int dest_y, uint8_t *data) { update_line_data(Line *line, unsigned int dest_y, uint8_t *data) {
size_t base = sizeof(GPUCell) * dest_y * line->xnum; size_t base = sizeof(GPUCell) * dest_y * line->xnum;
memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell)); memcpy(data + base, line->gpu_cells, line->xnum * sizeof(GPUCell));
} }
static inline void static void
screen_reset_dirty(Screen *self) { screen_reset_dirty(Screen *self) {
self->is_dirty = false; self->is_dirty = false;
self->history_line_added_count = 0; self->history_line_added_count = 0;
} }
static inline bool static bool
screen_has_marker(Screen *self) { screen_has_marker(Screen *self) {
return self->marker != NULL; return self->marker != NULL;
} }
@@ -1980,7 +1978,7 @@ iteration_data(const Screen *self, const Selection *sel, IterationData *ans, int
ans->y = MAX(ans->y, min_y); ans->y = MAX(ans->y, min_y);
} }
static inline XRange static XRange
xrange_for_iteration(const IterationData *idata, const int y, const Line *line) { xrange_for_iteration(const IterationData *idata, const int y, const Line *line) {
XRange ans = {.x_limit=xlimit_for_line(line)}; XRange ans = {.x_limit=xlimit_for_line(line)};
if (y == idata->y) { if (y == idata->y) {
@@ -1996,7 +1994,7 @@ xrange_for_iteration(const IterationData *idata, const int y, const Line *line)
return ans; return ans;
} }
static inline bool static bool
iteration_data_is_empty(const Screen *self, const IterationData *idata) { iteration_data_is_empty(const Screen *self, const IterationData *idata) {
if (idata->y >= idata->y_limit) return true; if (idata->y >= idata->y_limit) return true;
index_type xl = MIN(idata->first.x_limit, self->columns); index_type xl = MIN(idata->first.x_limit, self->columns);
@@ -2008,7 +2006,7 @@ iteration_data_is_empty(const Screen *self, const IterationData *idata) {
return true; return true;
} }
static inline void static void
apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) { apply_selection(Screen *self, uint8_t *data, Selection *s, uint8_t set_mask) {
iteration_data(self, s, &s->last_rendered, -self->historybuf->count, true); iteration_data(self, s, &s->last_rendered, -self->historybuf->count, true);
@@ -2047,7 +2045,7 @@ screen_apply_selection(Screen *self, void *address, size_t size) {
self->url_ranges.last_rendered_count = self->url_ranges.count; self->url_ranges.last_rendered_count = self->url_ranges.count;
} }
static inline PyObject* static PyObject*
text_for_range(Screen *self, const Selection *sel, bool insert_newlines) { text_for_range(Screen *self, const Selection *sel, bool insert_newlines) {
IterationData idata; IterationData idata;
iteration_data(self, sel, &idata, -self->historybuf->count, false); iteration_data(self, sel, &idata, -self->historybuf->count, false);
@@ -2065,7 +2063,7 @@ text_for_range(Screen *self, const Selection *sel, bool insert_newlines) {
return ans; return ans;
} }
static inline hyperlink_id_type static hyperlink_id_type
hyperlink_id_for_range(Screen *self, const Selection *sel) { hyperlink_id_for_range(Screen *self, const Selection *sel) {
IterationData idata; IterationData idata;
iteration_data(self, sel, &idata, -self->historybuf->count, false); iteration_data(self, sel, &idata, -self->historybuf->count, false);
@@ -2079,7 +2077,7 @@ hyperlink_id_for_range(Screen *self, const Selection *sel) {
return 0; return 0;
} }
static inline PyObject* static PyObject*
extend_tuple(PyObject *a, PyObject *b) { extend_tuple(PyObject *a, PyObject *b) {
Py_ssize_t bs = PyBytes_GET_SIZE(b); Py_ssize_t bs = PyBytes_GET_SIZE(b);
if (bs < 1) return a; if (bs < 1) return a;
@@ -2291,7 +2289,7 @@ as_text_non_visual(Screen *self, PyObject *args) {
return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf); return as_text_generic(args, self, get_range_line, self->lines, &self->as_ansi_buf);
} }
static inline PyObject* static PyObject*
as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) { as_text_generic_wrapper(Screen *self, PyObject *args, get_line_func get_line) {
return as_text_generic(args, self, get_line, self->lines, &self->as_ansi_buf); return as_text_generic(args, self, get_line, self->lines, &self->as_ansi_buf);
} }
@@ -2599,7 +2597,7 @@ screen_selection_range_for_line(Screen *self, index_type y, index_type *start, i
return true; return true;
} }
static inline bool static bool
is_opt_word_char(char_type ch) { is_opt_word_char(char_type ch) {
if (OPT(select_by_word_characters)) { if (OPT(select_by_word_characters)) {
for (const char_type *p = OPT(select_by_word_characters); *p; p++) { for (const char_type *p = OPT(select_by_word_characters); *p; p++) {
@@ -2722,7 +2720,7 @@ screen_start_selection(Screen *self, index_type x, index_type y, bool in_left_ha
#undef A #undef A
} }
static inline void static void
add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) { add_url_range(Screen *self, index_type start_x, index_type start_y, index_type end_x, index_type end_y) {
#define A(attr, val) r->attr = val; #define A(attr, val) r->attr = val;
ensure_space_for(&self->url_ranges, items, Selection, self->url_ranges.count + 8, capacity, 8, false); ensure_space_for(&self->url_ranges, items, Selection, self->url_ranges.count + 8, capacity, 8, false);
@@ -2827,12 +2825,12 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
s->input_current.x = x; s->input_current.y = y; s->input_current.x = x; s->input_current.y = y;
s->input_current.in_left_half_of_cell = in_left_half_of_cell; s->input_current.in_left_half_of_cell = in_left_half_of_cell;
SelectionBoundary start, end, *a = &s->start, *b = &s->end, abs_start, abs_end, abs_current_input; SelectionBoundary start, end, *a = &s->start, *b = &s->end, abs_start, abs_end, abs_current_input;
#define absy(b, scrolled_by) b.y = scrolled_by + self->lines - 1 - b.y #define set_abs(which, initializer, scrolled_by) which = initializer; which.y = scrolled_by + self->lines - 1 - which.y;
abs_start = s->start; absy(abs_start, s->start_scrolled_by); set_abs(abs_start, s->start, s->start_scrolled_by);
abs_end = s->end; absy(abs_end, s->end_scrolled_by); set_abs(abs_end, s->end, s->end_scrolled_by);
abs_current_input = s->input_current; absy(abs_current_input, self->scrolled_by); set_abs(abs_current_input, s->input_current, self->scrolled_by);
#undef absy if (upd.set_as_nearest_extend || self->selections.extension_in_progress) {
if (upd.set_as_nearest_extend) { self->selections.extension_in_progress = true;
bool start_is_nearer = false; bool start_is_nearer = false;
if (self->selections.extend_mode == EXTEND_LINE || self->selections.extend_mode == EXTEND_LINE_FROM_POINT) { if (self->selections.extend_mode == EXTEND_LINE || self->selections.extend_mode == EXTEND_LINE_FROM_POINT) {
if (abs_start.y == abs_end.y) { if (abs_start.y == abs_end.y) {
@@ -2843,7 +2841,25 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
} }
} else start_is_nearer = num_cells_between_selection_boundaries(self, &abs_start, &abs_current_input) < num_cells_between_selection_boundaries(self, &abs_end, &abs_current_input); } else start_is_nearer = num_cells_between_selection_boundaries(self, &abs_start, &abs_current_input) < num_cells_between_selection_boundaries(self, &abs_end, &abs_current_input);
if (start_is_nearer) s->adjusting_start = true; if (start_is_nearer) s->adjusting_start = true;
} else if (!upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) {
SelectionBoundary abs_initial_start, abs_initial_end;
set_abs(abs_initial_start, s->initial_extent.start, s->initial_extent.scrolled_by);
set_abs(abs_initial_end, s->initial_extent.end, s->initial_extent.scrolled_by);
if (self->selections.extend_mode == EXTEND_WORD) {
s->adjusting_start = selection_boundary_less_than(&abs_current_input, &abs_initial_end);
} else {
const unsigned int initial_line = abs_initial_start.y;
if (initial_line == abs_current_input.y) {
s->adjusting_start = false;
s->start = s->initial_extent.start; s->start_scrolled_by = s->initial_extent.scrolled_by;
s->end = s->initial_extent.end; s->end_scrolled_by = s->initial_extent.scrolled_by;
}
else {
s->adjusting_start = abs_current_input.y > initial_line;
}
}
} }
#undef set_abs
bool adjusted_boundary_is_before; bool adjusted_boundary_is_before;
if (s->adjusting_start) adjusted_boundary_is_before = selection_boundary_less_than(&abs_start, &abs_end); if (s->adjusting_start) adjusted_boundary_is_before = selection_boundary_less_than(&abs_start, &abs_end);
else { adjusted_boundary_is_before = selection_boundary_less_than(&abs_end, &abs_start); } else { adjusted_boundary_is_before = selection_boundary_less_than(&abs_end, &abs_start); }
@@ -2923,7 +2939,13 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool in_left_h
} }
if (!self->selections.in_progress) { if (!self->selections.in_progress) {
s->adjusting_start = false; s->adjusting_start = false;
self->selections.extension_in_progress = false;
call_boss(set_primary_selection, NULL); call_boss(set_primary_selection, NULL);
} else {
if (upd.start_extended_selection && self->selections.extend_mode != EXTEND_CELL) {
s->initial_extent.start = s->start; s->initial_extent.end = s->end;
s->initial_extent.scrolled_by = s->start_scrolled_by;
}
} }
} }
@@ -2961,7 +2983,7 @@ send_escape_code_to_child(Screen *self, PyObject *args) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static inline void static void
screen_mark_all(Screen *self) { screen_mark_all(Screen *self) {
for (index_type y = 0; y < self->main_linebuf->ynum; y++) { for (index_type y = 0; y < self->main_linebuf->ynum; y++) {
linebuf_init_line(self->main_linebuf, y); linebuf_init_line(self->main_linebuf, y);

View File

@@ -42,12 +42,16 @@ typedef struct {
bool rectangle_select, adjusting_start; bool rectangle_select, adjusting_start;
IterationData last_rendered; IterationData last_rendered;
int sort_y, sort_x; int sort_y, sort_x;
struct {
SelectionBoundary start, end;
unsigned int scrolled_by;
} initial_extent;
} Selection; } Selection;
typedef struct { typedef struct {
Selection *items; Selection *items;
size_t count, capacity, last_rendered_count; size_t count, capacity, last_rendered_count;
bool in_progress; bool in_progress, extension_in_progress;
SelectionExtendMode extend_mode; SelectionExtendMode extend_mode;
} Selections; } Selections;

View File

@@ -90,16 +90,16 @@ class Session:
self.tabs[-1].cwd = val self.tabs[-1].cwd = val
def parse_session(raw: str, opts: Options, default_title: Optional[str] = None) -> Generator[Session, None, None]: def parse_session(raw: str, opts: Options) -> Generator[Session, None, None]:
def finalize_session(ans: Session) -> Session: def finalize_session(ans: Session) -> Session:
from .tabs import SpecialWindow from .tabs import SpecialWindow
for t in ans.tabs: for t in ans.tabs:
if not t.windows: if not t.windows:
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), override_title=default_title)) t.windows.append(SpecialWindow(cmd=resolved_shell(opts)))
return ans return ans
ans = Session(default_title) ans = Session()
ans.add_tab(opts) ans.add_tab(opts)
for line in raw.splitlines(): for line in raw.splitlines():
line = line.strip() line = line.strip()
@@ -114,7 +114,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
ans.add_tab(opts, rest) ans.add_tab(opts, rest)
elif cmd == 'new_os_window': elif cmd == 'new_os_window':
yield finalize_session(ans) yield finalize_session(ans)
ans = Session(default_title) ans = Session()
ans.add_tab(opts, rest) ans.add_tab(opts, rest)
elif cmd == 'layout': elif cmd == 'layout':
ans.set_layout(rest) ans.set_layout(rest)
@@ -164,7 +164,7 @@ def create_sessions(
f = open(args.session) f = open(args.session)
with f: with f:
session_data = f.read() session_data = f.read()
yield from parse_session(session_data, opts, getattr(args, 'title', None)) yield from parse_session(session_data, opts)
return return
if default_session and default_session != 'none': if default_session and default_session != 'none':
try: try:
@@ -173,7 +173,7 @@ def create_sessions(
except OSError: except OSError:
log_error('Failed to read from session file, ignoring: {}'.format(default_session)) log_error('Failed to read from session file, ignoring: {}'.format(default_session))
else: else:
yield from parse_session(session_data, opts, getattr(args, 'title', None)) yield from parse_session(session_data, opts)
return return
default_watchers = args.watcher if args else () default_watchers = args.watcher if args else ()
ans = Session(default_watchers=default_watchers) ans = Session(default_watchers=default_watchers)
@@ -186,7 +186,6 @@ def create_sessions(
cmd = [kitty_exe(), '+hold'] + cmd cmd = [kitty_exe(), '+hold'] + cmd
from kitty.tabs import SpecialWindow from kitty.tabs import SpecialWindow
cwd: Optional[str] = args.directory if respect_cwd and args else None cwd: Optional[str] = args.directory if respect_cwd and args else None
title = getattr(args, 'title', None) special_window = SpecialWindow(cmd, cwd_from=cwd_from, cwd=cwd)
special_window = SpecialWindow(cmd, override_title=title, cwd_from=cwd_from, cwd=cwd)
ans.add_special_window(special_window) ans.add_special_window(special_window)
yield ans yield ans

View File

@@ -109,7 +109,7 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) {
sprite_map->texture_id = tex; sprite_map->texture_id = tex;
} }
static inline void static void
ensure_sprite_map(FONTS_DATA_HANDLE fg) { ensure_sprite_map(FONTS_DATA_HANDLE fg) {
SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map;
if (!sprite_map->texture_id) realloc_sprite_texture(fg); if (!sprite_map->texture_id) realloc_sprite_texture(fg);
@@ -241,7 +241,7 @@ struct CellUniformData {
static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=-1}; static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=-1};
static inline void static void
send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRenderData *render_data) { send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRenderData *render_data) {
size_t sz = sizeof(GLfloat) * 16 * image_count; size_t sz = sizeof(GLfloat) * 16 * image_count;
GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY); GLfloat *a = alloc_and_map_vao_buffer(gvao_idx, sz, 0, GL_STREAM_DRAW, GL_WRITE_ONLY);
@@ -249,7 +249,7 @@ send_graphics_data_to_gpu(size_t image_count, ssize_t gvao_idx, const ImageRende
unmap_vao_buffer(gvao_idx, 0); a = NULL; unmap_vao_buffer(gvao_idx, 0); a = NULL;
} }
static inline void static void
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) { cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted, OSWindow *os_window) {
struct CellRenderData { struct CellRenderData {
GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg; GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
@@ -306,7 +306,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL; unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL;
} }
static inline bool static bool
cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) { cell_prepare_to_render(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) {
size_t sz; size_t sz;
CELL_BUFFERS; CELL_BUFFERS;
@@ -440,7 +440,7 @@ draw_cells_simple(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) {
} }
} }
static inline bool static bool
has_bgimage(OSWindow *w) { has_bgimage(OSWindow *w) {
return w->bgimage && w->bgimage->texture_id > 0; return w->bgimage && w->bgimage->texture_id > 0;
} }
@@ -458,6 +458,27 @@ draw_tint(bool premult, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat
glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
} }
static void
draw_visual_bell_flash(GLfloat intensity, GLfloat xstart, GLfloat ystart, GLfloat w, GLfloat h, Screen *screen) {
glEnable(GL_BLEND);
// BLEND_PREMULT
glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
bind_program(TINT_PROGRAM);
GLfloat attenuation = 0.4f;
const color_type flash = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg);
#define C(shift) ((((GLfloat)((flash >> shift) & 0xFF)) / 255.0f) )
const GLfloat r = C(16), g = C(8), b = C(0);
const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b);
#undef C
#define C(x) (x * intensity * attenuation)
if (max_channel > 0.45) attenuation = 0.6f; // light color
glUniform4f(tint_program_layout.tint_color_location, C(r), C(g), C(b), C(1));
#undef C
glUniform4f(tint_program_layout.edges_location, xstart, ystart - h, xstart + w, ystart);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glDisable(GL_BLEND);
}
static void static void
draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *w, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) { draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *w, GLfloat xstart, GLfloat ystart, GLfloat width, GLfloat height) {
glEnable(GL_BLEND); glEnable(GL_BLEND);
@@ -570,7 +591,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
static inline void static void
set_cell_uniforms(float current_inactive_text_alpha, bool force) { set_cell_uniforms(float current_inactive_text_alpha, bool force) {
if (!cell_uniform_data.constants_set || force) { if (!cell_uniform_data.constants_set || force) {
cell_uniform_data.gploc = glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "inactive_text_alpha"); cell_uniform_data.gploc = glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "inactive_text_alpha");
@@ -613,6 +634,36 @@ send_cell_data_to_gpu(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat
return changed; return changed;
} }
static float
ease_out_cubic(float phase) {
return 1.0f - powf(1.0f - phase, 3.0f);
}
static float
ease_in_out_cubic(float phase) {
return phase < 0.5f ?
4.0f * powf(phase, 3.0f) :
1.0f - powf(-2.0f * phase + 2.0f, 3.0f) / 2.0f;
}
static float
visual_bell_intensity(float phase) {
static const float peak = 0.2f;
const float fade = 1.0f - peak;
return phase < peak ? ease_out_cubic(phase / peak) : ease_in_out_cubic((1.0f - phase) / fade);
}
static float
get_visual_bell_intensity(Screen *screen) {
if (screen->start_visual_bell_at > 0) {
monotonic_t progress = monotonic() - screen->start_visual_bell_at;
monotonic_t duration = OPT(visual_bell_duration);
if (progress <= duration) return visual_bell_intensity((float)progress / duration);
screen->start_visual_bell_at = 0;
}
return 0.0f;
}
void void
draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window, bool is_active_window, bool can_be_focused) { draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window, bool is_active_window, bool can_be_focused) {
CELL_BUFFERS; CELL_BUFFERS;
@@ -650,6 +701,11 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
vao_idx, gvao_idx, screen, os_window, xstart, ystart, w, h); vao_idx, gvao_idx, screen, os_window, xstart, ystart, w, h);
else draw_cells_simple(vao_idx, gvao_idx, screen); else draw_cells_simple(vao_idx, gvao_idx, screen);
} }
if (screen->start_visual_bell_at) {
GLfloat intensity = get_visual_bell_intensity(screen);
if (intensity > 0.0f) draw_visual_bell_flash(intensity, xstart, ystart, w, h, screen);
}
} }
// }}} // }}}

View File

@@ -196,7 +196,7 @@ add_os_window() {
return ans; return ans;
} }
static inline id_type static id_type
add_tab(id_type os_window_id) { add_tab(id_type os_window_id) {
WITH_OS_WINDOW(os_window_id) WITH_OS_WINDOW(os_window_id)
make_os_window_context_current(os_window); make_os_window_context_current(os_window);
@@ -209,13 +209,13 @@ add_tab(id_type os_window_id) {
return 0; return 0;
} }
static inline void static void
create_gpu_resources_for_window(Window *w) { create_gpu_resources_for_window(Window *w) {
w->render_data.vao_idx = create_cell_vao(); w->render_data.vao_idx = create_cell_vao();
w->render_data.gvao_idx = create_graphics_vao(); w->render_data.gvao_idx = create_graphics_vao();
} }
static inline void static void
release_gpu_resources_for_window(Window *w) { release_gpu_resources_for_window(Window *w) {
if (w->render_data.vao_idx > -1) remove_vao(w->render_data.vao_idx); if (w->render_data.vao_idx > -1) remove_vao(w->render_data.vao_idx);
w->render_data.vao_idx = -1; w->render_data.vao_idx = -1;
@@ -223,7 +223,7 @@ release_gpu_resources_for_window(Window *w) {
w->render_data.gvao_idx = -1; w->render_data.gvao_idx = -1;
} }
static inline void static void
initialize_window(Window *w, PyObject *title, bool init_gpu_resources) { initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
w->id = ++global_state.window_id_counter; w->id = ++global_state.window_id_counter;
w->visible = true; w->visible = true;
@@ -236,7 +236,7 @@ initialize_window(Window *w, PyObject *title, bool init_gpu_resources) {
} }
} }
static inline id_type static id_type
add_window(id_type os_window_id, id_type tab_id, PyObject *title) { add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
WITH_TAB(os_window_id, tab_id); WITH_TAB(os_window_id, tab_id);
ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true); ensure_space_for(tab, windows, Window, tab->num_windows + 1, capacity, 1, true);
@@ -248,7 +248,7 @@ add_window(id_type os_window_id, id_type tab_id, PyObject *title) {
return 0; return 0;
} }
static inline void static void
update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) { update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyObject *title) {
WITH_TAB(os_window_id, tab_id); WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) { for (size_t i = 0; i < tab->num_windows; i++) {
@@ -264,6 +264,7 @@ update_window_title(id_type os_window_id, id_type tab_id, id_type window_id, PyO
void void
set_os_window_title_from_window(Window *w, OSWindow *os_window) { set_os_window_title_from_window(Window *w, OSWindow *os_window) {
if (os_window->disallow_title_changes) return;
if (w->title && w->title != os_window->window_title) { if (w->title && w->title != os_window->window_title) {
Py_XDECREF(os_window->window_title); Py_XDECREF(os_window->window_title);
os_window->window_title = w->title; os_window->window_title = w->title;
@@ -283,13 +284,13 @@ update_os_window_title(OSWindow *os_window) {
} }
} }
static inline void static void
destroy_window(Window *w) { destroy_window(Window *w) {
Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title); Py_CLEAR(w->render_data.screen); Py_CLEAR(w->title);
release_gpu_resources_for_window(w); release_gpu_resources_for_window(w);
} }
static inline void static void
remove_window_inner(Tab *tab, id_type id) { remove_window_inner(Tab *tab, id_type id) {
id_type active_window_id = 0; id_type active_window_id = 0;
if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id; if (tab->active_window < tab->num_windows) active_window_id = tab->windows[tab->active_window].id;
@@ -303,7 +304,7 @@ remove_window_inner(Tab *tab, id_type id) {
} }
} }
static inline void static void
remove_window(id_type os_window_id, id_type tab_id, id_type id) { remove_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id); WITH_TAB(os_window_id, tab_id);
make_os_window_context_current(osw); make_os_window_context_current(osw);
@@ -325,7 +326,7 @@ add_detached_window(Window *w) {
memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window)); memcpy(detached_windows.windows + detached_windows.num_windows++, w, sizeof(Window));
} }
static inline void static void
detach_window(id_type os_window_id, id_type tab_id, id_type id) { detach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id); WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < tab->num_windows; i++) { for (size_t i = 0; i < tab->num_windows; i++) {
@@ -342,7 +343,7 @@ detach_window(id_type os_window_id, id_type tab_id, id_type id) {
} }
static inline void static void
resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) { resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
if (screen) { if (screen) {
screen->cell_size.width = os_window->fonts_data->cell_width; screen->cell_size.width = os_window->fonts_data->cell_width;
@@ -352,7 +353,7 @@ resize_screen(OSWindow *os_window, Screen *screen, bool has_graphics) {
} }
} }
static inline void static void
attach_window(id_type os_window_id, id_type tab_id, id_type id) { attach_window(id_type os_window_id, id_type tab_id, id_type id) {
WITH_TAB(os_window_id, tab_id); WITH_TAB(os_window_id, tab_id);
for (size_t i = 0; i < detached_windows.num_windows; i++) { for (size_t i = 0; i < detached_windows.num_windows; i++) {
@@ -376,7 +377,7 @@ attach_window(id_type os_window_id, id_type tab_id, id_type id) {
END_WITH_TAB; END_WITH_TAB;
} }
static inline void static void
destroy_tab(Tab *tab) { destroy_tab(Tab *tab) {
for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id); for (size_t i = tab->num_windows; i > 0; i--) remove_window_inner(tab, tab->windows[i - 1].id);
remove_vao(tab->border_rects.vao_idx); remove_vao(tab->border_rects.vao_idx);
@@ -384,7 +385,7 @@ destroy_tab(Tab *tab) {
free(tab->windows); tab->windows = NULL; free(tab->windows); tab->windows = NULL;
} }
static inline void static void
remove_tab_inner(OSWindow *os_window, id_type id) { remove_tab_inner(OSWindow *os_window, id_type id) {
id_type active_tab_id = 0; id_type active_tab_id = 0;
if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id; if (os_window->active_tab < os_window->num_tabs) active_tab_id = os_window->tabs[os_window->active_tab].id;
@@ -399,14 +400,14 @@ remove_tab_inner(OSWindow *os_window, id_type id) {
} }
} }
static inline void static void
remove_tab(id_type os_window_id, id_type id) { remove_tab(id_type os_window_id, id_type id) {
WITH_OS_WINDOW(os_window_id) WITH_OS_WINDOW(os_window_id)
remove_tab_inner(os_window, id); remove_tab_inner(os_window, id);
END_WITH_OS_WINDOW END_WITH_OS_WINDOW
} }
static inline void static void
destroy_os_window_item(OSWindow *w) { destroy_os_window_item(OSWindow *w) {
for (size_t t = w->num_tabs; t > 0; t--) { for (size_t t = w->num_tabs; t > 0; t--) {
Tab *tab = w->tabs + t - 1; Tab *tab = w->tabs + t - 1;
@@ -439,7 +440,7 @@ remove_os_window(id_type os_window_id) {
} }
static inline void static void
set_active_tab(id_type os_window_id, unsigned int idx) { set_active_tab(id_type os_window_id, unsigned int idx) {
WITH_OS_WINDOW(os_window_id) WITH_OS_WINDOW(os_window_id)
os_window->active_tab = idx; os_window->active_tab = idx;
@@ -447,7 +448,7 @@ set_active_tab(id_type os_window_id, unsigned int idx) {
END_WITH_OS_WINDOW END_WITH_OS_WINDOW
} }
static inline void static void
set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) { set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
WITH_WINDOW(os_window_id, tab_id, window_id) WITH_WINDOW(os_window_id, tab_id, window_id)
(void)window; (void)window;
@@ -456,7 +457,7 @@ set_active_window(id_type os_window_id, id_type tab_id, id_type window_id) {
END_WITH_WINDOW; END_WITH_WINDOW;
} }
static inline void static void
swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) { swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) {
WITH_OS_WINDOW(os_window_id) WITH_OS_WINDOW(os_window_id)
Tab t = os_window->tabs[b]; Tab t = os_window->tabs[b];
@@ -650,7 +651,7 @@ static PyStructSequence_Field region_fields[] = {
}; };
static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6}; static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6};
static inline PyObject* static PyObject*
wrap_region(Region *r) { wrap_region(Region *r) {
PyObject *ans = PyStructSequence_New(&RegionType); PyObject *ans = PyStructSequence_New(&RegionType);
if (ans) { if (ans) {
@@ -1019,7 +1020,7 @@ pycreate_mock_window(PyObject *self UNUSED, PyObject *args) {
return ans; return ans;
} }
static inline void static void
click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) { click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) {
WITH_WINDOW(os_window_id, tab_id, window_id); WITH_WINDOW(os_window_id, tab_id, window_id);
mouse_open_url(window); mouse_open_url(window);

View File

@@ -176,6 +176,7 @@ typedef struct {
double logical_dpi_x, logical_dpi_y, font_sz_in_pts; double logical_dpi_x, logical_dpi_y, font_sz_in_pts;
bool mouse_button_pressed[32]; bool mouse_button_pressed[32];
PyObject *window_title; PyObject *window_title;
bool disallow_title_changes;
bool viewport_size_dirty, viewport_updated_at_least_once; bool viewport_size_dirty, viewport_updated_at_least_once;
LiveResizeInfo live_resize; LiveResizeInfo live_resize;
bool has_pending_resizes, is_semi_transparent, shown_once, is_damaged; bool has_pending_resizes, is_semi_transparent, shown_once, is_damaged;

View File

@@ -13,7 +13,7 @@ from .fast_data_types import (
from .layout.base import Rect from .layout.base import Rect
from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color
from .types import WindowGeometry, run_once from .types import WindowGeometry, run_once
from .typing import PowerlineStyle from .typing import PowerlineStyle, EdgeLiteral
from .utils import color_as_int, log_error from .utils import color_as_int, log_error
from .window import calculate_gl_geometry from .window import calculate_gl_geometry
@@ -44,6 +44,7 @@ class DrawData(NamedTuple):
active_title_template: Optional[str] active_title_template: Optional[str]
tab_activity_symbol: Optional[str] tab_activity_symbol: Optional[str]
powerline_style: PowerlineStyle powerline_style: PowerlineStyle
tab_bar_edge: EdgeLiteral
def as_rgb(x: int) -> int: def as_rgb(x: int) -> int:
@@ -164,6 +165,44 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int)
screen.draw(title) screen.draw(title)
def draw_tab_with_slant(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int:
left_sep, right_sep = ('', '') if draw_data.tab_bar_edge == 'top' else ('', '')
tab_bg = as_rgb(color_as_int(draw_data.active_bg if tab.is_active else draw_data.inactive_bg))
slant_fg = as_rgb(color_as_int(draw_data.default_bg))
def draw_sep(which: str) -> None:
screen.cursor.bg = tab_bg
screen.cursor.fg = slant_fg
screen.draw(which)
screen.cursor.bg = tab_bg
screen.cursor.fg = 0
max_title_length += 1
if max_title_length <= 1:
screen.draw('')
elif max_title_length == 2:
screen.draw('…|')
elif max_title_length < 6:
draw_sep(left_sep)
screen.draw((' ' if max_title_length == 5 else '') + '' + (' ' if max_title_length >= 4 else ''))
draw_sep(right_sep)
else:
draw_sep(left_sep)
screen.draw(' ')
draw_title(draw_data, screen, tab, index)
extra = screen.cursor.x - before - max_title_length
if extra >= 0:
screen.cursor.x -= extra + 3
screen.draw('')
elif extra == -1:
screen.cursor.x -= 2
screen.draw('')
screen.draw(' ')
draw_sep(right_sep)
return screen.cursor.x
def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int: def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int:
tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg
screen.cursor.bg = as_rgb(color_as_int(tab_bg)) screen.cursor.bg = as_rgb(color_as_int(tab_bg))
@@ -332,12 +371,15 @@ class TabBar:
opts.tab_bar_background or opts.background, opts.tab_title_template, opts.tab_bar_background or opts.background, opts.tab_title_template,
opts.active_tab_title_template, opts.active_tab_title_template,
opts.tab_activity_symbol, opts.tab_activity_symbol,
opts.tab_powerline_style opts.tab_powerline_style,
'top' if opts.tab_bar_edge == 1 else 'bottom'
) )
if opts.tab_bar_style == 'separator': if opts.tab_bar_style == 'separator':
self.draw_func = draw_tab_with_separator self.draw_func = draw_tab_with_separator
elif opts.tab_bar_style == 'powerline': elif opts.tab_bar_style == 'powerline':
self.draw_func = draw_tab_with_powerline self.draw_func = draw_tab_with_powerline
elif opts.tab_bar_style == 'slant':
self.draw_func = draw_tab_with_slant
else: else:
self.draw_func = draw_tab_with_fade self.draw_func = draw_tab_with_fade

View File

@@ -26,6 +26,7 @@ from .fast_data_types import (
from .layout.base import Layout, Rect from .layout.base import Layout, Rect
from .layout.interface import create_layout_object_for, evict_cached_layouts from .layout.interface import create_layout_object_for, evict_cached_layouts
from .tab_bar import TabBar, TabBarData from .tab_bar import TabBar, TabBarData
from .types import ac
from .typing import EdgeLiteral, SessionTab, SessionType, TypedDict from .typing import EdgeLiteral, SessionTab, SessionType, TypedDict
from .utils import log_error, platform_window_id, resolved_shell from .utils import log_error, platform_window_id, resolved_shell
from .window import Watchers, Window, WindowDict from .window import Watchers, Window, WindowDict
@@ -222,8 +223,8 @@ class Tab: # {{{
def create_layout_object(self, name: str) -> Layout: def create_layout_object(self, name: str) -> Layout:
return create_layout_object_for(name, self.os_window_id, self.id) return create_layout_object_for(name, self.os_window_id, self.id)
@ac('lay', 'Go to the next enabled layout')
def next_layout(self) -> None: def next_layout(self) -> None:
'@ac:lay: Go to the next enabled layout'
if len(self.enabled_layouts) > 1: if len(self.enabled_layouts) > 1:
for i, layout_name in enumerate(self.enabled_layouts): for i, layout_name in enumerate(self.enabled_layouts):
if layout_name == self.current_layout.full_name: if layout_name == self.current_layout.full_name:
@@ -235,20 +236,20 @@ class Tab: # {{{
self._set_current_layout(nl) self._set_current_layout(nl)
self.relayout() self.relayout()
@ac('lay', 'Go to the previously used layout')
def last_used_layout(self) -> None: def last_used_layout(self) -> None:
'@ac:lay: Go to the previously used layout'
if len(self.enabled_layouts) > 1 and self._last_used_layout and self._last_used_layout != self._current_layout_name: if len(self.enabled_layouts) > 1 and self._last_used_layout and self._last_used_layout != self._current_layout_name:
self._set_current_layout(self._last_used_layout) self._set_current_layout(self._last_used_layout)
self.relayout() self.relayout()
def goto_layout(self, layout_name: str, raise_exception: bool = False) -> None: @ac('lay', '''
''' Switch to the named layout
@ac:lay: Switch to the named layout
For example:: For example::
map f1 goto_layout tall map f1 goto_layout tall
''' ''')
def goto_layout(self, layout_name: str, raise_exception: bool = False) -> None:
layout_name = layout_name.lower() layout_name = layout_name.lower()
if layout_name not in self.enabled_layouts: if layout_name not in self.enabled_layouts:
if raise_exception: if raise_exception:
@@ -258,16 +259,16 @@ class Tab: # {{{
self._set_current_layout(layout_name) self._set_current_layout(layout_name)
self.relayout() self.relayout()
def toggle_layout(self, layout_name: str) -> None: @ac('lay', '''
''' Toggle the named layout
@ac:lay: Toggle the named layout
Switches to the named layout if another layout is current, otherwise Switches to the named layout if another layout is current, otherwise
switches to the last used layout. Useful to "zoom" a window temporarily switches to the last used layout. Useful to "zoom" a window temporarily
by switching to the stack layout. For example:: by switching to the stack layout. For example::
map f1 toggle_layout stack map f1 toggle_layout stack
''' ''')
def toggle_layout(self, layout_name: str) -> None:
if self._current_layout_name == layout_name: if self._current_layout_name == layout_name:
self.last_used_layout() self.last_used_layout()
else: else:
@@ -280,12 +281,12 @@ class Tab: # {{{
return None return None
return 'Could not resize' return 'Could not resize'
def resize_window(self, quality: str, increment: int) -> None: @ac('win', '''
''' Resize the active window by the specified amount
@ac:win: Resize the active window by the specified amount
See :ref:`window_resizing` for details. See :ref:`window_resizing` for details.
''' ''')
def resize_window(self, quality: str, increment: int) -> None:
if increment < 1: if increment < 1:
raise ValueError(increment) raise ValueError(increment)
is_horizontal = quality in ('wider', 'narrower') is_horizontal = quality in ('wider', 'narrower')
@@ -296,13 +297,13 @@ class Tab: # {{{
if get_options().enable_audio_bell: if get_options().enable_audio_bell:
ring_bell() ring_bell()
@ac('win', 'Reset window sizes undoing any dynamic resizing of windows')
def reset_window_sizes(self) -> None: def reset_window_sizes(self) -> None:
'@ac:win:Reset window sizes undoing any dynamic resizing of windows'
if self.current_layout.remove_all_biases(): if self.current_layout.remove_all_biases():
self.relayout() self.relayout()
@ac('lay', 'Perform a layout specific action. See :doc:`layouts` for details')
def layout_action(self, action_name: str, args: Sequence[str]) -> None: def layout_action(self, action_name: str, args: Sequence[str]) -> None:
'@ac:lay: Perform a layout specific action. See :doc:`layouts` for details'
ret = self.current_layout.layout_action(action_name, args, self.windows) ret = self.current_layout.layout_action(action_name, args, self.windows)
if ret is None: if ret is None:
ring_bell() ring_bell()
@@ -424,14 +425,14 @@ class Tab: # {{{
allow_remote_control=allow_remote_control, watchers=special_window.watchers allow_remote_control=allow_remote_control, watchers=special_window.watchers
) )
@ac('win', 'Close the currently active window')
def close_window(self) -> None: def close_window(self) -> None:
'@ac:win: Close the currently active window'
w = self.active_window w = self.active_window
if w is not None: if w is not None:
self.remove_window(w) self.remove_window(w)
@ac('win', 'Close all windows in the tab other than the currently active window')
def close_other_windows_in_tab(self) -> None: def close_other_windows_in_tab(self) -> None:
'@ac:win: Close all windows in the tab other than the currently active window'
if len(self.windows) > 1: if len(self.windows) > 1:
active_window = self.active_window active_window = self.active_window
for window in tuple(self.windows): for window in tuple(self.windows):
@@ -469,14 +470,14 @@ class Tab: # {{{
if self.windows: if self.windows:
return self.current_layout.nth_window(self.windows, n) return self.current_layout.nth_window(self.windows, n)
def nth_window(self, num: int = 0) -> None: @ac('win', '''
''' Focus the nth window if positive or the previously active windows if negative
@ac:win: Focus the nth window if positive or the previously active windows if negative
For example, to ficus the previously active window:: For example, to ficus the previously active window::
map ctrl+p nth_window -1 map ctrl+p nth_window -1
''' ''')
def nth_window(self, num: int = 0) -> None:
if self.windows: if self.windows:
if num < 0: if num < 0:
self.windows.make_previous_group_active(-num) self.windows.make_previous_group_active(-num)
@@ -489,12 +490,12 @@ class Tab: # {{{
self.current_layout.next_window(self.windows, delta) self.current_layout.next_window(self.windows, delta)
self.relayout_borders() self.relayout_borders()
@ac('win', 'Focus the next window in the current tab')
def next_window(self) -> None: def next_window(self) -> None:
'@ac:win: Focus the next window in the current tab'
self._next_window() self._next_window()
@ac('win', 'Focus the previous window in the current tab')
def previous_window(self) -> None: def previous_window(self) -> None:
'@ac:win: Focus the previous window in the current tab'
self._next_window(-1) self._next_window(-1)
prev_window = previous_window prev_window = previous_window
@@ -516,28 +517,28 @@ class Tab: # {{{
if candidates: if candidates:
return self.most_recent_group(candidates) return self.most_recent_group(candidates)
def neighboring_window(self, which: EdgeLiteral) -> None: @ac('win', '''
''' Focus the neighboring window in the current tab
@ac:win: Focus the neighboring window in the current tab
For example:: For example::
map ctrl+left neighboring_window left map ctrl+left neighboring_window left
map ctrl+down neighboring_window bottom map ctrl+down neighboring_window bottom
''' ''')
def neighboring_window(self, which: EdgeLiteral) -> None:
neighbor = self.neighboring_group_id(which) neighbor = self.neighboring_group_id(which)
if neighbor: if neighbor:
self.windows.set_active_group(neighbor) self.windows.set_active_group(neighbor)
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None: @ac('win', '''
''' Move the window in the specified direction
@ac:win: Move the window in the specified direction
For example:: For example::
map ctrl+left move_window left map ctrl+left move_window left
map ctrl+down move_window bottom map ctrl+down move_window bottom
''' ''')
def move_window(self, delta: Union[EdgeLiteral, int] = 1) -> None:
if isinstance(delta, int): if isinstance(delta, int):
if self.current_layout.move_window(self.windows, delta): if self.current_layout.move_window(self.windows, delta):
self.relayout() self.relayout()
@@ -547,18 +548,18 @@ class Tab: # {{{
if self.current_layout.move_window_to_group(self.windows, neighbor): if self.current_layout.move_window_to_group(self.windows, neighbor):
self.relayout() self.relayout()
@ac('win', 'Move active window to the top (make it the first window)')
def move_window_to_top(self) -> None: def move_window_to_top(self) -> None:
'@ac:win: Move active window to the top (make it the first window)'
n = self.windows.active_group_idx n = self.windows.active_group_idx
if n > 0: if n > 0:
self.move_window(-n) self.move_window(-n)
@ac('win', 'Move active window forward (swap it with the next window)')
def move_window_forward(self) -> None: def move_window_forward(self) -> None:
'@ac:win: Move active window forward (swap it with the next window)'
self.move_window() self.move_window()
@ac('win', 'Move active window backward (swap it with the previous window)')
def move_window_backward(self) -> None: def move_window_backward(self) -> None:
'@ac:win: Move active window backward (swap it with the previous window)'
self.move_window(-1) self.move_window(-1)
def list_windows(self, active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]: def list_windows(self, active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:

View File

@@ -113,7 +113,7 @@ string_capabilities = {
# Clear screen # Clear screen
'clear': r'\E[H\E[2J', 'clear': r'\E[H\E[2J',
# Make cursor appear normal # Make cursor appear normal
'cnorm': r'\E[?12l\E[?25h', 'cnorm': r'\E[?12h\E[?25h',
# Carriage return # Carriage return
'cr': r'^M', # CR (carriage return \r) 'cr': r'^M', # CR (carriage return \r)
# Change scroll region # Change scroll region

View File

@@ -93,3 +93,22 @@ else:
def run_once(f: Callable[[], _T]) -> RunOnce: def run_once(f: Callable[[], _T]) -> RunOnce:
return RunOnce(f) return RunOnce(f)
if TYPE_CHECKING:
from typing import Literal
ActionGroup = Literal['cp', 'sc', 'win', 'tab', 'mouse', 'mk', 'lay', 'misc']
else:
ActionGroup = str
class ActionSpec(NamedTuple):
group: str
doc: str
def ac(group: ActionGroup, doc: str) -> Callable[[_T], _T]:
def w(f: _T) -> _T:
setattr(f, 'action_spec', ActionSpec(group, doc))
return f
return w

View File

@@ -528,7 +528,12 @@ def get_editor_from_env_vars(opts: Optional[Options] = None) -> List[str]:
def get_editor(opts: Optional[Options] = None) -> List[str]: def get_editor(opts: Optional[Options] = None) -> List[str]:
if opts is None: if opts is None:
from .fast_data_types import get_options from .fast_data_types import get_options
opts = get_options() try:
opts = get_options()
except RuntimeError:
# we are in a kitten
from .cli import create_default_opts
opts = create_default_opts()
if opts.editor == '.': if opts.editor == '.':
return get_editor_from_env_vars() return get_editor_from_env_vars()
import shlex import shlex
@@ -717,3 +722,37 @@ def get_new_os_window_size(
w = width or metrics['width'] w = width or metrics['width']
h = height or metrics['height'] h = height or metrics['height']
return w, h return w, h
def get_all_processes() -> Iterable[int]:
if is_macos:
from kitty.fast_data_types import get_all_processes as f
yield from f()
else:
for c in os.listdir('/proc'):
if c.isdigit():
yield int(c)
def is_kitty_gui_cmdline(*cmd: str) -> bool:
if not cmd:
return False
if os.path.basename(cmd[0]) != 'kitty':
return False
if len(cmd) == 1:
return True
if '+' in cmd or '@' in cmd or cmd[1].startswith('+') or cmd[1].startswith('@'):
return False
return True
def reload_conf_in_all_kitties() -> None:
import signal
from kitty.child import cmdline_of_process # type: ignore
for pid in get_all_processes():
try:
cmd = cmdline_of_process(pid)
except Exception:
continue
if cmd and is_kitty_gui_cmdline(*cmd):
os.kill(pid, signal.SIGUSR1)

View File

@@ -38,7 +38,7 @@ from .notify import NotificationCommand, handle_notification_cmd
from .options.types import Options from .options.types import Options
from .rgb import to_color from .rgb import to_color
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .types import MouseEvent, ScreenGeometry, WindowGeometry from .types import MouseEvent, ScreenGeometry, WindowGeometry, ac
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
from .utils import ( from .utils import (
color_as_int, get_primary_selection, load_shaders, log_error, open_cmd, color_as_int, get_primary_selection, load_shaders, log_error, open_cmd,
@@ -529,12 +529,12 @@ class Window:
def close(self) -> None: def close(self) -> None:
get_boss().close_window(self) get_boss().close_window(self)
def send_text(self, *args: str) -> bool: @ac('misc', '''
''' Send the specified text to the active window
@ac:misc: Send the specified text to the active window
For details, see :sc:`send_text`. For details, see :sc:`send_text`.
''' ''')
def send_text(self, *args: str) -> bool:
mode = keyboard_mode_name(self.screen) mode = keyboard_mode_name(self.screen)
required_mode_, text = args[-2:] required_mode_, text = args[-2:]
required_mode = frozenset(required_mode_.split(',')) required_mode = frozenset(required_mode_.split(','))
@@ -848,31 +848,31 @@ class Window:
# }}} # }}}
# mouse actions {{{ # mouse actions {{{
@ac('mouse', 'Click the URL under the mouse')
def mouse_click_url(self) -> None: def mouse_click_url(self) -> None:
'@ac:mouse: Click the URL under the mouse'
click_mouse_url(self.os_window_id, self.tab_id, self.id) click_mouse_url(self.os_window_id, self.tab_id, self.id)
@ac('mouse', 'Click the URL under the mouse only if the screen has no selection')
def mouse_click_url_or_select(self) -> None: def mouse_click_url_or_select(self) -> None:
'@ac:mouse: Click the URL under the mouse only if the screen has no selection'
if not self.screen.has_selection(): if not self.screen.has_selection():
self.mouse_click_url() self.mouse_click_url()
def mouse_selection(self, code: int) -> None: @ac('mouse', '''
''' Manipulate the selection based on the current mouse position
@ac:mouse: Manipulate the selection based on the current mouse position
For examples, see :ref:`conf-kitty-mouse.mousemap` For examples, see :ref:`conf-kitty-mouse.mousemap`
''' ''')
def mouse_selection(self, code: int) -> None:
mouse_selection(self.os_window_id, self.tab_id, self.id, code, self.current_mouse_event_button) mouse_selection(self.os_window_id, self.tab_id, self.id, code, self.current_mouse_event_button)
@ac('mouse', 'Paste the current primary selection')
def paste_selection(self) -> None: def paste_selection(self) -> None:
'@ac:mouse: Paste the current primary selection'
txt = get_boss().current_primary_selection() txt = get_boss().current_primary_selection()
if txt: if txt:
self.paste(txt) self.paste(txt)
@ac('mouse', 'Paste the current primary selection or the clipboard if no selection is present')
def paste_selection_or_clipboard(self) -> None: def paste_selection_or_clipboard(self) -> None:
'@ac:mouse: Paste the current primary selection or the clipboard if no selection is present'
txt = get_boss().current_primary_selection_or_clipboard() txt = get_boss().current_primary_selection_or_clipboard()
if txt: if txt:
self.paste(txt) self.paste(txt)
@@ -935,8 +935,8 @@ class Window:
# actions {{{ # actions {{{
@ac('cp', 'Show scrollback in a pager like less')
def show_scrollback(self) -> None: def show_scrollback(self) -> None:
'@ac:cp: Show scrollback in a pager like less'
text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True) text = self.as_text(as_ansi=True, add_history=True, add_wrap_markers=True)
data = self.pipe_data(text, has_wrap_markers=True) data = self.pipe_data(text, has_wrap_markers=True)
get_boss().display_scrollback(self, data['text'], data['input_line_number']) get_boss().display_scrollback(self, data['text'], data['input_line_number'])
@@ -947,8 +947,8 @@ class Window:
text = text.encode('utf-8') text = text.encode('utf-8')
self.screen.paste_bytes(text) self.screen.paste_bytes(text)
@ac('cp', 'Paste the specified text into the current window')
def paste(self, text: Union[str, bytes]) -> None: def paste(self, text: Union[str, bytes]) -> None:
'@ac:cp: Paste the specified text into the current window'
if text and not self.destroyed: if text and not self.destroyed:
if isinstance(text, str): if isinstance(text, str):
text = text.encode('utf-8') text = text.encode('utf-8')
@@ -964,8 +964,8 @@ class Window:
text = text.replace(b'\r\n', b'\n').replace(b'\n', b'\r') text = text.replace(b'\r\n', b'\n').replace(b'\n', b'\r')
self.screen.paste(text) self.screen.paste(text)
@ac('cp', 'Copy the selected text from the active window to the clipboard')
def copy_to_clipboard(self) -> None: def copy_to_clipboard(self) -> None:
'@ac:cp: Copy the selected text from the active window to the clipboard'
text = self.text_for_selection() text = self.text_for_selection()
if text: if text:
set_clipboard_string(text) set_clipboard_string(text)
@@ -978,21 +978,21 @@ class Window:
cursor_key_mode=self.screen.cursor_key_mode, cursor_key_mode=self.screen.cursor_key_mode,
).encode('ascii') ).encode('ascii')
@ac('cp', 'Copy the selected text from the active window to the clipboard, if no selection, send Ctrl-C')
def copy_or_interrupt(self) -> None: def copy_or_interrupt(self) -> None:
'@ac:cp: Copy the selected text from the active window to the clipboard, if no selection, send Ctrl-C'
text = self.text_for_selection() text = self.text_for_selection()
if text: if text:
set_clipboard_string(text) set_clipboard_string(text)
else: else:
self.write_to_child(self.encoded_key(KeyEvent(key=ord('c'), mods=GLFW_MOD_CONTROL))) self.write_to_child(self.encoded_key(KeyEvent(key=ord('c'), mods=GLFW_MOD_CONTROL)))
@ac('cp', 'Copy the selected text from the active window to the clipboard and clear selection, if no selection, send Ctrl-C')
def copy_and_clear_or_interrupt(self) -> None: def copy_and_clear_or_interrupt(self) -> None:
'@ac:cp: Copy the selected text from the active window to the clipboard and clear selection, if no selection, send Ctrl-C'
self.copy_or_interrupt() self.copy_or_interrupt()
self.screen.clear_selection() self.screen.clear_selection()
@ac('cp', 'Pass the selected text from the active window to the specified program')
def pass_selection_to_program(self, *args: str) -> None: def pass_selection_to_program(self, *args: str) -> None:
'@ac:cp: Pass the selected text from the active window to the specified program'
cwd = self.cwd_of_child cwd = self.cwd_of_child
text = self.text_for_selection() text = self.text_for_selection()
if text: if text:
@@ -1001,38 +1001,38 @@ class Window:
else: else:
open_url(text, cwd=cwd) open_url(text, cwd=cwd)
@ac('sc', 'Scroll up by one line')
def scroll_line_up(self) -> None: def scroll_line_up(self) -> None:
'@ac:sc: Scroll up by one line'
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_LINE, True) self.screen.scroll(SCROLL_LINE, True)
@ac('sc', 'Scroll down by one line')
def scroll_line_down(self) -> None: def scroll_line_down(self) -> None:
'@ac:sc: Scroll down by one line'
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_LINE, False) self.screen.scroll(SCROLL_LINE, False)
@ac('sc', 'Scroll up by one page')
def scroll_page_up(self) -> None: def scroll_page_up(self) -> None:
'@ac:sc: Scroll up by one page'
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_PAGE, True) self.screen.scroll(SCROLL_PAGE, True)
@ac('sc', 'Scroll down by one page')
def scroll_page_down(self) -> None: def scroll_page_down(self) -> None:
'@ac:sc: Scroll down by one page'
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_PAGE, False) self.screen.scroll(SCROLL_PAGE, False)
@ac('sc', 'Scroll to the top of the scrollback buffer')
def scroll_home(self) -> None: def scroll_home(self) -> None:
'@ac:sc: Scroll to the top of the scrollback buffer'
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_FULL, True) self.screen.scroll(SCROLL_FULL, True)
@ac('sc', 'Scroll to the bottom of the scrollback buffer')
def scroll_end(self) -> None: def scroll_end(self) -> None:
'@ac:sc: Scroll to the bottom of the scrollback buffer'
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll(SCROLL_FULL, False) self.screen.scroll(SCROLL_FULL, False)
@ac('mk', 'Toggle the current marker on/off')
def toggle_marker(self, ftype: str, spec: Union[str, Tuple[Tuple[int, str], ...]], flags: int) -> None: def toggle_marker(self, ftype: str, spec: Union[str, Tuple[Tuple[int, str], ...]], flags: int) -> None:
'@ac:mk: Toggle the current marker on/off'
from .marks import marker_from_spec from .marks import marker_from_spec
key = ftype, spec key = ftype, spec
if key == self.current_marker_spec: if key == self.current_marker_spec:
@@ -1052,24 +1052,24 @@ class Window:
self.screen.set_marker(marker_from_spec(ftype, spec_, flags)) self.screen.set_marker(marker_from_spec(ftype, spec_, flags))
self.current_marker_spec = key self.current_marker_spec = key
@ac('mk', 'Remove a previously created marker')
def remove_marker(self) -> None: def remove_marker(self) -> None:
'@ac:mk: Remove a previously created marker'
if self.current_marker_spec is not None: if self.current_marker_spec is not None:
self.screen.set_marker() self.screen.set_marker()
self.current_marker_spec = None self.current_marker_spec = None
@ac('mk', 'Scroll to the next or previous mark of the specified type')
def scroll_to_mark(self, prev: bool = True, mark: int = 0) -> None: def scroll_to_mark(self, prev: bool = True, mark: int = 0) -> None:
'@ac:mk: Scroll to the next or previous mark of the specified type'
self.screen.scroll_to_next_mark(mark, prev) self.screen.scroll_to_next_mark(mark, prev)
def signal_child(self, *signals: int) -> None: @ac('misc', '''
''' Send the specified SIGNAL to the foreground process in the active window
@ac:misc: Send the specified SIGNAL to the foreground process in the active window
For example:: For example::
map F1 signal_child SIGTERM map F1 signal_child SIGTERM
''' ''')
def signal_child(self, *signals: int) -> None:
pid = self.child.pid_for_cwd pid = self.child.pid_for_cwd
if pid is not None: if pid is not None:
for sig in signals: for sig in signals:

View File

@@ -11,7 +11,7 @@ from . import BaseTest
def run(input_data, query, **kw): def run(input_data, query, **kw):
kw['threads'] = kw.get('threads', 1) kw['threads'] = kw.get('threads', 1)
mark = kw.pop('mark', False) mark = kw.pop('mark', False)
from kittens.choose.main import match from kittens.choose.match import match
mark_before = mark_after = '' mark_before = mark_after = ''
if mark: if mark:
if mark is True: if mark is True:
@@ -59,6 +59,7 @@ class TestMatcher(BaseTest):
def test_positions(self): def test_positions(self):
' Output of positions ' ' Output of positions '
self.basic_test('abc\nac', 'ac', '0,1:ac\n0,2:abc', positions=True) self.basic_test('abc\nac', 'ac', '0,1:ac\n0,2:abc', positions=True)
self.basic_test('abc\nv', 'a', '0:abc', positions=True)
def test_delimiter(self): def test_delimiter(self):
' Test using a custom line delimiter ' ' Test using a custom line delimiter '

View File

@@ -150,6 +150,11 @@ class TestMouse(BaseTest):
multi_click(x=1, y=2) multi_click(x=1, y=2)
self.ae(sel(), 'stuvX') self.ae(sel(), 'stuvX')
release() release()
multi_click(x=3.6)
self.ae(sel(), 'cd')
move(0.2)
release()
self.ae(sel(), 'ab cd')
# Line select with drag # Line select with drag
s.reset() s.reset()
@@ -169,6 +174,15 @@ class TestMouse(BaseTest):
move() move()
self.ae(sel(), str(s.line(0))) self.ae(sel(), str(s.line(0)))
release() release()
multi_click(y=1, count=3)
self.ae(sel(), '4 5 6')
move(y=0)
self.ae(sel(), '1 2 3\n4 5 6')
move(y=1)
self.ae(sel(), '4 5 6')
move(y=2)
self.ae(sel(), '4 5 6\n7 8 9X')
release()
s.reset() s.reset()
s.draw(' 123') s.draw(' 123')
s.linefeed(), s.carriage_return() s.linefeed(), s.carriage_return()
@@ -183,8 +197,8 @@ class TestMouse(BaseTest):
release(x=2, y=1, button=GLFW_MOUSE_BUTTON_RIGHT) release(x=2, y=1, button=GLFW_MOUSE_BUTTON_RIGHT)
self.ae(sel(), '123\n 456') self.ae(sel(), '123\n 456')
press(button=GLFW_MOUSE_BUTTON_RIGHT) press(button=GLFW_MOUSE_BUTTON_RIGHT)
release(button=GLFW_MOUSE_BUTTON_RIGHT)
self.ae(sel(), ' 123\n 456') self.ae(sel(), ' 123\n 456')
release(button=GLFW_MOUSE_BUTTON_RIGHT)
# Rectangle select # Rectangle select
init() init()

View File

@@ -41,6 +41,8 @@ class TestConfParsing(BaseTest):
self.assertFalse(opts.keymap) self.assertFalse(opts.keymap)
opts = p('clear_all_shortcuts y', 'map f1 next_window') opts = p('clear_all_shortcuts y', 'map f1 next_window')
self.ae(len(opts.keymap), 1) self.ae(len(opts.keymap), 1)
opts = p('clear_all_mouse_actions y', 'mouse_map left click ungrabbed mouse_click_url_or_select')
self.ae(len(opts.mousemap), 1)
opts = p('strip_trailing_spaces always') opts = p('strip_trailing_spaces always')
self.ae(opts.strip_trailing_spaces, 'always') self.ae(opts.strip_trailing_spaces, 'always')
self.assertFalse(bad_lines) self.assertFalse(bad_lines)

View File

@@ -30,12 +30,12 @@
#define KITTY_LIB_DIR_NAME "lib" #define KITTY_LIB_DIR_NAME "lib"
#endif #endif
static inline void cleanup_free(void *p) { free(*(void**) p); } static void cleanup_free(void *p) { free(*(void**) p); }
#define FREE_AFTER_FUNCTION __attribute__((cleanup(cleanup_free))) #define FREE_AFTER_FUNCTION __attribute__((cleanup(cleanup_free)))
#ifndef __FreeBSD__ #ifndef __FreeBSD__
static inline bool static bool
safe_realpath(const char* src, char *buf, size_t buf_sz) { safe_realpath(const char* src, char *buf, size_t buf_sz) {
FREE_AFTER_FUNCTION char* ans = realpath(src, NULL); FREE_AFTER_FUNCTION char* ans = realpath(src, NULL);
if (ans == NULL) return false; if (ans == NULL) return false;
@@ -44,7 +44,7 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
} }
#endif #endif
static inline bool static bool
set_xoptions(const char *exe_dir_c, const char *lc_ctype, bool from_source) { set_xoptions(const char *exe_dir_c, const char *lc_ctype, bool from_source) {
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_c, NULL); wchar_t *exe_dir = Py_DecodeLocale(exe_dir_c, NULL);
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_c); return false; } if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_c); return false; }
@@ -199,7 +199,7 @@ run_embedded(const RunData run_data) {
// read_exe_path() {{{ // read_exe_path() {{{
#ifdef __APPLE__ #ifdef __APPLE__
static inline bool static bool
read_exe_path(char *exe, size_t buf_sz) { read_exe_path(char *exe, size_t buf_sz) {
(void)buf_sz; (void)buf_sz;
uint32_t size = PATH_MAX; uint32_t size = PATH_MAX;
@@ -212,7 +212,7 @@ read_exe_path(char *exe, size_t buf_sz) {
#include <sys/param.h> #include <sys/param.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
static inline bool static bool
read_exe_path(char *exe, size_t buf_sz) { read_exe_path(char *exe, size_t buf_sz) {
int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
size_t length = buf_sz; size_t length = buf_sz;
@@ -225,14 +225,14 @@ read_exe_path(char *exe, size_t buf_sz) {
} }
#elif defined(__NetBSD__) #elif defined(__NetBSD__)
static inline bool static bool
read_exe_path(char *exe, size_t buf_sz) { read_exe_path(char *exe, size_t buf_sz) {
if (!safe_realpath("/proc/curproc/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/curproc/exe\n"); return false; } if (!safe_realpath("/proc/curproc/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/curproc/exe\n"); return false; }
return true; return true;
} }
#elif defined(__OpenBSD__) #elif defined(__OpenBSD__)
static inline bool static bool
read_exe_path(char *exe, size_t buf_sz) { read_exe_path(char *exe, size_t buf_sz) {
const char *path = getenv("PATH"); const char *path = getenv("PATH");
if (!path) { fprintf(stderr, "No PATH environment variable set, aborting\n"); return false; } if (!path) { fprintf(stderr, "No PATH environment variable set, aborting\n"); return false; }
@@ -251,7 +251,7 @@ read_exe_path(char *exe, size_t buf_sz) {
#else #else
static inline bool static bool
read_exe_path(char *exe, size_t buf_sz) { read_exe_path(char *exe, size_t buf_sz) {
if (!safe_realpath("/proc/self/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/self/exe\n"); return false; } if (!safe_realpath("/proc/self/exe", exe, buf_sz)) { fprintf(stderr, "Failed to read /proc/self/exe\n"); return false; }
return true; return true;

View File

@@ -24,7 +24,7 @@ xterm-kitty|KovIdTTY,
cbt=\E[Z, cbt=\E[Z,
civis=\E[?25l, civis=\E[?25l,
clear=\E[H\E[2J, clear=\E[H\E[2J,
cnorm=\E[?12l\E[?25h, cnorm=\E[?12h\E[?25h,
cr=^M, cr=^M,
csr=\E[%i%p1%d;%p2%dr, csr=\E[%i%p1%d;%p2%dr,
cub=\E[%p1%dD, cub=\E[%p1%dD,

Binary file not shown.