mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-04 04:06:06 -05:00
Add support for opening an existing PTY
Virtual machine monitor programs (e.g. QEMU, Cloud Hypervisor) expose guest consoles as PTYs. With this patch, foot can access these guest consoles. Usually, the program used for accessing these PTYs is screen, but screen is barely developed, doesn't support resizing, and has a bunch of other unrelated stuff going on. It would be nice to have a terminal emulator that properly supported opening an existing PTY. The VMM controls the master end of the PTY, so to the other end (in this case foot), it just behaves like any application running in a directly-opened PTY, and all that's needed is to change foot's code to support opening an existing PTY rather than creating one. Co-authored-by: tanto <tanto@ccc.ac>
This commit is contained in:
parent
712bc95db3
commit
86894a1cd2
9 changed files with 76 additions and 32 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
: ;;
|
||||
*)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)]' \
|
||||
|
|
|
|||
|
|
@ -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_.
|
||||
|
|
|
|||
19
main.c
19
main.c
|
|
@ -3,6 +3,7 @@
|
|||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
2
server.c
2
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) {
|
||||
|
|
|
|||
67
terminal.c
67
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue