Compare commits

...

45 Commits

Author SHA1 Message Date
Kovid Goyal
13e59df1f8 version 0.22.2 2021-08-02 08:20:33 +05:30
Kovid Goyal
c797944923 Also set first underviced render frame request 2021-08-01 12:59:22 +05:30
Kovid Goyal
ade4e67b51 Possible fix for #3890
Try to ensure we have a functioning displaylink always.
GLFW skips over sleeping monitors during a poll and also
had a bug where the display link was not re-created for a monitor
that already had a glfw monitor entry.

Also add a bunch more debug reporting
2021-08-01 12:53:35 +05:30
Kovid Goyal
656f49f2ff ... 2021-08-01 07:56:55 +05:30
Kovid Goyal
0045244295 All option instances must not share the same color table 2021-07-31 18:44:39 +05:30
Kovid Goyal
6bfb704f6f When going from csd->no csd increase window height to compensate for titlebar.
Sway will send the next configure event (for example when focus changes)
with titlebar height added, so to avoid a sudden resize at that time,
bump the height by the titlebar.
2021-07-31 14:56:45 +05:30
Kovid Goyal
80cebdefcd Better fix for CSD toggling on Wayland
Draw CSD if the compositor wants us to. See #3888
2021-07-31 14:15:38 +05:30
Kovid Goyal
68cd863fe0 version 0.22.1 2021-07-31 12:03:42 +05:30
Kovid Goyal
65e5015d19 Allow linking to discussions in docs 2021-07-31 11:51:03 +05:30
Kovid Goyal
c26665ec4d Wayland: If the compositor turns off server side decorations after turning them on do not draw client side decorations
This matches the behavior of GNOME based clients and in any case sway
seems to get very confused if CSD is drawn.

Fixes #3888
2021-07-31 11:41:27 +05:30
Kovid Goyal
91d82ebf4b Remove unused import 2021-07-31 11:27:29 +05:30
Kovid Goyal
303e4baa58 Add GLFW API to set WM_COMMAND
Also have create_os_window take keyword arguments
2021-07-30 08:35:40 +05:30
Kovid Goyal
3c953d47ca Merge branch 'pr-fix-nix' of https://github.com/egnor/kitty 2021-07-29 14:23:08 +05:30
Kovid Goyal
5e629d5c09 Linux binary: Remove any RPATH build artifacts from bundled libraries
Thanks to Dan Egnor for pointing these out.
2021-07-29 13:12:49 +05:30
Kovid Goyal
c6ec2d4282 Update docs to reflect the fact that the shell is run with both --login and --interactive to read its env vars 2021-07-29 12:40:19 +05:30
Kovid Goyal
66b93a9af8 Merge branch 'pr-doc-editor-fix' of https://github.com/egnor/kitty 2021-07-29 12:24:17 +05:30
egnor
8d5479f55e minor tweaks to wording and formatting 2021-07-28 23:50:11 -07:00
egnor
96ce6c9504 Resolve some duplication in glossary.rst 2021-07-28 23:47:55 -07:00
egnor
0d55865545 more accurately describe editor behavior 2021-07-28 23:44:09 -07:00
Kovid Goyal
a1fc383e6f Cleanup the previous PR 2021-07-29 11:52:14 +05:30
Kovid Goyal
c3807e175d Merge branch 'pr-doc-editor' of https://github.com/egnor/kitty 2021-07-29 11:48:34 +05:30
Kovid Goyal
6f83f76d41 Make reading shell environment a bit more robust
Now ensure that --login flag is not duplicated. Also
run the shell in --interactive as well as --login mode.
2021-07-29 11:12:09 +05:30
egnor
9a56d619af clarify the text for the "editor" config variable slightly 2021-07-28 22:18:03 -07:00
Kovid Goyal
f692d586f7 Fix type checking in send_text 2021-07-29 09:04:07 +05:30
Kovid Goyal
d3d3e99979 broadcast kitten: Fix decoding of key events
No clue why mypy didnt catch this error
2021-07-29 08:55:34 +05:30
egnor
8e6a179efe fix shell.nix error with stdenv.lib.getLib 2021-07-28 17:16:52 -07:00
Kovid Goyal
17ff317d30 Allow specifying additional targets for detach_window 2021-07-28 21:50:09 +05:30
Kovid Goyal
faef9f0049 ... 2021-07-28 21:27:43 +05:30
Kovid Goyal
bb0c831601 Fix #3878 2021-07-28 18:22:08 +05:30
Kovid Goyal
9462654738 Update changelog 2021-07-28 15:52:54 +05:30
Kovid Goyal
e872169e4c Graphics protocol: Fix composing onto currently displayed frame not updating the frame on the GPU. Fixes #3874 2021-07-28 15:46:50 +05:30
Kovid Goyal
a22404abe6 Better names 2021-07-28 15:44:17 +05:30
Kovid Goyal
7c06313750 A couple more useful aliases 2021-07-28 15:40:48 +05:30
Kovid Goyal
6f265f448d DRYer 2021-07-28 15:39:21 +05:30
Kovid Goyal
acdc41bd03 Allow sending no payload 2021-07-28 13:39:15 +05:30
Kovid Goyal
bcff2a7fb6 Add aliases to GraphicsCommand to make it more useable 2021-07-28 13:28:21 +05:30
Kovid Goyal
94188fddce pep8-ify sample code 2021-07-28 11:13:29 +05:30
Kovid Goyal
3684e7f54a Merge branch 'cocoa-fix-m1' of https://github.com/horta/kitty 2021-07-28 07:45:50 +05:30
Danilo Horta
890181172f Fix "Cocoa: Failed to find service port...". 2021-07-28 00:39:50 +01:00
Kovid Goyal
9c9d68561e When calling the active tab idx setter do not re-record the current tab in the active tab history if the current tab will not actually change. Fixes #3871 2021-07-27 09:12:35 +05:30
Kovid Goyal
1bd39ff935 Use new URL scheme and define the kitty website centrally as much as possible 2021-07-27 09:03:11 +05:30
Kovid Goyal
17f3d2d581 DRYer 2021-07-27 08:44:50 +05:30
Kovid Goyal
55aa9e11db Dont use globals for role mapping 2021-07-27 08:39:04 +05:30
Kovid Goyal
bc895eacc5 Also prevent unknown roles from causing formatting to error 2021-07-27 08:07:11 +05:30
Kovid Goyal
6c8eb4a19a Fix #3869 2021-07-27 08:05:55 +05:30
45 changed files with 452 additions and 167 deletions

View File

@@ -22,7 +22,7 @@ If applicable, add screenshots to help explain your problem.
**Environment details** **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. configuration to the clipboard and paste it here.
On older versions of kitty, run kitty --debug-config instead On older versions of kitty, run kitty --debug-config instead

View File

@@ -1 +1 @@
See https://sw.kovidgoyal.net/kitty/changelog.html See https://sw.kovidgoyal.net/kitty/changelog/

View File

@@ -10,9 +10,9 @@ config to reproduce the issue with).
### Contributing code ### 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 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 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 beforehand to discuss it, so that you don't waste your time making a pull

View File

@@ -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/>

View File

@@ -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"] 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 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] https://www.reddit.com/r/KittyTerminal[Reddit community]

View File

@@ -88,6 +88,8 @@ def copy_libs(env):
for x in binary_includes(): for x in binary_includes():
dest = env.bin_dir if '/bin/' in x else env.lib_dir dest = env.bin_dir if '/bin/' in x else env.lib_dir
shutil.copy2(x, dest) shutil.copy2(x, dest)
dest = os.path.join(dest, os.path.basename(x))
subprocess.check_call(['chrpath', '-d', dest])
def copy_python(env): def copy_python(env):

View File

@@ -83,6 +83,10 @@ move it to another tab or another OS window::
map ctrl+f2 detach_window map ctrl+f2 detach_window
# moves the window into a new Tab # moves the window into a new Tab
map ctrl+f3 detach_window 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 # asks which tab to move the window into
map ctrl+f4 detach_window ask map ctrl+f4 detach_window ask

View File

@@ -4,6 +4,40 @@ Changelog
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal. |kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
To update |kitty|, :doc:`follow the instructions <binary>`. To update |kitty|, :doc:`follow the instructions <binary>`.
0.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] 0.22.0 [2021-07-26]
---------------------- ----------------------

View File

@@ -29,7 +29,7 @@ if kitty_src not in sys.path:
sys.path.insert(0, kitty_src) sys.path.insert(0, kitty_src)
from kitty.conf.types import Definition # noqa from kitty.conf.types import Definition # noqa
from kitty.constants import str_version # noqa from kitty.constants import str_version, website_url # noqa
# config {{{ # config {{{
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
@@ -65,7 +65,7 @@ extensions = [
] ]
# URL for OpenGraph tags # 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. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ['_templates']
@@ -516,4 +516,5 @@ def setup(app: Any) -> None:
app.add_role('link', link_role) app.add_role('link', link_role)
app.add_role('iss', partial(num_role, 'issues')) app.add_role('iss', partial(num_role, 'issues'))
app.add_role('pull', partial(num_role, 'pull')) app.add_role('pull', partial(num_role, 'pull'))
app.add_role('disc', partial(num_role, 'discussions'))
app.add_role('commit', commit_role) app.add_role('commit', commit_role)

View File

@@ -53,6 +53,17 @@ Variables that influence kitty behavior
directory lookup mechanism see, :option:`kitty --config`. 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 Variables that kitty sets when running child programs
.. envvar:: KITTY_WINDOW_ID .. envvar:: KITTY_WINDOW_ID

View File

@@ -97,32 +97,36 @@ features of the graphics protocol:
.. code-block:: python .. code-block:: python
import sys import sys
from base64 import standard_b64encode 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): def serialize_gr_command(**cmd):
data = standard_b64encode(cmd.pop('data')) payload = cmd.pop('payload', None)
while data: cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
chunk, data = data[:4096], data[4096:] ans = []
m = 1 if data else 0 w = ans.append
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m, **cmd)) w(b'\033_G'), w(cmd.encode('ascii'))
sys.stdout.flush() if payload:
cmd.clear() 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 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 Key Value Default Description
======= ==================== ========= ================= ======= ==================== ========= =================
``a`` Single character. ``t`` The overall action this graphics command is performing. ``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, ``p, q, t, T)`` ``q`` - query terminal, ``p`` - put (display) previous transmitted image,
``d`` - delete image, ``f`` - transmit data for animation frames, ``d`` - delete image, ``f`` - transmit data for animation frames,
``a`` - control animation, ``c`` - compose animation frames ``a`` - control animation, ``c`` - compose animation frames
@@ -753,7 +757,7 @@ Key Value Default Description
**Keys for animation frame composition** **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. ``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 ``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 ``y`` Positive integer ``0`` The top edge (in pixels) of the destination rectangle

View File

@@ -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 |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 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 :envvar:`EDITOR`. As you make changes to the file, they are automatically
to the remote computer. Note that this happens without needing to install *any* transferred to the remote computer. Note that this happens without needing
special software on the server, beyond ``ls`` that supports hyperlinks. to install *any* special software on the server, beyond ``ls`` that supports
hyperlinks.
.. versionadded:: 0.19.0 .. versionadded:: 0.19.0

View File

