Compare commits

..

59 Commits
x11 ... v0.42.0

Author SHA1 Message Date
Kovid Goyal
f2412cc9c8 version 0.42.0 2025-05-11 10:42:15 +05:30
Kovid Goyal
5dbd198fb9 Update changelog 2025-05-11 10:36:23 +05:30
Kovid Goyal
9b4643e8bc ... 2025-05-11 07:46:06 +05:30
Kovid Goyal
f2f914ed05 More natural limits for step size when sampling paramterized curve 2025-05-10 22:58:56 +05:30
Kovid Goyal
ec5062ceb4 Merge branch 'master' of https://github.com/nomisreual/kitty 2025-05-10 21:23:35 +05:30
Simon Antonius Lauer
eb70f81218 fix: add missing pkgs and manually add font for nix shell 2025-05-10 14:42:46 +02:00
Kovid Goyal
04a4d62859 Replace all remaining uses of old parametrized drawing code 2025-05-10 11:07:26 +05:30
Kovid Goyal
2aaa440519 Move calc of line width out of draw_parametrized_curve 2025-05-10 10:12:57 +05:30
Kovid Goyal
782e7cb2fb ... 2025-05-10 10:03:29 +05:30
Kovid Goyal
41b44fdafa Prepare for moving circle rendering to new code as well 2025-05-10 09:28:20 +05:30
Kovid Goyal
0f3603c0eb Use new parametrized curve rendering for rounded separators 2025-05-10 09:23:12 +05:30
Kovid Goyal
8ba9bfd460 Prepare for using new parametrized drawing code for beziers as well 2025-05-10 09:06:44 +05:30
Kovid Goyal
22fd548903 Also adjust in y direction for odd height 2025-05-10 09:00:57 +05:30
Kovid Goyal
7856046382 Proper odd width x-offset adjustment 2025-05-10 08:56:33 +05:30
Kovid Goyal
f93d57d919 Mark curve data as const 2025-05-10 08:50:29 +05:30
Kovid Goyal
e8d50d0734 Finish up implementation of drawing curve with derivative
Fixes #8299
2025-05-10 08:38:32 +05:30
Kovid Goyal
20e5c7b004 Simplify rectircle equations by using trigonometric super-ellipse parametrization 2025-05-10 07:31:33 +05:30
Kovid Goyal
a597764245 Refactor drawing of parametrized curve
Work towards a proper rendering of thick curves using a derivative to
control sampling frequency.
2025-05-09 21:55:51 +05:30
Kovid Goyal
d6225153ee Slightly improve rsync script 2025-05-09 15:33:22 +05:30
Kovid Goyal
cca838b952 ... 2025-05-09 07:48:56 +05:30
Kovid Goyal
6a53897c17 change_font_size: allow multiplying/dividing the current font size in addition to incrementing it
Fixes #8616
2025-05-09 07:39:06 +05:30
Kovid Goyal
e0e4e53e3b Allow using env vars that resolve to a full cmdline as program in launch mappings
Fixes #8613
2025-05-08 19:22:39 +05:30
Kovid Goyal
8a14a5638a ... 2025-05-08 15:30:09 +05:30
Kovid Goyal
2fed0ec562 Linux: Handle desktop settings portals that are so old they don't implement ReadOne
Sigh, outdated Linux software, just creating busy work for everyone.
2025-05-08 15:22:42 +05:30
Kovid Goyal
97f9d16046 ... 2025-05-08 07:27:16 +05:30
Kovid Goyal
ddd79f0733 Fix #8610 2025-05-07 16:23:17 +05:30
Kovid Goyal
53fd9892eb Allow custom kitten names with hyphens in them
Apparently there is no way to have config filenames on
nixOS/home-manager without hyphens in them.

Fixes #8608
2025-05-06 22:16:28 +05:30
Kovid Goyal
91eb0ec735 ... 2025-05-06 20:30:14 +05:30
Kovid Goyal
3c3ba4a9fb @ launch: Add a --wait-for-child-exit flag to get the child processes exit code even when it is running in a window. 2025-05-06 19:17:17 +05:30
Kovid Goyal
a4963a58e3 CLI parser: Suggest a correction for likely flag typo 2025-05-06 14:48:00 +05:30
Kovid Goyal
80bb9404d5 Have auto color scheme switching also control background image
Fixes #8603
2025-05-06 09:10:05 +05:30
Kovid Goyal
7045632d2e Fix #8606 2025-05-06 08:09:05 +05:30
Kovid Goyal
7b89477470 Allow changing some more background_image options in the API 2025-05-05 20:50:15 +05:30
Kovid Goyal
53bb4f0609 Remove useless code
default_background_changed is meant for individual windows not OS windows
2025-05-05 20:25:08 +05:30
Kovid Goyal
05e6ed685b Merge branch 'dependabot/go_modules/all-go-deps-0987ac49fb' of https://github.com/kovidgoyal/kitty 2025-05-05 08:45:25 +05:30
dependabot[bot]
e6e3e01795 Bump github.com/alecthomas/chroma/v2 in the all-go-deps group
Bumps the all-go-deps group with 1 update: [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma).


Updates `github.com/alecthomas/chroma/v2` from 2.17.0 to 2.17.2
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.17.0...v2.17.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-05 03:08:19 +00:00
Kovid Goyal
eb7ca8902b Add xmonad to the X11 compatibility table 2025-05-04 08:07:04 +05:30
Kovid Goyal
b5cbb501a4 Use the base name of the named pipe for the created temp file 2025-05-03 16:03:06 +05:30
Kovid Goyal
7ef2fe53e0 Avoid extra stat() 2025-05-03 15:55:36 +05:30
Kovid Goyal
a59a347903 diff kitten: Allow diffing named pipes
Fixes #8597
2025-05-03 15:53:29 +05:30
Kovid Goyal
d22381491d Restore top/bottom panel functionality on i3 2025-05-03 11:12:32 +05:30
Kovid Goyal
e961020330 Fix typo in docs 2025-05-03 10:58:39 +05:30
Kovid Goyal
1237f7667c ... 2025-05-03 10:47:03 +05:30
Kovid Goyal
7dc673e485 Add XFCE to the X11 compatibility matrix 2025-05-03 10:39:41 +05:30
Kovid Goyal
07cda6ac45 Add GNOME to the X11 compatibility matrix 2025-05-03 08:57:55 +05:30
Kovid Goyal
f30ec25c57 Start work on X11 compatibility matrix 2025-05-02 13:25:40 +05:30
Kovid Goyal
43be32a3d7 More atoms 2025-05-02 12:20:54 +05:30
Kovid Goyal
958489f97d X11: Workaround for floating window position on KDE 2025-05-02 10:11:15 +05:30
Kovid Goyal
afe7dc47c2 Update size in addition to position when updating layer properties 2025-05-02 09:35:25 +05:30
Kovid Goyal
11cb3adb8f Reduce roundtrips to X server to fetch atom values 2025-05-02 09:28:24 +05:30
Kovid Goyal
a9bc9962f4 X11: add support for --output-name 2025-05-02 08:37:49 +05:30
Kovid Goyal
a2631448e5 X11: fix window type for non background layers 2025-05-02 08:20:51 +05:30
Kovid Goyal
be9624bbdd Fix px suffix for lines/columns not working 2025-05-02 08:11:49 +05:30
Kovid Goyal
6c0e5f09d3 Fix layer size calculation on high DPI displays under X11 2025-05-02 08:01:35 +05:30
Kovid Goyal
233cd3e2b9 doc edits 2025-05-01 22:01:02 +05:30
Kovid Goyal
49ea2bf636 ... 2025-05-01 21:58:44 +05:30
Kovid Goyal
e87256793c ... 2025-05-01 21:49:48 +05:30
Kovid Goyal
f461039017 Modify docs in light of X11 support 2025-05-01 21:48:27 +05:30
Kovid Goyal
ca688be41c Use focus policy for controlling input on X11 2025-05-01 21:42:00 +05:30
40 changed files with 787 additions and 349 deletions

View File

