mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-14 12:37:48 +02:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13e59df1f8 | ||
|
|
c797944923 | ||
|
|
ade4e67b51 | ||
|
|
656f49f2ff | ||
|
|
0045244295 | ||
|
|
6bfb704f6f | ||
|
|
80cebdefcd | ||
|
|
68cd863fe0 | ||
|
|
65e5015d19 | ||
|
|
c26665ec4d | ||
|
|
91d82ebf4b | ||
|
|
303e4baa58 | ||
|
|
3c953d47ca | ||
|
|
5e629d5c09 | ||
|
|
c6ec2d4282 | ||
|
|
66b93a9af8 | ||
|
|
8d5479f55e | ||
|
|
96ce6c9504 | ||
|
|
0d55865545 | ||
|
|
a1fc383e6f | ||
|
|
c3807e175d | ||
|
|
6f83f76d41 | ||
|
|
9a56d619af | ||
|
|
f692d586f7 | ||
|
|
d3d3e99979 | ||
|
|
8e6a179efe | ||
|
|
17ff317d30 | ||
|
|
faef9f0049 | ||
|
|
bb0c831601 | ||
|
|
9462654738 | ||
|
|
e872169e4c | ||
|
|
a22404abe6 | ||
|
|
7c06313750 | ||
|
|
6f265f448d | ||
|
|
acdc41bd03 | ||
|
|
bcff2a7fb6 | ||
|
|
94188fddce | ||
|
|
3684e7f54a | ||
|
|
890181172f | ||
|
|
9c9d68561e | ||
|
|
1bd39ff935 | ||
|
|
17f3d2d581 | ||
|
|
55aa9e11db | ||
|
|
bc895eacc5 | ||
|
|
6c8eb4a19a |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -22,7 +22,7 @@ If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment details**
|
||||
```
|
||||
Press Ctrl+Shift+F6 (cmd+option+, on macOS) in kitty, to copy debug output about kitty and its
|
||||
Press Ctrl+Shift+F6 (cmd+option+comma on macOS) in kitty, to copy debug output about kitty and its
|
||||
configuration to the clipboard and paste it here.
|
||||
|
||||
On older versions of kitty, run kitty --debug-config instead
|
||||
|
||||
@@ -1 +1 @@
|
||||
See https://sw.kovidgoyal.net/kitty/changelog.html
|
||||
See https://sw.kovidgoyal.net/kitty/changelog/
|
||||
|
||||
@@ -10,9 +10,9 @@ config to reproduce the issue with).
|
||||
|
||||
### Contributing code
|
||||
|
||||
Install [the dependencies](https://sw.kovidgoyal.net/kitty/build.html#dependencies)
|
||||
Install [the dependencies](https://sw.kovidgoyal.net/kitty/build/#dependencies)
|
||||
using your favorite package manager. Build and run kitty [from
|
||||
source](https://sw.kovidgoyal.net/kitty/build.html#install-and-run-from-source).
|
||||
source](https://sw.kovidgoyal.net/kitty/build/#install-and-run-from-source).
|
||||
|
||||
Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue
|
||||
beforehand to discuss it, so that you don't waste your time making a pull
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
To build from source: <https://sw.kovidgoyal.net/kitty/build.html>
|
||||
To build from source: <https://sw.kovidgoyal.net/kitty/build/>
|
||||
|
||||
Pre-built binaries: <https://sw.kovidgoyal.net/kitty/binary.html>
|
||||
Pre-built binaries: <https://sw.kovidgoyal.net/kitty/binary/>
|
||||
|
||||
@@ -4,7 +4,7 @@ See https://sw.kovidgoyal.net/kitty/[the kitty website].
|
||||
|
||||
image:https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg["Build status", link="https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI"]
|
||||
|
||||
https://sw.kovidgoyal.net/kitty/faq.html[Frequently Asked Questions]
|
||||
https://sw.kovidgoyal.net/kitty/faq/[Frequently Asked Questions]
|
||||
|
||||
To ask other questions about kitty usage, use either the https://github.com/kovidgoyal/kitty/discussions/[discussions on GitHub] or the
|
||||
https://www.reddit.com/r/KittyTerminal[Reddit community]
|
||||
|
||||
@@ -88,6 +88,8 @@ def copy_libs(env):
|
||||
for x in binary_includes():
|
||||
dest = env.bin_dir if '/bin/' in x else env.lib_dir
|
||||
shutil.copy2(x, dest)
|
||||
dest = os.path.join(dest, os.path.basename(x))
|
||||
subprocess.check_call(['chrpath', '-d', dest])
|
||||
|
||||
|
||||
def copy_python(env):
|
||||
|
||||
@@ -83,6 +83,10 @@ move it to another tab or another OS window::
|
||||
map ctrl+f2 detach_window
|
||||
# moves the window into a new Tab
|
||||
map ctrl+f3 detach_window new-tab
|
||||
# moves the window into the previously active tab
|
||||
map ctrl+f3 detach_window tab-prev
|
||||
# moves the window into the tab at the left of the active tab
|
||||
map ctrl+f3 detach_window tab-left
|
||||
# asks which tab to move the window into
|
||||
map ctrl+f4 detach_window ask
|
||||
|
||||
|
||||
@@ -4,6 +4,40 @@ Changelog
|
||||
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.22.2 [2021-08-02]
|
||||
----------------------
|
||||
|
||||
- macOS: Fix a long standing bug that could cause kitty windows to stop
|
||||
updating, that got worse in the previous release (:iss:`3890` and
|
||||
:iss:`2016`)
|
||||
|
||||
- Wayland: A better fix for compositors like sway that can toggle client side
|
||||
decorations on and off (:iss:`3888`)
|
||||
|
||||
|
||||
0.22.1 [2021-07-31]
|
||||
----------------------
|
||||
|
||||
- Fix a regression in the previous release that broke ``kitty --help`` (:iss:`3869`)
|
||||
|
||||
- Graphics protocol: Fix composing onto currently displayed frame not updating the frame on the GPU (:iss:`3874`)
|
||||
|
||||
- Fix switching to previously active tab after detaching a tab not working (:pull:`3871`)
|
||||
|
||||
- macOS: Fix an error on Apple silicon when enumerating monitors (:pull:`3875`)
|
||||
|
||||
- detach_window: Allow specifying the previously active tab or the tab to the left/right of
|
||||
the active tab (:disc:`3877`)
|
||||
|
||||
- broadcast kitten: Fix a regression in ``0.20.0`` that broke sending of some
|
||||
keys, such as backspace
|
||||
|
||||
- Linux binary: Remove any RPATH build artifacts from bundled libraries
|
||||
|
||||
- Wayland: If the compositor turns off server side decorations after turning
|
||||
them on do not draw client side decorations (:iss:`3888`)
|
||||
|
||||
|
||||
0.22.0 [2021-07-26]
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ if kitty_src not in sys.path:
|
||||
sys.path.insert(0, kitty_src)
|
||||
|
||||
from kitty.conf.types import Definition # noqa
|
||||
from kitty.constants import str_version # noqa
|
||||
from kitty.constants import str_version, website_url # noqa
|
||||
|
||||
# config {{{
|
||||
# -- Project information -----------------------------------------------------
|
||||
@@ -65,7 +65,7 @@ extensions = [
|
||||
]
|
||||
|
||||
# URL for OpenGraph tags
|
||||
ogp_site_url = 'https://sw.kovidgoyal.net/kitty/'
|
||||
ogp_site_url = website_url()
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -516,4 +516,5 @@ def setup(app: Any) -> None:
|
||||
app.add_role('link', link_role)
|
||||
app.add_role('iss', partial(num_role, 'issues'))
|
||||
app.add_role('pull', partial(num_role, 'pull'))
|
||||
app.add_role('disc', partial(num_role, 'discussions'))
|
||||
app.add_role('commit', commit_role)
|
||||
|
||||
@@ -53,6 +53,17 @@ Variables that influence kitty behavior
|
||||
directory lookup mechanism see, :option:`kitty --config`.
|
||||
|
||||
|
||||
.. envvar:: VISUAL
|
||||
|
||||
The terminal editor (such as ``vi`` or ``nano``) kitty uses, when, for
|
||||
instance, opening :file:`kitty.conf` in response to :sc:`edit_config_file`.
|
||||
|
||||
|
||||
.. envvar:: EDITOR
|
||||
|
||||
Same as :envvar:`VISUAL`. Used if :envvar:`VISUAL` is not set.
|
||||
|
||||
|
||||
Variables that kitty sets when running child programs
|
||||
|
||||
.. envvar:: KITTY_WINDOW_ID
|
||||
|
||||
@@ -97,32 +97,36 @@ features of the graphics protocol:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
|
||||
def serialize_gr_command(**cmd):
|
||||
payload = cmd.pop('payload', None)
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
def write_chunked(**cmd):
|
||||
data = standard_b64encode(cmd.pop('data'))
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m, **cmd))
|
||||
sys.stdout.flush()
|
||||
cmd.clear()
|
||||
def serialize_gr_command(**cmd):
|
||||
payload = cmd.pop('payload', None)
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
with open(sys.argv[-1], 'rb') as f:
|
||||
write_chunked(a='T', f=100, data=f.read())
|
||||
|
||||
def write_chunked(**cmd):
|
||||
data = standard_b64encode(cmd.pop('data'))
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m,
|
||||
**cmd))
|
||||
sys.stdout.flush()
|
||||
cmd.clear()
|
||||
|
||||
|
||||
with open(sys.argv[-1], 'rb') as f:
|
||||
write_chunked(a='T', f=100, data=f.read())
|
||||
|
||||
|
||||
Save this script as :file:`png.py`, then you can use it to display any PNG
|
||||
@@ -693,7 +697,7 @@ take, and the default value they take when missing. All integers are 32-bit.
|
||||
Key Value Default Description
|
||||
======= ==================== ========= =================
|
||||
``a`` Single character. ``t`` The overall action this graphics command is performing.
|
||||
``(a, c, d, f, ` ``t`` - transmit data, ``T`` - transmit data and display image,
|
||||
``(a, c, d, f, `` ``t`` - transmit data, ``T`` - transmit data and display image,
|
||||
``p, q, t, T)`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image,
|
||||
``d`` - delete image, ``f`` - transmit data for animation frames,
|
||||
``a`` - control animation, ``c`` - compose animation frames
|
||||
@@ -753,7 +757,7 @@ Key Value Default Description
|
||||
**Keys for animation frame composition**
|
||||
-----------------------------------------------------------
|
||||
|
||||
``c`` Positive integer ``0`` The 1-based frame number of the frame whose image data serves as the base data
|
||||
``c`` Positive integer ``0`` The 1-based frame number of the frame whose image data serves as the overlaid data
|
||||
``r`` Positive integer ``0`` The 1-based frame number of the frame that is being edited.
|
||||
``x`` Positive integer ``0`` The left edge (in pixels) of the destination rectangle
|
||||
``y`` Positive integer ``0`` The top edge (in pixels) of the destination rectangle
|
||||
|
||||
@@ -17,9 +17,10 @@ Then hold down :kbd:`ctrl+shift` and click the name of the file.
|
||||
|
||||
|kitty| will ask you what you want to do with the remote file. You can choose
|
||||
to *Edit* it in which case kitty will download it and open it locally in your
|
||||
``EDITOR``. As you make changes to the file, they are automatically transferred
|
||||
to the remote computer. Note that this happens without needing to install *any*
|
||||
special software on the server, beyond ``ls`` that supports hyperlinks.
|
||||
:envvar:`EDITOR`. As you make changes to the file, they are automatically
|
||||
transferred to the remote computer. Note that this happens without needing
|
||||
to install *any* special software on the server, beyond ``ls`` that supports
|
||||
hyperlinks.
|
||||
|
||||
.. versionadded:: 0.19.0
|
||||
|
||||
|
||||
@@ -41,8 +41,22 @@
|
||||
|
||||
// Get the name of the specified display, or NULL
|
||||
//
|
||||
static char* getDisplayName(CGDirectDisplayID displayID)
|
||||
static char*
|
||||
getDisplayName(CGDirectDisplayID displayID, NSScreen* screen)
|
||||
{
|
||||
// IOKit doesn't work on Apple Silicon anymore
|
||||
// Luckily, 10.15 introduced -[NSScreen localizedName].
|
||||
// Use it if available, and fall back to IOKit otherwise.
|
||||
if (screen)
|
||||
{
|
||||
if ([screen respondsToSelector:@selector(localizedName)])
|
||||
{
|
||||
NSString* name = [screen valueForKey:@"localizedName"];
|
||||
if (name) {
|
||||
return _glfw_strdup([name UTF8String]);
|
||||
}
|
||||
}
|
||||
}
|
||||
io_iterator_t it;
|
||||
io_service_t service;
|
||||
CFDictionaryRef info;
|
||||
@@ -89,7 +103,7 @@ static char* getDisplayName(CGDirectDisplayID displayID)
|
||||
if (!service)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Cocoa: Failed to find service port for display, cannot get its name, using Unknown");
|
||||
"Cocoa: Failed to find service port for display");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -314,11 +328,9 @@ void _glfwClearDisplayLinks() {
|
||||
if (_glfw.ns.displayLinks.entries[i].displayLink) {
|
||||
CVDisplayLinkStop(_glfw.ns.displayLinks.entries[i].displayLink);
|
||||
CVDisplayLinkRelease(_glfw.ns.displayLinks.entries[i].displayLink);
|
||||
_glfw.ns.displayLinks.entries[i].displayLink = nil;
|
||||
_glfw.ns.displayLinks.entries[i].lastRenderFrameRequestedAt = 0;
|
||||
_glfw.ns.displayLinks.entries[i].first_unserviced_render_frame_request_at = 0;
|
||||
}
|
||||
}
|
||||
memset(_glfw.ns.displayLinks.entries, 0, sizeof(_GLFWDisplayLinkNS) * _glfw.ns.displayLinks.count);
|
||||
_glfw.ns.displayLinks.count = 0;
|
||||
}
|
||||
|
||||
@@ -340,16 +352,21 @@ _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry) {
|
||||
CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID);
|
||||
}
|
||||
|
||||
static void
|
||||
createDisplayLink(CGDirectDisplayID displayID) {
|
||||
if (_glfw.ns.displayLinks.count >= sizeof(_glfw.ns.displayLinks.entries)/sizeof(_glfw.ns.displayLinks.entries[0]) - 1) return;
|
||||
_GLFWDisplayLinkNS*
|
||||
_glfw_create_display_link(CGDirectDisplayID displayID) {
|
||||
if (_glfw.ns.displayLinks.count >= arraysz(_glfw.ns.displayLinks.entries) - 1) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many monitors cannot create display link");
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
|
||||
if (_glfw.ns.displayLinks.entries[i].displayID == displayID) return;
|
||||
// already created in this run
|
||||
if (_glfw.ns.displayLinks.entries[i].displayID == displayID) return _glfw.ns.displayLinks.entries + i;
|
||||
}
|
||||
_GLFWDisplayLinkNS *entry = &_glfw.ns.displayLinks.entries[_glfw.ns.displayLinks.count++];
|
||||
memset(entry, 0, sizeof(_GLFWDisplayLinkNS));
|
||||
entry->displayID = displayID;
|
||||
_glfw_create_cv_display_link(entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Poll for changes in the set of connected monitors
|
||||
@@ -357,11 +374,13 @@ createDisplayLink(CGDirectDisplayID displayID) {
|
||||
void _glfwPollMonitorsNS(void)
|
||||
{
|
||||
uint32_t displayCount;
|
||||
|
||||
CGGetOnlineDisplayList(0, NULL, &displayCount);
|
||||
CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
|
||||
CGGetOnlineDisplayList(displayCount, displays, &displayCount);
|
||||
_glfwClearDisplayLinks();
|
||||
if (_glfw.hints.init.debugRendering) {
|
||||
fprintf(stderr, "Polling for monitors: %u found\n", displayCount);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _glfw.monitorCount; i++)
|
||||
_glfw.monitors[i]->ns.screen = nil;
|
||||
@@ -378,32 +397,57 @@ void _glfwPollMonitorsNS(void)
|
||||
|
||||
for (uint32_t i = 0; i < displayCount; i++)
|
||||
{
|
||||
if (CGDisplayIsAsleep(displays[i]))
|
||||
if (CGDisplayIsAsleep(displays[i])) {
|
||||
if (_glfw.hints.init.debugRendering) fprintf(stderr, "Ignoring sleeping display: %u", displays[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
|
||||
NSScreen* screen = nil;
|
||||
|
||||
for (screen in [NSScreen screens])
|
||||
{
|
||||
NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
|
||||
|
||||
// HACK: Compare unit numbers instead of display IDs to work around
|
||||
// display replacement on machines with automatic graphics
|
||||
// switching
|
||||
if (CGDisplayUnitNumber([screenNumber unsignedIntValue]) == unitNumber)
|
||||
break;
|
||||
}
|
||||
|
||||
// HACK: Compare unit numbers instead of display IDs to work around
|
||||
// display replacement on machines with automatic graphics
|
||||
// switching
|
||||
const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
|
||||
|
||||
for (uint32_t j = 0; j < disconnectedCount; j++)
|
||||
uint32_t j;
|
||||
for (j = 0; j < disconnectedCount; j++)
|
||||
{
|
||||
if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
|
||||
{
|
||||
disconnected[j]->ns.displayID = displays[i];
|
||||
disconnected[j]->ns.screen = screen;
|
||||
_glfw_create_display_link(displays[i]);
|
||||
disconnected[j] = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j < disconnectedCount)
|
||||
continue;
|
||||
|
||||
const CGSize size = CGDisplayScreenSize(displays[i]);
|
||||
char* name = getDisplayName(displays[i]);
|
||||
if (!name)
|
||||
name = _glfw_strdup("Unknown");
|
||||
char* name = getDisplayName(displays[i], screen);
|
||||
if (!name) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Failed to get name for display, using generic name");
|
||||
name = _glfw_strdup("Display with no name");
|
||||
}
|
||||
|
||||
_GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height);
|
||||
monitor->ns.displayID = displays[i];
|
||||
monitor->ns.unitNumber = unitNumber;
|
||||
createDisplayLink(monitor->ns.displayID);
|
||||
monitor->ns.screen = screen;
|
||||
_glfw_create_display_link(monitor->ns.displayID);
|
||||
|
||||
free(name);
|
||||
|
||||
@@ -554,15 +598,16 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
|
||||
const GLFWvidmode mode =
|
||||
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
|
||||
CFIndex j;
|
||||
|
||||
for (CFIndex j = 0; j < *count; j++)
|
||||
for (j = 0; j < *count; j++)
|
||||
{
|
||||
if (_glfwCompareVideoModes(result + j, &mode) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip duplicate modes
|
||||
if (i < *count)
|
||||
if (j < *count)
|
||||
continue;
|
||||
|
||||
(*count)++;
|
||||
|
||||
1
glfw/cocoa_platform.h
vendored
1
glfw/cocoa_platform.h
vendored
@@ -250,3 +250,4 @@ void _glfwDispatchRenderFrame(CGDirectDisplayID);
|
||||
void _glfwShutdownCVDisplayLink(unsigned long long, void*);
|
||||
void _glfwCocoaPostEmptyEvent(void);
|
||||
void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry);
|
||||
_GLFWDisplayLinkNS* _glfw_create_display_link(CGDirectDisplayID);
|
||||
|
||||
@@ -337,9 +337,12 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
|
||||
display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
|
||||
}
|
||||
monotonic_t now = glfwGetTime();
|
||||
bool found_display_link = false;
|
||||
_GLFWDisplayLinkNS *dl = NULL;
|
||||
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) {
|
||||
_GLFWDisplayLinkNS *dl = &_glfw.ns.displayLinks.entries[i];
|
||||
dl = &_glfw.ns.displayLinks.entries[i];
|
||||
if (dl->displayID == displayID) {
|
||||
found_display_link = true;
|
||||
dl->lastRenderFrameRequestedAt = now;
|
||||
if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
|
||||
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
||||
@@ -359,6 +362,14 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
|
||||
dl->first_unserviced_render_frame_request_at = 0;
|
||||
}
|
||||
}
|
||||
if (!found_display_link) {
|
||||
dl = _glfw_create_display_link(displayID);
|
||||
if (dl) {
|
||||
dl->lastRenderFrameRequestedAt = now;
|
||||
dl->first_unserviced_render_frame_request_at = now;
|
||||
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -222,6 +222,7 @@ def generate_wrappers(glfw_header: str) -> None:
|
||||
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
|
||||
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
|
||||
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
||||
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
'''.splitlines():
|
||||
if line:
|
||||
functions.append(Function(line.strip(), check_fail=False))
|
||||
|
||||
1
glfw/wl_init.c
vendored
1
glfw/wl_init.c
vendored
@@ -29,7 +29,6 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "internal.h"
|
||||
#include "backend_utils.h"
|
||||
#include "wl_client_side_decorations.h"
|
||||
#include "linux_desktop_settings.h"
|
||||
#include "../kitty/monotonic.h"
|
||||
|
||||
|
||||
42
glfw/wl_window.c
vendored
42
glfw/wl_window.c
vendored
@@ -254,15 +254,39 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
|
||||
_glfwInputWindowDamage(window);
|
||||
}
|
||||
|
||||
static void
|
||||
inform_compositor_of_window_geometry(_GLFWwindow *window, const char *event) {
|
||||
#define geometry window->wl.decorations.geometry
|
||||
debug("Setting window geometry in %s event: x=%d y=%d %dx%d\n", event, geometry.x, geometry.y, geometry.width, geometry.height);
|
||||
xdg_surface_set_window_geometry(window->wl.xdg.surface, geometry.x, geometry.y, geometry.width, geometry.height);
|
||||
#undef geometry
|
||||
}
|
||||
|
||||
static void xdgDecorationHandleConfigure(void* data,
|
||||
|
||||
static void
|
||||
xdgDecorationHandleConfigure(void* data,
|
||||
struct zxdg_toplevel_decoration_v1* decoration UNUSED,
|
||||
uint32_t mode)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
|
||||
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
bool has_server_side_decorations = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
debug("XDG decoration configure event received: has_server_side_decorations: %d\n", has_server_side_decorations);
|
||||
if (has_server_side_decorations == window->wl.decorations.serverSide) return;
|
||||
window->wl.decorations.serverSide = has_server_side_decorations;
|
||||
int width = window->wl.width, height = window->wl.height;
|
||||
if (window->wl.decorations.serverSide) {
|
||||
free_csd_surfaces(window);
|
||||
height += window->wl.decorations.metrics.visible_titlebar_height;
|
||||
} else {
|
||||
ensure_csd_resources(window);
|
||||
}
|
||||
set_csd_window_geometry(window, &width, &height);
|
||||
dispatchChangesAfterConfigure(window, width, height);
|
||||
ensure_csd_resources(window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
debug("final window content size: %dx%d\n", window->wl.width, window->wl.height);
|
||||
inform_compositor_of_window_geometry(window, "configure-decorations");
|
||||
}
|
||||
|
||||
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
|
||||
@@ -396,14 +420,6 @@ _glfwPlatformToggleFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) {
|
||||
return !already_fullscreen;
|
||||
}
|
||||
|
||||
static void
|
||||
inform_compositor_of_window_geometry(_GLFWwindow *window, const char *event) {
|
||||
#define geometry window->wl.decorations.geometry
|
||||
debug("Setting window geometry in %s event: x=%d y=%d %dx%d\n", event, geometry.x, geometry.y, geometry.width, geometry.height);
|
||||
xdg_surface_set_window_geometry(window->wl.xdg.surface, geometry.x, geometry.y, geometry.width, geometry.height);
|
||||
#undef geometry
|
||||
}
|
||||
|
||||
static void
|
||||
xdgToplevelHandleConfigure(void* data,
|
||||
struct xdg_toplevel* toplevel UNUSED,
|
||||
@@ -488,7 +504,8 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
xdgSurfaceHandleConfigure
|
||||
};
|
||||
|
||||
static void setXdgDecorations(_GLFWwindow* window)
|
||||
static void
|
||||
setXdgDecorations(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.wl.decorationManager)
|
||||
{
|
||||
@@ -510,7 +527,8 @@ static void setXdgDecorations(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
static bool createXdgSurface(_GLFWwindow* window)
|
||||
static bool
|
||||
createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
|
||||
window->wl.surface);
|
||||
|
||||
7
glfw/x11_window.c
vendored
7
glfw/x11_window.c
vendored
@@ -3122,3 +3122,10 @@ GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char*
|
||||
GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) {
|
||||
glfw_dbus_set_user_notification_activated_handler(handler);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc);
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
See https://sw.kovidgoyal.net/kitty/kittens/diff.html
|
||||
See https://sw.kovidgoyal.net/kitty/kittens/diff/
|
||||
|
||||
@@ -16,6 +16,7 @@ from typing import (
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import HintsCLIOptions
|
||||
from kitty.constants import website_url
|
||||
from kitty.fast_data_types import set_clipboard_string
|
||||
from kitty.key_encoding import KeyEvent
|
||||
from kitty.typing import BossType, KittyCommonOpts
|
||||
@@ -640,7 +641,7 @@ The foreground color for text pointed to by the hints
|
||||
--customize-processing
|
||||
Name of a python file in the kitty config directory which will be imported to provide
|
||||
custom implementations for pattern finding and performing actions
|
||||
on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html
|
||||
on selected matches. See {hints_url}
|
||||
for details. You can also specify absolute paths to load the script from elsewhere.
|
||||
|
||||
|
||||
@@ -649,7 +650,8 @@ The window title for the hints window, default title is selected based on
|
||||
the type of text being hinted.
|
||||
'''.format(
|
||||
default_regex=DEFAULT_REGEX,
|
||||
line='{{line}}', path='{{path}}'
|
||||
line='{{line}}', path='{{path}}',
|
||||
hints_url=website_url('kittens/hints'),
|
||||
).format
|
||||
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
usage = ''
|
||||
|
||||
@@ -11,14 +11,15 @@ from contextlib import suppress
|
||||
from enum import IntEnum
|
||||
from itertools import count
|
||||
from typing import (
|
||||
Any, Callable, DefaultDict, Deque, Dict, Iterator, List, Optional,
|
||||
Sequence, Tuple, Union
|
||||
Any, Callable, ClassVar, DefaultDict, Deque, Dict, Generic, Iterator, List,
|
||||
Optional, Sequence, Tuple, Type, TypeVar, Union, cast
|
||||
)
|
||||
|
||||
from kitty.conf.utils import positive_float, positive_int
|
||||
from kitty.fast_data_types import create_canvas
|
||||
from kitty.typing import (
|
||||
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType
|
||||
GRT_C, CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t,
|
||||
HandlerType
|
||||
)
|
||||
from kitty.utils import ScreenSize, find_exe, fit_image
|
||||
|
||||
@@ -298,49 +299,75 @@ def can_display_images() -> bool:
|
||||
|
||||
ImageKey = Tuple[str, int, int]
|
||||
SentImageKey = Tuple[int, int, int]
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
class Alias(Generic[T]):
|
||||
|
||||
currently_processing: ClassVar[str] = ''
|
||||
|
||||
def __init__(self, defval: T) -> None:
|
||||
self.name = ''
|
||||
self.defval = defval
|
||||
|
||||
def __get__(self, instance: Optional['GraphicsCommand'], cls: Optional[Type['GraphicsCommand']] = None) -> T:
|
||||
if instance is None:
|
||||
return self.defval
|
||||
return cast(T, instance._actual_values.get(self.name, self.defval))
|
||||
|
||||
def __set__(self, instance: 'GraphicsCommand', val: T) -> None:
|
||||
if val == self.defval:
|
||||
instance._actual_values.pop(self.name, None)
|
||||
else:
|
||||
instance._actual_values[self.name] = val
|
||||
|
||||
def __set_name__(self, owner: Type['GraphicsCommand'], name: str) -> None:
|
||||
if len(name) == 1:
|
||||
Alias.currently_processing = name
|
||||
self.name = Alias.currently_processing
|
||||
|
||||
|
||||
class GraphicsCommand:
|
||||
a: GRT_a = 't' # action
|
||||
q: int = 0 # suppress responses
|
||||
f: GRT_f = 32 # image data format
|
||||
t: GRT_t = 'd' # transmission medium
|
||||
s: int = 0 # sent image width
|
||||
v: int = 0 # sent image height
|
||||
S: int = 0 # size of data to read from file
|
||||
O: int = 0 # offset of data to read from file
|
||||
i: int = 0 # image id
|
||||
I: int = 0 # image number
|
||||
p: int = 0 # placement id
|
||||
o: Optional[GRT_o] = None # type of compression
|
||||
m: GRT_m = 0 # 0 or 1 whether there is more chunked data
|
||||
x: int = 0 # left edge of image area to display
|
||||
y: int = 0 # top edge of image area to display
|
||||
w: int = 0 # image width to display
|
||||
h: int = 0 # image height to display
|
||||
X: int = 0 # X-offset within cell
|
||||
Y: int = 0 # Y-offset within cell
|
||||
c: int = 0 # number of cols to display image over
|
||||
r: int = 0 # number of rows to display image over
|
||||
z: int = 0 # z-index
|
||||
d: GRT_d = 'a' # what to delete
|
||||
a = action = Alias(cast(GRT_a, 't'))
|
||||
q = quiet = Alias(0)
|
||||
f = format = Alias(32)
|
||||
t = transmission_type = Alias(cast(GRT_t, 'd'))
|
||||
s = data_width = animation_state = Alias(0)
|
||||
v = data_height = loop_count = Alias(0)
|
||||
S = data_size = Alias(0)
|
||||
O = data_offset = Alias(0) # noqa
|
||||
i = image_id = Alias(0)
|
||||
I = image_number = Alias(0) # noqa
|
||||
p = placement_id = Alias(0)
|
||||
o = compression = Alias(cast(Optional[GRT_o], None))
|
||||
m = more = Alias(cast(GRT_m, 0))
|
||||
x = left_edge = Alias(0)
|
||||
y = top_edge = Alias(0)
|
||||
w = width = Alias(0)
|
||||
h = height = Alias(0)
|
||||
X = cell_x_offset = blend_mode = Alias(0)
|
||||
Y = cell_y_offset = bgcolor = Alias(0)
|
||||
c = columns = other_frame_number = dest_frame = Alias(0)
|
||||
r = rows = frame_number = source_frame = Alias(0)
|
||||
z = z_index = gap = Alias(0)
|
||||
C = cursor_movement = compose_mode = Alias(cast(GRT_C, 0))
|
||||
d = delete_action = Alias(cast(GRT_d, 'a'))
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._actual_values: Dict[str, Any] = {}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.serialize().decode('ascii').replace('\033', '^]')
|
||||
|
||||
def clone(self) -> 'GraphicsCommand':
|
||||
ans = GraphicsCommand()
|
||||
for k in GraphicsCommand.__annotations__:
|
||||
setattr(ans, k, getattr(self, k))
|
||||
ans._actual_values = self._actual_values.copy()
|
||||
return ans
|
||||
|
||||
def serialize(self, payload: Union[bytes, str] = b'') -> bytes:
|
||||
items = []
|
||||
for k in GraphicsCommand.__annotations__:
|
||||
val: Union[str, None, int] = getattr(self, k)
|
||||
defval: Union[str, None, int] = getattr(GraphicsCommand, k)
|
||||
if val != defval and val is not None:
|
||||
items.append('{}={}'.format(k, val))
|
||||
for k, val in self._actual_values.items():
|
||||
items.append('{}={}'.format(k, val))
|
||||
|
||||
ans: List[bytes] = []
|
||||
w = ans.append
|
||||
@@ -355,9 +382,27 @@ class GraphicsCommand:
|
||||
return b''.join(ans)
|
||||
|
||||
def clear(self) -> None:
|
||||
for k in GraphicsCommand.__annotations__:
|
||||
defval: Union[str, None, int] = getattr(GraphicsCommand, k)
|
||||
setattr(self, k, defval)
|
||||
self._actual_values = {}
|
||||
|
||||
def iter_transmission_chunks(self, data: Optional[bytes] = None, level: int = -1, compression_threshold: int = 1024) -> Iterator[bytes]:
|
||||
if data is None:
|
||||
yield self.serialize()
|
||||
return
|
||||
gc = self.clone()
|
||||
gc.S = len(data)
|
||||
if level and len(data) >= compression_threshold:
|
||||
import zlib
|
||||
compressed = zlib.compress(data, level)
|
||||
if len(compressed) < len(data):
|
||||
gc.o = 'z'
|
||||
data = compressed
|
||||
gc.S = len(data)
|
||||
data = standard_b64encode(data)
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
gc.m = 1 if data else 0
|
||||
yield gc.serialize(chunk)
|
||||
gc.clear()
|
||||
|
||||
|
||||
class Placement:
|
||||
|
||||
@@ -24,7 +24,7 @@ from .conf.utils import BadLine, KeyAction, to_cmdline
|
||||
from .config import common_opts_as_dict, prepare_config_file_for_editing
|
||||
from .constants import (
|
||||
appname, config_dir, is_macos, is_wayland, kitty_exe,
|
||||
supports_primary_selection
|
||||
supports_primary_selection, website_url
|
||||
)
|
||||
from .fast_data_types import (
|
||||
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
|
||||
@@ -1141,7 +1141,7 @@ class Boss:
|
||||
|
||||
self._run_kitten('ask', [
|
||||
'--name=create-marker', '--message',
|
||||
_('Create marker, for example:\ntext 1 ERROR\nSee https://sw.kovidgoyal.net/kitty/marks.html\n')
|
||||
_('Create marker, for example:\ntext 1 ERROR\nSee {}\n').format(website_url('marks'))
|
||||
],
|
||||
custom_callback=done, action_on_removal=done2)
|
||||
|
||||
@@ -1691,14 +1691,17 @@ class Boss:
|
||||
target_tab = tm.new_tab(empty_tab=True)
|
||||
else:
|
||||
target_os_window_id = target_os_window_id or current_os_window()
|
||||
if target_tab_id == 'new':
|
||||
if isinstance(target_tab_id, str):
|
||||
if not isinstance(target_os_window_id, int):
|
||||
q = self.active_tab_manager
|
||||
assert q is not None
|
||||
tm = q
|
||||
else:
|
||||
tm = self.os_window_map[target_os_window_id]
|
||||
target_tab = tm.new_tab(empty_tab=True)
|
||||
if target_tab_id == 'new':
|
||||
target_tab = tm.new_tab(empty_tab=True)
|
||||
else:
|
||||
target_tab = tm.tab_at_location(target_tab_id) or tm.new_tab(empty_tab=True)
|
||||
else:
|
||||
for tab in self.all_tabs:
|
||||
if tab.id == target_tab_id:
|
||||
@@ -1766,8 +1769,9 @@ class Boss:
|
||||
'''
|
||||
if not args or args[0] == 'new':
|
||||
return self._move_window_to(target_os_window_id='new')
|
||||
if args[0] == 'new-tab':
|
||||
return self._move_window_to(target_tab_id='new')
|
||||
if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right'):
|
||||
where = 'new' if args[0] == 'new-tab' else args[0][4:]
|
||||
return self._move_window_to(target_tab_id=where)
|
||||
title = 'Choose a tab to move the window to'
|
||||
lines = [title, '']
|
||||
fmt = ': {1}'
|
||||
|
||||
39
kitty/cli.py
39
kitty/cli.py
@@ -12,7 +12,7 @@ from typing import (
|
||||
|
||||
from .cli_stub import CLIOptions
|
||||
from .conf.utils import resolve_config
|
||||
from .constants import appname, defconf, is_macos, str_version
|
||||
from .constants import appname, defconf, is_macos, str_version, website_url
|
||||
from .options.types import Options as KittyOpts
|
||||
from .typing import BadLineType, TypedDict
|
||||
|
||||
@@ -57,42 +57,60 @@ def surround(x: str, start: int, end: int) -> str:
|
||||
return x
|
||||
|
||||
|
||||
role_map: Dict[str, Callable[[str], str]] = {}
|
||||
|
||||
|
||||
def role(func: Callable[[str], str]) -> Callable[[str], str]:
|
||||
role_map[func.__name__] = func
|
||||
return func
|
||||
|
||||
|
||||
@role
|
||||
def emph(x: str) -> str:
|
||||
return surround(x, 91, 39)
|
||||
|
||||
|
||||
@role
|
||||
def cyan(x: str) -> str:
|
||||
return surround(x, 96, 39)
|
||||
|
||||
|
||||
@role
|
||||
def green(x: str) -> str:
|
||||
return surround(x, 32, 39)
|
||||
|
||||
|
||||
@role
|
||||
def blue(x: str) -> str:
|
||||
return surround(x, 34, 39)
|
||||
|
||||
|
||||
@role
|
||||
def yellow(x: str) -> str:
|
||||
return surround(x, 93, 39)
|
||||
|
||||
|
||||
@role
|
||||
def italic(x: str) -> str:
|
||||
return surround(x, 3, 23)
|
||||
|
||||
|
||||
@role
|
||||
def bold(x: str) -> str:
|
||||
return surround(x, 1, 22)
|
||||
|
||||
|
||||
@role
|
||||
def title(x: str) -> str:
|
||||
return blue(bold(x))
|
||||
|
||||
|
||||
@role
|
||||
def opt(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
@role
|
||||
def option(x: str) -> str:
|
||||
idx = x.rfind('--')
|
||||
if idx < 0:
|
||||
@@ -103,24 +121,32 @@ def option(x: str) -> str:
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
@role
|
||||
def code(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
@role
|
||||
def kbd(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
@role
|
||||
def env(x: str) -> str:
|
||||
return italic(x)
|
||||
|
||||
|
||||
role_map['envvar'] = role_map['env']
|
||||
|
||||
|
||||
@role
|
||||
def file(x: str) -> str:
|
||||
return italic(x)
|
||||
|
||||
|
||||
@role
|
||||
def doc(x: str) -> str:
|
||||
return f'https://sw.kovidgoyal.net/kitty/{x}.html'
|
||||
return website_url(x)
|
||||
|
||||
|
||||
OptionSpecSeq = List[Union[str, OptionDict]]
|
||||
@@ -197,11 +223,13 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option
|
||||
|
||||
|
||||
def prettify(text: str) -> str:
|
||||
role_map = globals()
|
||||
|
||||
def identity(x: str) -> str:
|
||||
return x
|
||||
|
||||
def sub(m: Match) -> str:
|
||||
role, text = m.group(1, 2)
|
||||
return str(role_map[role](text))
|
||||
return role_map.get(role, identity)(text)
|
||||
|
||||
text = re.sub(r':([a-z]+):`([^`]+)`', sub, text)
|
||||
return text
|
||||
@@ -265,7 +293,8 @@ Run the :italic:`{appname}` terminal emulator. You can also specify the :italic:
|
||||
to run inside :italic:`{appname}` as normal arguments following the :italic:`options`.
|
||||
For example: {appname} sh -c "echo hello, world. Press ENTER to quit; read"
|
||||
|
||||
For comprehensive documentation for kitty, please see: https://sw.kovidgoyal.net/kitty/''').format(appname=appname)
|
||||
For comprehensive documentation for kitty, please see: {url}''').format(
|
||||
appname=appname, url=website_url())
|
||||
|
||||
|
||||
class PrintHelpForSeq:
|
||||
|
||||
@@ -182,6 +182,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
||||
a(' config_overrides: typing.Tuple[str, ...] = ()')
|
||||
a('')
|
||||
a(' def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:')
|
||||
if defn.has_color_table:
|
||||
a(' self.color_table = array(self.color_table.typecode, self.color_table)')
|
||||
a(' if options_dict is not None:')
|
||||
a(' for key in option_names:')
|
||||
a(' setattr(self, key, options_dict[key])')
|
||||
|
||||
@@ -12,6 +12,7 @@ from typing import (
|
||||
)
|
||||
|
||||
import kitty.conf.utils as generic_parsers
|
||||
from kitty.constants import website_url
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
Only = typing.Literal['macos', 'linux', '']
|
||||
@@ -41,15 +42,15 @@ def expand_opt_references(conf_name: str, text: str) -> str:
|
||||
|
||||
|
||||
def remove_markup(text: str) -> str:
|
||||
base = 'https://sw.kovidgoyal.net/kitty'
|
||||
ref_map = {
|
||||
'layouts': f'{website_url("overview")}#layouts',
|
||||
'sessions': f'{website_url("overview")}#layouts',
|
||||
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
|
||||
}
|
||||
|
||||
def sub(m: Match) -> str:
|
||||
if m.group(1) == 'ref':
|
||||
return {
|
||||
'layouts': f'{base}/overview/#layouts',
|
||||
'sessions': f'{base}/overview/#sessions',
|
||||
'functional': f'{base}/keyboard-protocol/#functional-key-definitions',
|
||||
}[m.group(2)]
|
||||
return ref_map[m.group(2)]
|
||||
return str(m.group(2))
|
||||
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||
|
||||
@@ -23,7 +23,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 22, 0)
|
||||
version: Version = Version(0, 22, 2)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
@@ -186,3 +186,11 @@ def read_kitty_resource(name: str) -> bytes:
|
||||
except ImportError:
|
||||
from importlib_resources import read_binary # type: ignore
|
||||
return read_binary('kitty', name)
|
||||
|
||||
|
||||
def website_url(doc_name: str = '') -> str:
|
||||
if doc_name:
|
||||
doc_name = doc_name.rstrip('/')
|
||||
if doc_name:
|
||||
doc_name += '/'
|
||||
return f'https://sw.kovidgoyal.net/kitty/{doc_name}'
|
||||
|
||||
@@ -7,12 +7,14 @@ from functools import partial
|
||||
from pprint import pformat
|
||||
from typing import Callable, Dict, Generator, Iterable, Set, Tuple
|
||||
|
||||
from kittens.tui.operations import colored, styled
|
||||
|
||||
from .cli import version
|
||||
from .conf.utils import KeyAction
|
||||
from .constants import is_macos, is_wayland
|
||||
from kittens.tui.operations import colored
|
||||
from .options.types import Options as KittyOpts, defaults
|
||||
from .options.utils import MouseMap
|
||||
from .rgb import Color, color_as_sharp
|
||||
from .types import MouseEvent, SingleKey
|
||||
from .typing import SequenceMap
|
||||
|
||||
@@ -113,6 +115,7 @@ def compare_opts(opts: KittyOpts, print: Callable) -> None:
|
||||
]
|
||||
field_len = max(map(len, changed_opts)) if changed_opts else 20
|
||||
fmt = '{{:{:d}s}}'.format(field_len)
|
||||
colors = []
|
||||
for f in changed_opts:
|
||||
val = getattr(opts, f)
|
||||
if isinstance(val, dict):
|
||||
@@ -123,7 +126,11 @@ def compare_opts(opts: KittyOpts, print: Callable) -> None:
|
||||
else:
|
||||
print(pformat(val))
|
||||
else:
|
||||
print(title(fmt.format(f)), str(getattr(opts, f)))
|
||||
val = getattr(opts, f)
|
||||
if isinstance(val, Color):
|
||||
colors.append(fmt.format(f) + ' ' + color_as_sharp(val) + ' ' + styled(' ', bg=val))
|
||||
else:
|
||||
print(fmt.format(f), str(getattr(opts, f)))
|
||||
|
||||
compare_mousemaps(opts.mousemap, default_opts.mousemap, print)
|
||||
final_, initial_ = opts.keymap, default_opts.keymap
|
||||
@@ -133,6 +140,9 @@ def compare_opts(opts: KittyOpts, print: Callable) -> None:
|
||||
final.update(final_s)
|
||||
initial.update(initial_s)
|
||||
compare_keymaps(final, initial, print)
|
||||
if colors:
|
||||
print(f'{title("Colors")}:', end='\n\t')
|
||||
print('\n\t'.join(sorted(colors)))
|
||||
|
||||
|
||||
def debug_config(opts: KittyOpts) -> str:
|
||||
|
||||
3
kitty/glfw-wrapper.c
generated
3
kitty/glfw-wrapper.c
generated
@@ -441,6 +441,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwDBusSetUserNotificationHandler_impl) = dlsym(handle, "glfwDBusSetUserNotificationHandler");
|
||||
if (glfwDBusSetUserNotificationHandler_impl == NULL) dlerror(); // clear error indicator
|
||||
|
||||
*(void **) (&glfwSetX11LaunchCommand_impl) = dlsym(handle, "glfwSetX11LaunchCommand");
|
||||
if (glfwSetX11LaunchCommand_impl == NULL) dlerror(); // clear error indicator
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
4
kitty/glfw-wrapper.h
generated
4
kitty/glfw-wrapper.h
generated
@@ -2168,4 +2168,8 @@ typedef void (*glfwDBusSetUserNotificationHandler_func)(GLFWDBusnotificationacti
|
||||
GFW_EXTERN glfwDBusSetUserNotificationHandler_func glfwDBusSetUserNotificationHandler_impl;
|
||||
#define glfwDBusSetUserNotificationHandler glfwDBusSetUserNotificationHandler_impl
|
||||
|
||||
typedef int (*glfwSetX11LaunchCommand_func)(GLFWwindow*, char**, int);
|
||||
GFW_EXTERN glfwSetX11LaunchCommand_func glfwSetX11LaunchCommand_impl;
|
||||
#define glfwSetX11LaunchCommand glfwSetX11LaunchCommand_impl
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
|
||||
@@ -622,11 +622,13 @@ native_window_handle(GLFWwindow *w) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
|
||||
int x = -1, y = -1;
|
||||
char *title, *wm_class_class, *wm_class_name;
|
||||
PyObject *load_programs = NULL, *get_window_size, *pre_show_callback;
|
||||
if (!PyArg_ParseTuple(args, "OOsss|Oii", &get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
|
||||
static const char* kwlist[] = {"get_window_size", "pre_show_callback", "title", "wm_class_name", "wm_class_class", "load_programs", "x", "y", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kw, "OOsss|Oii", (char**)kwlist,
|
||||
&get_window_size, &pre_show_callback, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
|
||||
|
||||
static bool is_first_window = true;
|
||||
if (is_first_window) {
|
||||
@@ -1431,7 +1433,7 @@ stop_main_loop(void) {
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(set_custom_cursor, METH_VARARGS),
|
||||
METHODB(create_os_window, METH_VARARGS),
|
||||
{"create_os_window", (PyCFunction)(void (*) (void))(create_os_window), METH_VARARGS | METH_KEYWORDS, NULL},
|
||||
METHODB(set_default_window_icon, METH_VARARGS),
|
||||
METHODB(get_clipboard_string, METH_NOARGS),
|
||||
METHODB(get_content_scale_for_window, METH_NOARGS),
|
||||
|
||||
@@ -1362,6 +1362,7 @@ handle_compose_command(GraphicsManager *self, bool *is_dirty, const GraphicsComm
|
||||
dest_frame->x = 0; dest_frame->y = 0; dest_frame->width = img->width; dest_frame->height = img->height;
|
||||
dest_frame->base_frame_id = 0; dest_frame->bgcolor = 0;
|
||||
*is_dirty = (g->other_frame_number - 1) == img->current_frame_index;
|
||||
if (*is_dirty) update_current_frame(self, img, &dest_data);
|
||||
}
|
||||
// }}}
|
||||
|
||||
|
||||
@@ -2410,19 +2410,23 @@ opt('shell', '.',
|
||||
long_text='''
|
||||
The shell program to execute. The default value of . means to use whatever shell
|
||||
is set as the default shell for the current user. Note that on macOS if you
|
||||
change this, you might need to add :code:`--login` to ensure that the shell
|
||||
starts in interactive mode and reads its startup rc files.
|
||||
change this, you might need to add :code:`--login` and :code:`--interactive`
|
||||
to ensure that the shell starts in interactive mode and reads its startup rc files.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('editor', '.',
|
||||
long_text='''
|
||||
The console editor to use when editing the kitty config file or similar tasks. A
|
||||
value of . means to use the environment variables VISUAL and EDITOR in that
|
||||
order. Note that this environment variable has to be set not just in your shell
|
||||
startup scripts but system-wide, otherwise kitty will not see it.
|
||||
'''
|
||||
)
|
||||
The terminal editor (such as ``vim`` or ``nano``) to use when editing the kitty
|
||||
config file or similar tasks.
|
||||
|
||||
The default value of . means to use the environment variables :envvar:`VISUAL`
|
||||
and :envvar:`EDITOR` in that order. If these variables aren't set, kitty will
|
||||
run your :opt:`shell` (``$SHELL -l -i -c env``) to see if your shell config
|
||||
files set :envvar:`VISUAL` or :envvar:`EDITOR`. If that doesn't work, kitty
|
||||
will cycle through various known editors (``vim``, ``emacs``, etc) and take the
|
||||
first one that exists on your system.
|
||||
''')
|
||||
|
||||
opt('close_on_child_death', 'no',
|
||||
option_type='to_bool', ctype='bool',
|
||||
|
||||
1
kitty/options/types.py
generated
1
kitty/options/types.py
generated
@@ -606,6 +606,7 @@ class Options:
|
||||
config_overrides: typing.Tuple[str, ...] = ()
|
||||
|
||||
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
|
||||
self.color_table = array(self.color_table.typecode, self.color_table)
|
||||
if options_dict is not None:
|
||||
for key in option_names:
|
||||
setattr(self, key, options_dict[key])
|
||||
|
||||
@@ -102,7 +102,7 @@ def goto_tab_parse(func: str, rest: str) -> FuncArgsType:
|
||||
|
||||
@func_with_args('detach_window')
|
||||
def detach_window_parse(func: str, rest: str) -> FuncArgsType:
|
||||
if rest not in ('new', 'new-tab', 'ask'):
|
||||
if rest not in ('new', 'new-tab', 'ask', 'tab-prev', 'tab-left', 'tab-right'):
|
||||
log_error('Ignoring invalid detach_window argument: {}'.format(rest))
|
||||
rest = 'new'
|
||||
return func, (rest,)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from kitty.constants import website_url
|
||||
from kitty.options.utils import parse_marker_spec
|
||||
|
||||
from .base import (
|
||||
@@ -26,8 +27,8 @@ class CreateMarker(RemoteCommand):
|
||||
short_desc = 'Create a marker that highlights specified text'
|
||||
desc = (
|
||||
'Create a marker which can highlight text in the specified window. For example: '
|
||||
'create_marker text 1 ERROR. For full details see: https://sw.kovidgoyal.net/kitty/marks.html'
|
||||
)
|
||||
'create_marker text 1 ERROR. For full details see: {}'
|
||||
).format(website_url('marks'))
|
||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Dict, Generator, List, Optional
|
||||
from typing import TYPE_CHECKING, Dict, Generator, List, Optional, Union
|
||||
|
||||
from kitty.options.utils import parse_send_text_bytes
|
||||
from kitty.key_encoding import decode_key_event_as_window_system_key
|
||||
from kitty.fast_data_types import KeyEvent as WindowSystemKeyEvent
|
||||
from kitty.key_encoding import decode_key_event_as_window_system_key
|
||||
from kitty.options.utils import parse_send_text_bytes
|
||||
|
||||
from .base import (
|
||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
|
||||
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType,
|
||||
Window
|
||||
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -139,14 +138,18 @@ Do not send text to the active window, even if it is one of the matched windows.
|
||||
raise MatchError(payload_get('match_tab'), 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
encoding, _, q = payload_get('data').partition(':')
|
||||
pdata: str = payload_get('data')
|
||||
encoding, _, q = pdata.partition(':')
|
||||
if encoding == 'text':
|
||||
data = q.encode('utf-8')
|
||||
data: Union[bytes, WindowSystemKeyEvent] = q.encode('utf-8')
|
||||
elif encoding == 'base64':
|
||||
data = base64.standard_b64decode(q)
|
||||
elif encoding == 'kitty-key':
|
||||
data = base64.standard_b64decode(q)
|
||||
data = decode_key_event_as_window_system_key(data)
|
||||
bdata = base64.standard_b64decode(q)
|
||||
candidate = decode_key_event_as_window_system_key(bdata.decode('ascii'))
|
||||
if candidate is None:
|
||||
raise ValueError(f'Could not decode window system key: {q}')
|
||||
data = candidate
|
||||
else:
|
||||
raise TypeError(f'Invalid encoding for send-text data: {encoding}')
|
||||
exclude_active = payload_get('exclude_active')
|
||||
|
||||
11
kitty/rgb.py
generated
11
kitty/rgb.py
generated
@@ -8,9 +8,9 @@ from typing import Optional, NamedTuple
|
||||
|
||||
|
||||
class Color(NamedTuple):
|
||||
red: int
|
||||
green: int
|
||||
blue: int
|
||||
red: int = 0
|
||||
green: int = 0
|
||||
blue: int = 0
|
||||
|
||||
def luminance(self) -> float:
|
||||
return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
|
||||
@@ -25,6 +25,11 @@ class Color(NamedTuple):
|
||||
def __int__(self) -> int:
|
||||
return self.red << 16 | self.green << 8 | self.blue
|
||||
|
||||
def as_bytearray(self, alpha: Optional[int] = None) -> bytearray:
|
||||
if alpha is None:
|
||||
return bytearray((self.red, self.green, self.blue))
|
||||
return bytearray((self.red, self.green, self.blue, alpha))
|
||||
|
||||
|
||||
def alpha_blend_channel(top_color: int, bottom_color: int, alpha: float) -> int:
|
||||
return int(alpha * top_color + (1 - alpha) * bottom_color)
|
||||
|
||||
@@ -293,7 +293,8 @@ class Tab: # {{{
|
||||
w = self.active_window
|
||||
if w is not None and self.resize_window_by(
|
||||
w.id, increment, is_horizontal) is not None:
|
||||
ring_bell()
|
||||
if get_options().enable_audio_bell:
|
||||
ring_bell()
|
||||
|
||||
def reset_window_sizes(self) -> None:
|
||||
'@ac:win:Reset window sizes undoing any dynamic resizing of windows'
|
||||
@@ -625,6 +626,9 @@ class TabManager: # {{{
|
||||
|
||||
@active_tab_idx.setter
|
||||
def active_tab_idx(self, val: int) -> None:
|
||||
new_active_tab_idx = max(0, min(val, len(self.tabs) - 1))
|
||||
if new_active_tab_idx == self._active_tab_idx:
|
||||
return
|
||||
try:
|
||||
old_active_tab: Optional[Tab] = self.tabs[self._active_tab_idx]
|
||||
except Exception:
|
||||
@@ -632,7 +636,7 @@ class TabManager: # {{{
|
||||
else:
|
||||
assert old_active_tab is not None
|
||||
add_active_id_to_history(self.active_tab_history, old_active_tab.id)
|
||||
self._active_tab_idx = max(0, min(val, len(self.tabs) - 1))
|
||||
self._active_tab_idx = new_active_tab_idx
|
||||
try:
|
||||
new_active_tab: Optional[Tab] = self.tabs[self._active_tab_idx]
|
||||
except Exception:
|
||||
@@ -716,6 +720,18 @@ class TabManager: # {{{
|
||||
if len(self.tabs) > 1:
|
||||
self.set_active_tab_idx((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs))
|
||||
|
||||
def tab_at_location(self, loc: str) -> Optional[Tab]:
|
||||
if loc == 'prev':
|
||||
if self.active_tab_history:
|
||||
old_active_tab_id = self.active_tab_history[-1]
|
||||
for idx, tab in enumerate(self.tabs):
|
||||
if tab.id == old_active_tab_id:
|
||||
return tab
|
||||
elif loc in ('left', 'right'):
|
||||
delta = -1 if loc == 'left' else 1
|
||||
idx = (len(self.tabs) + self.active_tab_idx + delta) % len(self.tabs)
|
||||
return self.tabs[idx]
|
||||
|
||||
def goto_tab(self, tab_num: int) -> None:
|
||||
if tab_num >= len(self.tabs):
|
||||
tab_num = max(0, len(self.tabs) - 1)
|
||||
|
||||
@@ -11,7 +11,7 @@ AddressFamily = PopenType = Socket = StartupCtx = None
|
||||
SessionTab = SessionType = LayoutType = SpecialWindowInstance = None
|
||||
MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None
|
||||
KeyEventType = ImageManagerType = KittyCommonOpts = HandlerType = None
|
||||
GRT_t = GRT_a = GRT_d = GRT_f = GRT_m = GRT_o = None
|
||||
GRT_t = GRT_a = GRT_d = GRT_f = GRT_m = GRT_o = GRT_C = None
|
||||
ScreenSize = KittensKeyActionType = MouseEvent = AbstractEventLoop = None
|
||||
TermManagerType = LoopType = Debug = GraphicsCommandType = None
|
||||
|
||||
|
||||
@@ -37,11 +37,12 @@ from .window import Window as WindowType
|
||||
EdgeLiteral = Literal['left', 'top', 'right', 'bottom']
|
||||
MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches']
|
||||
PowerlineStyle = Literal['angled', 'slanted', 'round']
|
||||
GRT_a = Literal['t', 'T', 'q', 'p', 'd', 'f', 'a']
|
||||
GRT_a = Literal['t', 'T', 'q', 'p', 'd', 'f', 'a', 'c', 'q']
|
||||
GRT_f = Literal[24, 32, 100]
|
||||
GRT_t = Literal['d', 'f', 't', 's']
|
||||
GRT_o = Literal['z']
|
||||
GRT_m = Literal[0, 1]
|
||||
GRT_C = Literal[0, 1]
|
||||
GRT_d = Literal['a', 'A', 'c', 'C', 'i', 'I', 'p', 'P', 'q', 'Q', 'x', 'X', 'y', 'Y', 'z', 'Z', 'f', 'F']
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@ from typing import Dict, NamedTuple, Optional
|
||||
from urllib.request import urlopen
|
||||
|
||||
from .config import atomic_save
|
||||
from .constants import Version, cache_dir, kitty_exe, version
|
||||
from .constants import Version, cache_dir, kitty_exe, version, website_url
|
||||
from .fast_data_types import add_timer, get_boss, monitor_pid
|
||||
from .notify import notify
|
||||
from .utils import log_error, open_url
|
||||
|
||||
CHANGELOG_URL = 'https://sw.kovidgoyal.net/kitty/changelog.html'
|
||||
RELEASED_VERSION_URL = 'https://sw.kovidgoyal.net/kitty/current-version.txt'
|
||||
CHANGELOG_URL = website_url('changelog')
|
||||
RELEASED_VERSION_URL = website_url() + 'current-version.txt'
|
||||
CHECK_INTERVAL = 24 * 60 * 60.
|
||||
|
||||
|
||||
@@ -104,8 +104,8 @@ def process_current_release(raw: str) -> None:
|
||||
|
||||
|
||||
def run_worker() -> None:
|
||||
import time
|
||||
import random
|
||||
import time
|
||||
time.sleep(random.randint(1000, 4000) / 1000)
|
||||
with suppress(BrokenPipeError): # happens if parent process is killed before us
|
||||
print(get_released_version())
|
||||
|
||||
@@ -623,8 +623,12 @@ def read_shell_environment(opts: Optional[Options] = None) -> Dict[str, str]:
|
||||
shell = resolved_shell(opts)
|
||||
master, slave = openpty()
|
||||
remove_blocking(master)
|
||||
if '-l' not in shell and '--login' not in shell:
|
||||
shell += ['-l']
|
||||
if '-i' not in shell and '--interactive' not in shell:
|
||||
shell += ['-i']
|
||||
try:
|
||||
p = subprocess.Popen(shell + ['-l', '-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
|
||||
p = subprocess.Popen(shell + ['-c', 'env'], stdout=slave, stdin=slave, stderr=slave, start_new_session=True, close_fds=True)
|
||||
except FileNotFoundError:
|
||||
log_error('Could not find shell to read environment')
|
||||
return ans
|
||||
|
||||
@@ -328,7 +328,7 @@ class GitHub(Base): # {{{
|
||||
'target_commitish': 'master',
|
||||
'name': 'version %s' % self.version,
|
||||
'body': f'Release version {self.version}.'
|
||||
' For changelog, see https://sw.kovidgoyal.net/kitty/changelog.html'
|
||||
' For changelog, see https://sw.kovidgoyal.net/kitty/changelog/'
|
||||
' GPG key used for signing tarballs is: https://calibre-ebook.com/signatures/kovid.gpg',
|
||||
'draft': False,
|
||||
'prerelease': False
|
||||
|
||||
@@ -48,7 +48,7 @@ mkShell rec {
|
||||
shellHook = if stdenv.isDarwin then ''
|
||||
export KITTY_NO_LTO=
|
||||
'' else ''
|
||||
export KITTY_EGL_LIBRARY='${stdenv.lib.getLib libGL}/lib/libEGL.so.1'
|
||||
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'
|
||||
'';
|
||||
|
||||
Reference in New Issue
Block a user