mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-14 20:47:58 +02:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2412cc9c8 | ||
|
|
5dbd198fb9 | ||
|
|
9b4643e8bc | ||
|
|
f2f914ed05 | ||
|
|
ec5062ceb4 | ||
|
|
eb70f81218 | ||
|
|
04a4d62859 | ||
|
|
2aaa440519 | ||
|
|
782e7cb2fb | ||
|
|
41b44fdafa | ||
|
|
0f3603c0eb | ||
|
|
8ba9bfd460 | ||
|
|
22fd548903 | ||
|
|
7856046382 | ||
|
|
f93d57d919 | ||
|
|
e8d50d0734 | ||
|
|
20e5c7b004 | ||
|
|
a597764245 | ||
|
|
d6225153ee | ||
|
|
cca838b952 | ||
|
|
6a53897c17 | ||
|
|
e0e4e53e3b | ||
|
|
8a14a5638a | ||
|
|
2fed0ec562 | ||
|
|
97f9d16046 | ||
|
|
ddd79f0733 | ||
|
|
53fd9892eb | ||
|
|
91eb0ec735 | ||
|
|
3c3ba4a9fb | ||
|
|
a4963a58e3 | ||
|
|
80bb9404d5 | ||
|
|
7045632d2e | ||
|
|
7b89477470 | ||
|
|
53bb4f0609 | ||
|
|
05e6ed685b | ||
|
|
e6e3e01795 | ||
|
|
eb7ca8902b | ||
|
|
b5cbb501a4 | ||
|
|
7ef2fe53e0 | ||
|
|
a59a347903 | ||
|
|
d22381491d | ||
|
|
e961020330 | ||
|
|
1237f7667c | ||
|
|
7dc673e485 | ||
|
|
07cda6ac45 | ||
|
|
f30ec25c57 | ||
|
|
43be32a3d7 | ||
|
|
958489f97d | ||
|
|
afe7dc47c2 | ||
|
|
11cb3adb8f | ||
|
|
a9bc9962f4 | ||
|
|
a2631448e5 | ||
|
|
be9624bbdd | ||
|
|
6c0e5f09d3 | ||
|
|
233cd3e2b9 | ||
|
|
49ea2bf636 | ||
|
|
e87256793c | ||
|
|
f461039017 | ||
|
|
ca688be41c |
@@ -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]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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`::
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
11
glfw/dbus_glfw.c
vendored
@@ -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
2
glfw/dbus_glfw.h
vendored
@@ -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
12
glfw/ibus_glfw.c
vendored
@@ -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);
|
||||
|
||||
40
glfw/linux_desktop_settings.c
vendored
40
glfw/linux_desktop_settings.c
vendored
@@ -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
10
glfw/linux_notify.c
vendored
@@ -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
2
glfw/wl_window.c
vendored
@@ -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
82
glfw/x11_init.c
vendored
@@ -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
6
glfw/x11_platform.h
vendored
@@ -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
48
glfw/x11_window.c
vendored
@@ -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
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}:'))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
''') # }}}
|
||||
|
||||
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
32
kitty_tests/panels.py
Executable 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')
|
||||
@@ -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
|
||||
|
||||
17
shell.nix
17
shell.nix
@@ -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
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -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:]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user