@@ -106,29 +106,37 @@ consumption to do the same tasks.
Detailed list of changes
-------------------------------------
0.42.0 [future]
0.42.0 [2025-05-11]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A new kitten: :doc:`quick-access-terminal </kittens/quick-access-terminal>` to :ref:`quake`
- The :doc:`panel kitten </kittens/panel>` now works on macOS as well as Wayland (:iss:`2590`)
- The :doc:`panel kitten </kittens/panel>` works on macOS and X11 as well as Wayland (:iss:`2590`)
- **Behavior change**: Now kitty does full grapheme segmentation following the
Unicode 16 spec when splitting text into cells (:iss:`8533`)
- **Behavior change**: The :ref:`automatic color switching functionality <auto_color_scheme>` now also controls background image settings (:iss:`8603`)
- panel kitten: Allow using :option:`kitty +kitten panel --single-instance` to create multiple panels in one process (:iss:`8549`)
- launch: Allow creating desktop panels such as those created by the :doc:`panel kitten </kittens/panel>` (:iss:`8549`)
- Remote control: Allow modifying desktop panels and showing/hiding OS Windows
using the `kitten @ resize-os-window` command (:iss:`8550`)
using the ``kitten @ resize-os-window`` command (:iss:`8550`)
- Allow starting kitty with the OS window hidden via :option:`kitty --start-as`\=hidden useful for single instance mode (:iss:`3466`)
- Remote control launch: Allow waiting for a program launched in a new window
to exit and get the exit code via the `kitty +launch
--wait-for-child-to-exit` command line flag (:disc:`8573`)
- Allow starting kitty with the OS window hidden via :option:`kitty --start-as=hidden <kitty --start-as>`, useful for single instance mode (:iss:`3466`)
- Allow configuring the mouse unhide behavior when using :opt:`mouse_hide_wait` (:pull:`8508`)
- diff kitten: Add half page and full page scroll vim-like bindings (:pull:`8514`)
- diff kitten: Allow diffing named pipes (:iss:`8597`)
- Fix a regression that caused automatic color themes to not be re-applied after config file reload (:iss:`8530`)
- Wayland: When the compositor supports the `xdg-system-bell
@@ -137,8 +145,8 @@ Detailed list of changes
- panel kitten: Allow specifying panel size in pixels in addition to cells
- Fix a regression in 0.36.0 that caused using = with single letter options to
no longer work correctly (:iss:`8556`)
- Fix a regression in 0.36.0 that caused using = with single letter command
line flags to no longer work correctly (:iss:`8556`)
- Single instance: Preserve environment variables from invoking environment in
newly created window (:disc:`8567`)
@@ -148,6 +156,12 @@ Detailed list of changes
- macOS: Fix text color in visual window select ignoring the color theme (:iss:`8579`)
- Launch action: Allow using an env var that resolves to a full command-line as the program to launch (:pull:`8613`)
- :ac:`change_font_size` allow multiplying/dividing the current font size in addition to incrementing it (:iss:`8616`)
- Box drawing: Improve appearance of rounder corners, giving them a uniform line width (:iss:`8299`)
0.41.1 [2025-04-03]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -470,7 +470,8 @@ You need to make sure that the environment variables you define in your shell's
rc files are either also defined system wide or via the :opt:`env` directive in
:file:`kitty.conf`. Common environment variables that cause issues are those
related to localization, such as :envvar:`LANG`, ``LC_*`` and loading of
configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY`.
configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY` and,
most importantly, ``PATH`` to locate binaries.
To see the environment variables that kitty sees, you can add the following
mapping to :file:`kitty.conf`::

View File

@@ -149,14 +149,14 @@ user presses, for example, :kbd:`ctrl+shift+a` the escape code would be ``CSI
97;modifiers u``. It *must not* be ``CSI 65; modifiers u``.
If *alternate key reporting* is requested by the program running in the
terminal, the terminal can send two additional Unicode codepoints, the
*shifted key* and *base layout key*, separated by colons.
The shifted key is simply the upper-case version of ``unicode-codepoint``, or
more technically, the shifted version. So `a` becomes `A` and so on, based on
the current keyboard layout. This is needed to be able to match against a
shortcut such as :kbd:`ctrl+plus` which depending on the type of keyboard could
be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. Note that the shifted
key must be present only if shift is also present in the modifiers.
terminal, the terminal can send two additional Unicode codepoints, the *shifted
key* and *base layout key*, separated by colons. The shifted key is simply the
upper-case version of ``unicode-codepoint``, or more technically, the shifted
version, in the currently active keyboard layout. So `a` becomes `A` and so on,
based on the current keyboard layout. This is needed to be able to match
against a shortcut such as :kbd:`ctrl+plus` which depending on the type of
keyboard could be either :kbd:`ctrl+shift+equal` or :kbd:`ctrl+plus`. Note that
the shifted key must be present only if shift is also present in the modifiers.
The *base layout key* is the key corresponding to the physical key in the
standard PC-101 key layout. So for example, if the user is using a Cyrillic

View File

@@ -25,20 +25,14 @@ a dock panel showing system information (Linux only).
.. versionadded:: 0.42.0
Support for macOS (the edge based panels do not prevent other windows from
floating over them because of limitations in Cocoa, but background and
overlay panels work well)
Support for macOS, see :ref:`compatibility matrix <panel_compat>` for details.
and X11 (background and overlay).
.. versionadded:: 0.34.0
Support for Wayland. See :ref:`below <panel_wayland_status>` for which
Support for Wayland. See :ref:`below <panel_compat>` for which
Wayland compositors work.
.. note::
On X11, only the ``top`` and ``bottom`` panels are widely supported,
the other types depend on the window manager used.
Using this kitten is simple, for example::
kitten panel sh -c 'printf "\n\n\nHello, world."; sleep 5s'
@@ -128,44 +122,104 @@ actual script is not public, but there are :ref:`public projects implementing
general purpose panels using kitty <panel_projects>`.
.. _panel_wayland_status:
.. _panel_compat:
Wayland compositor status
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Compatibility with various platforms
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Below is a list of the status of various Wayland compositors. The panel kitten
relies of the `wlr layer shell protocol
<https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__,
which is technically supported by almost all Wayland compositors, but the
implementation in some of them is quite buggy.
.. only:: man
🟢 **Hyprland**
Fully working, no known issues
See the HTML documentation for the compatibility matrix.
🟢 **KDE** (kwin)
Fully working, no known issues
.. only:: not man
🟠 **Sway**
Partially working. Issues include:
* Renders its configured background over the background window instead of
under it. This is likely because it uses the wlr protocol for
backgrounds itself.
* Hiding a dock panel (unmapping the window) does not release the space
used by the dock.
Generated with the help of the :file:`panels.py` test script.
🟠 niri
Breaks when hiding (unmapping) layer shell windows. This means the quick
access terminal is non-functional, but background and dock panels work.
More technically, keyboard focus gets stuck in the hidden window and when trying
to remap the hidden window niri never sends configure events for the remapped surface.
.. tab:: Wayland
🟠 labwc
Breaks when hiding (unmapping) layer shell windows. This means the quick
access terminal is non-functional, but background and dock panels work.
More technically, when unmapping the surface (attaching a NULL buffer to
it) labwc continues to send configure events to the unmapped surface,
leading to Wayland protocol errors and a crash of labwc.
Below is a list of the status of various Wayland compositors. The panel kitten
relies of the `wlr layer shell protocol
<https://wayland.app/protocols/wlr-layer-shell-unstable-v1#compositor-support>`__,
which is technically supported by almost all Wayland compositors, but the
implementation in some of them is quite buggy.
🔴 GNOME (mutter)
Does not implement the wlr protocol at all, nothing works.
🟢 **Hyprland**
Fully working, no known issues
🟢 **KDE** (kwin)
Fully working, no known issues
🟠 **Sway**
Partially working. Issues include:
* Renders its configured background over the background window instead of
under it. This is likely because it uses the wlr protocol for
backgrounds itself.
* Hiding a dock panel (unmapping the window) does not release the space
used by the dock.
🟠 **niri**
Breaks when hiding (unmapping) layer shell windows. This means the quick
access terminal is non-functional, but background and dock panels work.
More technically, keyboard focus gets stuck in the hidden window and when trying
to remap the hidden window niri never sends configure events for the remapped surface.
🟠 **labwc**
Breaks when hiding (unmapping) layer shell windows. This means the quick
access terminal is non-functional, but background and dock panels work.
More technically, when unmapping the surface (attaching a NULL buffer to
it) labwc continues to send configure events to the unmapped surface,
leading to Wayland protocol errors and a crash of labwc.
🔴 **GNOME** (mutter)
Does not implement the wlr protocol at all, nothing works.
.. tab:: macOS
Mostly everything works, with the notable exception that dock panels do not
prevent other windows from covering them. This is because Apple does not
provide and way to do this in their APIs.
.. tab:: X11
Support is highly dependent on the quirks of individual window
managers. See the matrix below:
.. list-table:: Compatibility matrix
:header-rows: 1
:stub-columns: 1
* - WM
- Desktop
- Dock
- Quick
- Notes
* - KDE
- 🟠
- 🟢
- 🟢
- transparency does not work for :option:`--edge=background <--edge>`
* - GNOME
- 🟢
- 🟢
- 🟢
-
* - XFCE
- 🟢
- 🟢
- 🟢
-
* - i3
- 🔴
- 🟠
- 🔴
- only top and bottom dock panels, without transparency
* - xmonad
- 🔴
- 🔴
- 🔴
- doesn't support the needed NET_WM protocols

View File

@@ -14,8 +14,7 @@ Make a Quake like quick access terminal
.. include:: ../quake-screenshots.rst
.. versionadded:: 0.42.0
Works on macOS and Wayland, see :ref:`here for Wayland compositor support
status <panel_wayland_status>`.
See :ref:`here for what platforms it works on <panel_compat>`.
This kitten can be used to make a quick access terminal, that appears and
disappears at a key press. To do so use the following command:

View File

@@ -70,7 +70,8 @@ This works by creating three files: :file:`dark-theme.auto.conf`,
:file:`light-theme.auto.conf` and :file:`no-preference-theme.auto.conf` in the
kitty config directory. When these files exist, kitty queries the OS for its color scheme
and uses the appropriate file. Note that the colors in these files override all other
colors, even those specified using the :option:`kitty --override` command line flag.
colors, and also all background image settings,
even those specified using the :option:`kitty --override` command line flag.
kitty will also automatically change colors when the OS color scheme changes,
for example, during night/day transitions.

11
glfw/dbus_glfw.c vendored
View File

@@ -214,13 +214,6 @@ typedef struct {
void *user_data;
} MethodResponse;
static const char*
format_message_error(DBusError *err) {
static char buf[1024];
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
return buf;
}
static void
method_reply_received(DBusPendingCall *pending, void *user_data) {
MethodResponse *res = (MethodResponse*)user_data;
@@ -228,7 +221,7 @@ method_reply_received(DBusPendingCall *pending, void *user_data) {
if (msg) {
DBusError err;
dbus_error_init(&err);
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, &err, res->user_data);
else res->callback(msg, NULL, res->user_data);
}
}
@@ -243,7 +236,7 @@ call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_p
DBusError error; dbus_error_init(&error);
RAII_MSG(reply, dbus_connection_send_with_reply_and_block(session_bus, msg, timeout, &error));
if (dbus_error_is_set(&error)) {
callback(reply, error.message, user_data);
callback(reply, &error, user_data);
return false;
} else if (reply) {
callback(reply, NULL, user_data);

2
glfw/dbus_glfw.h vendored
View File

@@ -33,7 +33,7 @@
static inline void cleanup_msg(void *p) { DBusMessage *m = *(DBusMessage**)p; if (m) dbus_message_unref(m); m = NULL; }
#define RAII_MSG(name, initializer) __attribute__((cleanup(cleanup_msg))) DBusMessage *name = initializer
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
typedef void(*dbus_pending_callback)(DBusMessage *msg, const DBusError *err, void* data);
typedef struct {
EventLoopData* eld;

12
glfw/ibus_glfw.c vendored
View File

@@ -373,9 +373,9 @@ read_ibus_address(_GLFWIBUSData *ibus) {
}
void
input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg);
input_context_created(DBusMessage *msg, const DBusError *err, void *data) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s: %s", err->name, err->message);
return;
}
const char *path = NULL;
@@ -488,15 +488,15 @@ glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) {
}
void
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
key_event_processed(DBusMessage *msg, const DBusError *err, void *data) {
uint32_t handled = 0;
_GLFWIBUSKeyEvent *ev = (_GLFWIBUSKeyEvent*)data;
// Restore key's text from the text embedded in the structure.
ev->glfw_ev.text = ev->__embedded_text;
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
bool failed = false;
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s: %s", err->name, err->message);
failed = true;
} else {
glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID);

View File

