diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b898c77..a67ff8fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ ## Unreleased ### Added +- Support for opening an existing PTY, e.g. a VM console. + ([#1564][1564]) * Unicode input mode now accepts input from the numpad as well, numlock is ignored. * A new `resize-by-cells` option, enabled by default, allows the size @@ -69,6 +71,7 @@ [1348]: https://codeberg.org/dnkl/foot/issues/1348 [1633]: https://codeberg.org/dnkl/foot/issues/1633 +[1564]: https://codeberg.org/dnkl/foot/pulls/1564 ### Changed diff --git a/completions/bash/foot b/completions/bash/foot index eb17dad1..25aa2c49 100644 --- a/completions/bash/foot +++ b/completions/bash/foot @@ -19,6 +19,7 @@ _foot() "--maximized" "--override" "--print-pid" + "--pty" "--server" "--term" "--title" @@ -39,7 +40,7 @@ _foot() for word in "${previous_words[@]}" ; do match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null) if [[ ! -z "$match" ]] ; then - if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--config|--font|--log-level|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then + if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--config|--font|--log-level|--pty|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then (( i++ )) continue fi @@ -74,7 +75,7 @@ _foot() COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;; --log-colorize|-l) COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;; - --app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC]) + --app-id|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC]) # Don't autocomplete for these flags : ;; *) diff --git a/completions/fish/foot.fish b/completions/fish/foot.fish index 86f6616d..eecb6488 100644 --- a/completions/fish/foot.fish +++ b/completions/fish/foot.fish @@ -18,5 +18,6 @@ complete -c foot -r -s p -l print-pid complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (warning)" complete -c foot -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr" complete -c foot -s S -l log-no-syslog -d "disable syslog logging (server mode only)" +complete -c foot -x -l pty -a '(__fish_complete_path)' -d "display an existing pty instead of creating one" complete -c foot -s v -l version -d "show the version number and quit" complete -c foot -s h -l help -d "show help message and quit" diff --git a/completions/zsh/_foot b/completions/zsh/_foot index b9f46cdc..2a0dc7b0 100644 --- a/completions/zsh/_foot +++ b/completions/zsh/_foot @@ -18,6 +18,7 @@ _arguments \ '(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]:server:_files' \ '(-H --hold)'{-H,--hold}'[remain open after child process exits]' \ '(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running (server mode only)]:pidfile:_files' \ + '--pty=[display an existing pty instead of creating one]:pty:_files' \ '(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \ '(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \ '(-S --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging (server mode only)]' \ diff --git a/doc/foot.1.scd b/doc/foot.1.scd index 8e2e6a48..86975464 100644 --- a/doc/foot.1.scd +++ b/doc/foot.1.scd @@ -78,6 +78,13 @@ the foot command line *-L*,*--login-shell* Start a login shell, by prepending a '-' to argv[0]. +*--pty* + Display an existing pty instead of creating one. This is useful + for interacting with VM consoles. + + This option is not currently supported in combination with + *-s*,*--server*. + *-D*,*--working-directory*=_DIR_ Initial working directory for the client application. Default: _CWD of foot_. diff --git a/main.c b/main.c index 8b4d2715..24ba45ca 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -77,6 +78,7 @@ print_usage(const char *prog_name) " -m,--maximized start in maximized mode\n" " -F,--fullscreen start in fullscreen mode\n" " -L,--login-shell start shell as a login shell\n" + " --pty=PATH display an existing PTY instead of creating one\n" " -D,--working-directory=DIR directory to start in (CWD)\n" " -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n" " -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n" @@ -172,6 +174,10 @@ sanitize_signals(void) sigaction(i, &dfl, NULL); } +enum { + PTY_OPTION = CHAR_MAX + 1, +}; + int main(int argc, char *const *argv) { @@ -209,6 +215,7 @@ main(int argc, char *const *argv) {"maximized", no_argument, NULL, 'm'}, {"fullscreen", no_argument, NULL, 'F'}, {"presentation-timings", no_argument, NULL, 'P'}, /* Undocumented */ + {"pty", required_argument, NULL, PTY_OPTION}, {"print-pid", required_argument, NULL, 'p'}, {"log-level", required_argument, NULL, 'd'}, {"log-colorize", optional_argument, NULL, 'l'}, @@ -221,6 +228,7 @@ main(int argc, char *const *argv) bool check_config = false; const char *conf_path = NULL; const char *custom_cwd = NULL; + const char *pty_path = NULL; bool as_server = false; const char *conf_server_socket_path = NULL; bool presentation_timings = false; @@ -316,6 +324,10 @@ main(int argc, char *const *argv) conf_server_socket_path = optarg; break; + case PTY_OPTION: + pty_path = optarg; + break; + case 'P': presentation_timings = true; break; @@ -383,6 +395,11 @@ main(int argc, char *const *argv) } } + if (as_server && pty_path) { + fputs("error: --pty is incompatible with server mode\n", stderr); + return ret; + } + log_init(log_colorize, as_server && log_syslog, as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, log_level); @@ -574,7 +591,7 @@ main(int argc, char *const *argv) goto out; if (!as_server && (term = term_init( - &conf, fdm, reaper, wayl, "foot", cwd, token, + &conf, fdm, reaper, wayl, "foot", cwd, token, pty_path, argc, argv, NULL, &term_shutdown_cb, &shutdown_ctx)) == NULL) { goto out; diff --git a/server.c b/server.c index 068ca057..53e86088 100644 --- a/server.c +++ b/server.c @@ -332,7 +332,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data) instance->terminal = term_init( conf != NULL ? conf : server->conf, server->fdm, server->reaper, server->wayl, "footclient", cwd, token, - cdata.argc, argv, (const char *const *)envp, + NULL, cdata.argc, argv, (const char *const *)envp, &term_shutdown_handler, instance); if (instance->terminal == NULL) { diff --git a/terminal.c b/terminal.c index 2248bd02..3cf44d5a 100644 --- a/terminal.c +++ b/terminal.c @@ -367,6 +367,8 @@ fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) del_utmp_record(term->conf, term->reaper, term->ptmx); fdm_del(fdm, fd); term->ptmx = -1; + if (!term->conf->hold_at_exit) + term_shutdown(term); } return true; @@ -1062,10 +1064,13 @@ load_fonts_from_conf(struct terminal *term) static void fdm_client_terminated( struct reaper *reaper, pid_t pid, int status, void *data); +static const int PTY_OPEN_FLAGS = O_RDWR | O_NOCTTY; + struct terminal * term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, struct wayland *wayl, const char *foot_exe, const char *cwd, - const char *token, int argc, char *const *argv, const char *const *envp, + const char *token, const char *pty_path, + int argc, char *const *argv, const char *const *envp, void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data) { int ptmx = -1; @@ -1082,7 +1087,8 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, return NULL; } - if ((ptmx = posix_openpt(O_RDWR | O_NOCTTY)) < 0) { + ptmx = pty_path ? open(pty_path, PTY_OPEN_FLAGS) : posix_openpt(PTY_OPEN_FLAGS); + if (ptmx < 0) { LOG_ERRNO("failed to open PTY"); goto close_fds; } @@ -1156,6 +1162,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .fdm = fdm, .reaper = reaper, .conf = conf, + .slave = -1, .ptmx = ptmx, .ptmx_buffers = tll_init(), .ptmx_paste_buffers = tll_init(), @@ -1290,16 +1297,18 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, add_utmp_record(conf, reaper, ptmx); - /* Start the slave/client */ - if ((term->slave = slave_spawn( - term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars, - conf->term, conf->shell, conf->login_shell, - &conf->notifications)) == -1) - { - goto err; - } + if (!pty_path) { + /* Start the slave/client */ + if ((term->slave = slave_spawn( + term->ptmx, argc, term->cwd, argv, envp, &conf->env_vars, + conf->term, conf->shell, conf->login_shell, + &conf->notifications)) == -1) + { + goto err; + } - reaper_add(term->reaper, term->slave, &fdm_client_terminated, term); + reaper_add(term->reaper, term->slave, &fdm_client_terminated, term); + } /* Guess scale; we're not mapped yet, so we don't know on which * output we'll be. Use scaling factor from first monitor */ @@ -1561,26 +1570,30 @@ term_shutdown(struct terminal *term) close(term->ptmx); if (!term->shutdown.client_has_terminated) { - LOG_DBG("initiating asynchronous terminate of slave; " - "sending SIGTERM to PID=%u", term->slave); + if (term->slave <= 0) { + term->shutdown.client_has_terminated = true; + } else { + LOG_DBG("initiating asynchronous terminate of slave; " + "sending SIGTERM to PID=%u", term->slave); - kill(-term->slave, SIGTERM); + kill(-term->slave, SIGTERM); - const struct itimerspec timeout = {.it_value = {.tv_sec = 60}}; + const struct itimerspec timeout = {.it_value = {.tv_sec = 60}}; - int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); - if (timeout_fd < 0 || - timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0 || - !fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_terminate_timeout, term)) - { - if (timeout_fd >= 0) - close(timeout_fd); - LOG_ERRNO("failed to create slave terminate timeout FD"); - return false; + int timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC | TFD_NONBLOCK); + if (timeout_fd < 0 || + timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0 || + !fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_terminate_timeout, term)) + { + if (timeout_fd >= 0) + close(timeout_fd); + LOG_ERRNO("failed to create slave terminate timeout FD"); + return false; + } + + xassert(term->shutdown.terminate_timeout_fd < 0); + term->shutdown.terminate_timeout_fd = timeout_fd; } - - xassert(term->shutdown.terminate_timeout_fd < 0); - term->shutdown.terminate_timeout_fd = timeout_fd; } term->selection.auto_scroll.fd = -1; diff --git a/terminal.h b/terminal.h index 0dd40c51..eeff8af0 100644 --- a/terminal.h +++ b/terminal.h @@ -736,7 +736,8 @@ struct config; struct terminal *term_init( const struct config *conf, struct fdm *fdm, struct reaper *reaper, struct wayland *wayl, const char *foot_exe, const char *cwd, - const char *token, int argc, char *const *argv, const char *const *envp, + const char *token, const char *pty_path, + int argc, char *const *argv, const char *const *envp, void (*shutdown_cb)(void *data, int exit_code), void *shutdown_data); bool term_shutdown(struct terminal *term);