diff --git a/kitty/boss.py b/kitty/boss.py index 08b8ff270..dd724b387 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -149,7 +149,6 @@ from .utils import ( remove_socket_file, safe_print, sanitize_url_for_dispay_to_user, - single_instance, startup_notification_handler, timed_debug_print, which, @@ -329,6 +328,7 @@ class Boss: args: CLIOptions, cached_values: Dict[str, Any], global_shortcuts: Dict[str, SingleKey], + talk_fd: int = -1, ): set_layout_options(opts) self.clipboard = Clipboard() @@ -352,9 +352,6 @@ class Boss: self.cursor_blinking = True self.shutting_down = False 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 # 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'): self.allow_remote_control = 'n' self.listening_on = '' + listen_fd = -1 if args.listen_on and self.allow_remote_control in ('y', 'socket', 'socket-only', 'password'): try: listen_fd, self.listening_on = listen_on(args.listen_on) diff --git a/kitty/charsets.c b/kitty/charsets.c index 9223ac3b6..9c8778d12 100644 --- a/kitty/charsets.c +++ b/kitty/charsets.c @@ -9,6 +9,7 @@ #include "data-types.h" +#ifndef NO_SINGLE_BYTE_CHARSETS static uint32_t charset_translations[4][256] = { /* VT100 graphics mapped to Unicode */ { @@ -167,7 +168,7 @@ translation_table(uint32_t which) { return charset_translations[3]; } } - +#endif // 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 }; -uint32_t +#ifndef CHARSETS_STORAGE +#define CHARSETS_STORAGE +#endif + +CHARSETS_STORAGE uint32_t decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) { uint32_t type = utf8_data[byte]; @@ -200,7 +205,7 @@ decode_utf8(UTF8State* state, uint32_t* codep, uint8_t byte) { return *state; } -size_t +CHARSETS_STORAGE size_t decode_utf8_string(const char *src, size_t sz, uint32_t *dest) { // dest must be a zeroed array of size at least sz uint32_t codep = 0; @@ -221,7 +226,7 @@ decode_utf8_string(const char *src, size_t sz, uint32_t *dest) { return d; } -unsigned int +CHARSETS_STORAGE unsigned int encode_utf8(uint32_t ch, char* dest) { if (ch < 0x80) { // only lower 7 bits can be 1 dest[0] = (char)ch; // 0xxxxxxx diff --git a/kitty/launcher/launcher.h b/kitty/launcher/launcher.h new file mode 100644 index 000000000..d30476a0c --- /dev/null +++ b/kitty/launcher/launcher.h @@ -0,0 +1,19 @@ +/* + * launcher.h + * Copyright (C) 2024 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#pragma once + +#include + +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); diff --git a/kitty/launcher/main.c b/kitty/launcher/main.c index 1b755c4b7..e7855c52b 100644 --- a/kitty/launcher/main.c +++ b/kitty/launcher/main.c @@ -20,6 +20,7 @@ #include #include #include +#include "launcher.h" #ifndef KITTY_LIB_PATH #define KITTY_LIB_PATH "../.." @@ -367,10 +368,16 @@ is_boolean_flag(const char *x) { static void handle_fast_commandline(int argc, char *argv[]) { char current_option_expecting_argument[128] = {0}; - bool version_requested = false; + CLIOptions opts = {0}; for (int i = 1; i < argc; i++) { const char *arg = argv[i]; 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; } else { 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, '='); if (equal == NULL) { 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)) { 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 { char buf[2] = {0}; for (int i = 1; arg[i] != 0; 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: 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; } @@ -397,7 +413,7 @@ handle_fast_commandline(int argc, char *argv[]) { } } - if (version_requested) { + if (opts.version_requested) { 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); } else { @@ -405,6 +421,7 @@ handle_fast_commandline(int argc, char *argv[]) { } exit(0); } + if (opts.single_instance) single_instance_main(argc, argv, &opts); } int main(int argc, char *argv[], char* envp[]) { diff --git a/kitty/launcher/single-instance.c b/kitty/launcher/single-instance.c new file mode 100644 index 000000000..f7bf6fc3c --- /dev/null +++ b/kitty/launcher/single-instance.c @@ -0,0 +1,290 @@ +/* + * single-instance.c + * Copyright (C) 2024 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#ifdef __APPLE__ +// Needed for _CS_DARWIN_USER_CACHE_DIR +#define _DARWIN_C_SOURCE +#include +#undef _DARWIN_C_SOURCE +#else +#include +#endif + +#include "launcher.h" +#include "../safe-wrappers.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + diff --git a/kitty/main.py b/kitty/main.py index 20b86f9b3..476b4e038 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -50,7 +50,7 @@ from .options.utils import DELETE_ENV_VAR from .os_window_size import edge_spacing, initial_window_size_func from .session import create_sessions, get_os_window_sizing_data from .shaders import CompileError, load_shader_programs -from .types import LayerShellConfig, SingleInstanceData +from .types import LayerShellConfig from .utils import ( cleanup_ssh_control_masters, detach, @@ -60,9 +60,7 @@ from .utils import ( parse_os_window_state, safe_mtime, shlex_split, - single_instance, 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}') -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: try: load_shader_programs(semi_transparent) @@ -246,7 +199,7 @@ def set_cocoa_global_shortcuts(opts: Options) -> Dict[str, SingleKey]: 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: global_shortcuts = set_cocoa_global_shortcuts(opts) if opts.macos_custom_beam_cursor: @@ -270,7 +223,7 @@ def _run_app(opts: Options, args: CLIOptions, bad_lines: Sequence[BadLine] = ()) pre_show_callback, 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) - boss = Boss(opts, args, cached_values, global_shortcuts) + boss = Boss(opts, args, cached_values, global_shortcuts, talk_fd) boss.start(window_id, startup_sessions) if bad_lines or 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.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_options(opts, is_wayland(), args.debug_rendering, args.debug_font_fallback) try: 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: set_options(None) 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 client_main(cli_opts.replay_commands) return + talk_fd = -1 if cli_opts.single_instance: - is_first = single_instance(cli_opts.instance_group) - if not is_first: - talk_to_instance(cli_opts) - return + si_data = os.environ.pop('KITTY_SI_DATA', '') + if si_data: + import atexit + 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] = [] opts = create_opts(cli_opts, accumulate_bad_lines=bad_lines) setup_environment(opts, cli_opts) @@ -551,7 +513,7 @@ def _main() -> None: try: with setup_profiling(): # 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: glfw_terminate() cleanup_ssh_control_masters() diff --git a/kitty/safe-wrappers.h b/kitty/safe-wrappers.h index 8fa19ce8c..00188aa75 100644 --- a/kitty/safe-wrappers.h +++ b/kitty/safe-wrappers.h @@ -8,7 +8,48 @@ #include "data-types.h" #include #include +#include +#include +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 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 safe_shm_open(const char *path, int flags, mode_t mode) { diff --git a/kitty/utils.py b/kitty/utils.py index e5c03e23e..cbab7463c 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -394,6 +394,9 @@ def unix_socket_directories() -> Iterator[str]: if is_macos: from .fast_data_types import user_cache_dir 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: if os.access(loc, os.W_OK | os.R_OK | os.X_OK): yield loc diff --git a/setup.py b/setup.py index 294348103..df48170cb 100755 --- a/setup.py +++ b/setup.py @@ -1286,7 +1286,7 @@ def build_launcher(args: Options, launcher_dir: str = '.', bundle_type: str = 's objects = [] cppflags.append('-DKITTY_CLI_BOOL_OPTIONS=" ' + ' '.join(kitty_cli_boolean_options()) + ' "') 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')) objects.append(obj) cmd = env.cc + cppflags + cflags + ['-c', src, '-o', obj]