mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 14:18:26 +02:00
Make kitty --single-instance fast
No longer pay the overhead of starting the Python interpreter just to write a message to the single instance socket. This reduces the time for kitty --single-instance (for second and later instances) from 70ms to 3ms an almost 25x improvement. Needs testing on macOS and also porting of the +open handling.
This commit is contained in:
@@ -149,7 +149,6 @@ from .utils import (
|
|||||||
remove_socket_file,
|
remove_socket_file,
|
||||||
safe_print,
|
safe_print,
|
||||||
sanitize_url_for_dispay_to_user,
|
sanitize_url_for_dispay_to_user,
|
||||||
single_instance,
|
|
||||||
startup_notification_handler,
|
startup_notification_handler,
|
||||||
timed_debug_print,
|
timed_debug_print,
|
||||||
which,
|
which,
|
||||||
@@ -329,6 +328,7 @@ class Boss:
|
|||||||
args: CLIOptions,
|
args: CLIOptions,
|
||||||
cached_values: Dict[str, Any],
|
cached_values: Dict[str, Any],
|
||||||
global_shortcuts: Dict[str, SingleKey],
|
global_shortcuts: Dict[str, SingleKey],
|
||||||
|
talk_fd: int = -1,
|
||||||
):
|
):
|
||||||
set_layout_options(opts)
|
set_layout_options(opts)
|
||||||
self.clipboard = Clipboard()
|
self.clipboard = Clipboard()
|
||||||
@@ -352,9 +352,6 @@ class Boss:
|
|||||||
self.cursor_blinking = True
|
self.cursor_blinking = True
|
||||||
self.shutting_down = False
|
self.shutting_down = False
|
||||||
self.misc_config_errors: List[str] = []
|
self.misc_config_errors: List[str] = []
|
||||||
talk_fd = getattr(single_instance, 'socket', None)
|
|
||||||
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
|
|
||||||
listen_fd = -1
|
|
||||||
# we dont allow reloading the config file to change
|
# we dont allow reloading the config file to change
|
||||||
# allow_remote_control
|
# allow_remote_control
|
||||||
self.allow_remote_control = opts.allow_remote_control
|
self.allow_remote_control = opts.allow_remote_control
|
||||||
@@ -363,6 +360,7 @@ class Boss:
|
|||||||
elif self.allow_remote_control in ('n', 'no', 'false'):
|
elif self.allow_remote_control in ('n', 'no', 'false'):
|
||||||
self.allow_remote_control = 'n'
|
self.allow_remote_control = 'n'
|
||||||
self.listening_on = ''
|
self.listening_on = ''
|
||||||
|
listen_fd = -1
|
||||||
if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'):
|
if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'):
|
||||||
try:
|
try:
|
||||||
listen_fd, self.listening_on = listen_on(args.listen_on)
|
listen_fd, self.listening_on = listen_on(args.listen_on)
|
||||||
|
|||||||
13
kitty/charsets.c
generated
13
kitty/charsets.c
generated
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
|
|
||||||
|
#ifndef NO_SINGLE_BYTE_CHARSETS
|
||||||
static uint32_t charset_translations[4][256] = {
|
static uint32_t charset_translations[4][256] = {
|
||||||
/* VT100 graphics mapped to Unicode */
|
/* VT100 graphics mapped to Unicode */
|
||||||
{
|
{
|
||||||
@@ -167,7 +168,7 @@ translation_table(uint32_t which) {
|
|||||||
return charset_translations[3];
|
return charset_translations[3];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// UTF-8 decode taken from: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
// UTF-8 decode taken from: https://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||||
|
|
||||||
@@ -188,7 +189,11 @@ static const uint8_t utf8_data[] = {
|
|||||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
|
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
|
||||||
};
|
};
|
||||||
|
|
||||||
uint32_t
|
#ifndef CHARSETS_STORAGE
|
||||||
|
#define CHARSETS_STORAGE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CHARSETS_STORAGE uint32_t
|
||||||
decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) {
|
decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) {
|
||||||
uint32_t type = utf8_data[byte];
|
uint32_t type = utf8_data[byte];
|
||||||
|
|
||||||
@@ -200,7 +205,7 @@ decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) {
|
|||||||
return *state;
|
return *state;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t
|
CHARSETS_STORAGE size_t
|
||||||
decode_utf8_string(const char *src, size_t sz, uint32_t *dest) {
|
decode_utf8_string(const char *src, size_t sz, uint32_t *dest) {
|
||||||
// dest must be a zeroed array of size at least sz
|
// dest must be a zeroed array of size at least sz
|
||||||
uint32_t codep = 0;
|
uint32_t codep = 0;
|
||||||
@@ -221,7 +226,7 @@ decode_utf8_string(const char *src, size_t sz, uint32_t *dest) {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int
|
CHARSETS_STORAGE unsigned int
|
||||||
encode_utf8(uint32_t ch, char* dest) {
|
encode_utf8(uint32_t ch, char* dest) {
|
||||||
if (ch < 0x80) { // only lower 7 bits can be 1
|
if (ch < 0x80) { // only lower 7 bits can be 1
|
||||||
dest[0] = (char)ch; // 0xxxxxxx
|
dest[0] = (char)ch; // 0xxxxxxx
|
||||||
|
|||||||
19
kitty/launcher/launcher.h
Normal file
19
kitty/launcher/launcher.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* launcher.h
|
||||||
|
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef struct CLIOptions {
|
||||||
|
const char *session, *instance_group;
|
||||||
|
bool single_instance, version_requested, wait_for_single_instance_window_close;
|
||||||
|
} CLIOptions;
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
single_instance_main(int argc, char *argv[], const CLIOptions *opts);
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <wchar.h>
|
#include <wchar.h>
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include "launcher.h"
|
||||||
|
|
||||||
#ifndef KITTY_LIB_PATH
|
#ifndef KITTY_LIB_PATH
|
||||||
#define KITTY_LIB_PATH "../.."
|
#define KITTY_LIB_PATH "../.."
|
||||||
@@ -367,10 +368,16 @@ is_boolean_flag(const char *x) {
|
|||||||
static void
|
static void
|
||||||
handle_fast_commandline(int argc, char *argv[]) {
|
handle_fast_commandline(int argc, char *argv[]) {
|
||||||
char current_option_expecting_argument[128] = {0};
|
char current_option_expecting_argument[128] = {0};
|
||||||
bool version_requested = false;
|
CLIOptions opts = {0};
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
const char *arg = argv[i];
|
const char *arg = argv[i];
|
||||||
if (current_option_expecting_argument[0]) {
|
if (current_option_expecting_argument[0]) {
|
||||||
|
handle_option_value:
|
||||||
|
if (strcmp(current_option_expecting_argument, "session") == 0) {
|
||||||
|
opts.session = arg;
|
||||||
|
} else if (strcmp(current_option_expecting_argument, "instance-group") == 0) {
|
||||||
|
opts.instance_group = arg;
|
||||||
|
}
|
||||||
current_option_expecting_argument[0] = 0;
|
current_option_expecting_argument[0] = 0;
|
||||||
} else {
|
} else {
|
||||||
if (!arg || !arg[0] || !arg[1] || arg[0] != '-' || strcmp(arg, "--") == 0) break;
|
if (!arg || !arg[0] || !arg[1] || arg[0] != '-' || strcmp(arg, "--") == 0) break;
|
||||||
@@ -378,16 +385,25 @@ handle_fast_commandline(int argc, char *argv[]) {
|
|||||||
const char *equal = strchr(arg, '=');
|
const char *equal = strchr(arg, '=');
|
||||||
if (equal == NULL) {
|
if (equal == NULL) {
|
||||||
if (strcmp(arg+2, "version") == 0) {
|
if (strcmp(arg+2, "version") == 0) {
|
||||||
version_requested = true;
|
opts.version_requested = true;
|
||||||
|
} else if (strcmp(arg+2, "single-instance") == 0) {
|
||||||
|
opts.single_instance = true;
|
||||||
|
} else if (strcmp(arg+2, "wait-for-single-instance-window-close") == 0) {
|
||||||
|
opts.wait_for_single_instance_window_close = true;
|
||||||
} else if (!is_boolean_flag(arg+2)) {
|
} else if (!is_boolean_flag(arg+2)) {
|
||||||
strncpy(current_option_expecting_argument, arg+2, sizeof(current_option_expecting_argument)-1);
|
strncpy(current_option_expecting_argument, arg+2, sizeof(current_option_expecting_argument)-1);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
memcpy(current_option_expecting_argument, arg+2, equal - (arg + 2));
|
||||||
|
arg = equal + 1;
|
||||||
|
goto handle_option_value;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
char buf[2] = {0};
|
char buf[2] = {0};
|
||||||
for (int i = 1; arg[i] != 0; i++) {
|
for (int i = 1; arg[i] != 0; i++) {
|
||||||
switch(arg[i]) {
|
switch(arg[i]) {
|
||||||
case 'v': version_requested = true; break;
|
case 'v': opts.version_requested = true; break;
|
||||||
|
case '1': opts.single_instance = true; break;
|
||||||
default:
|
default:
|
||||||
buf[0] = arg[i]; buf[1] = 0;
|
buf[0] = arg[i]; buf[1] = 0;
|
||||||
if (!is_boolean_flag(buf)) { current_option_expecting_argument[0] = arg[i]; current_option_expecting_argument[1] = 0; }
|
if (!is_boolean_flag(buf)) { current_option_expecting_argument[0] = arg[i]; current_option_expecting_argument[1] = 0; }
|
||||||
@@ -397,7 +413,7 @@ handle_fast_commandline(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_requested) {
|
if (opts.version_requested) {
|
||||||
if (isatty(STDOUT_FILENO)) {
|
if (isatty(STDOUT_FILENO)) {
|
||||||
printf("\x1b[3mkitty\x1b[23m \x1b[32m%s\x1b[39m created by \x1b[1;34mKovid Goyal\x1b[22;39m\n", KITTY_VERSION);
|
printf("\x1b[3mkitty\x1b[23m \x1b[32m%s\x1b[39m created by \x1b[1;34mKovid Goyal\x1b[22;39m\n", KITTY_VERSION);
|
||||||
} else {
|
} else {
|
||||||
@@ -405,6 +421,7 @@ handle_fast_commandline(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
if (opts.single_instance) single_instance_main(argc, argv, &opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[], char* envp[]) {
|
int main(int argc, char *argv[], char* envp[]) {
|
||||||
|
|||||||
290
kitty/launcher/single-instance.c
Normal file
290
kitty/launcher/single-instance.c
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* single-instance.c
|
||||||
|
* Copyright (C) 2024 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// Needed for _CS_DARWIN_USER_CACHE_DIR
|
||||||
|
#define _DARWIN_C_SOURCE
|
||||||
|
#include <unistd.h>
|
||||||
|
#undef _DARWIN_C_SOURCE
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "launcher.h"
|
||||||
|
#include "../safe-wrappers.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#define CHARSETS_STORAGE static inline
|
||||||
|
#define NO_SINGLE_BYTE_CHARSETS
|
||||||
|
#include "../charsets.c"
|
||||||
|
|
||||||
|
#define fail_on_errno(msg) { perror(msg); exit(1); }
|
||||||
|
|
||||||
|
void
|
||||||
|
log_error(const char *fmt, ...) {
|
||||||
|
va_list ar;
|
||||||
|
va_start(ar, fmt);
|
||||||
|
vfprintf(stderr, fmt, ar);
|
||||||
|
va_end(ar);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_ok_tmpdir(const char *x) {
|
||||||
|
if (!x || !x[0]) return false;
|
||||||
|
char path[2048];
|
||||||
|
snprintf(path, sizeof(path), "%s/kitty-si-test-tmpdir-XXXXXXXXXXXX", x);
|
||||||
|
int fd = safe_mkstemp(path);
|
||||||
|
if (fd > -1) {
|
||||||
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
|
unlink(path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
get_socket_dir(char *output, size_t output_capacity) {
|
||||||
|
#define ret_if_ok(x) if (is_ok_tmpdir(x)) { if (snprintf(output, output_capacity, "%s", x) < output_capacity-1); return; }
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if (confstr(_CS_DARWIN_USER_CACHE_DIR, output, output_capacity)) return output;
|
||||||
|
snprintf(output, output_capacity, "%s", "/Library/Caches");
|
||||||
|
#else
|
||||||
|
#define test_env(x) { const char *e = getenv(#x); ret_if_ok(e); }
|
||||||
|
test_env(XDG_RUNTIME_DIR); test_env(TMPDIR); test_env(TEMP); test_env(TMP);
|
||||||
|
|
||||||
|
ret_if_ok("/tmp"); ret_if_ok("/var/tmp"); ret_if_ok("/usr/tmp");
|
||||||
|
|
||||||
|
test_env(HOME);
|
||||||
|
|
||||||
|
const char *home = getpwuid(geteuid())->pw_dir;
|
||||||
|
ret_if_ok(home);
|
||||||
|
|
||||||
|
if (getcwd(output, output_capacity)) return;
|
||||||
|
snprintf(output, output_capacity, "%s", ".");
|
||||||
|
#undef test_env
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_single_instance_socket(int fd, const char *socket_path) {
|
||||||
|
if (listen(fd, 5) != 0) fail_on_errno("Failed to listen on single instance socket");
|
||||||
|
char buf[256];
|
||||||
|
if (socket_path && socket_path[0]) snprintf(buf, sizeof(buf), "%d:%s", fd, socket_path);
|
||||||
|
else snprintf(buf, sizeof(buf), "%d", fd);
|
||||||
|
setenv("KITTY_SI_DATA", buf, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct membuf {
|
||||||
|
char *data;
|
||||||
|
size_t used, capacity;
|
||||||
|
} membuf;
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_to_membuf(membuf *m, void *data, size_t sz) {
|
||||||
|
ensure_space_for(m, data, char, m->used + sz, capacity, 8192, false);
|
||||||
|
memcpy(m->data + m->used, data, sz); m->used += sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_escaped_char(membuf *m, char ch) {
|
||||||
|
char buf[8];
|
||||||
|
int n = snprintf(buf, sizeof(buf), "\\u%04x", ch);
|
||||||
|
write_to_membuf(m, buf, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_json_string(membuf *m, const char *src, size_t src_len) {
|
||||||
|
ensure_space_for(m, data, char, m->used + 2 + 8 * src_len, capacity, 8192, false);
|
||||||
|
m->data[m->used++] = '"';
|
||||||
|
uint32_t codep = 0;
|
||||||
|
UTF8State state = 0, prev = UTF8_ACCEPT;
|
||||||
|
for (size_t i = 0; i < src_len; i++) {
|
||||||
|
switch(decode_utf8(&state, &codep, src[i])) {
|
||||||
|
case UTF8_ACCEPT:
|
||||||
|
switch(codep) {
|
||||||
|
case '"': write_to_membuf(m, "\\\"", 2); break;
|
||||||
|
case '\\': write_to_membuf(m, "\\\\", 2); break;
|
||||||
|
case '\t': write_to_membuf(m, "\\t", 2); break;
|
||||||
|
case '\n': write_to_membuf(m, "\\n", 2); break;
|
||||||
|
case '\r': write_to_membuf(m, "\\r", 2); break;
|
||||||
|
START_ALLOW_CASE_RANGE
|
||||||
|
case 0 ... 8: case 11: case 12: case 14 ... 31:
|
||||||
|
write_escaped_char(m, codep); break;
|
||||||
|
END_ALLOW_CASE_RANGE
|
||||||
|
default: m->used += encode_utf8(codep, m->data + m->used);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UTF8_REJECT:
|
||||||
|
state = UTF8_ACCEPT;
|
||||||
|
if (prev != UTF8_ACCEPT && i > 0) i--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = state;
|
||||||
|
}
|
||||||
|
m->data[m->used++] = '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
write_json_string_array(membuf *m, int argc, char *argv[]) {
|
||||||
|
write_to_membuf(m, "[", 1);
|
||||||
|
for (int i = 0; i < argc; i++) {
|
||||||
|
if (i) write_to_membuf(m, ",", 1);
|
||||||
|
write_json_string(m, argv[i], strlen(argv[i]));
|
||||||
|
}
|
||||||
|
write_to_membuf(m, "]", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
read_till_eof(FILE *f, membuf *m) {
|
||||||
|
while (!feof(f)) {
|
||||||
|
ensure_space_for(m, data, char, m->used + 8192, capacity, 4*8192, false);
|
||||||
|
m->used += fread(m->data, 1, m->capacity - m->used, f);
|
||||||
|
if (ferror(f)) { fclose(f); fail_on_errno("Failed to read from session file"); }
|
||||||
|
}
|
||||||
|
write_to_membuf(m, "\0", 1);
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
bind_unix_socket(int s, const char *basename, struct sockaddr_un *addr) {
|
||||||
|
addr->sun_family = AF_UNIX;
|
||||||
|
const size_t blen = strlen(basename);
|
||||||
|
// First try abstract socket
|
||||||
|
addr->sun_path[0] = 0;
|
||||||
|
memcpy(addr->sun_path + 1, basename, blen + 1);
|
||||||
|
if (safe_bind(s, (struct sockaddr*)addr, sizeof(sa_family_t) + 1 + blen) > -1) return true;
|
||||||
|
if (errno != ENOENT) return false;
|
||||||
|
// Try an actual filesystem file
|
||||||
|
get_socket_dir(addr->sun_path, sizeof(addr->sun_path) - blen - 2);
|
||||||
|
const size_t dlen = strlen(addr->sun_path);
|
||||||
|
if (snprintf(addr->sun_path + dlen, sizeof(addr->sun_path) - dlen, "/%s", basename) < blen + 1) {
|
||||||
|
fprintf(stderr, "Socket directory has path too long for single instance socket file %s\n", addr->sun_path);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (safe_bind(s, (struct sockaddr*)addr, sizeof(*addr)) > -1) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
create_unix_socket(void) {
|
||||||
|
int s = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (s < 0) fail_on_errno("Failed to create single instance socket object");
|
||||||
|
int flags;
|
||||||
|
if ((flags = fcntl(s, F_GETFD)) == -1) fail_on_errno("Failed to get fcntl flags for single instance socket");
|
||||||
|
if (fcntl(s, F_SETFD, flags | FD_CLOEXEC) == -1) fail_on_errno("Failed to set single instance socket to CLOEXEC");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern char **environ;
|
||||||
|
|
||||||
|
static void
|
||||||
|
talk_to_instance(int s, struct sockaddr_un *server_addr, int argc, char *argv[], const CLIOptions *opts) {
|
||||||
|
membuf session_data = {0};
|
||||||
|
if (opts->session && opts->session[0]) {
|
||||||
|
if (strcmp(opts->session, "none") == 0) {
|
||||||
|
session_data.data = "none"; session_data.used = 4;
|
||||||
|
} else if (strcmp(opts->session, "-")) {
|
||||||
|
read_till_eof(stdin, &session_data);
|
||||||
|
} else {
|
||||||
|
FILE *f = safe_fopen(opts->session, "r");
|
||||||
|
if (f == NULL) fail_on_errno("Failed to open session file for reading");
|
||||||
|
read_till_eof(f, &session_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
membuf output = {0};
|
||||||
|
#define w(literal) write_to_membuf(&output, literal, sizeof(literal)-1)
|
||||||
|
w("{\"cmd\":\"new_instance\",\"session_data\":");
|
||||||
|
if (session_data.used) write_json_string(&output, session_data.data, session_data.used);
|
||||||
|
else write_json_string(&output, "", 0);
|
||||||
|
w(",\"args\":"); write_json_string_array(&output, argc, argv);
|
||||||
|
char cwd[4096];
|
||||||
|
if (!getcwd(cwd, sizeof(cwd))) fail_on_errno("Failed to get cwd");
|
||||||
|
w(",\"cwd\":"); write_json_string(&output, cwd, strlen(cwd));
|
||||||
|
w(",\"environ\":{");
|
||||||
|
char **e = environ;
|
||||||
|
for (; *e; e++) {
|
||||||
|
const char *eq = strchr(*e, '=');
|
||||||
|
if (eq) {
|
||||||
|
if (e != environ) write_to_membuf(&output, ",", 1);
|
||||||
|
write_json_string(&output, *e, eq - *e);
|
||||||
|
w(":");
|
||||||
|
write_json_string(&output, eq + 1, strlen(eq + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w("}");
|
||||||
|
|
||||||
|
w(",\"cmdline_args_for_open\":[]");
|
||||||
|
|
||||||
|
w(",\"notify_on_os_window_death\":");
|
||||||
|
int notify_socket = -1;
|
||||||
|
if (opts->wait_for_single_instance_window_close) {
|
||||||
|
notify_socket = create_unix_socket();
|
||||||
|
struct sockaddr_un server_addr;
|
||||||
|
char addr[128];
|
||||||
|
snprintf(addr, sizeof(addr), "kitty-os-window-close-notify-%d-%d", getpid(), geteuid());
|
||||||
|
if (!bind_unix_socket(notify_socket, addr, &server_addr)) fail_on_errno("Failed to bind notification socket");
|
||||||
|
size_t len = strlen(server_addr.sun_path);
|
||||||
|
if (len == 0) len = 1 + strlen(server_addr.sun_path +1);
|
||||||
|
if (listen(notify_socket, 5) != 0) fail_on_errno("Failed to listen on notify socket");
|
||||||
|
write_json_string(&output, server_addr.sun_path, len);
|
||||||
|
} else w("null");
|
||||||
|
|
||||||
|
w("}");
|
||||||
|
#undef w
|
||||||
|
size_t addr_len = strlen(server_addr->sun_path);
|
||||||
|
if (!addr_len) addr_len = strlen(server_addr->sun_path + 1) + 1;
|
||||||
|
if (safe_connect(s, (struct sockaddr*)server_addr, sizeof(sa_family_t) + addr_len) != 0) {
|
||||||
|
fail_on_errno("Failed to connect to single instance socket");
|
||||||
|
}
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < output.used) {
|
||||||
|
errno = 0;
|
||||||
|
ssize_t nbytes = write(s, output.data + pos, output.used - pos);
|
||||||
|
if (nbytes <= 0) {
|
||||||
|
if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += nbytes;
|
||||||
|
}
|
||||||
|
if (pos < output.used) fail_on_errno("Failed to write message to single instance socket");
|
||||||
|
shutdown(s, SHUT_RDWR);
|
||||||
|
safe_close(s, __FILE__, __LINE__);
|
||||||
|
if (notify_socket > -1) {
|
||||||
|
int fd = safe_accept(notify_socket, NULL, NULL);
|
||||||
|
if (fd < 0) fail_on_errno("Failed to accept connection on notify socket");
|
||||||
|
char rbuf;
|
||||||
|
while (true) {
|
||||||
|
ssize_t n = recv(notify_socket, &rbuf, 1, 0);
|
||||||
|
if (n < 0 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
shutdown(notify_socket, SHUT_RDWR);
|
||||||
|
safe_close(notify_socket, __FILE__, __LINE__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
single_instance_main(int argc, char *argv[], const CLIOptions *opts) {
|
||||||
|
struct sockaddr_un server_addr;
|
||||||
|
char addr_buf[sizeof(server_addr.sun_path)-1];
|
||||||
|
if (opts->instance_group) snprintf(addr_buf, sizeof(addr_buf), "kitty-ipc-%d-%s", geteuid(), opts->instance_group);
|
||||||
|
else snprintf(addr_buf, sizeof(addr_buf), "kitty-ipc-%d", geteuid());
|
||||||
|
|
||||||
|
int s = create_unix_socket();
|
||||||
|
if (!bind_unix_socket(s, addr_buf, &server_addr)) {
|
||||||
|
if (errno == EADDRINUSE) { talk_to_instance(s, &server_addr, argc, argv, opts); exit(0); }
|
||||||
|
else fail_on_errno("Failed to bind single instance socket");
|
||||||
|
} else set_single_instance_socket(s, server_addr.sun_path);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ from .options.utils import DELETE_ENV_VAR
|
|||||||
from .os_window_size import edge_spacing, initial_window_size_func
|
from .os_window_size import edge_spacing, initial_window_size_func
|
||||||
from .session import create_sessions, get_os_window_sizing_data
|
from .session import create_sessions, get_os_window_sizing_data
|
||||||
from .shaders import CompileError, load_shader_programs
|
from .shaders import CompileError, load_shader_programs
|
||||||
from .types import LayerShellConfig, SingleInstanceData
|
from .types import LayerShellConfig
|
||||||
from .utils import (
|
from .utils import (
|
||||||
cleanup_ssh_control_masters,
|
cleanup_ssh_control_masters,
|
||||||
detach,
|
detach,
|
||||||
@@ -60,9 +60,7 @@ from .utils import (
|
|||||||
parse_os_window_state,
|
parse_os_window_state,
|
||||||
safe_mtime,
|
safe_mtime,
|
||||||
shlex_split,
|
shlex_split,
|
||||||
single_instance,
|
|
||||||
startup_notification_handler,
|
startup_notification_handler,
|
||||||
unix_socket_paths,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -81,51 +79,6 @@ def set_custom_ibeam_cursor() -> None:
|
|||||||
log_error(f'Failed to set custom beam cursor with error: {e}')
|
log_error(f'Failed to set custom beam cursor with error: {e}')
|
||||||
|
|
||||||
|
|
||||||
def talk_to_instance(args: CLIOptions) -> None:
|
|
||||||
import json
|
|
||||||
import socket
|
|
||||||
session_data = ''
|
|
||||||
if args.session == '-':
|
|
||||||
session_data = sys.stdin.read()
|
|
||||||
elif args.session == 'none':
|
|
||||||
session_data = 'none'
|
|
||||||
elif args.session:
|
|
||||||
with open(args.session) as f:
|
|
||||||
session_data = f.read()
|
|
||||||
|
|
||||||
data: SingleInstanceData = {
|
|
||||||
'cmd': 'new_instance', 'args': tuple(sys.argv), 'cmdline_args_for_open': getattr(sys, 'cmdline_args_for_open', ()),
|
|
||||||
'cwd': os.getcwd(), 'session_data': session_data, 'environ': dict(os.environ), 'notify_on_os_window_death': None
|
|
||||||
}
|
|
||||||
notify_socket = None
|
|
||||||
if args.wait_for_single_instance_window_close:
|
|
||||||
address = f'\0{appname}-os-window-close-notify-{os.getpid()}-{os.geteuid()}'
|
|
||||||
notify_socket = socket.socket(family=socket.AF_UNIX)
|
|
||||||
try:
|
|
||||||
notify_socket.bind(address)
|
|
||||||
except FileNotFoundError:
|
|
||||||
for address in unix_socket_paths(address[1:], ext='.sock'):
|
|
||||||
notify_socket.bind(address)
|
|
||||||
break
|
|
||||||
data['notify_on_os_window_death'] = address
|
|
||||||
notify_socket.listen()
|
|
||||||
|
|
||||||
sdata = json.dumps(data, ensure_ascii=False).encode('utf-8')
|
|
||||||
assert single_instance.socket is not None
|
|
||||||
single_instance.socket.sendall(sdata)
|
|
||||||
with suppress(OSError):
|
|
||||||
single_instance.socket.shutdown(socket.SHUT_RDWR)
|
|
||||||
single_instance.socket.close()
|
|
||||||
|
|
||||||
if args.wait_for_single_instance_window_close:
|
|
||||||
assert notify_socket is not None
|
|
||||||
conn = notify_socket.accept()[0]
|
|
||||||
conn.recv(1)
|
|
||||||
with suppress(OSError):
|
|
||||||
conn.shutdown(socket.SHUT_RDWR)
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
def load_all_shaders(semi_transparent: bool = False) -> None:
|
def load_all_shaders(semi_transparent: bool = False) -> None:
|
||||||
try:
|
try:
|
||||||
load_shader_programs(semi_transparent)
|
load_shader_programs(semi_transparent)
|
||||||
@@ -246,7 +199,7 @@ def set_cocoa_global_shortcuts(opts: Options) -> Dict[str, SingleKey]:
|
|||||||
return global_shortcuts
|
return global_shortcuts
|
||||||
|
|
||||||
|
|
||||||
def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None:
|
def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None:
|
||||||
if is_macos:
|
if is_macos:
|
||||||
global_shortcuts = set_cocoa_global_shortcuts(opts)
|
global_shortcuts = set_cocoa_global_shortcuts(opts)
|
||||||
if opts.macos_custom_beam_cursor:
|
if opts.macos_custom_beam_cursor:
|
||||||
@@ -270,7 +223,7 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ())
|
|||||||
pre_show_callback,
|
pre_show_callback,
|
||||||
args.title or appname, args.name or args.cls or appname,
|
args.title or appname, args.name or args.cls or appname,
|
||||||
wincls, wstate, load_all_shaders, disallow_override_title=bool(args.title), layer_shell_config=run_app.layer_shell_config)
|
wincls, wstate, load_all_shaders, disallow_override_title=bool(args.title), layer_shell_config=run_app.layer_shell_config)
|
||||||
boss = Boss(opts, args, cached_values, global_shortcuts)
|
boss = Boss(opts, args, cached_values, global_shortcuts, talk_fd)
|
||||||
boss.start(window_id, startup_sessions)
|
boss.start(window_id, startup_sessions)
|
||||||
if bad_lines or boss.misc_config_errors:
|
if bad_lines or boss.misc_config_errors:
|
||||||
boss.show_bad_config_lines(bad_lines, boss.misc_config_errors)
|
boss.show_bad_config_lines(bad_lines, boss.misc_config_errors)
|
||||||
@@ -289,12 +242,12 @@ class AppRunner:
|
|||||||
self.layer_shell_config: Optional[LayerShellConfig] = None
|
self.layer_shell_config: Optional[LayerShellConfig] = None
|
||||||
self.initial_window_size_func = initial_window_size_func
|
self.initial_window_size_func = initial_window_size_func
|
||||||
|
|
||||||
def __call__(self, opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) -> None:
|
def __call__(self, opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = (), talk_fd: int = -1) -> None:
|
||||||
set_scale(opts.box_drawing_scale)
|
set_scale(opts.box_drawing_scale)
|
||||||
set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback)
|
set_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback)
|
||||||
try:
|
try:
|
||||||
set_font_family(opts, debug_font_matching=args.debug_font_fallback)
|
set_font_family(opts, debug_font_matching=args.debug_font_fallback)
|
||||||
_run_app(opts, args, bad_lines)
|
_run_app(opts, args, bad_lines, talk_fd)
|
||||||
finally:
|
finally:
|
||||||
set_options(None)
|
set_options(None)
|
||||||
free_font_data() # must free font data before glfw/freetype/fontconfig/opengl etc are finalized
|
free_font_data() # must free font data before glfw/freetype/fontconfig/opengl etc are finalized
|
||||||
@@ -521,11 +474,20 @@ def _main() -> None:
|
|||||||
from kitty.client import main as client_main
|
from kitty.client import main as client_main
|
||||||
client_main(cli_opts.replay_commands)
|
client_main(cli_opts.replay_commands)
|
||||||
return
|
return
|
||||||
|
talk_fd = -1
|
||||||
if cli_opts.single_instance:
|
if cli_opts.single_instance:
|
||||||
is_first = single_instance(cli_opts.instance_group)
|
si_data = os.environ.pop('KITTY_SI_DATA', '')
|
||||||
if not is_first:
|
if si_data:
|
||||||
talk_to_instance(cli_opts)
|
import atexit
|
||||||
return
|
fdnum, sep, socket_path = si_data.partition(':')
|
||||||
|
talk_fd = int(fdnum)
|
||||||
|
def cleanup_si() -> None:
|
||||||
|
with suppress(OSError):
|
||||||
|
os.close(talk_fd)
|
||||||
|
with suppress(OSError):
|
||||||
|
if sep and socket_path:
|
||||||
|
os.unlink(socket_path)
|
||||||
|
atexit.register(cleanup_si)
|
||||||
bad_lines: List[BadLine] = []
|
bad_lines: List[BadLine] = []
|
||||||
opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines)
|
opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines)
|
||||||
setup_environment(opts, cli_opts)
|
setup_environment(opts, cli_opts)
|
||||||
@@ -551,7 +513,7 @@ def _main() -> None:
|
|||||||
try:
|
try:
|
||||||
with setup_profiling():
|
with setup_profiling():
|
||||||
# Avoid needing to launch threads to reap zombies
|
# Avoid needing to launch threads to reap zombies
|
||||||
run_app(opts, cli_opts, bad_lines)
|
run_app(opts, cli_opts, bad_lines, talk_fd)
|
||||||
finally:
|
finally:
|
||||||
glfw_terminate()
|
glfw_terminate()
|
||||||
cleanup_ssh_control_masters()
|
cleanup_ssh_control_masters()
|
||||||
|
|||||||
@@ -8,7 +8,48 @@
|
|||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
safe_connect(int socket_fd, struct sockaddr *addr, socklen_t addrlen) {
|
||||||
|
while (true) {
|
||||||
|
int ret = connect(socket_fd, addr, addrlen);
|
||||||
|
if (ret < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
safe_bind(int socket_fd, struct sockaddr *addr, socklen_t addrlen) {
|
||||||
|
while (true) {
|
||||||
|
int ret = bind(socket_fd, addr, addrlen);
|
||||||
|
if (ret < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
safe_accept(int socket_fd, struct sockaddr *addr, socklen_t *addrlen) {
|
||||||
|
while (true) {
|
||||||
|
int ret = accept(socket_fd, addr, addrlen);
|
||||||
|
if (ret < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
safe_mkstemp(char *template) {
|
||||||
|
while (true) {
|
||||||
|
int fd = mkstemp(template);
|
||||||
|
if (fd == -1 && errno == EINTR) continue;
|
||||||
|
if (fd > -1) {
|
||||||
|
int flags = fcntl(fd, F_GETFD);
|
||||||
|
if (flags > -1) fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
safe_open(const char *path, int flags, mode_t mode) {
|
safe_open(const char *path, int flags, mode_t mode) {
|
||||||
@@ -19,6 +60,15 @@ safe_open(const char *path, int flags, mode_t mode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline FILE*
|
||||||
|
safe_fopen(const char *path, const char *mode) {
|
||||||
|
while (true) {
|
||||||
|
FILE *f = fopen(path, mode);
|
||||||
|
if (f == NULL && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
safe_shm_open(const char *path, int flags, mode_t mode) {
|
safe_shm_open(const char *path, int flags, mode_t mode) {
|
||||||
|
|||||||
@@ -394,6 +394,9 @@ def unix_socket_directories() -> Iterator[str]:
|
|||||||
if is_macos:
|
if is_macos:
|
||||||
from .fast_data_types import user_cache_dir
|
from .fast_data_types import user_cache_dir
|
||||||
candidates = [user_cache_dir(), '/Library/Caches']
|
candidates = [user_cache_dir(), '/Library/Caches']
|
||||||
|
else:
|
||||||
|
if os.environ.get('XDG_RUNTIME_DIR'):
|
||||||
|
candidates.insert(0, os.environ['XDG_RUNTIME_DIR'])
|
||||||
for loc in candidates:
|
for loc in candidates:
|
||||||
if os.access(loc, os.W_OK | os.R_OK | os.X_OK):
|
if os.access(loc, os.W_OK | os.R_OK | os.X_OK):
|
||||||
yield loc
|
yield loc
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -1286,7 +1286,7 @@ def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 's
|
|||||||
objects = []
|
objects = []
|
||||||
cppflags.append('-DKITTY_CLI_BOOL_OPTIONS=" ' + ' '.join(kitty_cli_boolean_options()) + ' "')
|
cppflags.append('-DKITTY_CLI_BOOL_OPTIONS=" ' + ' '.join(kitty_cli_boolean_options()) + ' "')
|
||||||
cppflags.append('-DKITTY_VERSION="' + '.'.join(map(str, version)) + '"')
|
cppflags.append('-DKITTY_VERSION="' + '.'.join(map(str, version)) + '"')
|
||||||
for src in ('kitty/launcher/main.c',):
|
for src in ('kitty/launcher/main.c', 'kitty/launcher/single-instance.c'):
|
||||||
obj = os.path.join(build_dir, src.replace('/', '-').replace('.c', '.o'))
|
obj = os.path.join(build_dir, src.replace('/', '-').replace('.c', '.o'))
|
||||||
objects.append(obj)
|
objects.append(obj)
|
||||||
cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]
|
cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]
|
||||||
|
|||||||
Reference in New Issue
Block a user