@@ -41,8 +41,22 @@
// Get the name of the specified display, or NULL // 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_iterator_t it;
io_service_t service; io_service_t service;
CFDictionaryRef info; CFDictionaryRef info;
@@ -89,7 +103,7 @@ static char* getDisplayName(CGDirectDisplayID displayID)
if (!service) if (!service)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _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; return NULL;
} }
@@ -314,11 +328,9 @@ void _glfwClearDisplayLinks() {
if (_glfw.ns.displayLinks.entries[i].displayLink) { if (_glfw.ns.displayLinks.entries[i].displayLink) {
CVDisplayLinkStop(_glfw.ns.displayLinks.entries[i].displayLink); CVDisplayLinkStop(_glfw.ns.displayLinks.entries[i].displayLink);
CVDisplayLinkRelease(_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; _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); CVDisplayLinkSetOutputCallback(entry->displayLink, &displayLinkCallback, (void*)(uintptr_t)entry->displayID);
} }
static void _GLFWDisplayLinkNS*
createDisplayLink(CGDirectDisplayID displayID) { _glfw_create_display_link(CGDirectDisplayID displayID) {
if (_glfw.ns.displayLinks.count >= sizeof(_glfw.ns.displayLinks.entries)/sizeof(_glfw.ns.displayLinks.entries[0]) - 1) return; 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++) { 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++]; _GLFWDisplayLinkNS *entry = &_glfw.ns.displayLinks.entries[_glfw.ns.displayLinks.count++];
memset(entry, 0, sizeof(_GLFWDisplayLinkNS)); memset(entry, 0, sizeof(_GLFWDisplayLinkNS));
entry->displayID = displayID; entry->displayID = displayID;
_glfw_create_cv_display_link(entry); _glfw_create_cv_display_link(entry);
return entry;
} }
// Poll for changes in the set of connected monitors // Poll for changes in the set of connected monitors
@@ -357,11 +374,13 @@ createDisplayLink(CGDirectDisplayID displayID) {
void _glfwPollMonitorsNS(void) void _glfwPollMonitorsNS(void)
{ {
uint32_t displayCount; uint32_t displayCount;
CGGetOnlineDisplayList(0, NULL, &displayCount); CGGetOnlineDisplayList(0, NULL, &displayCount);
CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID)); CGDirectDisplayID* displays = calloc(displayCount, sizeof(CGDirectDisplayID));
CGGetOnlineDisplayList(displayCount, displays, &displayCount); CGGetOnlineDisplayList(displayCount, displays, &displayCount);
_glfwClearDisplayLinks(); _glfwClearDisplayLinks();
if (_glfw.hints.init.debugRendering) {
fprintf(stderr, "Polling for monitors: %u found\n", displayCount);
}
for (int i = 0; i < _glfw.monitorCount; i++) for (int i = 0; i < _glfw.monitorCount; i++)
_glfw.monitors[i]->ns.screen = nil; _glfw.monitors[i]->ns.screen = nil;
@@ -378,32 +397,57 @@ void _glfwPollMonitorsNS(void)
for (uint32_t i = 0; i < displayCount; i++) 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; 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 // HACK: Compare unit numbers instead of display IDs to work around
// display replacement on machines with automatic graphics // display replacement on machines with automatic graphics
// switching // switching
const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]); uint32_t j;
for (j = 0; j < disconnectedCount; j++)
for (uint32_t j = 0; j < disconnectedCount; j++)
{ {
if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber) 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; disconnected[j] = NULL;
break; break;
} }
} }
if (j < disconnectedCount)
continue;
const CGSize size = CGDisplayScreenSize(displays[i]); const CGSize size = CGDisplayScreenSize(displays[i]);
char* name = getDisplayName(displays[i]); char* name = getDisplayName(displays[i], screen);
if (!name) if (!name) {
name = _glfw_strdup("Unknown"); _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); _GLFWmonitor* monitor = _glfwAllocMonitor(name, (int)size.width, (int)size.height);
monitor->ns.displayID = displays[i]; monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber; monitor->ns.unitNumber = unitNumber;
createDisplayLink(monitor->ns.displayID); monitor->ns.screen = screen;
_glfw_create_display_link(monitor->ns.displayID);
free(name); free(name);
@@ -554,15 +598,16 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
const GLFWvidmode mode = const GLFWvidmode mode =
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate); 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) if (_glfwCompareVideoModes(result + j, &mode) == 0)
break; break;
} }
// Skip duplicate modes // Skip duplicate modes
if (i < *count) if (j < *count)
continue; continue;
(*count)++; (*count)++;

View File

@@ -250,3 +250,4 @@ void _glfwDispatchRenderFrame(CGDirectDisplayID);
void _glfwShutdownCVDisplayLink(unsigned long long, void*); void _glfwShutdownCVDisplayLink(unsigned long long, void*);
void _glfwCocoaPostEmptyEvent(void); void _glfwCocoaPostEmptyEvent(void);
void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry); void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry);
_GLFWDisplayLinkNS* _glfw_create_display_link(CGDirectDisplayID);

View File

