diff --git a/kitty/boss.py b/kitty/boss.py index 9a90df410..2ca136737 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -18,7 +18,8 @@ from .keys import get_key_map, get_shortcut from .session import create_session from .tabs import SpecialWindow, TabManager from .utils import ( - get_primary_selection, open_url, safe_print, set_primary_selection + get_primary_selection, open_url, safe_print, set_primary_selection, + single_instance ) @@ -61,9 +62,13 @@ class Boss: self.os_window_map = {} self.cursor_blinking = True self.shutting_down = False + talk_fd = getattr(single_instance, 'socket', None) + talk_fd = -1 if talk_fd is None else talk_fd.fileno() self.child_monitor = ChildMonitor( self.on_child_death, - DumpCommands(args) if args.dump_commands or args.dump_bytes else None) + DumpCommands(args) if args.dump_commands or args.dump_bytes else None, + talk_fd + ) set_boss(self) self.current_font_size = opts.font_size set_font_family(opts) @@ -88,6 +93,12 @@ class Boss: self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.window_id_map[window.id] = window + def peer_msg_received(self, msg): + import json + msg = json.loads(msg.decode('utf-8')) + if msg.get('cmd') == 'new_instance': + print(msg['args']) + def on_child_death(self, window_id): window = self.window_id_map.pop(window_id, None) if window is None: diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index 5c1672f45..d1b786ac6 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -223,9 +223,6 @@ join(ChildMonitor *self) { #define join_doc "join() -> Wait for the I/O thread to finish" int ret = pthread_join(self->io_thread, NULL); if (ret != 0) return PyErr_SetFromErrno(PyExc_OSError); - if (self->talk_fd > -1) { - if (pthread_join(self->talk_thread, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError); - } Py_RETURN_NONE; } @@ -341,10 +338,10 @@ parse_input(ChildMonitor *self) { } if (UNLIKELY(self->messages_count)) { - while(self->messages_count--) { - Message *m = self->messages + self->messages_count; + while(self->messages_count) { + Message *m = self->messages + --self->messages_count; call_boss(peer_msg_received, "y#", m->data, m->sz); - free(m->data); + free(m->data); m->data = NULL; m->sz = 0; } } @@ -928,14 +925,13 @@ io_loop(void *data) { static inline void handle_peer(ChildMonitor *self, int s) { - size_t bufsz = 1024; + size_t bufsz = 0; char *buf = NULL; size_t buf_used = 0; - if (buf == NULL) return; while(true) { if (buf_used >= bufsz) { - bufsz *= 2; + bufsz = MAX(1024, bufsz) * 2; if (bufsz > 1024 * 1024) return; buf = realloc(buf, bufsz); if (buf == NULL) return; @@ -951,7 +947,7 @@ handle_peer(ChildMonitor *self, int s) { } if (buf_used) { children_mutex(lock); - ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, false); + ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true); Message *m = self->messages + self->messages_count++; m->data = buf; m->sz = buf_used; children_mutex(unlock); @@ -969,7 +965,7 @@ talk_loop(void *data) { int peer = accept(self->talk_fd, NULL, NULL); if (peer == -1) { if (errno == EINTR) continue; - perror("accept() on talk socket failed!"); + if (!self->shutting_down) perror("accept() on talk socket failed!"); break; } handle_peer(self, peer); diff --git a/kitty/cli.py b/kitty/cli.py index ed77795d0..c7df4fed5 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -101,6 +101,27 @@ def option_parser(): 'Path to a file containing the startup session (tabs, windows, layout, programs)' ) ) + a( + '-1', '--single-instance', + default=False, + action='store_true', + help=_( + 'If specified only a single instance of {0} will run. New invocations will' + ' instead create a new top-level window in the existing {0} instance. This' + ' allows {0} to share a single sprite cache on the GPU and also reduces' + ' startup time. You can also have groups of {0} instances by using the' + ' {1} option.' + ).format(appname, '--instance-group') + ) + a( + '--instance-group', + default=None, + help=_( + 'Used in combination with the --single-instance option. All {0} invocations' + ' with the same --instance-group will result in new windows being created' + ' in the first {0} instance with that group.' + ).format(appname) + ) a( 'args', nargs=argparse.REMAINDER, diff --git a/kitty/main.py b/kitty/main.py index fe2bd6122..e693e3ec2 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -23,7 +23,7 @@ from .fast_data_types import ( from .fonts.box_drawing import set_scale from .utils import ( detach, end_startup_notification, get_logical_dpi, - init_startup_notification + init_startup_notification, single_instance ) from .window import load_shader_programs @@ -129,6 +129,14 @@ def main(): from kitty.client import main main(args.replay_commands) return + if args.single_instance: + is_first = single_instance(args.instance_group) + if not is_first: + import json + data = {'cmd': 'new_instance', 'args': tuple(sys.argv)} + data = json.dumps(data, ensure_ascii=False).encode('utf-8') + single_instance.socket.sendall(data) + return config = args.config or (defconf, ) overrides = (a.replace('=', ' ', 1) for a in args.override or ()) opts = load_config(*config, overrides=overrides)