From 74aca06df856c5518daf31d2b7a00cc3d798fcaf Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 29 Mar 2003 18:30:14 +0000 Subject: [PATCH] pterm now uses the new uxsel module, so it's one step closer to being able to be a PuTTY as well as a pterm. In the process I've also moved icky things like actually reading from the pty fd and printing the `terminated on signal' messages into pty.c where they obviously should have been in the first place. Also there's been one interesting repercussion in the terminal code: terminal.c's from_backend now calls term_out() directly rather than expecting the front end to call it afterwards. This has had the entertaining side effect of fixing a Windows-specific bug whereby activity in a port forwarding through a PuTTY with a blinking cursor caused the cursor to blink to ON (!!!!). So, a surprisingly far-reaching checkin as it turns out... git-svn-id: svn://svn.tartarus.org/sgt/putty@3017 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 2 +- terminal.c | 8 ++++ terminal.h | 10 +++++ unix/pterm.c | 118 +++++++++++++------------------------------------ unix/pty.c | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++------- unix/unix.h | 3 ++ unix/uxplink.c | 7 +++ unix/uxsel.c | 4 ++ window.c | 3 -- 9 files changed, 183 insertions(+), 109 deletions(-) diff --git a/Recipe b/Recipe index 1040bb99..ad04c4f8 100644 --- a/Recipe +++ b/Recipe @@ -150,7 +150,7 @@ puttygen : [G] puttygen sshrsag sshdssg sshprime sshdes sshbn sshmd5 version + sshpubk sshaes sshsh512 import winutils puttygen.res tree234 LIBS pterm : [X] pterm terminal wcwidth uxucs uxmisc tree234 misc ldisc ldiscucs - + logging uxprint settings pty be_none uxstore signal CHARSET + + logging uxprint settings pty uxsel be_none uxstore signal CHARSET plink : [U] uxplink uxcons NONSSH UXSSH be_all logging UXMISC signal ux_x11 diff --git a/terminal.c b/terminal.c index 362d8b58..cd32a7e4 100644 --- a/terminal.c +++ b/terminal.c @@ -418,6 +418,7 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, term->attr_mask = 0xffffffff; term->resize_fn = NULL; term->resize_ctx = NULL; + term->in_term_out = FALSE; return term; } @@ -4715,6 +4716,13 @@ int from_backend(void *vterm, int is_stderr, const char *data, int len) bufchain_add(&term->inbuf, data, len); + if (!term->in_term_out) { + term->in_term_out = TRUE; + term_blink(term, 1); + term_out(term); + term->in_term_out = FALSE; + } + /* * term_out() always completely empties inbuf. Therefore, * there's no reason at all to return anything other than zero diff --git a/terminal.h b/terminal.h index 647b55db..0050c0e2 100644 --- a/terminal.h +++ b/terminal.h @@ -198,6 +198,16 @@ struct terminal_tag { * than only the default. */ Config cfg; + + /* + * from_backend calls term_out, but it can also be called from + * the ldisc if the ldisc is called _within_ term_out. So we + * have to guard against re-entrancy - if from_backend is + * called recursively like this, it will simply add data to the + * end of the buffer term_out is in the process of working + * through. + */ + int in_term_out; }; #define in_utf(term) ((term)->utf || (term)->ucsdata->line_codepage==CP_UTF8) diff --git a/unix/pterm.c b/unix/pterm.c index 6d6a9a58..d04b9e3d 100644 --- a/unix/pterm.c +++ b/unix/pterm.c @@ -61,12 +61,13 @@ struct gui_data { int alt_digits; char wintitle[sizeof(((Config *)0)->wintitle)]; char icontitle[sizeof(((Config *)0)->wintitle)]; - int master_fd, master_func_id, exited; + int master_fd, master_func_id; void *ldisc; Backend *back; void *backhandle; Terminal *term; void *logctx; + int exited; struct unicode_data ucsdata; Config cfg; }; @@ -989,54 +990,6 @@ gint motion_event(GtkWidget *widget, GdkEventMotion *event, gpointer data) 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 && inst->back->exitcode(inst->backhandle) >= 0) { - int exitcode = inst->back->exitcode(inst->backhandle); - int clean; - - clean = WIFEXITED(exitcode) && (WEXITSTATUS(exitcode) == 0); - - /* - * Terminate now, if the Close On Exit setting is - * appropriate. - */ - if (inst->cfg.close_on_exit == FORCE_ON || - (inst->cfg.close_on_exit == AUTO && 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 *)inst->term, 0, message, strlen(message)); - } - inst->exited = 1; - } -} - void frontend_keypress(void *handle) { struct gui_data *inst = (struct gui_data *)handle; @@ -1052,18 +1005,13 @@ void frontend_keypress(void *handle) gint timer_func(gpointer data) { struct gui_data *inst = (struct gui_data *)data; + int exitcode; - if (inst->back->exitcode(inst->backhandle) >= 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 - * (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. - */ - done_with_pty(inst); + if ((exitcode = inst->back->exitcode(inst->backhandle)) >= 0) { + inst->exited = TRUE; + if (inst->cfg.close_on_exit == FORCE_ON || + (inst->cfg.close_on_exit == AUTO && exitcode == 0)) + exit(0); /* just go. */ } term_update(inst->term); @@ -1071,28 +1019,12 @@ gint timer_func(gpointer data) return TRUE; } -void pty_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) +void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) { - struct gui_data *inst = (struct gui_data *)data; - char buf[4096]; - int ret; - - ret = read(sourcefd, buf, sizeof(buf)); - - /* - * Clean termination condition is that either ret == 0, or ret - * < 0 and errno == EIO. Not sure why the latter, but it seems - * to happen. Boo. - */ - if (ret == 0 || (ret < 0 && errno == EIO)) { - done_with_pty(inst); - } else if (ret < 0) { - perror("read pty master"); - exit(1); - } else if (ret > 0) - from_backend(inst->term, 0, buf, ret); - term_blink(inst->term, 1); - term_out(inst->term); + select_result(sourcefd, + (condition == GDK_INPUT_READ ? 1 : + condition == GDK_INPUT_WRITE ? 2 : + condition == GDK_INPUT_EXCEPTION ? 4 : -1)); } void destroy(GtkWidget *widget, gpointer data) @@ -2275,9 +2207,20 @@ static int set_font_info(struct gui_data *inst, int fontid) return retval; } +int uxsel_input_add(int fd, int rwx) { + int flags = 0; + if (rwx & 1) flags |= GDK_INPUT_READ; + if (rwx & 2) flags |= GDK_INPUT_WRITE; + if (rwx & 4) flags |= GDK_INPUT_EXCEPTION; + return gdk_input_add(fd, flags, fd_input_func, NULL); +} + +void uxsel_input_remove(int id) { + gdk_input_remove(id); +} + int main(int argc, char **argv) { - extern int pty_master_fd; /* declared in pty.c */ extern void pty_pre_init(void); /* declared in pty.c */ struct gui_data *inst; int font_charset; @@ -2452,6 +2395,8 @@ int main(int argc, char **argv) inst->logctx = log_init(inst, &inst->cfg); term_provide_logctx(inst->term, inst->logctx); + uxsel_init(); + inst->back = &pty_backend; inst->back->init((void *)inst->term, &inst->backhandle, &inst->cfg, NULL, 0, NULL, 0); @@ -2465,15 +2410,12 @@ int main(int argc, char **argv) ldisc_create(&inst->cfg, inst->term, inst->back, inst->backhandle, inst); ldisc_send(inst->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */ - 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); - /* now we're reday to deal with the child exit handler being * called */ block_signal(SIGCHLD, 0); - + + inst->exited = FALSE; + gtk_main(); return 0; diff --git a/unix/pty.c b/unix/pty.c index 6d90abbd..eed8ecf7 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -13,6 +13,7 @@ #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED +#define _GNU_SOURCE #include #include @@ -68,20 +69,24 @@ #endif #endif -int pty_master_fd; +static Config pty_cfg; +static int pty_master_fd; static void *pty_frontend; static char pty_name[FILENAME_MAX]; +static int pty_signal_pipe[2]; static int pty_stamped_utmp = 0; static int pty_child_pid; static int pty_utmp_helper_pid, pty_utmp_helper_pipe; static int pty_term_width, pty_term_height; -static volatile sig_atomic_t pty_child_dead; -static volatile int pty_exit_code; +static int pty_child_dead, pty_finished; +static int pty_exit_code; #ifndef OMIT_UTMP static struct utmp utmp_entry; #endif char **pty_argv; +static void pty_close(void); + static void setup_utmp(char *ttyname, char *location) { #ifndef OMIT_UTMP @@ -164,18 +169,7 @@ static void cleanup_utmp(void) static void sigchld_handler(int signum) { - int save_errno = errno; - pid_t pid; - int status; - - do { - pid = waitpid(-1, &status, WNOHANG); - if (pid == pty_child_pid && (WIFEXITED(status) || WIFSIGNALED(status))) { - pty_exit_code = status; - pty_child_dead = TRUE; - } - } while(pid > 0); - errno = save_errno; + write(pty_signal_pipe[1], "x", 1); } static void fatal_sig_handler(int signum) @@ -381,6 +375,107 @@ void pty_pre_init(void) } } +int pty_select_result(int fd, int event) +{ + char buf[4096]; + int ret; + int finished = FALSE; + + if (fd == pty_master_fd && event == 1) { + + ret = read(pty_master_fd, buf, sizeof(buf)); + + /* + * Clean termination condition is that either ret == 0, or ret + * < 0 and errno == EIO. Not sure why the latter, but it seems + * to happen. Boo. + */ + if (ret == 0 || (ret < 0 && errno == EIO)) { + /* + * We assume a clean exit if the pty has closed but the + * actual child process hasn't. The only way I can + * imagine this happening is if it detaches itself from + * the pty and goes daemonic - in which case the + * expected usage model would precisely _not_ be for + * the pterm window to hang around! + */ + finished = TRUE; + if (!pty_child_dead) + pty_exit_code = 0; + } else if (ret < 0) { + perror("read pty master"); + exit(1); + } else if (ret > 0) { + from_backend(pty_frontend, 0, buf, ret); + } + } else if (fd == pty_signal_pipe[0]) { + pid_t pid; + int status; + char c[1]; + + read(pty_signal_pipe[0], c, 1); /* ignore its value; it'll be `x' */ + + do { + pid = waitpid(-1, &status, WNOHANG); + if (pid == pty_child_pid && + (WIFEXITED(status) || WIFSIGNALED(status))) { + /* + * 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 (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. + */ + pty_exit_code = status; + pty_child_dead = TRUE; + finished = TRUE; + } + } while(pid > 0); + } + + if (finished && !pty_finished) { + uxsel_del(pty_master_fd); + pty_close(); + pty_master_fd = -1; + + pty_finished = TRUE; + + /* + * This is a slight layering-violation sort of hack: only + * if we're not closing on exit (COE is set to Never, or to + * Only On Clean and it wasn't a clean exit) do we output a + * `terminated' message. + */ + if (pty_cfg.close_on_exit == FORCE_OFF || + (pty_cfg.close_on_exit == AUTO && pty_exit_code != 0)) { + char message[512]; + if (WIFEXITED(pty_exit_code)) + sprintf(message, "\r\n[pterm: process terminated with exit" + " code %d]\r\n", WEXITSTATUS(pty_exit_code)); + else if (WIFSIGNALED(pty_exit_code)) +#ifdef HAVE_NO_STRSIGNAL + sprintf(message, "\r\n[pterm: process terminated on signal" + " %d]\r\n", WTERMSIG(pty_exit_code)); +#else + sprintf(message, "\r\n[pterm: process terminated on signal" + " %d (%.400s)]\r\n", WTERMSIG(pty_exit_code), + strsignal(WTERMSIG(pty_exit_code))); +#endif + from_backend(pty_frontend, 0, message, strlen(message)); + } + } + return !finished; +} + +static void pty_uxsel_setup(void) +{ + uxsel_set(pty_master_fd, 1, pty_select_result); + uxsel_set(pty_signal_pipe[0], 1, pty_select_result); +} + /* * Called to set up the pty. * @@ -399,6 +494,7 @@ static char *pty_init(void *frontend, void **backend_handle, Config *cfg, pty_frontend = frontend; *backend_handle = NULL; /* we can't sensibly use this, sadly */ + pty_cfg = *cfg; /* structure copy */ pty_term_width = cfg->width; pty_term_height = cfg->height; @@ -514,8 +610,15 @@ static char *pty_init(void *frontend, void **backend_handle, Config *cfg, } else { pty_child_pid = pid; pty_child_dead = FALSE; + pty_finished = FALSE; } + if (pipe(pty_signal_pipe) < 0) { + perror("pipe"); + exit(1); + } + pty_uxsel_setup(); + return NULL; } @@ -554,7 +657,7 @@ static int pty_send(void *handle, char *buf, int len) return 0; } -void pty_close(void) +static void pty_close(void) { if (pty_master_fd >= 0) { close(pty_master_fd); @@ -632,7 +735,7 @@ static void pty_provide_logctx(void *handle, void *logctx) static int pty_exitcode(void *handle) { - if (!pty_child_dead) + if (!pty_finished) return -1; /* not dead yet */ else return pty_exit_code; diff --git a/unix/unix.h b/unix/unix.h index 9fd15699..16156c93 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -69,6 +69,9 @@ void uxsel_del(int fd); int select_result(int fd, int event); int first_fd(int *state, int *rwx); int next_fd(int *state, int *rwx); +/* The following are expected to be provided _to_ uxsel.c by the frontend */ +int uxsel_input_add(int fd, int rwx); /* returns an id */ +void uxsel_input_remove(int id); /* uxcfg.c */ struct controlbox; diff --git a/unix/uxplink.c b/unix/uxplink.c index a62d34eb..451d1121 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -237,6 +237,13 @@ void sigwinch(int signum) } /* + * In Plink our selects are synchronous, so these functions are + * empty stubs. + */ +int uxsel_input_add(int fd, int rwx) { return 0; } +void uxsel_input_remove(int id) { } + +/* * Short description of parameters. */ static void usage(void) diff --git a/unix/uxsel.c b/unix/uxsel.c index aaedc022..7142acb2 100644 --- a/unix/uxsel.c +++ b/unix/uxsel.c @@ -19,6 +19,7 @@ struct fd { int fd; int rwx; /* 4=except 2=write 1=read */ uxsel_callback_fn callback; + int id; /* for uxsel_input_remove */ }; static tree234 *fds; @@ -70,17 +71,20 @@ void uxsel_set(int fd, int rwx, uxsel_callback_fn callback) oldfd = find234(fds, newfd, NULL); if (oldfd) { + uxsel_input_remove(oldfd->id); del234(fds, oldfd); sfree(oldfd); } add234(fds, newfd); + newfd->id = uxsel_input_add(fd, rwx); } void uxsel_del(int fd) { struct fd *oldfd = find234(fds, &fd, uxsel_fd_findcmp); if (oldfd) { + uxsel_input_remove(oldfd->id); del234(fds, oldfd); sfree(oldfd); } diff --git a/window.c b/window.c index 5d6b2cf3..326d19c9 100644 --- a/window.c +++ b/window.c @@ -788,9 +788,6 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (pending_netevent) { enact_pending_netevent(); - /* Force the cursor blink on */ - term_blink(term, 1); - if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) continue; } -- 2.11.0