diff --git a/docs/changelog.rst b/docs/changelog.rst index e2b3ecb6b..1fc5de2e9 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,9 @@ Changelog are formatted. In particular the template can be used to display the tab number next to the title (:iss:`1223`) +- Report the current foreground processes as well as the original child process, + when using `kitty @ ls` + - Fix setting :opt:`background_opacity` causing window margins/padding to be slightly different shade from background (:iss:`1221`) diff --git a/kitty/boss.py b/kitty/boss.py index 936ea7daa..5b77ecab4 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -10,6 +10,7 @@ from functools import partial from gettext import gettext as _ from weakref import WeakValueDictionary +from .child import cached_process_data from .cli import create_opts, parse_args from .conf.utils import to_cmdline from .config import initial_window_size_func, prepare_config_file_for_editing @@ -150,14 +151,15 @@ class Boss: return os_window_id def list_os_windows(self): - active_tab, active_window = self.active_tab, self.active_window - active_tab_manager = self.active_tab_manager - for os_window_id, tm in self.os_window_map.items(): - yield { - 'id': os_window_id, - 'is_focused': tm is active_tab_manager, - 'tabs': list(tm.list_tabs(active_tab, active_window)), - } + with cached_process_data(): + active_tab, active_window = self.active_tab, self.active_window + active_tab_manager = self.active_tab_manager + for os_window_id, tm in self.os_window_map.items(): + yield { + 'id': os_window_id, + 'is_focused': tm is active_tab_manager, + 'tabs': list(tm.list_tabs(active_tab, active_window)), + } @property def all_tab_managers(self): diff --git a/kitty/child.py b/kitty/child.py index 5f3f67c16..519d67820 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -4,17 +4,28 @@ import fcntl import os +from collections import defaultdict +from contextlib import contextmanager import kitty.fast_data_types as fast_data_types from .constants import is_macos, shell_path, terminfo_dir if is_macos: - from kitty.fast_data_types import cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process + from kitty.fast_data_types import ( + cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process, + process_group_map as _process_group_map + ) def cwd_of_process(pid): return os.path.realpath(_cwd(pid)) + def process_group_map(): + ans = defaultdict(list) + for pid, pgid in _process_group_map(): + ans[pgid].append(pid) + return ans + else: def cmdline_of_process(pid): @@ -27,6 +38,46 @@ else: def _environ_of_process(pid): return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8') + def process_group_map(): + ans = defaultdict(list) + for x in os.listdir('/proc'): + try: + pid = int(x) + except Exception: + continue + try: + raw = open('/proc/' + x + '/stat', 'rb').read().decode('utf-8') + except EnvironmentError: + continue + try: + q = int(raw.split(' ', 5)[4]) + except Exception: + continue + ans[q].append(pid) + return ans + + +def processes_in_group(grp): + gmap = getattr(process_group_map, 'cached_map', None) + if gmap is None: + try: + gmap = process_group_map() + except Exception: + gmap = {} + return gmap.get(grp, []) + + +@contextmanager +def cached_process_data(): + try: + process_group_map.cached_map = process_group_map() + except Exception: + process_group_map.cached_map = {} + try: + yield + finally: + process_group_map.cached_map = None + def parse_environ_block(data): """Parse a C environ block of environment variables into a dictionary.""" @@ -142,6 +193,24 @@ class Child: os.close(self.terminal_ready_fd) self.terminal_ready_fd = -1 + @property + def foreground_processes(self): + try: + pgrp = os.tcgetpgrp(self.child_fd) + foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else [] + + def process_desc(pid): + ans = {'pid': pid} + try: + ans['cmdline'] = cmdline_of_process(pid) + except Exception: + pass + return ans + + return list(map(process_desc, foreground_processes)) + except Exception: + return [] + @property def cmdline(self): try: diff --git a/kitty/data-types.c b/kitty/data-types.c index eca5a315b..2428a1511 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -40,6 +40,7 @@ #define NS_TO_US (1000) #ifdef __APPLE__ +#include #include static mach_timebase_info_data_t timebase = {0}; @@ -54,6 +55,25 @@ user_cache_dir() { return PyUnicode_FromString(buf); } +static PyObject* +process_group_map() { + int num_of_processes = proc_listallpids(NULL, 0); + size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024); + pid_t *buf = malloc(bufsize); + if (!buf) return PyErr_NoMemory(); + num_of_processes = proc_listallpids(buf, (int)bufsize); + PyObject *ans = PyTuple_New(num_of_processes); + if (ans == NULL) { free(buf); return PyErr_NoMemory(); } + for (int i = 0; i < num_of_processes; i++) { + long pid = buf[i], pgid = getpgid(buf[i]); + PyObject *t = Py_BuildValue("ll", pid, pgid); + if (t == NULL) { free(buf); Py_DECREF(ans); return NULL; } + PyTuple_SET_ITEM(ans, i, t); + } + free(buf); + return ans; +} + #else #include static inline double monotonic_() { @@ -177,6 +197,7 @@ static PyMethodDef module_methods[] = { {"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""}, #ifdef __APPLE__ METHODB(user_cache_dir, METH_NOARGS), + METHODB(process_group_map, METH_NOARGS), #endif #ifdef WITH_PROFILER {"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""}, diff --git a/kitty/window.py b/kitty/window.py index 9695e906c..b44442c52 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -168,6 +168,7 @@ class Window: cwd=self.child.current_cwd or self.child.cwd, cmdline=self.child.cmdline, env=self.child.environ, + foreground_processes=self.child.foreground_processes ) @property