@@ -24,15 +24,47 @@ static int theme_size = -1;
static GLFWColorScheme appearance = GLFW_COLOR_SCHEME_NO_PREFERENCE;
static bool cursor_theme_changed = false, appearance_initialized = false;
#define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
#define HANDLER(name_) static void name_(DBusMessage *msg, const DBusError* err, void *data) { \
(void)data; \
if (errmsg) { \
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \
if (err) { \
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s: %s", #name_, err->name, err->message); \
return; \
}
HANDLER(get_color_scheme_legacy)
DBusMessageIter iter, variant_iter, variant_iter2;
if (!dbus_message_iter_init(msg, &iter)) return;
dbus_message_iter_recurse(&iter, &variant_iter);
int type = dbus_message_iter_get_arg_type(&variant_iter);
if (type != DBUS_TYPE_VARIANT) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Read for color-scheme did not return a variant"); return;
}
dbus_message_iter_recurse(&variant_iter, &variant_iter2);
if (type != DBUS_TYPE_VARIANT) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Read for color-scheme did not return a nested variant"); return;
}
uint32_t val;
dbus_message_iter_get_basic(&variant_iter2, &val);
if (val < 3) appearance = val;
}
HANDLER(get_color_scheme)
static void
get_color_scheme(DBusMessage *msg, const DBusError* err, void *data) {
(void) data;
if (err) {
if (strcmp("org.freedesktop.DBus.Error.UnknownMethod", err->name) == 0) {
DBusConnection *session_bus = glfw_dbus_session_bus();
if (session_bus) {
const char *namespace = FDO_DESKTOP_NAMESPACE, *key = FDO_APPEARANCE_KEY;
glfw_dbus_call_blocking_method(session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "Read", DBUS_TIMEOUT_USE_DEFAULT,
get_color_scheme_legacy, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
}
return;
} else {
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s: %s", "get_color_scheme", err->name, err->message);
return;
}
}
uint32_t val;
DBusMessageIter iter, variant_iter;
if (!dbus_message_iter_init(msg, &iter)) return;

10
glfw/linux_notify.c vendored
View File

@@ -32,9 +32,9 @@ glfw_dbus_set_user_notification_activated_handler(GLFWDBusnotificationactivatedf
}
void
notification_created(DBusMessage *msg, const char* errmsg, void *data) {
if (errmsg) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s", errmsg);
notification_created(DBusMessage *msg, const DBusError* err, void *data) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to create notification error: %s: %s", err->name, err->message);
if (data) free(data);
return;
}
@@ -95,9 +95,9 @@ cancel_user_notification(DBusConnection *session_bus, uint32_t *id) {
}
static void
got_capabilities(DBusMessage *msg, const char* err, void* data UNUSED) {
got_capabilities(DBusMessage *msg, const DBusError* err, void* data UNUSED) {
if (err) {
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s", err);
_glfwInputError(GLFW_PLATFORM_ERROR, "Notify: Failed to get server capabilities error: %s: %s", err->name, err->message);
return;
}
#define check_call(func, err, ...) if (!func(__VA_ARGS__)) { _glfwInputError(GLFW_PLATFORM_ERROR, "Notify: GetCapabilities: %s", err); return; }

2
glfw/wl_window.c vendored
View File

@@ -1069,9 +1069,9 @@ calculate_layer_size(_GLFWwindow *window, uint32_t *width, uint32_t *height) {
if (!*height) *height = monitor_height;
return;
}
debug("Calculating layer shell window size at scale: %f\n", xscale);
const unsigned xsz = config->x_size_in_pixels ? (unsigned)(config->x_size_in_pixels * xscale) : (cell_width * config->x_size_in_cells);
const unsigned ysz = config->y_size_in_pixels ? (unsigned)(config->y_size_in_pixels * yscale) : (cell_height * config->y_size_in_cells);
debug("Calculating layer shell window size at scale: %f cell_size: %u %u sz: %u %u\n", xscale, cell_width, cell_height, xsz, ysz);
if (config->edge == GLFW_EDGE_LEFT || config->edge == GLFW_EDGE_RIGHT) {
if (!*height) *height = monitor_height;
double spacing = spacing_x;

82
glfw/x11_init.c vendored
View File

@@ -47,16 +47,11 @@
//
static Atom getAtomIfSupported(Atom* supportedAtoms,
unsigned long atomCount,
const char* atomName)
const Atom atom)
{
const Atom atom = XInternAtom(_glfw.x11.display, atomName, False);
for (unsigned long i = 0; i < atomCount; i++)
{
if (supportedAtoms[i] == atom)
return atom;
for (unsigned long i = 0; i < atomCount; i++) {
if (supportedAtoms[i] == atom) return atom;
}
return None;
}
@@ -120,49 +115,36 @@ static void detectEWMH(void)
// See which of the atoms we support that are supported by the WM
_glfw.x11.NET_WM_STATE =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE");
_glfw.x11.NET_WM_STATE_ABOVE =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
_glfw.x11.NET_WM_STATE_BELOW =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_BELOW");
_glfw.x11.NET_WM_STATE_FULLSCREEN =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
_glfw.x11.NET_WM_STATE_SKIP_TASKBAR =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_SKIP_TASKBAR");
_glfw.x11.NET_WM_STATE_SKIP_PAGER =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_SKIP_PAGER");
_glfw.x11.NET_WM_STATE_STICKY =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_STICKY");
_glfw.x11.NET_WM_FULLSCREEN_MONITORS =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
_glfw.x11.NET_WM_WINDOW_TYPE =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
_glfw.x11.NET_WM_WINDOW_TYPE_DOCK =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_DOCK");
_glfw.x11.NET_WM_WINDOW_TYPE_DESKTOP =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_DESKTOP");
_glfw.x11.NET_WORKAREA =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
_glfw.x11.NET_CURRENT_DESKTOP =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
_glfw.x11.NET_ACTIVE_WINDOW =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
_glfw.x11.NET_FRAME_EXTENTS =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
_glfw.x11.NET_WM_STRUT_PARTIAL =
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STRUT_PARTIAL");
#define ALL_ATOMS \
S(NET_WM_STATE) S(NET_WM_STATE_ABOVE) S(NET_WM_STATE_BELOW) S(NET_WM_STATE_FULLSCREEN) \
S(NET_WM_STATE_MAXIMIZED_VERT) S(NET_WM_STATE_MAXIMIZED_HORZ) S(NET_WM_STATE_DEMANDS_ATTENTION) \
S(NET_WM_STATE_SKIP_TASKBAR) S(NET_WM_STATE_SKIP_PAGER) S(NET_WM_STATE_STICKY) \
\
S(NET_WM_FULLSCREEN_MONITORS) S(NET_WM_STRUT_PARTIAL) \
\
S(NET_WM_WINDOW_TYPE) S(NET_WM_WINDOW_TYPE_NORMAL) S(NET_WM_WINDOW_TYPE_DOCK) S(NET_WM_WINDOW_TYPE_DESKTOP) \
S(NET_WM_WINDOW_TYPE_UTILITY) S(NET_WM_WINDOW_TYPE_SPLASH) S(NET_WM_WINDOW_TYPE_DIALOG) S(NET_WM_WINDOW_TYPE_MENU) \
S(NET_WM_WINDOW_TYPE_NOTIFICATION) \
\
S(NET_WORKAREA) S(NET_CURRENT_DESKTOP) S(NET_ACTIVE_WINDOW) S(NET_FRAME_EXTENTS) S(NET_REQUEST_FRAME_EXTENTS) \
\
S(NET_WM_ALLOWED_ACTIONS) S(NET_WM_ACTION_MOVE) S(NET_WM_ACTION_RESIZE) S(NET_WM_ACTION_MINIMIZE) \
S(NET_WM_ACTION_SHADE) S(NET_WM_ACTION_STICK) S(NET_WM_ACTION_MAXIMIZE_HORZ) S(NET_WM_ACTION_MAXIMIZE_VERT) \
S(NET_WM_ACTION_FULLSCREEN) S(NET_WM_ACTION_CHANGE_DESKTOP) S(NET_WM_ACTION_CLOSE) S(NET_WM_ACTION_ABOVE) \
S(NET_WM_ACTION_BELOW) S(NET_WM_ACTION_ABOVE_BELOW)
static const char* atom_names[40] = {
#define S(x) "_" #x,
ALL_ATOMS
};
#undef S
Atom atoms[arraysz(atom_names)];
XInternAtoms(_glfw.x11.display, (char**)atom_names, arraysz(atom_names), False, atoms);
unsigned i = 0;
#define S(name) _glfw.x11.name = getAtomIfSupported(supportedAtoms, atomCount, atoms[i++]);
ALL_ATOMS
#undef S
#undef ALL_ATOMS
XFree(supportedAtoms);
}

6
glfw/x11_platform.h vendored
View File

@@ -251,14 +251,12 @@ typedef struct _GLFWlibraryX11
Atom WM_STATE;
Atom WM_DELETE_WINDOW;
Atom NET_WM_NAME;
Atom NET_WM_ALLOWED_ACTIONS, NET_WM_ACTION_MOVE, NET_WM_ACTION_RESIZE, NET_WM_ACTION_MINIMIZE, NET_WM_ACTION_SHADE, NET_WM_ACTION_STICK, NET_WM_ACTION_MAXIMIZE_HORZ, NET_WM_ACTION_MAXIMIZE_VERT, NET_WM_ACTION_FULLSCREEN, NET_WM_ACTION_CHANGE_DESKTOP, NET_WM_ACTION_CLOSE, NET_WM_ACTION_ABOVE, NET_WM_ACTION_BELOW, NET_WM_ACTION_ABOVE_BELOW;
Atom NET_WM_ICON_NAME;
Atom NET_WM_ICON;
Atom NET_WM_PID;
Atom NET_WM_PING;
Atom NET_WM_WINDOW_TYPE;
Atom NET_WM_WINDOW_TYPE_NORMAL;
Atom NET_WM_WINDOW_TYPE_DOCK;
Atom NET_WM_WINDOW_TYPE_DESKTOP;
Atom NET_WM_WINDOW_TYPE, NET_WM_WINDOW_TYPE_NORMAL, NET_WM_WINDOW_TYPE_DOCK, NET_WM_WINDOW_TYPE_DESKTOP, NET_WM_WINDOW_TYPE_UTILITY, NET_WM_WINDOW_TYPE_SPLASH, NET_WM_WINDOW_TYPE_DIALOG, NET_WM_WINDOW_TYPE_MENU, NET_WM_WINDOW_TYPE_NOTIFICATION;
Atom NET_WM_STATE;
Atom NET_WM_STATE_ABOVE;
Atom NET_WM_STATE_BELOW;

48
glfw/x11_window.c vendored
View File

@@ -538,13 +538,28 @@ typedef struct WindowGeometry {
strut_type struts[12];
} WindowGeometry;
#define config (window->x11.layer_shell.config)
static _GLFWmonitor*
find_monitor_by_name(const char* name) {
if (!name || !name[0]) return (_GLFWmonitor*)glfwGetPrimaryMonitor();;
for (int i = 0; i < _glfw.monitorCount; i++) {
_GLFWmonitor *m = _glfw.monitors[i];
if (strcmp(m->name, name) == 0) return m;
}
return (_GLFWmonitor*)glfwGetPrimaryMonitor();;
}
static WindowGeometry
calculate_layer_geometry(_GLFWwindow *window) {
_GLFWmonitor *monitor = find_monitor_by_name(config.output_name);
MonitorGeometry mg = _glfwPlatformGetMonitorGeometry((_GLFWmonitor*)glfwGetPrimaryMonitor());
WindowGeometry ans = {0};
debug_rendering("Monitor: %s full: %dx%d@%dx%d workarea: %dx%d@%dx%d\n", monitor->name,
mg.full.width, mg.full.height, mg.full.x, mg.full.y, mg.workarea.width, mg.workarea.height, mg.workarea.x, mg.workarea.y);
ans.width = mg.full.width; ans.height = mg.full.height;
ans.x = mg.full.x; ans.y = mg.full.y;
#define config (window->x11.layer_shell.config)
ans.needs_strut = config.type == GLFW_LAYER_SHELL_PANEL;
if (config.type == GLFW_LAYER_SHELL_BACKGROUND) {
ans.x += config.requested_left_margin; ans.y += config.requested_top_margin;
@@ -560,7 +575,6 @@ calculate_layer_geometry(_GLFWwindow *window) {
double spacing_y = top_edge_spacing + bottom_edge_spacing;
double xsz = config.x_size_in_pixels ? (unsigned)(config.x_size_in_pixels * xscale) : (cell_width * config.x_size_in_cells);
double ysz = config.y_size_in_pixels ? (unsigned)(config.y_size_in_pixels * yscale) : (cell_height * config.y_size_in_cells);
xsz /= xscale; ysz /= yscale;
ans.width = (int)(1. + spacing_x + xsz); ans.height = (int)(1. + spacing_y + ysz);
GeometryRect m = config.type == GLFW_LAYER_SHELL_TOP || config.type == GLFW_LAYER_SHELL_OVERLAY ? mg.workarea : mg.full;
static const struct {
@@ -600,6 +614,8 @@ calculate_layer_geometry(_GLFWwindow *window) {
ans.width = m.width - config.requested_right_margin - config.requested_left_margin;
break;
}
debug_rendering("Calculating layer geometry at scale: %f cell size: (%u, %u) -> %dx%d@%dx%d needs_strut: %d\n",
xscale, cell_width, cell_height, ans.width, ans.height, ans.x, ans.y, ans.needs_strut)
return ans;
}
@@ -616,15 +632,15 @@ update_wm_hints(_GLFWwindow *window, const WindowGeometry *wg, const _GLFWwndcon
hints->flags = StateHint | InputHint;
hints->initial_state = NormalState;
hints->input = true;
if (is_layer_shell && (config.type == GLFW_LAYER_SHELL_BACKGROUND || config.type == GLFW_LAYER_SHELL_PANEL)) hints->input = false;
if (is_layer_shell && config.focus_policy == GLFW_FOCUS_NOT_ALLOWED) hints->input = false;
XSetWMHints(_glfw.x11.display, window->x11.handle, hints);
XFree(hints);
} else _glfwInputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate WM hints");
if (_glfw.x11.NET_WM_WINDOW_TYPE) {
Atom type = 0;
if (window->x11.layer_shell.is_active) {
if (is_layer_shell) {
const char *name = NULL;
#define S(which) type = _glfw.x11.NET_WM_WINDOW_TYPE_DESKTOP; name = #which
#define S(which) type = _glfw.x11.which; name = #which
switch (config.type) {
case GLFW_LAYER_SHELL_BACKGROUND: S(NET_WM_WINDOW_TYPE_DESKTOP); break;
case GLFW_LAYER_SHELL_PANEL: S(NET_WM_WINDOW_TYPE_DOCK); break;
@@ -663,7 +679,11 @@ update_wm_hints(_GLFWwindow *window, const WindowGeometry *wg, const _GLFWwndcon
#define S(x) if (_glfw.x11.x) { states[count++] = _glfw.x11.x; } else { _glfwInputError(GLFW_PLATFORM_ERROR, "X11: Window manager does not support _%s", #x); ok = false; }
switch (config.type) {
case GLFW_LAYER_SHELL_NONE: break;
case GLFW_LAYER_SHELL_BACKGROUND: case GLFW_LAYER_SHELL_PANEL: S(NET_WM_STATE_BELOW); break;
case GLFW_LAYER_SHELL_BACKGROUND: S(NET_WM_STATE_BELOW); break;
case GLFW_LAYER_SHELL_PANEL:
// i3 does not support NET_WM_STATE_BELOW but panels work without it
if (_glfw.x11.NET_WM_STATE_BELOW) { S(NET_WM_STATE_BELOW); }
break;
case GLFW_LAYER_SHELL_TOP: case GLFW_LAYER_SHELL_OVERLAY: S(NET_WM_STATE_ABOVE); break;
}
#undef S
@@ -685,7 +705,10 @@ update_wm_hints(_GLFWwindow *window, const WindowGeometry *wg, const _GLFWwndcon
if (count && _glfw.x11.NET_WM_STATE) XChangeProperty(_glfw.x11.display, window->x11.handle, _glfw.x11.NET_WM_STATE,
XA_ATOM, 32, PropModeReplace, (unsigned char*) states, count);
}
if (!wndconfig && ok) _glfwPlatformSetWindowPos(window, wg->x, wg->y);
if (!wndconfig && ok) {
_glfwPlatformSetWindowPos(window, wg->x, wg->y);
_glfwPlatformSetWindowSize(window, wg->width, wg->height);
}
return ok;
#undef config
}
@@ -720,6 +743,7 @@ static bool createNativeWindow(_GLFWwindow* window,
_glfwGrabErrorHandlerX11();
window->x11.parent = _glfw.x11.root;
debug_rendering("Creating window with geometry: %dx%d@%dx%d\n", wg.width, wg.height, wg.x, wg.y);
window->x11.handle = XCreateWindow(_glfw.x11.display,
_glfw.x11.root,
wg.x, wg.y, // Position
@@ -768,6 +792,8 @@ static bool createNativeWindow(_GLFWwindow* window,
}
if (!update_wm_hints(window, &wg, wndconfig)) return false;
// without this floating window position is incorrect on KDE
if (window->x11.layer_shell.is_active) _glfwPlatformSetWindowPos(window, wg.x, wg.y);
// Set ICCCM WM_CLASS property
{
@@ -1530,6 +1556,7 @@ static void processEvent(XEvent *event)
if (event->xconfigure.width != window->x11.width ||
event->xconfigure.height != window->x11.height)
{
debug_rendering("Window resized to: %d %d from: %d %d\n", event->xconfigure.width, event->xconfigure.height, window->x11.width, window->x11.height);
_glfwInputFramebufferSize(window,
event->xconfigure.width,
event->xconfigure.height);
@@ -1564,9 +1591,9 @@ static void processEvent(XEvent *event)
return;
}
}
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
{
debug_rendering("Window moved to: %d %d from: %d %d\n", xpos, ypos, window->x11.xpos, window->x11.xpos);
_glfwInputWindowPos(window, xpos, ypos);
window->x11.xpos = xpos;
window->x11.ypos = ypos;
@@ -2425,6 +2452,11 @@ void _glfwPlatformShowWindow(_GLFWwindow* window)
return;
XMapWindow(_glfw.x11.display, window->x11.handle);
// without this floating window position is incorrect on KDE
if (window->x11.layer_shell.is_active) {
WindowGeometry wg = calculate_layer_geometry(window);
_glfwPlatformSetWindowPos(window, wg.x, wg.y);
}
waitForVisibilityNotify(window);
}

2
go.mod
View File

@@ -6,7 +6,7 @@ toolchain go1.24.1
require (
github.com/ALTree/bigfloat v0.2.0
github.com/alecthomas/chroma/v2 v2.17.0
github.com/alecthomas/chroma/v2 v2.17.2
github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/dlclark/regexp2 v1.11.5
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be

4
go.sum
View File

@@ -2,8 +2,8 @@ github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.17.0 h1:3r2Cgk+nXNICMBxIFGnTRTbQFUwMiLisW+9uos0TtUI=
github.com/alecthomas/chroma/v2 v2.17.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI=
github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=

View File

@@ -6,6 +6,7 @@ import (
"archive/tar"
"bytes"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
@@ -36,16 +37,33 @@ var conf *Config
var opts *Options
var lp *loop.Loop
func isdir(path string) bool {
if s, err := os.Stat(path); err == nil {
return s.IsDir()
}
return false
}
var temp_files []string
func exists(path string) bool {
_, err := os.Stat(path)
return err == nil
func resolve_path(path string) (ans string, is_dir bool, err error) {
var s fs.FileInfo
if s, err = os.Stat(path); err != nil {
return
} else {
if s.Mode()&fs.ModeNamedPipe != 0 {
var src, dest *os.File
if src, err = os.Open(path); err != nil {
return
}
defer src.Close()
if dest, err = os.CreateTemp("", fmt.Sprintf("*-pipe-%s", filepath.Base(path))); err != nil {
return
}
defer dest.Close()
temp_files = append(temp_files, dest.Name())
if _, err = io.Copy(dest, src); err != nil {
return
}
return dest.Name(), false, nil
} else {
return path, s.IsDir(), nil
}
}
}
func get_ssh_file(hostname, rpath string) (string, error) {
@@ -133,15 +151,22 @@ func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
if err != nil {
return 1, err
}
if isdir(left) != isdir(right) {
defer func() {
for _, path := range temp_files {
os.Remove(path)
}
}()
var left_is_dir, right_is_dir bool
if left, left_is_dir, err = resolve_path(left); err != nil {
return 1, err
}
if right, right_is_dir, err = resolve_path(right); err != nil {
return 1, err
}
if left_is_dir != right_is_dir {
return 1, fmt.Errorf("The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.'")
}
if !exists(left) {
return 1, fmt.Errorf("%s does not exist", left)
}
if !exists(right) {
return 1, fmt.Errorf("%s does not exist", right)
}
lp, err = loop.New()
loop.MouseTrackingMode(lp, loop.BUTTONS_AND_DRAG_MOUSE_TRACKING)
if err != nil {

View File

@@ -37,8 +37,8 @@ func main(cmd *cli.Command, opts *Options, args []string) (rc int, err error) {
return 1, err
}
argv := []string{kitty_exe, "+kitten", "panel", "--toggle-visibility", "--exclusive-zone=0", "--override-exclusive-zone", "--layer=overlay", "--single-instance"}
argv = append(argv, fmt.Sprintf("--lines=%d", conf.Lines))
argv = append(argv, fmt.Sprintf("--columns=%d", conf.Columns))
argv = append(argv, fmt.Sprintf("--lines=%s", conf.Lines))
argv = append(argv, fmt.Sprintf("--columns=%s", conf.Columns))
argv = append(argv, fmt.Sprintf("--edge=%s", conf.Edge))
if conf.Margin_top != 0 {
argv = append(argv, fmt.Sprintf("--margin-top=%d", conf.Margin_top))

View File

@@ -19,13 +19,13 @@ opt = definition.add_option
agr('qat', 'Window appearance')
opt('lines', '25', option_type='positive_int',
opt('lines', '25',
long_text='''
The number of lines shown in the window, when the window is along the top or bottom edges of the screen.
If it has the suffix :code:`px` then it sets the height of the window in pixels instead of lines.
''',)
opt('columns', '80', option_type='positive_int',
opt('columns', '80',
long_text='''
The number of columns shown in the window, when the window is along the left or right edges of the screen.
If it has the suffix :code:`px` then it sets the width of the window in pixels instead of columns.

View File

@@ -87,8 +87,8 @@ class KittenMetadata(NamedTuple):
def create_kitten_handler(kitten: str, orig_args: list[str]) -> KittenMetadata:
from kitty.constants import config_dir
kitten = resolved_kitten(kitten)
m = import_kitten_main_module(config_dir, kitten)
kitten = resolved_kitten(kitten)
main = m['start']
handle_result = m['end']
return KittenMetadata(
@@ -110,12 +110,13 @@ def set_debug(kitten: str) -> None:
def launch(args: list[str]) -> None:
config_dir, kitten = args[:2]
original_kitten_name = kitten
kitten = resolved_kitten(kitten)
del args[:2]
args = [kitten] + args
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
set_debug(kitten)
m = import_kitten_main_module(config_dir, kitten)
m = import_kitten_main_module(config_dir, original_kitten_name)
try:
result = m['start'](args)
finally:
@@ -139,11 +140,14 @@ def run_kitten(kitten: str, run_name: str = '__main__') -> None:
if kitten in all_kitten_names():
runpy.run_module(f'kittens.{kitten}.main', run_name=run_name)
return
kitten = original_kitten_name
# Look for a custom kitten
if not kitten.endswith('.py'):
kitten += '.py'
from kitty.constants import config_dir
path = path_to_custom_kitten(config_dir, kitten)
if not os.path.exists(path):
path = path_to_custom_kitten(config_dir, resolved_kitten(kitten))
if not os.path.exists(path):
print('Available builtin kittens:', file=sys.stderr)
for kitten in all_kitten_names():

View File

@@ -141,6 +141,7 @@ from .utils import (
platform_window_id,
safe_print,
sanitize_url_for_dispay_to_user,
shlex_split,
startup_notification_handler,
timed_debug_print,
which,
@@ -1357,7 +1358,17 @@ class Boss:
new_size = get_options().font_size
else:
if increment_operation:
new_size += (1 if increment_operation == '+' else -1) * amt
match increment_operation:
case '+':
new_size += amt
case '-':
new_size -= amt
case '*':
new_size *= amt
case '/':
new_size /= amt
case _:
pass # no-op
else:
new_size = amt
new_size = max(MINIMUM_FONT_SIZE, min(new_size, get_options().font_size * 5))
@@ -2655,6 +2666,11 @@ class Boss:
def launch(self, *args: str) -> None:
from kitty.launch import launch, parse_launch_args
opts, args_ = parse_launch_args(args)
if args_ and ' ' in args_[0]:
# this can happen for example with map f1 launch $EDITOR when $EDITOR is not a single command
q = which(args_[0])
if not q or (q is args_[0] and not os.access(q, os.X_OK)):
args_[:1] = shlex_split(args_[0])
if self.window_for_dispatch:
opts.source_window = opts.source_window or f'id:{self.window_for_dispatch.id}'
opts.next_to = opts.next_to or f'id:{self.window_for_dispatch.id}'
@@ -2782,6 +2798,10 @@ class Boss:
self.update_check_process.kill()
self.update_check_process = process
def monitor_pid(self, pid: int, callback: Callable[[int, Exception | None], None]) -> None:
self.background_process_death_notify_map[pid] = callback
monitor_pid(pid)
def on_monitored_pid_death(self, pid: int, exit_status: int) -> None:
callback = self.background_process_death_notify_map.pop(pid, None)
if callback is not None:
@@ -3025,10 +3045,11 @@ class Boss:
self.choose_entry('Choose an OS window to move the tab to', items, chosen)
def set_background_image(self, path: str | None, os_windows: tuple[int, ...], configured: bool, layout: str | None, png_data: bytes = b'') -> None:
set_background_image(path, os_windows, configured, layout, png_data)
for os_window_id in os_windows:
self.default_bg_changed_for(os_window_id)
def set_background_image(
self, path: str | None, os_windows: tuple[int, ...], configured: bool, layout: str | None, png_data: bytes = b'',
linear_interpolation: bool | None = None, tint: float | None = None, tint_gaps: float | None = None
) -> None:
set_background_image(path, os_windows, configured, layout, png_data, linear_interpolation, tint, tint_gaps)
# Can be called with kitty -o "map f1 send_test_notification"
def send_test_notification(self) -> None:
@@ -3052,6 +3073,19 @@ class Boss:
def close_shared_ssh_connections(self) -> None:
cleanup_ssh_control_masters()
@ac('debug', '''Simulate a change in OS color scheme preference''')
def simulate_color_scheme_preference_change(self, which: str) -> None:
which = which.lower().replace('-', '_')
match which:
case 'light':
self.on_system_color_scheme_change('light', False)
case 'dark':
self.on_system_color_scheme_change('dark', False)
case 'no_preference':
self.on_system_color_scheme_change('no_preference', False)
case _:
self.show_error(_('Unknown color scheme type'), _('{} is not a valid color scheme type').format(which))
def launch_urls(self, *urls: str, no_replace_window: bool = False) -> None:
from .launch import force_window_launch
from .open_actions import actions_for_launch

View File

@@ -5,7 +5,7 @@ import os
from collections.abc import Iterable, Sequence
from contextlib import suppress
from enum import Enum
from typing import Literal, Optional
from typing import Literal, Optional, TypedDict
from .config import parse_config
from .constants import config_dir
@@ -20,6 +20,14 @@ ColorSchemes = Literal['light', 'dark', 'no_preference']
Colors = tuple[ColorsSpec, TransparentBackgroundColors]
class BackgroundImageOptions(TypedDict, total=False):
background_image: str | None
background_image_layout: str | None
background_image_linear: bool | None
background_tint: float | None
background_tint_gaps: float | None
class ThemeFile(Enum):
dark = 'dark-theme.auto.conf'
light = 'light-theme.auto.conf'
@@ -33,6 +41,7 @@ class ThemeColors:
no_preference_mtime: int = -1
applied_theme: Literal['light', 'dark', 'no_preference', ''] = ''
default_colors: ColorsSpec | None = None
default_background_image_options: BackgroundImageOptions| None = None
def get_default_colors(self) -> ColorsSpec:
if self.default_colors is None:
@@ -44,14 +53,19 @@ class ThemeColors:
if isinstance(defval, Color):
ans[name] = int(defval)
self.default_colors = ans
self.default_background_image_options: BackgroundImageOptions = {
k: getattr(defaults, k) for k in BackgroundImageOptions.__optional_keys__} # type: ignore
return self.default_colors
def parse_colors(self, f: Iterable[str]) -> Colors:
def parse_colors(self, f: Iterable[str], background_image_options: BackgroundImageOptions | None = None) -> Colors:
# When parsing the theme file we first apply the default theme so that
# all colors are reset to default values first. This is needed for themes
# that don't specify all colors.
spec, tbc = parse_colors((f,))
dc_spec = self.get_default_colors()
if background_image_options is not None and self.default_background_image_options:
background_image_options.update(self.default_background_image_options)
spec, tbc = parse_colors((f,), background_image_options)
ans = dc_spec.copy()
ans.update(spec)
return ans, tbc
@@ -64,21 +78,27 @@ class ThemeColors:
mtime = x.stat().st_mtime_ns
if mtime > self.dark_mtime:
with open(x.path) as f:
self.dark_spec, self.dark_tbc = self.parse_colors(f)
d: BackgroundImageOptions = {}
self.dark_spec, self.dark_tbc = self.parse_colors(f, d)
self.dark_background_image_options = d
self.dark_mtime = mtime
found = True
elif x.name == ThemeFile.light.value:
mtime = x.stat().st_mtime_ns
if mtime > self.light_mtime:
with open(x.path) as f:
self.light_spec, self.light_tbc = self.parse_colors(f)
d = {}
self.light_spec, self.light_tbc = self.parse_colors(f, d)
self.light_background_image_options = d
self.light_mtime = mtime
found = True
elif x.name == ThemeFile.no_preference.value:
mtime = x.stat().st_mtime_ns
if mtime > self.no_preference_mtime:
with open(x.path) as f:
self.no_preference_spec, self.no_preference_tbc = self.parse_colors(f)
d = {}
self.no_preference_spec, self.no_preference_tbc = self.parse_colors(f, d)
self.no_preference_background_image_options = d
self.no_preference_mtime = mtime
found = True
return found
@@ -115,14 +135,18 @@ class ThemeColors:
if debug_rendering:
log_error('Current system color scheme:', which)
cols: Colors | None = None
bgo: BackgroundImageOptions | None = None
if which == 'dark' and self.has_dark_theme:
cols = self.dark_spec, self.dark_tbc
bgo = self.dark_background_image_options
elif which == 'light' and self.has_light_theme:
cols = self.light_spec, self.light_tbc
bgo = self.light_background_image_options
elif which == 'no_preference' and self.has_no_preference_theme:
cols = self.no_preference_spec, self.no_preference_tbc
bgo = self.no_preference_background_image_options
if cols is not None:
patch_options_with_color_spec(opts, *cols)
patch_options_with_color_spec(opts, *cols, background_image_options=bgo)
patch_global_colors(cols[0], True)
self.applied_theme = which
if debug_rendering:
@@ -138,19 +162,23 @@ class ThemeColors:
from .utils import log_error
boss = get_boss()
if new_value == 'dark' and self.has_dark_theme:
patch_colors(self.dark_spec, self.dark_tbc, True, notify_on_bg_change=notify_on_bg_change)
patch_colors(
self.dark_spec, self.dark_tbc, True, notify_on_bg_change=notify_on_bg_change, background_image_options=self.dark_background_image_options)
self.applied_theme = new_value
if boss.args.debug_rendering:
log_error(f'Applied color theme {new_value}')
return True
if new_value == 'light' and self.has_light_theme:
patch_colors(self.light_spec, self.light_tbc, True, notify_on_bg_change=notify_on_bg_change)
patch_colors(
self.light_spec, self.light_tbc, True, notify_on_bg_change=notify_on_bg_change, background_image_options=self.light_background_image_options)
self.applied_theme = new_value
if boss.args.debug_rendering:
log_error(f'Applied color theme {new_value}')
return True
if new_value == 'no_preference' and self.has_no_preference_theme:
patch_colors(self.no_preference_spec, self.no_preference_tbc, True, notify_on_bg_change=notify_on_bg_change)
patch_colors(
self.no_preference_spec, self.no_preference_tbc, True, notify_on_bg_change=notify_on_bg_change,
background_image_options=self.no_preference_background_image_options)
self.applied_theme = new_value
if boss.args.debug_rendering:
log_error(f'Applied color theme {new_value}')
@@ -161,7 +189,7 @@ class ThemeColors:
theme_colors = ThemeColors()
def parse_colors(args: Iterable[str | Iterable[str]]) -> Colors:
def parse_colors(args: Iterable[str | Iterable[str]], background_image_options: BackgroundImageOptions | None = None) -> Colors:
colors: dict[str, Color | None] = {}
nullable_color_map: dict[str, int | None] = {}
transparent_background_colors = ()
@@ -175,6 +203,10 @@ def parse_colors(args: Iterable[str | Iterable[str]]) -> Colors:
else:
conf = parse_config(spec)
transparent_background_colors = conf.pop('transparent_background_colors', ())
if background_image_options is not None:
for key in BackgroundImageOptions.__optional_keys__:
if key in conf:
background_image_options.__setitem__(key, conf[key])
colors.update(conf)
for k in nullable_colors:
q = colors.pop(k, False)
@@ -186,7 +218,10 @@ def parse_colors(args: Iterable[str | Iterable[str]]) -> Colors:
return ans, transparent_background_colors
def patch_options_with_color_spec(opts: Options, spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors) -> None:
def patch_options_with_color_spec(
opts: Options, spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors,
background_image_options: BackgroundImageOptions | None = None
) -> None:
for k, v in spec.items():
if hasattr(opts, k):
if v is None:
@@ -195,11 +230,16 @@ def patch_options_with_color_spec(opts: Options, spec: ColorsSpec, transparent_b
else:
setattr(opts, k, color_from_int(v))
opts.transparent_background_colors = transparent_background_colors
if background_image_options is not None:
for k, bv in background_image_options.items():
if hasattr(opts, k):
setattr(opts, k, bv)
def patch_colors(
spec: ColorsSpec, transparent_background_colors: TransparentBackgroundColors, configured: bool = False,
windows: Sequence[WindowType] | None = None, notify_on_bg_change: bool = True,
background_image_options: BackgroundImageOptions | None = None
) -> None:
boss = get_boss()
if windows is None:
@@ -209,7 +249,8 @@ def patch_colors(
patch_color_profiles(spec, transparent_background_colors, profiles, configured)
opts = get_options()
if configured:
patch_options_with_color_spec(opts, spec, transparent_background_colors)
patch_options_with_color_spec(opts, spec, transparent_background_colors, background_image_options)
os_window_ids = set()
for tm in get_boss().all_tab_managers:
tm.tab_bar.patch_colors(spec)
tm.tab_bar.layout()
@@ -218,10 +259,17 @@ def patch_colors(
if t is not None:
t.relayout_borders()
set_os_window_chrome(tm.os_window_id)
os_window_ids.add(tm.os_window_id)
patch_global_colors(spec, configured)
default_bg_changed = 'background' in spec
notify_bg = notify_on_bg_change and default_bg_changed
boss = get_boss()
if background_image_options is not None:
boss.set_background_image(
background_image_options.get('background_image'), tuple(os_window_ids), configured,
layout=background_image_options.get('background_image_layout'),
linear_interpolation=background_image_options.get('background_image_linear'), tint=background_image_options.get('background_tint'),
tint_gaps=background_image_options.get('background_tint_gaps'))
for w in windows:
if w:
if notify_bg and w.screen.color_profile.default_bg != bg_colors_before.get(w.id):

View File

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

View File

@@ -21,7 +21,7 @@ from .colors import theme_colors
from .constants import extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path
from .fast_data_types import Color, SingleKey, current_fonts, glfw_get_system_color_theme, num_users, opengl_version_string, wayland_compositor_data
from .options.types import Options as KittyOpts
from .options.types import defaults
from .options.types import defaults, secret_options
from .options.utils import KeyboardMode, KeyDefinition
from .rgb import color_as_sharp, color_from_int
from .types import MouseEvent, Shortcut, mod_to_names
@@ -85,6 +85,9 @@ def compare_opts(opts: KittyOpts, global_shortcuts: dict[str, SingleKey] | None,
fmt = f'{{:{field_len:d}s}}'
colors = []
for f in changed_opts:
if f in secret_options:
print(title(f'{f}:'), 'REDACTED FOR SECURITY')
continue
val = getattr(opts, f)
if isinstance(val, dict):
print(title(f'{f}:'))

View File

@@ -258,13 +258,17 @@ append_limit(Canvas *self, double upper, double lower) {
self->y_limits[self->y_limits_count++].lower = lower;
}
static uint
thickness(Canvas *self, uint level, bool horizontal) {
static double
thickness_as_float(Canvas *self, uint level, bool horizontal) {
level = min(level, arraysz(OPT(box_drawing_scale)));
double pts = OPT(box_drawing_scale)[level];
double dpi = horizontal ? self->dpi.x : self->dpi.y;
return (uint)ceil(self->supersample_factor * self->scale * pts * dpi / 72.0);
return self->supersample_factor * self->scale * pts * dpi / 72.0;
}
static uint
thickness(Canvas *self, uint level, bool horizontal) {
return (uint)ceil(thickness_as_float(self, level, horizontal));
}
static const uint hole_factor = 8;
@@ -588,16 +592,27 @@ typedef struct CubicBezier {
} CubicBezier;
#define bezier_eq(which) { \
double tm1 = 1 - t; \
double tm1_3 = tm1 * tm1 * tm1; \
double t_3 = t * t * t; \
return tm1_3 * cb.start.which + 3 * t * tm1 * (tm1 * cb.c1.which + t * cb.c2.which) + t_3 * cb.end.which; \
const CubicBezier *cb = v; \
const double u = 1. - t; \
const double u_3 = u * u * u; \
const double t_3 = t * t * t; \
return u_3 * cb->start.which + 3 * t * u * (u * cb->c1.which + t * cb->c2.which) + t_3 * cb->end.which; \
}
static double
bezier_x(CubicBezier cb, double t) { bezier_eq(x); }
static double
bezier_y(CubicBezier cb, double t) { bezier_eq(y); }
#define bezier_prime_eq(which) { \
const CubicBezier *cb = v; \
const double u = 1. - t; \
const double u_2 = u * u; \
const double t_2 = t * t; \
return 3 * u_2 * (cb->c1.which - cb->start.which) + 6 * t * u * (cb->c2.which - cb->c1.which) + 3 * t_2 * (cb->end.which - cb->c2.which); \
}
static double bezier_x(const void *v, double t) { bezier_eq(x); }
static double bezier_y(const void *v, double t) { bezier_eq(y); }
static double bezier_prime_x(const void *v, double t) { bezier_prime_eq(x); }
static double bezier_prime_y(const void *v, double t) { bezier_prime_eq(y); }
#undef bezier_eq
#undef bezier_prime_eq
static int
find_bezier_for_D(int width, int height) {
@@ -605,13 +620,13 @@ find_bezier_for_D(int width, int height) {
CubicBezier cb = {.end={.x=0, .y=height - 1}, .c2={.x=0, .y=height - 1}};
while (true) {
cb.c1.x = cx; cb.c2.x = cx;
if (bezier_x(cb, 0.5) > width - 1) return last_cx;
if (bezier_x(&cb, 0.5) > width - 1) return last_cx;
last_cx = cx++;
}
}
static double
find_t_for_x(CubicBezier cb, int x, double start_t) {
find_t_for_x(const CubicBezier *cb, int x, double start_t) {
if (fabs(bezier_x(cb, start_t) - x) < 0.1) return start_t;
static const double t_limit = 0.5;
double increment = t_limit - start_t;
@@ -635,7 +650,7 @@ find_t_for_x(CubicBezier cb, int x, double start_t) {
static void
get_bezier_limits(Canvas *self, CubicBezier cb) {
get_bezier_limits(Canvas *self, const CubicBezier *cb) {
int start_x = (int)bezier_x(cb, 0), max_x = (int)bezier_x(cb, 0.5);
double last_t = 0.;
for (int x = start_x; x < max_x + 1; x++) {
@@ -666,38 +681,54 @@ static void
filled_D(Canvas *self, bool left) {
int c1x = find_bezier_for_D(self->width, self->height);
CubicBezier cb = {.end={.y=self->height-1}, .c1 = {.x=c1x}, .c2 = {.x=c1x, .y=self->height - 1}};
get_bezier_limits(self, cb);
get_bezier_limits(self, &cb);
if (left) fill_region(self, false);
else mirror_horizontally(fill_region(self, false));
}
#define NAME position_set
#define KEY_TY Point
#define HASH_FN hash_point
#define CMPR_FN cmpr_point
static uint64_t hash_point(Point p);
static bool cmpr_point(Point, Point);
#include "kitty-verstable.h"
static uint64_t hash_point(Point p) { return vt_hash_integer(p.val); }
static bool cmpr_point(Point a, Point b) { return a.val == b.val; }
static double
distance(double x1, double y1, double x2, double y2) {
const double dx = x1 - x2;
const double dy = y1 - y2;
return sqrt(dx * dx + dy * dy);
}
#define draw_parametrized_curve(self, level, xfunc, yfunc) { \
div_t d = div(thickness(self, level, true), 2u); \
int delta = d.quot, extra = d.rem; \
uint num_samples = self->height * 8; \
position_set seen; vt_init(&seen); \
for (uint i = 0; i < num_samples + 1; i++) { \
double t = i / (double)num_samples; \
Point p = {.x=(int32_t)xfunc, .y=(int32_t)yfunc}; \
position_set_itr q = vt_get(&seen, p); \
if (!vt_is_end(q)) continue; \
if (vt_is_end(vt_insert(&seen, p))) fatal("Out of memory"); \
for (int y = MAX(0, p.y - delta); y < MIN(p.y + delta + extra, (int)self->height); y++) { \
uint offset = y * self->width, start = MAX(0, p.x - delta); \
memset(self->mask + offset + start, 255, minus((uint)MIN(p.x + delta + extra, (int)self->width), start)); \
} \
} \
vt_cleanup(&seen); \
typedef double(*curve_func)(const void *, double t);
static void
draw_parametrized_curve_with_derivative(
Canvas *self, void *curve_data, double line_width, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime,
int x_offset, int yoffset, double thickness_fudge
) {
double larger_dim = fmax(self->height, self->width);
double step = 1.0 / larger_dim;
const double min_step = step / 100., max_step = step;
line_width = fmax(1., line_width);
const double half_thickness = line_width / 2.0;
const double distance_limit = half_thickness + thickness_fudge;
double t = 0;
while(true) {
double x = xfunc(curve_data, t), y = yfunc(curve_data, t);
for (double dy = -line_width; dy <= line_width; dy++) {
for (double dx = -line_width; dx <= line_width; dx++) {
double px = x + dx, py = y + dy;
double dist = distance(x, y, px, py);
int row = (int)py + yoffset, col = (int)px + x_offset;
if (dist > distance_limit || row >= (int)self->height || row < 0 || col >= (int)self->width || col < 0) continue;
const int offset = row * self->width + col;
double alpha = 1.0 - (dist / half_thickness);
uint8_t old_alpha = self->mask[offset];
self->mask[offset] = (uint8_t)(alpha * 255 + (1 - alpha) * old_alpha);
}
}
if (t >= 1.0) break;
// Dynamically adjust step size based on curve's derivative
double dx = x_prime(curve_data, t), dy = y_prime(curve_data, t);
double d = sqrt(dx * dx + dy * dy);
step = 1.0 / fmax(1e-6, d);
step = fmax(min_step, fmin(step, max_step));
t = fmin(t + step, 1.0);
}
}
static void
@@ -705,8 +736,10 @@ rounded_separator(Canvas *self, uint level, bool left) {
uint gap = thickness(self, level, true);
int c1x = find_bezier_for_D(minus(self->width, gap), self->height);
CubicBezier cb = {.end={.y=self->height - 1}, .c1={.x=c1x}, .c2={.x=c1x, .y=self->height - 1}};
if (left) { draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t)); }
else { mirror_horizontally(draw_parametrized_curve(self, level, bezier_x(cb, t), bezier_y(cb, t))); }
double line_width = thickness_as_float(self, level, true);
#define d draw_parametrized_curve_with_derivative(self, &cb, line_width, bezier_x, bezier_y, bezier_prime_x, bezier_prime_y, 0, 0, 0)
if (left) { d; } else { mirror_horizontally(d); }
#undef d
}
static void
@@ -724,56 +757,61 @@ corner_triangle(Canvas *self, const Corner corner) {
}
typedef struct Circle {
Point origin;
double radius;
double x, y, radius;
double start, end, amt;
} Circle;
static Circle
circle(Point origin, double radius, double start_at, double end_at) {
circle(double x, double y, double radius, double start_at, double end_at) {
double conv = M_PI / 180.;
Circle ans = {.origin=origin, .radius=radius, .start=start_at*conv, .end=end_at*conv};
Circle ans = {.x=x, .y=y, .radius=radius, .start=start_at*conv, .end=end_at*conv};
ans.amt = ans.end - ans.start;
return ans;
}
static double
circle_x(Circle c, double t) { return c.origin.x + c.radius * cos(c.start + c.amt * t); }
static double
circle_y(Circle c, double t) { return c.origin.y + c.radius * sin(c.start + c.amt * t); }
static double circle_x(const void *v, double t) { const Circle *c=v; return c->x + c->radius * cos(c->start + c->amt * t); }
static double circle_y(const void *v, double t) { const Circle *c=v; return c->y + c->radius * sin(c->start + c->amt * t); }
static double circle_prime_x(const void *v, double t) { const Circle *c=v; return -c->radius * sin(c->start + c->amt * t); }
static double circle_prime_y(const void *v, double t) { const Circle *c=v; return c->radius * cos(c->start + c->amt * t); }
static void
spinner(Canvas *self, uint level, double start_degrees, double end_degrees) {
uint w = self->width / 2, h = self->height / 2;
uint radius = minus(min(w, h), thickness(self, level, true) / 2);
Circle c = circle((Point){.x=w, .y=h}, radius, start_degrees, end_degrees);
draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t));
double x = self->width / 2.0, y = self->height / 2.0;
double line_width = thickness_as_float(self, level, true);
double radius = fmax(0, fmin(x, y) - line_width / 2.0);
Circle c = circle(x, y, radius, start_degrees, end_degrees);
draw_parametrized_curve_with_derivative(self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, 0);
}
static void
draw_circle(Canvas *self, double scale, double gap, bool invert) {
const uint w = self->width / 2, h = self->height / 2;
const double radius = (int)(scale * min(w, h) - gap / 2);
const uint8_t fill = invert ? 0 : 255;
fill_circle_of_radius(Canvas *self, double origin_x, double origin_y, double radius, uint8_t alpha) {
const double limit = radius * radius;
for (uint y = 0; y < self->height; y++) {
for (uint x = 0; x < self->width; x++) {
double xw = (double)x - w, yh = (double)y - h;
if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = fill;
double xw = (double)x - origin_x, yh = (double)y - origin_y;
if (xw * xw + yh * yh <= limit) self->mask[y * self->width + x] = alpha;
}
}
}
static void
draw_fish_eye(Canvas *self, uint level) {
uint w = self->width / 2, h = self->height / 2;
uint line_width = thickness(self, level, true) / 2;
uint radius = minus(min(w, h), line_width);
Circle c = circle((Point){.x=w, .y=h}, radius, 0, 360);
draw_parametrized_curve(self, level, circle_x(c, t), circle_y(c, t));
uint gap = minus(radius, radius / 10);
draw_circle(self, 1.0, gap, false);
fill_circle(Canvas *self, double scale, double gap, bool invert) {
const uint w = self->width / 2, h = self->height / 2;
const double radius = (int)(scale * min(w, h) - gap / 2);
const uint8_t fill = invert ? 0 : 255;
fill_circle_of_radius(self, w, h, radius, fill);
}
static void
draw_fish_eye(Canvas *self, uint level UNUSED) {
double x = self->width / 2., y = self->height / 2.;
double radius = fmin(x, y);
double central_radius = (2./3.) * radius;
fill_circle_of_radius(self, x, y, central_radius, 255);
double line_width = fmax(1. * self->supersample_factor, (radius - central_radius) / 2.5);
radius = fmax(0, fmin(x, y) - line_width / 2.);
Circle c = circle(x, y, radius, 0, 360);
draw_parametrized_curve_with_derivative(self, &c, line_width, circle_x, circle_y, circle_prime_x, circle_prime_y, 0, 0, 0);
}
static void
@@ -1187,38 +1225,36 @@ fading_vline(Canvas *self, uint level, uint num, Edge fade) {
}
typedef struct Rectircle Rectircle;
typedef double (*Rectircle_equation)(Rectircle r, double t);
typedef struct Rectircle {
uint a, b;
double yexp, xexp, adjust_x;
uint cell_width;
Rectircle_equation x, y;
double a, b, yexp, xexp, x_sign, y_sign, x_start, y_start;
double x_prime_coeff, x_prime_exp, y_prime_coeff, y_prime_exp;
} Rectircle;
static double
rectircle_lower_quadrant_y(Rectircle r, double t) {
return r.b * t; // 0 -> top of cell, 1 -> middle of cell
rectircle_x(const void *v, double t) {
const Rectircle *r = v;
return r->x_start + r->x_sign * r->a * pow(cos(t * (M_PI / 2.0)), r->xexp);
}
static double
rectircle_upper_quadrant_y(Rectircle r, double t) {
return r.b * (2. - t); // 0 -> bottom of cell, 1 -> middle of cell
}
// x(t). To get this we first need |y(t)|/b. This is just t since as t goes
// from 0 to 1 y goes from either 0 to b or 0 to -b
static double
rectircle_left_quadrant_x(Rectircle r, double t) {
double xterm = 1 - pow(t, r.yexp);
return floor(r.cell_width - fabs(r.a * pow(xterm, r.xexp)) - r.adjust_x);
rectircle_x_prime(const void *v, double t) {
const Rectircle *r = v;
t *= (M_PI / 2.0);
return r->x_prime_coeff * pow(cos(t), r->x_prime_exp) * sin(t);
}
static double
rectircle_right_quadrant_x(Rectircle r, double t) {
double xterm = 1 - pow(t, r.yexp);
return ceil(fabs(r.a * pow(xterm, r.xexp)));
rectircle_y_prime(const void *v, double t) {
const Rectircle *r = v;
t *= (M_PI / 2.0);
return r->y_prime_coeff * pow(sin(t), r->y_prime_exp) * cos(t);
}
static double
rectircle_y(const void *v, double t) {
const Rectircle *r = v;
return r->y_start + r->y_sign * r->b * pow(sin(t * (M_PI / 2.0)), r->yexp);
}
static Rectircle
@@ -1227,10 +1263,14 @@ rectcircle(Canvas *self, Corner which) {
Return two functions, x(t) and y(t) that map the parameter t which must be
in the range [0, 1] to x and y coordinates in the cell. The rectircle equation
we use is:
(|x| / a) ^ (2a / r) + (|y| / a) ^ (2b / r) = 1
(|x| / a) ^ (2a / r) + (|y| / b) ^ (2b / r) = 1
where 2a = width, 2b = height and r is radius
See https://math.stackexchange.com/questions/1649714
This is a super-ellipse, its parametrized form is:
x = ± a * (cos(theta) ^ (r / a)); y = ± b * (sin(theta) ^ (r / b)); theta is in [0, pi/2]
https://en.wikipedia.org/wiki/Superellipse
The plus minus signs are chosen to give the four quadrants.
The entire rectircle fits in four cells, each cell being one quadrant
of the full rectircle and the origin being the center of the rectircle.
@@ -1238,18 +1278,17 @@ rectcircle(Canvas *self, Corner which) {
╭╮ ╭─╮
╰╯ │ │
╰─╯
See https://math.stackexchange.com/questions/1649714
*/
double radius = self->width / 2.;
uint cell_width_is_odd = (self->width / self->supersample_factor) & 1;
double radius = self->width / 2., a = self->width / 2., b = self->height / 2.;
Rectircle ans = {
.a = half_width(self), .b = half_height(self),
.yexp = self->height / radius,
.xexp = radius / self->width,
.cell_width = self->width,
.adjust_x = cell_width_is_odd * self->supersample_factor,
.x = which & LEFT_EDGE ? rectircle_left_quadrant_x : rectircle_right_quadrant_x,
.y = which & TOP_EDGE ? rectircle_upper_quadrant_y : rectircle_lower_quadrant_y,
.a = a, .b = b,
.xexp = radius / a, .yexp = radius / b,
.x_prime_coeff = radius, .x_prime_exp = radius / a - 1.,
.y_prime_coeff = radius, .y_prime_exp = radius / b - 1.,
.x_sign = which & RIGHT_EDGE ? 1. : -1,
.x_start = which & RIGHT_EDGE ? 0. : 2 * a,
.y_start = which & BOTTOM_EDGE ? 0. : 2 * b,
.y_sign = which & BOTTOM_EDGE ? 1. : -1,
};
return ans;
@@ -1258,7 +1297,12 @@ rectcircle(Canvas *self, Corner which) {
static void
rounded_corner(Canvas *self, uint level, Corner which) {
Rectircle r = rectcircle(self, which);
draw_parametrized_curve(self, level, r.x(r, t), r.y(r, t));
uint cell_width_is_odd = (self->width / self->supersample_factor) & 1;
uint cell_height_is_odd = (self->height / self->supersample_factor) & 1;
// adjust for odd cell dimensions to line up with box drawing lines
int x_offset = -(cell_width_is_odd & 1), y_offset = -(cell_height_is_odd & 1);
double line_width = thickness_as_float(self, level, true);
draw_parametrized_curve_with_derivative(self, &r, line_width, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, y_offset, 0.1);
}
static void
@@ -1269,8 +1313,8 @@ commit(Canvas *self, Edge lines, bool solid) {
if (lines & LEFT_EDGE) draw_hline(self, 0, hw, hh, level);
if (lines & TOP_EDGE) draw_vline(self, 0, hh, hw, level);
if (lines & BOTTOM_EDGE) draw_vline(self, hh, self->height, hw, level);
draw_circle(self, scale, 0, false);
if (!solid) draw_circle(self, scale, thickness(self, level, true), true);
fill_circle(self, scale, 0, false);
if (!solid) fill_circle(self, scale, thickness(self, level, true), true);
}
// thin and fat line levels
@@ -1527,7 +1571,7 @@ START_ALLOW_CASE_RANGE
S(L'', spinner, 1, 450, 540);
S(L'', spinner, 1, 180, 360);
S(L'', spinner, 1, 0, 180);
S(L'', draw_circle, 1.0, 0, false);
S(L'', fill_circle, 1.0, 0, false);
S(L'', draw_fish_eye, 0);
C(L'', dhline, 1, TOP_EDGE | BOTTOM_EDGE);

View File

@@ -709,7 +709,10 @@ def set_background_image(
os_window_ids: Tuple[int, ...],
configured: bool = True,
layout_name: Optional[str] = None,
png_data: bytes = b''
png_data: bytes = b'',
linear: bool | None = None,
tint: float | None = None,
tint_gaps: float | None = None,
) -> None:
pass

View File

@@ -6,7 +6,7 @@ import os
import shutil
from collections.abc import Container, Iterable, Iterator, Sequence
from contextlib import suppress
from typing import Any, NamedTuple, TypedDict
from typing import Any, Callable, NamedTuple, TypedDict
from .boss import Boss
from .child import Child
@@ -593,6 +593,7 @@ def _launch(
is_clone_launch: str = '',
rc_from_window: Window | None = None,
base_env: dict[str, str] | None = None,
child_death_callback: Callable[[int, Exception | None], None] | None = None,
) -> Window | None:
source_window = boss.active_window_for_cwd
if opts.source_window:
@@ -730,7 +731,8 @@ def _launch(
raise ValueError('The cmd to run must be specified when running a background process')
boss.run_background_process(
cmd, cwd=kw['cwd'], cwd_from=kw['cwd_from'], env=env or None, stdin=kw['stdin'],
allow_remote_control=kw['allow_remote_control'], remote_control_passwords=kw['remote_control_passwords']
allow_remote_control=kw['allow_remote_control'], remote_control_passwords=kw['remote_control_passwords'],
notify_on_death=child_death_callback,
)
elif opts.type in ('clipboard', 'primary'):
stdin = kw.get('stdin')
@@ -741,6 +743,8 @@ def _launch(
else:
set_primary_selection(stdin)
boss.handle_clipboard_loss('primary')
if child_death_callback is not None:
child_death_callback(0, None)
else:
kw['hold'] = opts.hold
if force_target_tab and target_tab is not None:
@@ -751,6 +755,8 @@ def _launch(
with Window.set_ignore_focus_changes_for_new_windows(opts.keep_focus):
new_window: Window = tab.new_window(
env=env or None, watchers=watchers or None, is_clone_launch=is_clone_launch, next_to=next_to, **kw)
if child_death_callback is not None:
boss.monitor_pid(new_window.child.pid or 0, child_death_callback)
if spacing:
patch_window_edges(new_window, spacing)
tab.relayout()
@@ -781,12 +787,13 @@ def launch(
is_clone_launch: str = '',
rc_from_window: Window | None = None,
base_env: dict[str, str] | None = None,
child_death_callback: Callable[[int, Exception | None], None] | None = None,
) -> Window | None:
active = boss.active_window
if opts.keep_focus and active:
orig, active.ignore_focus_changes = active.ignore_focus_changes, True
try:
return _launch(boss, opts, args, target_tab, force_target_tab, is_clone_launch, rc_from_window, base_env)
return _launch(boss, opts, args, target_tab, force_target_tab, is_clone_launch, rc_from_window, base_env, child_death_callback)
finally:
if opts.keep_focus and active:
active.ignore_focus_changes = orig

View File

@@ -13,6 +13,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "../iqsort.h"
#ifndef RAII_PyObject
static inline void cleanup_decref2(PyObject **p) { Py_CLEAR(*p); }
@@ -122,6 +123,43 @@ alloc_for_cli(CLISpec *spec, size_t sz) {
snprintf(buf, sz + 4, fmt, __VA_ARGS__); spec->errmsg = buf; \
}
static ssize_t
levenshtein_distance(size_t *cache, const char *a, const char *b) {
if (a == b) return 0;
const size_t length = strlen(a);
const size_t bLength = strlen(b);
if (length == 0) return bLength;
if (bLength == 0) return length;
size_t index = 0, bIndex = 0, distance = 0, bDistance = 0, result = 0;
char code = 0;
// initialize the vector.
while (index < length) {
cache[index] = index + 1;
index++;
}
while (bIndex < bLength) {
code = b[bIndex];
result = distance = bIndex++;
index = SIZE_MAX;
while (++index < length) {
bDistance = code == a[index] ? distance : distance + 1;
distance = cache[index];
cache[index] = result = distance > result
? bDistance > result
? result + 1
: bDistance
: bDistance > distance
? distance + 1
: bDistance;
}
}
return result;
}
static bool
add_to_listval(CLISpec *spec, CLIValue *v, const char *val) {
if (v->listval.count + 1 >= v->listval.capacity) {
@@ -158,6 +196,11 @@ formatted_text(CLISpec *spec, const char *start_code, const char *text, const ch
#define green_text(text) formatted_text(spec, "32", text, "39")
#define italic_text(text) formatted_text(spec, "3", text, "23")
typedef struct similiar_alias {
const char *alias;
ssize_t distance;
} similiar_alias;
static const char*
dest_for_alias(CLISpec *spec, const char *alias) {
alias_hash_itr itr = vt_get(&spec->alias_map, alias);
@@ -186,7 +229,23 @@ dest_for_alias(CLISpec *spec, const char *alias) {
spec->errmsg = buf;
return NULL;
}
size_t *cache = alloc_for_cli(spec, sizeof(size_t) * strlen(alias));
size_t num_aliases = vt_size(&spec->alias_map);
similiar_alias *candidates = alloc_for_cli(spec, sizeof(similiar_alias) * num_aliases);
size_t num_candidates = 0;
alias_map_for_loop(&spec->alias_map) {
const char *q = itr.data->key;
ssize_t d = levenshtein_distance(cache, alias, q);
if (d < 0) break;
if (d < 3) candidates[num_candidates++] = (similiar_alias){.alias=q, .distance=d};
}
if (num_candidates) {
#define lt(a, b) (a->distance < b->distance)
QSORT(similiar_alias, candidates, num_candidates, lt);
set_err("Unknown flag: %s. Did you mean: %s?", red_text(alias), green_text(candidates[0].alias));
return NULL;
#undef lt
}
set_err("Unknown flag: %s use --help.", red_text(alias));
return NULL;
}

View File

@@ -269,9 +269,9 @@ class AppRunner:
self.initial_window_size_func = initial_window_size_func
def __call__(self, opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None:
set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback)
if theme_colors.refresh():
theme_colors.patch_opts(opts, args.debug_rendering)
set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback)
try:
set_font_family(opts, add_builtin_nerd_font=True)
_run_app(opts, args, bad_lines, talk_fd)

View File

@@ -1597,27 +1597,6 @@ depending on how the platform implements it, so use with care. Currently support
on macOS and KDE.
''')
opt('background_image', 'none',
option_type='config_or_absolute_path', ctype='!background_image',
long_text='Path to a background image. Must be in PNG/JPEG/WEBP/TIFF/GIF/BMP format.'
)
opt('background_image_layout', 'tiled',
choices=('mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled'),
ctype='bglayout',
long_text='''
Whether to tile, scale or clamp the background image. The value can be one of
:code:`tiled`, :code:`mirror-tiled`, :code:`scaled`, :code:`clamped`, :code:`centered`
or :code:`cscaled`. The :code:`scaled` and :code:`cscaled` values scale the image to the
window size, with :code:`cscaled` preserving the image aspect ratio.
'''
)
opt('background_image_linear', 'no',
option_type='to_bool', ctype='bool',
long_text='When background image is scaled, whether linear interpolation should be used.'
)
opt('transparent_background_colors', '', option_type='transparent_background_colors', long_text='''
A space separated list of upto 7 colors, with opacity. When the background color of a cell matches one of these colors,
it is rendered semi-transparent using the specified opacity.
@@ -1645,6 +1624,31 @@ this option by reloading the config is not supported.
'''
)
opt('background_image', 'none',
option_type='config_or_absolute_path', ctype='!background_image',
long_text='Path to a background image. Must be in PNG/JPEG/WEBP/TIFF/GIF/BMP format.'
' Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.'
)
opt('background_image_layout', 'tiled',
choices=('mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered', 'cscaled'),
ctype='bglayout',
long_text='''
Whether to tile, scale or clamp the background image. The value can be one of
:code:`tiled`, :code:`mirror-tiled`, :code:`scaled`, :code:`clamped`, :code:`centered`
or :code:`cscaled`. The :code:`scaled` and :code:`cscaled` values scale the image to the
window size, with :code:`cscaled` preserving the image aspect ratio.
Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.
'''
)
opt('background_image_linear', 'no',
option_type='to_bool', ctype='bool',
long_text='When background image is scaled, whether linear interpolation should be used.'
' Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.'
)
opt('background_tint', '0.0',
option_type='unit_float', ctype='float',
long_text='''
@@ -1652,6 +1656,7 @@ How much to tint the background image by the background color. This option
makes it easier to read the text. Tinting is done using the current background
color for each window. This option applies only if :opt:`background_opacity` is
set and transparent windows are supported or :opt:`background_image` is set.
Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.
'''
)
@@ -1662,6 +1667,7 @@ How much to tint the background image at the window gaps by the background
color, after applying :opt:`background_tint`. Since this is multiplicative
with :opt:`background_tint`, it can be used to lighten the tint over the window
gaps for a *separated* look.
Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect.
'''
)
@@ -4176,6 +4182,11 @@ To setup shortcuts for specific font sizes::
To setup shortcuts to change only the current OS window's font size::
map kitty_mod+f6 change_font_size current 10.0
To setup shortcuts to multiply/divide the font size::
map kitty_mod+f6 change_font_size all *2.0
map kitty_mod+f6 change_font_size all /2.0
''') # }}}

View File

@@ -165,7 +165,7 @@ def detach_tab_parse(func: str, rest: str) -> FuncArgsType:
@func_with_args(
'set_background_opacity', 'goto_layout', 'toggle_layout', 'toggle_tab', 'kitty_shell', 'show_kitty_doc',
'set_tab_title', 'push_keyboard_mode', 'dump_lines_with_attrs', 'set_window_title',
'set_tab_title', 'push_keyboard_mode', 'dump_lines_with_attrs', 'set_window_title', 'simulate_color_scheme_preference_change',
)
def simple_parse(func: str, rest: str) -> FuncArgsType:
return func, (rest,)
@@ -199,7 +199,7 @@ def parse_change_font_size(func: str, rest: str) -> tuple[str, tuple[bool, str |
c_all = vals[0].lower() == 'all'
sign: str | None = None
amt = vals[1]
if amt[0] in '+-':
if amt[0] in '+-*/':
sign = amt[0]
amt = amt[1:]
return func, (c_all, sign, float(amt.strip()))

View File

@@ -2,12 +2,14 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import os
from typing import TYPE_CHECKING
from kitty.cli_stub import LaunchCLIOptions
from kitty.launch import launch as do_launch
from kitty.launch import options_spec as launch_options_spec
from kitty.launch import parse_launch_args
from kitty.types import AsyncResponse
from .base import MATCH_TAB_OPTION, ArgsType, Boss, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window
@@ -54,6 +56,7 @@ class Launch(RemoteCommand):
color/list.str: list of color specifications such as foreground=red
watcher/list.str: list of paths to watcher files
bias/float: The bias with which to create the new window in the current layout
wait_for_child_to_exit/bool: Boolean indicating whether to wait and return child exit code
'''
short_desc = 'Run an arbitrary process in a new window/tab'
@@ -64,6 +67,21 @@ class Launch(RemoteCommand):
' kitten @ launch --title=Email mutt'
)
options_spec = MATCH_TAB_OPTION + '\n\n' + '''\
--wait-for-child-to-exit
type=bool-set
Wait until the launched program exits and print out its exit code. The exit code is
printed out instead of the window id. If the program exited nromally its exit code is printed, which
is always greater than or equal to zero. If the program was killed by a signal, the symbolic name
of the SIGNAL is printed, if available, otherwise the signal number with a leading minus sign is printed.
--response-timeout
type=float
default=86400
The time in seconds to wait for the started process to exit, when using the :option:`--wait-for-child-to-exit`
option. Defaults to one day.
--no-response
type=bool-set
Do not print out the id of the newly created window.
@@ -76,14 +94,17 @@ instead of the active tab
''' + '\n\n' + launch_options_spec().replace(':option:`launch', ':option:`kitten @ launch')
args = RemoteCommand.Args(spec='[CMD ...]', json_field='args', completion=RemoteCommand.CompletionSpec.from_string(
'type:special group:cli.CompleteExecutableFirstArg'))
is_asynchronous = True
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
ans = {'args': args or []}
for attr, val in opts.__dict__.items():
ans[attr] = val
# ans['wait_for_child_to_exit'] = opts.wait_for_child_to_exit
return ans
def response_from_kitty(self, boss: Boss, window: Window | None, payload_get: PayloadGetType) -> ResponseType:
# responder.send_data(getattr(w, 'id', 0))
default_opts = parse_launch_args()[0]
opts = LaunchCLIOptions()
for key, default_value in default_opts.__dict__.items():
@@ -108,10 +129,31 @@ instead of the active tab
tabs = self.tabs_for_match_payload(boss, window, payload_get)
if tabs and tabs[0]:
target_tab = tabs[0]
elif payload_get('type') not in ('background', 'os-window', 'tab', 'window'):
return None
w = do_launch(boss, opts, payload_get('args') or [], target_tab=target_tab, rc_from_window=window, base_env=base_env)
return None if payload_get('no_response') else str(getattr(w, 'id', 0))
def on_child_death(exit_status: int, exc: Exception | None) -> None:
code = os.waitstatus_to_exitcode(exit_status)
ans = str(code)
if code < 0:
try:
from signal import Signals
ans = Signals(-code).name
except ValueError:
pass
responder.send_data(ans)
w = do_launch(
boss, opts, payload_get('args') or [], target_tab=target_tab, rc_from_window=window, base_env=base_env,
child_death_callback=on_child_death if payload_get('wait_for_child_to_exit') and not payload_get('no_response') else None)
if payload_get('no_response'):
return None
if not payload_get('wait_for_child_to_exit'):
return str(0 if w is None else w.id)
responder = self.create_async_responder(payload_get, window)
return AsyncResponse()
def cancel_async_request(self, boss: 'Boss', window: Window | None, payload_get: PayloadGetType) -> None:
pass
launch = Launch()

View File

@@ -13,7 +13,7 @@ class SetFontSize(RemoteCommand):
protocol_spec = __doc__ = '''
size+/float: The new font size in pts (a positive number). If absent is assumed to be zero which means reset to default.
all/bool: Boolean whether to change font size in the current window or all windows
increment_op/choices.+.-: The string ``+`` or ``-`` to interpret size as an increment
increment_op/choices.+.-.*./: The string ``+``, ``-``, ``*`` or ``/`` to interpret size as an increment
'''
short_desc = 'Set the font size in the active top-level OS window'
@@ -22,7 +22,7 @@ class SetFontSize(RemoteCommand):
' that in kitty all sub-windows in the same OS window'
' must have the same font size. A value of zero'
' resets the font size to default. Prefixing the value'
' with a :code:`+` or :code:`-` increments the font size by the specified'
' with a :code:`+`, :code:`-`, :code:`*` or :code:`/` changes the font size by the specified'
' amount. Use -- before using - to have it not mistaken for a option. For example:'
' kitten @ set-font-size -- -2'
)

View File

@@ -597,10 +597,9 @@ choices=top,bottom,left,right,background,center,none
default={edge}
Which edge of the screen to place the panel on. Note that some window managers
(such as i3) do not support placing docked windows on the left and right edges.
The value :code:`background` means make the panel the "desktop wallpaper". This
is not supported on X11 and note that when using sway if you set
a background in your sway config it will cover the background drawn using this
kitten.
The value :code:`background` means make the panel the "desktop wallpaper".
Note that when using sway if you set a background in your sway config it will
cover the background drawn using this kitten.
Additionally, there are two more values: :code:`center` and :code:`none`.
The value :code:`center` anchors the panel to all sides and covers the entire
display (on macOS the part of the display not covered by titlebar and dock).
@@ -632,7 +631,7 @@ Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font
--output-name
On Wayland, the panel can only be displayed on a single monitor (output) at a time. This allows
On Wayland or X11, the panel can only be displayed on a single monitor (output) at a time. This allows
you to specify which output is used, by name. If not specified the compositor will choose an
output automatically, typically the last output the user interacted with or the primary monitor.
@@ -655,7 +654,7 @@ default={focus_policy}
On a Wayland compositor that supports the wlr layer shell protocol, specify the focus policy for keyboard
interactivity with the panel. Please refer to the wlr layer shell protocol documentation for more details.
Note that different Wayland compositors behave very differently with :code:`exclusive`, your mileage may vary.
On macOS, :code:`exclusive` and :code:`on-demand` are currently the same. Ignored on X11.
On macOS, :code:`exclusive` and :code:`on-demand` are currently the same.
--hide-on-focus-loss

View File

@@ -1215,13 +1215,14 @@ PYWRAP1(update_tab_bar_edge_colors) {
}
static PyObject*
pyset_background_image(PyObject *self UNUSED, PyObject *args) {
pyset_background_image(PyObject *self UNUSED, PyObject *args, PyObject *kw) {
const char *path;
PyObject *layout_name = NULL;
PyObject *layout_name = NULL, *pylinear = NULL, *pytint = NULL, *pytint_gaps = NULL;
PyObject *os_window_ids;
int configured = 0;
char *png_data = NULL; Py_ssize_t png_data_size = 0;
PA("zO!|pOy#", &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size);
static char *kwds[] = {"path", "os_window_ids", "configured", "layout_name", "png_data", "linear", "tint", "tint_gaps", NULL};
if (!PyArg_ParseTupleAndKeywords(args, kw, "zO!|pOy#OOO", kwds, &path, &PyTuple_Type, &os_window_ids, &configured, &layout_name, &png_data, &png_data_size, &pylinear, &pytint, &pytint_gaps)) return NULL;
size_t size;
BackgroundImageLayout layout = PyUnicode_Check(layout_name) ? bglayout(layout_name) : OPT(background_image_layout);
BackgroundImage *bgimage = NULL;
@@ -1247,6 +1248,9 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args) {
global_state.bgimage = bgimage;
if (bgimage) bgimage->refcnt++;
OPT(background_image_layout) = layout;
if (pylinear && pylinear != Py_None) convert_from_python_background_image_linear(pylinear, &global_state.opts);
if (pytint && pytint != Py_None) convert_from_python_background_tint(pytint, &global_state.opts);
if (pytint_gaps && pytint_gaps != Py_None) convert_from_python_background_tint_gaps(pytint_gaps, &global_state.opts);
}
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(os_window_ids); i++) {
id_type os_window_id = PyLong_AsUnsignedLongLong(PyTuple_GET_ITEM(os_window_ids, i));
@@ -1494,7 +1498,7 @@ static PyMethodDef module_methods[] = {
MW(get_os_window_pos, METH_VARARGS),
MW(set_os_window_pos, METH_VARARGS),
MW(global_font_size, METH_VARARGS),
MW(set_background_image, METH_VARARGS),
{"set_background_image", (PyCFunction)(void (*) (void))pyset_background_image, METH_VARARGS | METH_KEYWORDS, ""},
MW(os_window_font_size, METH_VARARGS),
MW(set_os_window_size, METH_VARARGS),
MW(get_os_window_size, METH_VARARGS),

32
kitty_tests/panels.py Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2025, Kovid Goyal <kovid at kovidgoyal.net>
import shlex
import subprocess
def r(msg: str, cmdline: str) -> None:
try:
q = input('Test ' + msg + '? (y/n): ').lower()
if q in ('y', 'yes'):
try:
subprocess.run(['kitten'] + shlex.split(cmdline))
except KeyboardInterrupt:
pass
except KeyboardInterrupt:
raise SystemExit(1)
if __name__ == '__main__':
r('top panel check transpareny, no input focus, margins and struts',
'panel -o background_opacity=0.2 --edge=top --lines=2 --margin-left=50 --margin-right=100')
r('bottom panel, check struts', 'panel -o background_opacity=0.2 --edge=bottom --lines=2 --margin-left=100 --margin-right=50')
r('left panel, check struts', 'panel -o background_opacity=0.2 --edge=left --columns=2 --margin-top=50 --margin-bottom=100')
r('right panel, check struts', 'panel -o background_opacity=0.2 --edge=right --columns=2 --margin-top=50 --margin-bottom=100')
r('background, check transparency and margins and no input focus',
'panel -o background_opacity=0.2 --edge=background --margin-top=50 --margin-bottom=50 --margin-left=100 --margin-right=100')
r('quake, check transparency and focus on show/re-show', 'quick-access-terminal')

View File

@@ -4,11 +4,12 @@
: <<'COMMENT'
export RSYNC_PASSWORD=password
export BUILDBOT=rsync://useranme@server/path/to/this/directory
mkdir -p ~/kitty-src
cd ~/kitty-src || exit 1
script=rsync-and-build.sh
if [[ -e "$script" ]]; then
source "$script"
. "./$script"
else
rsync -a --include "$script" --exclude '*' "$BUILDBOT" . && source "$script"
fi

View File

@@ -15,6 +15,7 @@ in
xxHash
simde
go_1_23
matplotlib
]
++ optionals stdenv.isDarwin [
Cocoa
@@ -25,7 +26,6 @@ in
OpenGL
UniformTypeIdentifiers
libpng
python3
zlib
]
++ lib.optionals (stdenv.isDarwin && (builtins.hasAttr "UserNotifications" darwin.apple_sdk.frameworks)) [
@@ -46,6 +46,10 @@ in
wayland
openssl
dbus
cairo #
]
++ lib.optionals stdenv.hostPlatform.isLinux [
wayland-scanner
]
++ checkInputs;
@@ -77,10 +81,21 @@ in
if stdenv.isDarwin
then ''
export KITTY_NO_LTO=
# Add fonts by hand
if [ ! -e ./fonts/SymbolsNerdFontMono-Regular.ttf ]; then
cp "${nerd-fonts.symbols-only}/share/fonts/truetype/NerdFonts/Symbols/SymbolsNerdFontMono-Regular.ttf" ./fonts/
fi
''
else ''
export KITTY_EGL_LIBRARY='${lib.getLib libGL}/lib/libEGL.so.1'
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
export KITTY_FONTCONFIG_LIBRARY='${fontconfig.lib}/lib/libfontconfig.so'
# Add fonts by hand
if [ ! -e ./fonts/SymbolsNerdFontMono-Regular.ttf ]; then
cp "${nerd-fonts.symbols-only}/share/fonts/truetype/NerdFonts/Symbols/SymbolsNerdFontMono-Regular.ttf" ./fonts/
fi
'';
}

View File

@@ -3,11 +3,12 @@
package at
import (
"bytes"
"strconv"
)
func parse_set_font_size(arg string, payload *set_font_size_json_type) error {
if len(arg) > 0 && (arg[0] == '+' || arg[0] == '-') {
if len(arg) > 0 && (bytes.IndexByte([]byte{'+', '-', '/', '*'}, arg[0]) > -1) {
payload.Increment_op = arg[:1]
arg = arg[1:]
}