for some FOO; you can make \fIpterm\fP look them up under another
name, such as \fIxyz.FOO\fP, by specifying the command-line option
"-name xyz".
+.IP "\fBpterm.CloseOnExit\fP"
+This option should be set to 0, 1 or 2; the default is 1. It
+controls what \fIpterm\fP does when the process running inside it
+terminates. When set to 2, \fIpterm\fP will close its window as soon
+as the process inside it terminates. When set to 0, \fIpterm\fP will
+print the process's exit status, and the window will remain present
+until a key is pressed (allowing you to inspect the scrollback, and
+copy and paste text out of it).
+
+When this setting is set to 1 (the default), \fIpterm\fP will close
+immediately if the process exits cleanly (with an exit status of
+zero), but the window will stay around if the process exits with a
+non-zero code or on a signal. This enables you to see what went
+wrong if the process suffers an error, but not to have to bother
+closing the window in normal circumstances.
.IP "\fBpterm.TerminalType\fP"
This controls the value set in the TERM environment variable inside
the new terminal. The default is "xterm".
* back end, all running as a GTK application. Wish me luck.
*/
+#define _GNU_SOURCE
+
#include <string.h>
#include <assert.h>
#include <stdlib.h>
+#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <gtk/gtk.h>
int alt_keycode;
char wintitle[sizeof(((Config *)0)->wintitle)];
char icontitle[sizeof(((Config *)0)->wintitle)];
+ int master_fd, master_func_id, exited;
};
static struct gui_data the_inst;
return TRUE;
}
+void done_with_pty(struct gui_data *inst)
+{
+ extern void pty_close(void);
+
+ if (inst->master_fd >= 0) {
+ pty_close();
+ inst->master_fd = -1;
+ gtk_input_remove(inst->master_func_id);
+ }
+
+ if (!inst->exited && back->exitcode() >= 0) {
+ int exitcode = back->exitcode();
+ int clean;
+
+ clean = WIFEXITED(exitcode) && (WEXITSTATUS(exitcode) == 0);
+
+ /*
+ * Terminate now, if the Close On Exit setting is
+ * appropriate.
+ */
+ if (cfg.close_on_exit == COE_ALWAYS ||
+ (cfg.close_on_exit == COE_NORMAL && clean))
+ exit(0);
+
+ /*
+ * Otherwise, output an indication that the session has
+ * closed.
+ */
+ {
+ char message[512];
+ if (WIFEXITED(exitcode))
+ sprintf(message, "\r\n[pterm: process terminated with exit"
+ " code %d]\r\n", WEXITSTATUS(exitcode));
+ else if (WIFSIGNALED(exitcode))
+#ifdef HAVE_NO_STRSIGNAL
+ sprintf(message, "\r\n[pterm: process terminated on signal"
+ " %d]\r\n", WTERMSIG(exitcode));
+#else
+ sprintf(message, "\r\n[pterm: process terminated on signal"
+ " %d (%.400s)]\r\n", WTERMSIG(exitcode),
+ strsignal(WTERMSIG(exitcode)));
+#endif
+ from_backend((void *)term, 0, message, strlen(message));
+ }
+ inst->exited = 1;
+ }
+}
+
+void frontend_keypress(void)
+{
+ /*
+ * If our child process has exited but not closed, terminate on
+ * any keypress.
+ */
+ if (inst->exited)
+ exit(0);
+}
+
gint timer_func(gpointer data)
{
- /* struct gui_data *inst = (struct gui_data *)data; */
- extern int pty_child_is_dead(); /* declared in pty.c */
+ struct gui_data *inst = (struct gui_data *)data;
- if (pty_child_is_dead()) {
+ if (back->exitcode() >= 0) {
/*
* The primary child process died. We could keep the
* terminal open for remaining subprocesses to output to,
* but conventional wisdom seems to feel that that's the
- * Wrong Thing for an xterm-alike, so we bail out now. This
- * would be easy enough to change or make configurable if
- * necessary.
+ * Wrong Thing for an xterm-alike, so we bail out now
+ * (though we don't necessarily _close_ the window,
+ * depending on the state of Close On Exit). This would be
+ * easy enough to change or make configurable if necessary.
*/
- exit(0);
+ done_with_pty(inst);
}
term_update(term);
void pty_input_func(gpointer data, gint sourcefd, GdkInputCondition condition)
{
- /* struct gui_data *inst = (struct gui_data *)data; */
+ struct gui_data *inst = (struct gui_data *)data;
char buf[4096];
int ret;
* to happen. Boo.
*/
if (ret == 0 || (ret < 0 && errno == EIO)) {
- exit(0);
- }
-
- if (ret < 0) {
+ done_with_pty(inst);
+ } else if (ret < 0) {
perror("read pty master");
exit(1);
- }
- if (ret > 0)
+ } else if (ret > 0)
from_backend(term, 0, buf, ret);
term_blink(term, 1);
term_out(term);
term_size(term, cfg.height, cfg.width, cfg.savelines);
ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */
- gdk_input_add(pty_master_fd, GDK_INPUT_READ, pty_input_func, inst);
+ inst->master_fd = pty_master_fd;
+ inst->exited = FALSE;
+ inst->master_func_id = gdk_input_add(pty_master_fd, GDK_INPUT_READ,
+ pty_input_func, inst);
gtk_main();
static int pty_utmp_helper_pid, pty_utmp_helper_pipe;
static int pty_term_width, pty_term_height;
static sig_atomic_t pty_child_dead;
+static int pty_exit_code;
#ifndef OMIT_UTMP
static struct utmp utmp_entry;
#endif
char **pty_argv;
-int pty_child_is_dead(void)
-{
- return pty_child_dead;
-}
-
static void setup_utmp(char *ttyname, char *location)
{
#ifndef OMIT_UTMP
pid_t pid;
int status;
pid = waitpid(-1, &status, WNOHANG);
- if (pid == pty_child_pid && (WIFEXITED(status) || WIFSIGNALED(status)))
- pty_child_dead = TRUE;
+ if (pid == pty_child_pid && (WIFEXITED(status) || WIFSIGNALED(status))) {
+ pty_exit_code = status;
+ pty_child_dead = TRUE;
+ }
}
static void fatal_sig_handler(int signum)
*/
static int pty_send(char *buf, int len)
{
+ if (pty_master_fd < 0)
+ return 0; /* ignore all writes if fd closed */
+
while (len > 0) {
int ret = write(pty_master_fd, buf, len);
if (ret < 0) {
return 0;
}
+void pty_close(void)
+{
+ if (pty_master_fd >= 0) {
+ close(pty_master_fd);
+ pty_master_fd = -1;
+ }
+ close(pty_utmp_helper_pipe); /* this causes utmp to be cleaned up */
+}
+
/*
* Called to query the current socket sendability status.
*/
static int pty_exitcode(void)
{
- /* Shouldn't ever be required */
- return 0;
+ if (!pty_child_dead)
+ return -1; /* not dead yet */
+ else
+ return pty_exit_code;
}
Backend pty_backend = {