@@ -337,9 +337,12 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL); display_link_shutdown_timer = _glfwPlatformAddTimer(DISPLAY_LINK_SHUTDOWN_CHECK_INTERVAL, false, _glfwShutdownCVDisplayLink, NULL, NULL);
} }
monotonic_t now = glfwGetTime(); monotonic_t now = glfwGetTime();
bool found_display_link = false;
_GLFWDisplayLinkNS *dl = NULL;
for (size_t i = 0; i < _glfw.ns.displayLinks.count; i++) { 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) { if (dl->displayID == displayID) {
found_display_link = true;
dl->lastRenderFrameRequestedAt = now; dl->lastRenderFrameRequestedAt = now;
if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now; if (!dl->first_unserviced_render_frame_request_at) dl->first_unserviced_render_frame_request_at = now;
if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink); if (!CVDisplayLinkIsRunning(dl->displayLink)) CVDisplayLinkStart(dl->displayLink);
@@ -359,6 +362,14 @@ requestRenderFrame(_GLFWwindow *w, GLFWcocoarenderframefun callback) {
dl->first_unserviced_render_frame_request_at = 0; 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 void

View File

@@ -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, \ 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) const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
'''.splitlines(): '''.splitlines():
if line: if line:
functions.append(Function(line.strip(), check_fail=False)) functions.append(Function(line.strip(), check_fail=False))

1
glfw/wl_init.c vendored
View File

@@ -29,7 +29,6 @@
#define _GNU_SOURCE #define _GNU_SOURCE
#include "internal.h" #include "internal.h"
#include "backend_utils.h" #include "backend_utils.h"
#include "wl_client_side_decorations.h"
#include "linux_desktop_settings.h" #include "linux_desktop_settings.h"
#include "../kitty/monotonic.h" #include "../kitty/monotonic.h"

42
glfw/wl_window.c vendored
View File

@@ -254,15 +254,39 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
_glfwInputWindowDamage(window); _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, struct zxdg_toplevel_decoration_v1* decoration UNUSED,
uint32_t mode) uint32_t mode)
{ {
_GLFWwindow* window = data; _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); 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 = { static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
@@ -396,14 +420,6 @@ _glfwPlatformToggleFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) {
return !already_fullscreen; 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 static void
xdgToplevelHandleConfigure(void* data, xdgToplevelHandleConfigure(void* data,
struct xdg_toplevel* toplevel UNUSED, struct xdg_toplevel* toplevel UNUSED,
@@ -488,7 +504,8 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
xdgSurfaceHandleConfigure xdgSurfaceHandleConfigure
}; };
static void setXdgDecorations(_GLFWwindow* window) static void
setXdgDecorations(_GLFWwindow* window)
{ {
if (_glfw.wl.decorationManager) 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.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
window->wl.surface); window->wl.surface);

7
glfw/x11_window.c vendored
View File

@@ -3122,3 +3122,10 @@ GLFWAPI unsigned long long glfwDBusUserNotify(const char *app_name, const char*
GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) { GLFWAPI void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler) {
glfw_dbus_set_user_notification_activated_handler(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);
}

View File

@@ -1 +1 @@
See https://sw.kovidgoyal.net/kitty/kittens/diff.html See https://sw.kovidgoyal.net/kitty/kittens/diff/

View File

@@ -16,6 +16,7 @@ from typing import (
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.cli_stub import HintsCLIOptions from kitty.cli_stub import HintsCLIOptions
from kitty.constants import website_url
from kitty.fast_data_types import set_clipboard_string from kitty.fast_data_types import set_clipboard_string
from kitty.key_encoding import KeyEvent from kitty.key_encoding import KeyEvent
from kitty.typing import BossType, KittyCommonOpts from kitty.typing import BossType, KittyCommonOpts
@@ -640,7 +641,7 @@ The foreground color for text pointed to by the hints
--customize-processing --customize-processing
Name of a python file in the kitty config directory which will be imported to provide Name of a python file in the kitty config directory which will be imported to provide
custom implementations for pattern finding and performing actions 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. 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. the type of text being hinted.
'''.format( '''.format(
default_regex=DEFAULT_REGEX, default_regex=DEFAULT_REGEX,
line='{{line}}', path='{{path}}' line='{{line}}', path='{{path}}',
hints_url=website_url('kittens/hints'),
).format ).format
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.' help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
usage = '' usage = ''

View File

@@ -11,14 +11,15 @@ from contextlib import suppress
from enum import IntEnum from enum import IntEnum
from itertools import count from itertools import count
from typing import ( from typing import (
Any, Callable, DefaultDict, Deque, Dict, Iterator, List, Optional, Any, Callable, ClassVar, DefaultDict, Deque, Dict, Generic, Iterator, List,
Sequence, Tuple, Union Optional, Sequence, Tuple, Type, TypeVar, Union, cast
) )
from kitty.conf.utils import positive_float, positive_int from kitty.conf.utils import positive_float, positive_int
from kitty.fast_data_types import create_canvas from kitty.fast_data_types import create_canvas
from kitty.typing import ( 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 from kitty.utils import ScreenSize, find_exe, fit_image
@@ -298,49 +299,75 @@ def can_display_images() -> bool:
ImageKey = Tuple[str, int, int] ImageKey = Tuple[str, int, int]
SentImageKey = Tuple[int, 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: class GraphicsCommand:
a: GRT_a = 't' # action a = action = Alias(cast(GRT_a, 't'))
q: int = 0 # suppress responses q = quiet = Alias(0)
f: GRT_f = 32 # image data format f = format = Alias(32)
t: GRT_t = 'd' # transmission medium t = transmission_type = Alias(cast(GRT_t, 'd'))
s: int = 0 # sent image width s = data_width = animation_state = Alias(0)
v: int = 0 # sent image height v = data_height = loop_count = Alias(0)
S: int = 0 # size of data to read from file S = data_size = Alias(0)
O: int = 0 # offset of data to read from file O = data_offset = Alias(0) # noqa
i: int = 0 # image id i = image_id = Alias(0)
I: int = 0 # image number I = image_number = Alias(0) # noqa
p: int = 0 # placement id p = placement_id = Alias(0)
o: Optional[GRT_o] = None # type of compression o = compression = Alias(cast(Optional[GRT_o], None))
m: GRT_m = 0 # 0 or 1 whether there is more chunked data m = more = Alias(cast(GRT_m, 0))
x: int = 0 # left edge of image area to display x = left_edge = Alias(0)
y: int = 0 # top edge of image area to display y = top_edge = Alias(0)
w: int = 0 # image width to display w = width = Alias(0)
h: int = 0 # image height to display h = height = Alias(0)
X: int = 0 # X-offset within cell X = cell_x_offset = blend_mode = Alias(0)
Y: int = 0 # Y-offset within cell Y = cell_y_offset = bgcolor = Alias(0)
c: int = 0 # number of cols to display image over c = columns = other_frame_number = dest_frame = Alias(0)
r: int = 0 # number of rows to display image over r = rows = frame_number = source_frame = Alias(0)
z: int = 0 # z-index z = z_index = gap = Alias(0)
d: GRT_d = 'a' # what to delete 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: def __repr__(self) -> str:
return self.serialize().decode('ascii').replace('\033', '^]') return self.serialize().decode('ascii').replace('\033', '^]')
def clone(self) -> 'GraphicsCommand': def clone(self) -> 'GraphicsCommand':
ans = GraphicsCommand() ans = GraphicsCommand()
for k in GraphicsCommand.__annotations__: ans._actual_values = self._actual_values.copy()
setattr(ans, k, getattr(self, k))
return ans return ans
def serialize(self, payload: Union[bytes, str] = b'') -> bytes: def serialize(self, payload: Union[bytes, str] = b'') -> bytes:
items = [] items = []
for k in GraphicsCommand.__annotations__: for k, val in self._actual_values.items():
val: Union[str, None, int] = getattr(self, k) items.append('{}={}'.format(k, val))
defval: Union[str, None, int] = getattr(GraphicsCommand, k)
if val != defval and val is not None:
items.append('{}={}'.format(k, val))
ans: List[bytes] = [] ans: List[bytes] = []
w = ans.append w = ans.append
@@ -355,9 +382,27 @@ class GraphicsCommand:
return b''.join(ans) return b''.join(ans)
def clear(self) -> None: def clear(self) -> None:
for k in GraphicsCommand.__annotations__: self._actual_values = {}
defval: Union[str, None, int] = getattr(GraphicsCommand, k)
setattr(self, k, defval) 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: class Placement:

View File

@@ -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 .config import common_opts_as_dict, prepare_config_file_for_editing
from .constants import ( from .constants import (
appname, config_dir, is_macos, is_wayland, kitty_exe, appname, config_dir, is_macos, is_wayland, kitty_exe,
supports_primary_selection supports_primary_selection, website_url
) )
from .fast_data_types import ( from .fast_data_types import (
CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, CLOSE_BEING_CONFIRMED, IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED,
@@ -1141,7 +1141,7 @@ class Boss:
self._run_kitten('ask', [ self._run_kitten('ask', [
'--name=create-marker', '--message', '--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) custom_callback=done, action_on_removal=done2)
@@ -1691,14 +1691,17 @@ class Boss:
target_tab = tm.new_tab(empty_tab=True) target_tab = tm.new_tab(empty_tab=True)
else: else:
target_os_window_id = target_os_window_id or current_os_window() 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): if not isinstance(target_os_window_id, int):
q = self.active_tab_manager q = self.active_tab_manager
assert q is not None assert q is not None
tm = q tm = q
else: else:
tm = self.os_window_map[target_os_window_id] 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: else:
for tab in self.all_tabs: for tab in self.all_tabs:
if tab.id == target_tab_id: if tab.id == target_tab_id:
@@ -1766,8 +1769,9 @@ class Boss:
''' '''
if not args or args[0] == 'new': if not args or args[0] == 'new':
return self._move_window_to(target_os_window_id='new') return self._move_window_to(target_os_window_id='new')
if args[0] == 'new-tab': if args[0] in ('new-tab', 'tab-prev', 'tab-left', 'tab-right'):
return self._move_window_to(target_tab_id='new') 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' title = 'Choose a tab to move the window to'
lines = [title, ''] lines = [title, '']
fmt = ': {1}' fmt = ': {1}'

View File

@@ -12,7 +12,7 @@ from typing import (
from .cli_stub import CLIOptions from .cli_stub import CLIOptions
from .conf.utils import resolve_config 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 .options.types import Options as KittyOpts
from .typing import BadLineType, TypedDict from .typing import BadLineType, TypedDict
@@ -57,42 +57,60 @@ def surround(x: str, start: int, end: int) -> str:
return x 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: def emph(x: str) -> str:
return surround(x, 91, 39) return surround(x, 91, 39)
@role
def cyan(x: str) -> str: def cyan(x: str) -> str:
return surround(x, 96, 39) return surround(x, 96, 39)
@role
def green(x: str) -> str: def green(x: str) -> str:
return surround(x, 32, 39) return surround(x, 32, 39)
@role
def blue(x: str) -> str: def blue(x: str) -> str:
return surround(x, 34, 39) return surround(x, 34, 39)
@role
def yellow(x: str) -> str: def yellow(x: str) -> str:
return surround(x, 93, 39) return surround(x, 93, 39)
@role
def italic(x: str) -> str: def italic(x: str) -> str:
return surround(x, 3, 23) return surround(x, 3, 23)
@role
def bold(x: str) -> str: def bold(x: str) -> str:
return surround(x, 1, 22) return surround(x, 1, 22)
@role
def title(x: str) -> str: def title(x: str) -> str:
return blue(bold(x)) return blue(bold(x))
@role
def opt(text: str) -> str: def opt(text: str) -> str:
return text return text
@role
def option(x: str) -> str: def option(x: str) -> str:
idx = x.rfind('--') idx = x.rfind('--')
if idx < 0: if idx < 0:
@@ -103,24 +121,32 @@ def option(x: str) -> str:
return ' '.join(parts) return ' '.join(parts)
@role
def code(x: str) -> str: def code(x: str) -> str:
return x return x
@role
def kbd(x: str) -> str: def kbd(x: str) -> str:
return x return x
@role
def env(x: str) -> str: def env(x: str) -> str:
return italic(x) return italic(x)
role_map['envvar'] = role_map['env']
@role
def file(x: str) -> str: def file(x: str) -> str:
return italic(x) return italic(x)
@role
def doc(x: str) -> str: def doc(x: str) -> str:
return f'https://sw.kovidgoyal.net/kitty/{x}.html' return website_url(x)
OptionSpecSeq = List[Union[str, OptionDict]] 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: def prettify(text: str) -> str:
role_map = globals()
def identity(x: str) -> str:
return x
def sub(m: Match) -> str: def sub(m: Match) -> str:
role, text = m.group(1, 2) 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) text = re.sub(r':([a-z]+):`([^`]+)`', sub, text)
return 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`. 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 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: class PrintHelpForSeq:

View File

@@ -182,6 +182,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
a(' config_overrides: typing.Tuple[str, ...] = ()') a(' config_overrides: typing.Tuple[str, ...] = ()')
a('') a('')
a(' def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:') 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(' if options_dict is not None:')
a(' for key in option_names:') a(' for key in option_names:')
a(' setattr(self, key, options_dict[key])') a(' setattr(self, key, options_dict[key])')

View File

@@ -12,6 +12,7 @@ from typing import (
) )
import kitty.conf.utils as generic_parsers import kitty.conf.utils as generic_parsers
from kitty.constants import website_url
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
Only = typing.Literal['macos', 'linux', ''] 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: 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: def sub(m: Match) -> str:
if m.group(1) == 'ref': if m.group(1) == 'ref':
return { return ref_map[m.group(2)]
'layouts': f'{base}/overview/#layouts',
'sessions': f'{base}/overview/#sessions',
'functional': f'{base}/keyboard-protocol/#functional-key-definitions',
}[m.group(2)]
return str(m.group(2)) return str(m.group(2))
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL) return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)

View File

@@ -23,7 +23,7 @@ class Version(NamedTuple):
appname: str = 'kitty' appname: str = 'kitty'
kitty_face = '🐱' kitty_face = '🐱'
version: Version = Version(0, 22, 0) version: Version = Version(0, 22, 2)
str_version: str = '.'.join(map(str, version)) str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat is_macos: bool = 'darwin' in _plat
@@ -186,3 +186,11 @@ def read_kitty_resource(name: str) -> bytes:
except ImportError: except ImportError:
from importlib_resources import read_binary # type: ignore from importlib_resources import read_binary # type: ignore
return read_binary('kitty', name) 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}'

View File

@@ -7,12 +7,14 @@ from functools import partial
from pprint import pformat from pprint import pformat
from typing import Callable, Dict, Generator, Iterable, Set, Tuple from typing import Callable, Dict, Generator, Iterable, Set, Tuple
from kittens.tui.operations import colored, styled
from .cli import version from .cli import version
from .conf.utils import KeyAction from .conf.utils import KeyAction
from .constants import is_macos, is_wayland from .constants import is_macos, is_wayland
from kittens.tui.operations import colored
from .options.types import Options as KittyOpts, defaults from .options.types import Options as KittyOpts, defaults
from .options.utils import MouseMap from .options.utils import MouseMap
from .rgb import Color, color_as_sharp
from .types import MouseEvent, SingleKey from .types import MouseEvent, SingleKey
from .typing import SequenceMap 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 field_len = max(map(len, changed_opts)) if changed_opts else 20
fmt = '{{:{:d}s}}'.format(field_len) fmt = '{{:{:d}s}}'.format(field_len)
colors = []
for f in changed_opts: for f in changed_opts:
val = getattr(opts, f) val = getattr(opts, f)
if isinstance(val, dict): if isinstance(val, dict):
@@ -123,7 +126,11 @@ def compare_opts(opts: KittyOpts, print: Callable) -> None:
else: else:
print(pformat(val)) print(pformat(val))
else: 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) compare_mousemaps(opts.mousemap, default_opts.mousemap, print)
final_, initial_ = opts.keymap, default_opts.keymap final_, initial_ = opts.keymap, default_opts.keymap
@@ -133,6 +140,9 @@ def compare_opts(opts: KittyOpts, print: Callable) -> None:
final.update(final_s) final.update(final_s)
initial.update(initial_s) initial.update(initial_s)
compare_keymaps(final, initial, print) 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: def debug_config(opts: KittyOpts) -> str:

3
kitty/glfw-wrapper.c generated
View File

@@ -441,6 +441,9 @@ load_glfw(const char* path) {
*(void **) (&glfwDBusSetUserNotificationHandler_impl) = dlsym(handle, "glfwDBusSetUserNotificationHandler"); *(void **) (&glfwDBusSetUserNotificationHandler_impl) = dlsym(handle, "glfwDBusSetUserNotificationHandler");
if (glfwDBusSetUserNotificationHandler_impl == NULL) dlerror(); // clear error indicator if (glfwDBusSetUserNotificationHandler_impl == NULL) dlerror(); // clear error indicator
*(void **) (&glfwSetX11LaunchCommand_impl) = dlsym(handle, "glfwSetX11LaunchCommand");
if (glfwSetX11LaunchCommand_impl == NULL) dlerror(); // clear error indicator
return NULL; return NULL;
} }

4
kitty/glfw-wrapper.h generated
View File

@@ -2168,4 +2168,8 @@ typedef void (*glfwDBusSetUserNotificationHandler_func)(GLFWDBusnotificationacti
GFW_EXTERN glfwDBusSetUserNotificationHandler_func glfwDBusSetUserNotificationHandler_impl; GFW_EXTERN glfwDBusSetUserNotificationHandler_func glfwDBusSetUserNotificationHandler_impl;
#define glfwDBusSetUserNotificationHandler 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); const char* load_glfw(const char* path);

View File

@@ -622,11 +622,13 @@ native_window_handle(GLFWwindow *w) {
} }
static PyObject* 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; int x = -1, y = -1;
char *title, *wm_class_class, *wm_class_name; char *title, *wm_class_class, *wm_class_name;
PyObject *load_programs = NULL, *get_window_size, *pre_show_callback; PyObject *load_programs = NULL, *get_window_size, *pre_show_callback;
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; static bool is_first_window = true;
if (is_first_window) { if (is_first_window) {
@@ -1431,7 +1433,7 @@ stop_main_loop(void) {
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
METHODB(set_custom_cursor, METH_VARARGS), 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(set_default_window_icon, METH_VARARGS),
METHODB(get_clipboard_string, METH_NOARGS), METHODB(get_clipboard_string, METH_NOARGS),
METHODB(get_content_scale_for_window, METH_NOARGS), METHODB(get_content_scale_for_window, METH_NOARGS),

View File

@@ -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->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; dest_frame->base_frame_id = 0; dest_frame->bgcolor = 0;
*is_dirty = (g->other_frame_number - 1) == img->current_frame_index; *is_dirty = (g->other_frame_number - 1) == img->current_frame_index;
if (*is_dirty) update_current_frame(self, img, &dest_data);
} }
// }}} // }}}

View File

@@ -2410,19 +2410,23 @@ opt('shell', '.',
long_text=''' long_text='''
The shell program to execute. The default value of . means to use whatever shell 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 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 change this, you might need to add :code:`--login` and :code:`--interactive`
starts in interactive mode and reads its startup rc files. to ensure that the shell starts in interactive mode and reads its startup rc files.
''' '''
) )
opt('editor', '.', opt('editor', '.',
long_text=''' long_text='''
The console editor to use when editing the kitty config file or similar tasks. A The terminal editor (such as ``vim`` or ``nano``) to use when editing the kitty
value of . means to use the environment variables VISUAL and EDITOR in that config file or similar tasks.
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 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', opt('close_on_child_death', 'no',
option_type='to_bool', ctype='bool', option_type='to_bool', ctype='bool',

View File

@@ -606,6 +606,7 @@ class Options:
config_overrides: typing.Tuple[str, ...] = () config_overrides: typing.Tuple[str, ...] = ()
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None: def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
self.color_table = array(self.color_table.typecode, self.color_table)
if options_dict is not None: if options_dict is not None:
for key in option_names: for key in option_names:
setattr(self, key, options_dict[key]) setattr(self, key, options_dict[key])

View File

@@ -102,7 +102,7 @@ def goto_tab_parse(func: str, rest: str) -> FuncArgsType:
@func_with_args('detach_window') @func_with_args('detach_window')
def detach_window_parse(func: str, rest: str) -> FuncArgsType: 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)) log_error('Ignoring invalid detach_window argument: {}'.format(rest))
rest = 'new' rest = 'new'
return func, (rest,) return func, (rest,)

View File

@@ -4,6 +4,7 @@
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from kitty.constants import website_url
from kitty.options.utils import parse_marker_spec from kitty.options.utils import parse_marker_spec
from .base import ( from .base import (
@@ -26,8 +27,8 @@ class CreateMarker(RemoteCommand):
short_desc = 'Create a marker that highlights specified text' short_desc = 'Create a marker that highlights specified text'
desc = ( desc = (
'Create a marker which can highlight text in the specified window. For example: ' '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 options_spec = MATCH_WINDOW_OPTION + '''\n
--self --self
type=bool-set type=bool-set

View File

@@ -5,16 +5,15 @@
import base64 import base64
import os import os
import sys 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.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 ( from .base import (
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError, MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, PayloadGetType, PayloadType, RCOptions, RemoteCommand, ResponseType, Window
Window
) )
if TYPE_CHECKING: 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') raise MatchError(payload_get('match_tab'), 'tabs')
for tab in tabs: for tab in tabs:
windows += tuple(tab) windows += tuple(tab)
encoding, _, q = payload_get('data').partition(':') pdata: str = payload_get('data')
encoding, _, q = pdata.partition(':')
if encoding == 'text': if encoding == 'text':
data = q.encode('utf-8') data: Union[bytes, WindowSystemKeyEvent] = q.encode('utf-8')
elif encoding == 'base64': elif encoding == 'base64':
data = base64.standard_b64decode(q) data = base64.standard_b64decode(q)
elif encoding == 'kitty-key': elif encoding == 'kitty-key':
data = base64.standard_b64decode(q) bdata = base64.standard_b64decode(q)
data = decode_key_event_as_window_system_key(data) 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: else:
raise TypeError(f'Invalid encoding for send-text data: {encoding}') raise TypeError(f'Invalid encoding for send-text data: {encoding}')
exclude_active = payload_get('exclude_active') exclude_active = payload_get('exclude_active')

11
kitty/rgb.py generated
View File

@@ -8,9 +8,9 @@ from typing import Optional, NamedTuple
class Color(NamedTuple): class Color(NamedTuple):
red: int red: int = 0
green: int green: int = 0
blue: int blue: int = 0
def luminance(self) -> float: def luminance(self) -> float:
return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue return 0.299 * self.red + 0.587 * self.green + 0.114 * self.blue
@@ -25,6 +25,11 @@ class Color(NamedTuple):
def __int__(self) -> int: def __int__(self) -> int:
return self.red << 16 | self.green << 8 | self.blue 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: def alpha_blend_channel(top_color: int, bottom_color: int, alpha: float) -> int:
return int(alpha * top_color + (1 - alpha) * bottom_color) return int(alpha * top_color + (1 - alpha) * bottom_color)

View File

@@ -293,7 +293,8 @@ class Tab: # {{{
w = self.active_window w = self.active_window
if w is not None and self.resize_window_by( if w is not None and self.resize_window_by(
w.id, increment, is_horizontal) is not None: w.id, increment, is_horizontal) is not None:
ring_bell() if get_options().enable_audio_bell:
ring_bell()
def reset_window_sizes(self) -> None: def reset_window_sizes(self) -> None:
'@ac:win:Reset window sizes undoing any dynamic resizing of windows' '@ac:win:Reset window sizes undoing any dynamic resizing of windows'
@@ -625,6 +626,9 @@ class TabManager: # {{{
@active_tab_idx.setter @active_tab_idx.setter
def active_tab_idx(self, val: int) -> None: 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: try:
old_active_tab: Optional[Tab] = self.tabs[self._active_tab_idx] old_active_tab: Optional[Tab] = self.tabs[self._active_tab_idx]
except Exception: except Exception:
@@ -632,7 +636,7 @@ class TabManager: # {{{
else: else:
assert old_active_tab is not None assert old_active_tab is not None
add_active_id_to_history(self.active_tab_history, old_active_tab.id) 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: try:
new_active_tab: Optional[Tab] = self.tabs[self._active_tab_idx] new_active_tab: Optional[Tab] = self.tabs[self._active_tab_idx]
except Exception: except Exception:
@@ -716,6 +720,18 @@ class TabManager: # {{{
if len(self.tabs) > 1: if len(self.tabs) > 1:
self.set_active_tab_idx((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs)) 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: def goto_tab(self, tab_num: int) -> None:
if tab_num >= len(self.tabs): if tab_num >= len(self.tabs):
tab_num = max(0, len(self.tabs) - 1) tab_num = max(0, len(self.tabs) - 1)

View File

@@ -11,7 +11,7 @@ AddressFamily = PopenType = Socket = StartupCtx = None
SessionTab = SessionType = LayoutType = SpecialWindowInstance = None SessionTab = SessionType = LayoutType = SpecialWindowInstance = None
MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None MarkType = RemoteCommandType = CoreTextFont = FontConfigPattern = None
KeyEventType = ImageManagerType = KittyCommonOpts = HandlerType = 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 ScreenSize = KittensKeyActionType = MouseEvent = AbstractEventLoop = None
TermManagerType = LoopType = Debug = GraphicsCommandType = None TermManagerType = LoopType = Debug = GraphicsCommandType = None

View File

@@ -37,11 +37,12 @@ from .window import Window as WindowType
EdgeLiteral = Literal['left', 'top', 'right', 'bottom'] EdgeLiteral = Literal['left', 'top', 'right', 'bottom']
MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches'] MatchType = Literal['mime', 'ext', 'protocol', 'file', 'path', 'url', 'fragment_matches']
PowerlineStyle = Literal['angled', 'slanted', 'round'] 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_f = Literal[24, 32, 100]
GRT_t = Literal['d', 'f', 't', 's'] GRT_t = Literal['d', 'f', 't', 's']
GRT_o = Literal['z'] GRT_o = Literal['z']
GRT_m = Literal[0, 1] 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'] GRT_d = Literal['a', 'A', 'c', 'C', 'i', 'I', 'p', 'P', 'q', 'Q', 'x', 'X', 'y', 'Y', 'z', 'Z', 'f', 'F']

View File

@@ -10,13 +10,13 @@ from typing import Dict, NamedTuple, Optional
from urllib.request import urlopen from urllib.request import urlopen
from .config import atomic_save 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 .fast_data_types import add_timer, get_boss, monitor_pid
from .notify import notify from .notify import notify
from .utils import log_error, open_url from .utils import log_error, open_url
CHANGELOG_URL = 'https://sw.kovidgoyal.net/kitty/changelog.html' CHANGELOG_URL = website_url('changelog')
RELEASED_VERSION_URL = 'https://sw.kovidgoyal.net/kitty/current-version.txt' RELEASED_VERSION_URL = website_url() + 'current-version.txt'
CHECK_INTERVAL = 24 * 60 * 60. CHECK_INTERVAL = 24 * 60 * 60.
@@ -104,8 +104,8 @@ def process_current_release(raw: str) -> None:
def run_worker() -> None: def run_worker() -> None:
import time
import random import random
import time
time.sleep(random.randint(1000, 4000) / 1000) time.sleep(random.randint(1000, 4000) / 1000)
with suppress(BrokenPipeError): # happens if parent process is killed before us with suppress(BrokenPipeError): # happens if parent process is killed before us
print(get_released_version()) print(get_released_version())

View File

@@ -623,8 +623,12 @@ def read_shell_environment(opts: Optional[Options] = None) -> Dict[str, str]:
shell = resolved_shell(opts) shell = resolved_shell(opts)
master, slave = openpty() master, slave = openpty()
remove_blocking(master) 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: 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: except FileNotFoundError:
log_error('Could not find shell to read environment') log_error('Could not find shell to read environment')
return ans return ans

View File

@@ -328,7 +328,7 @@ class GitHub(Base): # {{{
'target_commitish': 'master', 'target_commitish': 'master',
'name': 'version %s' % self.version, 'name': 'version %s' % self.version,
'body': f'Release version {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', ' GPG key used for signing tarballs is: https://calibre-ebook.com/signatures/kovid.gpg',
'draft': False, 'draft': False,
'prerelease': False 'prerelease': False

View File

@@ -48,7 +48,7 @@ mkShell rec {
shellHook = if stdenv.isDarwin then '' shellHook = if stdenv.isDarwin then ''
export KITTY_NO_LTO= export KITTY_NO_LTO=
'' else '' '' 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_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so' export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
''; '';