From 39934deb5202149f98198c111a35c21cb4d0d0f8 Mon Sep 17 00:00:00 2001 From: simon Date: Sat, 27 Nov 2004 13:20:21 +0000 Subject: [PATCH] New timing infrastructure. There's a new function schedule_timer() which pretty much any module can call to request a call-back in the future. So terminal.c can do its own handling of blinking, visual bells and deferred screen updates, without having to rely on term_update() being called 50 times a second (fixes: pterm-timer); and ssh.c and telnet.c both invoke a new module pinger.c which takes care of sending keepalives, so they get sent uniformly in all front ends (fixes: plink-keepalives, unix-keepalives). git-svn-id: svn://svn.tartarus.org/sgt/putty@4906 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 15 ++- doc/udp.but | 20 ++-- mac/macterm.c | 3 +- misc.c | 23 ++++ misc.h | 3 + pinger.c | 71 ++++++++++++ psftp.c | 50 ++++----- psftp.h | 6 + putty.h | 44 ++++++++ raw.c | 1 + rlogin.c | 1 + ssh.c | 13 +++ telnet.c | 9 ++ terminal.c | 321 +++++++++++++++++++++++++++++++++-------------------- terminal.h | 20 +++- timing.c | 173 +++++++++++++++++++++++++++++ unix/pterm.c | 50 +++++++-- unix/pty.c | 2 + unix/unix.h | 4 +- unix/uxcons.c | 8 ++ unix/uxmisc.c | 9 +- unix/uxplink.c | 20 +++- unix/uxsftp.c | 148 ++++++++++++++++++------ unix/uxstore.c | 23 ---- windows/wincons.c | 8 ++ windows/window.c | 149 ++++++++----------------- windows/winnet.c | 10 ++ windows/winplink.c | 30 ++++- windows/winsftp.c | 236 +++++++++++++++++++++++++++++++++++++-- windows/winstuff.h | 2 + 30 files changed, 1106 insertions(+), 366 deletions(-) create mode 100644 pinger.c create mode 100644 timing.c diff --git a/Recipe b/Recipe index ac7171cc..18f5637a 100644 --- a/Recipe +++ b/Recipe @@ -182,14 +182,15 @@ GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint # Same thing on Unix. UXTERM = TERMINAL pterm uxcfg gtkdlg gtkcols gtkpanel uxucs uxprint xkeysym + + timing # Non-SSH back ends (putty, puttytel, plink). -NONSSH = telnet raw rlogin ldisc +NONSSH = telnet raw rlogin ldisc pinger # SSH back end (putty, plink, pscp, psftp). SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd - + sshaes sshsh512 sshbn wildcard + + sshaes sshsh512 sshbn wildcard pinger WINSSH = SSH winnoise winpgntc UXSSH = SSH uxnoise uxagentc MACSSH = SSH macnoise @@ -199,12 +200,10 @@ SFTP = sftp int64 logging # Miscellaneous objects appearing in all the network utilities (not # Pageant or PuTTYgen). -WINMISC = misc version winstore settings tree234 winnet proxy cmdline - + windefs winmisc pproxy -UXMISC = misc version uxstore settings tree234 uxsel uxnet proxy cmdline - + uxmisc uxproxy -MACMISC = misc version macstore settings tree234 macnet mtcpnet otnet proxy - + macmisc macabout pproxy +MISC = timing misc version settings tree234 proxy +WINMISC = MISC winstore winnet cmdline windefs winmisc pproxy +UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy +MACMISC = MISC macstore macnet mtcpnet otnet macmisc macabout pproxy # Character set library, for use in pterm. CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc diff --git a/doc/udp.but b/doc/udp.but index 8e425616..796b2c62 100644 --- a/doc/udp.but +++ b/doc/udp.but @@ -75,21 +75,23 @@ Some ports of PuTTY - notably the in-progress Mac port - are constrained by the operating system to run as a single process potentially managing multiple sessions. -Therefore, the platform-independent parts of PuTTY use \e{hardly -any} global variables. The very few that do exist, such as -\c{flags}, are tolerated because they are not specific to a -particular login session: instead, they define properties that are -expected to apply equally to \e{all} the sessions run by a single -PuTTY process. Any data that is specific to a particular network -session is stored in dynamically allocated data structures, and -pointers to these structures are passed around between functions. +Therefore, the platform-independent parts of PuTTY never use global +variables to store per-session data. The global variables that do +exist are tolerated because they are not specific to a particular +login session: \c{flags} defines properties that are expected to +apply equally to \e{all} the sessions run by a single PuTTY process, +the random number state in \cw{sshrand.c} and the timer list in +\cw{timing.c} serve all sessions equally, and so on. But most data +is specific to a particular network session, and is therefore stored +in dynamically allocated data structures, and pointers to these +structures are passed around between functions. Platform-specific code can reverse this decision if it likes. The Windows code, for historical reasons, stores most of its data as global variables. That's OK, because \e{on Windows} we know there is only one session per PuTTY process, so it's safe to do that. But changes to the platform-independent code should avoid introducing -any more global variables than already exist. +global variables, unless they are genuinely cross-session. \H{udp-pure-c} C, not C++ diff --git a/mac/macterm.c b/mac/macterm.c index 46c30f66..5831cd26 100644 --- a/mac/macterm.c +++ b/mac/macterm.c @@ -1,4 +1,4 @@ -/* $Id: macterm.c,v 1.78 2004/10/14 16:42:43 simon Exp $ */ +/* $Id$ */ /* * Copyright (c) 1999 Simon Tatham * Copyright (c) 1999, 2002 Ben Harris @@ -314,7 +314,6 @@ void mac_pollterm(void) Session *s; for (s = sesslist; s != NULL; s = s->next) { - term_out(s->term); term_update(s->term); } } diff --git a/misc.c b/misc.c index 50c38d57..474bf024 100644 --- a/misc.c +++ b/misc.c @@ -141,6 +141,29 @@ char *dupvprintf(const char *fmt, va_list ap) } } +/* + * Read an entire line of text from a file. Return a buffer + * malloced to be as big as necessary (caller must free). + */ +char *fgetline(FILE *fp) +{ + char *ret = snewn(512, char); + int size = 512, len = 0; + while (fgets(ret + len, size - len, fp)) { + len += strlen(ret + len); + if (ret[len-1] == '\n') + break; /* got a newline, we're done */ + size = len + 512; + ret = sresize(ret, size, char); + } + if (len == 0) { /* first fgets returned NULL */ + sfree(ret); + return NULL; + } + ret[len] = '\0'; + return ret; +} + /* ---------------------------------------------------------------------- * Base64 encoding routine. This is required in public-key writing * but also in HTTP proxy handling, so it's centralised here. diff --git a/misc.h b/misc.h index 7d0ee4d5..cd144b53 100644 --- a/misc.h +++ b/misc.h @@ -3,6 +3,7 @@ #include "puttymem.h" +#include /* for FILE * */ #include /* for va_list */ #ifndef FALSE @@ -20,6 +21,8 @@ char *dupcat(const char *s1, ...); char *dupprintf(const char *fmt, ...); char *dupvprintf(const char *fmt, va_list ap); +char *fgetline(FILE *fp); + void base64_encode_atom(unsigned char *data, int n, char *out); struct bufchain_granule; diff --git a/pinger.c b/pinger.c new file mode 100644 index 00000000..c9b75033 --- /dev/null +++ b/pinger.c @@ -0,0 +1,71 @@ +/* + * pinger.c: centralised module that deals with sending TS_PING + * keepalives, to avoid replicating this code in multiple backends. + */ + +#include "putty.h" + +struct pinger_tag { + int interval; + int pending; + long next; + Backend *back; + void *backhandle; +}; + +static void pinger_schedule(Pinger pinger); + +static void pinger_timer(void *ctx, long now) +{ + Pinger pinger = (Pinger)ctx; + + if (pinger->pending && now - pinger->next >= 0) { + pinger->back->special(pinger->backhandle, TS_PING); + pinger->pending = FALSE; + pinger_schedule(pinger); + } +} + +static void pinger_schedule(Pinger pinger) +{ + int next; + + if (!pinger->interval) { + pinger->pending = FALSE; /* cancel any pending ping */ + return; + } + + next = schedule_timer(pinger->interval * TICKSPERSEC, + pinger_timer, pinger); + if (!pinger->pending || next < pinger->next) { + pinger->next = next; + pinger->pending = TRUE; + } +} + +Pinger pinger_new(Config *cfg, Backend *back, void *backhandle) +{ + Pinger pinger = snew(struct pinger_tag); + + pinger->interval = cfg->ping_interval; + pinger->pending = FALSE; + pinger->back = back; + pinger->backhandle = backhandle; + pinger_schedule(pinger); + + return pinger; +} + +void pinger_reconfig(Pinger pinger, Config *oldcfg, Config *newcfg) +{ + if (oldcfg->ping_interval != newcfg->ping_interval) { + pinger->interval = newcfg->ping_interval; + pinger_schedule(pinger); + } +} + +void pinger_free(Pinger pinger) +{ + expire_timer_context(pinger); + sfree(pinger); +} diff --git a/psftp.c b/psftp.c index 66c59947..1def80eb 100644 --- a/psftp.c +++ b/psftp.c @@ -1361,45 +1361,34 @@ static int sftp_cmd_help(struct sftp_command *cmd) struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) { char *line; - int linelen, linesize; struct sftp_command *cmd; char *p, *q, *r; int quoting; - if ((mode == 0) || (modeflags & 1)) { - printf("psftp> "); - } - fflush(stdout); - cmd = snew(struct sftp_command); cmd->words = NULL; cmd->nwords = 0; cmd->wordssize = 0; line = NULL; - linesize = linelen = 0; - while (1) { - int len; - char *ret; - - linesize += 512; - line = sresize(line, linesize, char); - ret = fgets(line + linelen, linesize - linelen, fp); - - if (!ret || (linelen == 0 && line[0] == '\0')) { - cmd->obey = sftp_cmd_quit; - if ((mode == 0) || (modeflags & 1)) - printf("quit\n"); - return cmd; /* eof */ - } - len = linelen + strlen(line + linelen); - linelen += len; - if (line[linelen - 1] == '\n') { - linelen--; - line[linelen] = '\0'; - break; - } + + if (fp) { + if (modeflags & 1) + printf("psftp> "); + line = fgetline(fp); + } else { + line = ssh_sftp_get_cmdline("psftp> "); + } + + if (!line || !*line) { + cmd->obey = sftp_cmd_quit; + if ((mode == 0) || (modeflags & 1)) + printf("quit\n"); + return cmd; /* eof */ } + + line[strcspn(line, "\r\n")] = '\0'; + if (modeflags & 1) { printf("%s\n", line); } @@ -1464,7 +1453,8 @@ struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) } } - sfree(line); + sfree(line); + /* * Now parse the first word and assign a function. */ @@ -1551,7 +1541,7 @@ void do_sftp(int mode, int modeflags, char *batchfile) */ while (1) { struct sftp_command *cmd; - cmd = sftp_getcmd(stdin, 0, 0); + cmd = sftp_getcmd(NULL, 0, 0); if (!cmd) break; ret = cmd->obey(cmd); diff --git a/psftp.h b/psftp.h index 0034f84a..6ce24e8f 100644 --- a/psftp.h +++ b/psftp.h @@ -33,6 +33,12 @@ void get_file_times(char *filename, unsigned long *mtime, int ssh_sftp_loop_iteration(void); /* + * Read a command line for PSFTP from standard input. Caller must + * free. + */ +char *ssh_sftp_get_cmdline(char *prompt); + +/* * The main program in psftp.c. Called from main() in the platform- * specific code, after doing any platform-specific initialisation. */ diff --git a/putty.h b/putty.h index 061f6275..d4deb62b 100644 --- a/putty.h +++ b/putty.h @@ -578,6 +578,7 @@ void ldisc_update(void *frontend, int echo, int edit); * shutdown. */ void update_specials_menu(void *frontend); int from_backend(void *frontend, int is_stderr, const char *data, int len); +void notify_remote_exit(void *frontend); #define OPTIMISE_IS_SCROLL 1 void set_iconic(void *frontend, int iconic); @@ -745,6 +746,14 @@ void random_get_savedata(void **data, int *len); extern int random_active; /* + * Exports from pinger.c. + */ +typedef struct pinger_tag *Pinger; +Pinger pinger_new(Config *cfg, Backend *back, void *backhandle); +void pinger_reconfig(Pinger, Config *oldcfg, Config *newcfg); +void pinger_free(Pinger); + +/* * Exports from misc.c. */ @@ -895,4 +904,39 @@ int filename_is_null(Filename fn); char *get_username(void); /* return value needs freeing */ char *get_random_data(int bytes); /* used in cmdgen.c */ +/* + * Exports and imports from timing.c. + * + * schedule_timer() asks the front end to schedule a callback to a + * timer function in a given number of ticks. The returned value is + * the time (in ticks since an arbitrary offset) at which the + * callback can be expected. This value will also be passed as the + * `now' parameter to the callback function. Hence, you can (for + * example) schedule an event at a particular time by calling + * schedule_timer() and storing the return value in your context + * structure as the time when that event is due. The first time a + * callback function gives you that value or more as `now', you do + * the thing. + * + * expire_timer_context() drops all current timers associated with + * a given value of ctx (for when you're about to free ctx). + * + * run_timers() is called from the front end when it has reason to + * think some timers have reached their moment, or when it simply + * needs to know how long to wait next. We pass it the time we + * think it is. It returns TRUE and places the time when the next + * timer needs to go off in `next', or alternatively it returns + * FALSE if there are no timers at all pending. + * + * timer_change_notify() must be supplied by the front end; it + * notifies the front end that a new timer has been added to the + * list which is sooner than any existing ones. It provides the + * time when that timer needs to go off. + */ +typedef void (*timer_fn_t)(void *ctx, long now); +long schedule_timer(int ticks, timer_fn_t fn, void *ctx); +void expire_timer_context(void *ctx); +int run_timers(long now, long *next); +void timer_change_notify(long next); + #endif diff --git a/raw.c b/raw.c index 8c1f97fd..8b1b1e9b 100644 --- a/raw.c +++ b/raw.c @@ -37,6 +37,7 @@ static int raw_closing(Plug plug, const char *error_msg, int error_code, if (raw->s) { sk_close(raw->s); raw->s = NULL; + notify_remote_exit(raw->frontend); } if (error_msg) { /* A socket error has occurred. */ diff --git a/rlogin.c b/rlogin.c index f2b61d27..06c8b12b 100644 --- a/rlogin.c +++ b/rlogin.c @@ -39,6 +39,7 @@ static int rlogin_closing(Plug plug, const char *error_msg, int error_code, if (rlogin->s) { sk_close(rlogin->s); rlogin->s = NULL; + notify_remote_exit(rlogin->frontend); } if (error_msg) { /* A socket error has occurred. */ diff --git a/ssh.c b/ssh.c index 34260981..1d8e6b8d 100644 --- a/ssh.c +++ b/ssh.c @@ -698,6 +698,11 @@ struct ssh_tag { * with at any time. */ handler_fn_t packet_dispatch[256]; + + /* + * This module deals with sending keepalives. + */ + Pinger pinger; }; #define logevent(s) logevent(ssh->frontend, s) @@ -2157,6 +2162,7 @@ static int do_ssh_init(Ssh ssh, unsigned char c) } update_specials_menu(ssh->frontend); ssh->state = SSH_STATE_BEFORE_SIZE; + ssh->pinger = pinger_new(&ssh->cfg, &ssh_backend, ssh); sfree(s->vstring); @@ -2216,6 +2222,7 @@ static void ssh_do_close(Ssh ssh) if (ssh->s) { sk_close(ssh->s); ssh->s = NULL; + notify_remote_exit(ssh->frontend); } /* * Now we must shut down any port and X forwardings going @@ -2327,6 +2334,7 @@ static const char *connect_to_host(Ssh ssh, char *host, int port, 0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg); if ((err = sk_socket_error(ssh->s)) != NULL) { ssh->s = NULL; + notify_remote_exit(ssh->frontend); return err; } @@ -6933,6 +6941,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->protocol_initial_phase_done = FALSE; + ssh->pinger = NULL; + p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); if (p != NULL) return p; @@ -7012,6 +7022,8 @@ static void ssh_free(void *handle) if (ssh->s) ssh_do_close(ssh); sfree(ssh); + if (ssh->pinger) + pinger_free(ssh->pinger); } /* @@ -7026,6 +7038,7 @@ static void ssh_free(void *handle) static void ssh_reconfig(void *handle, Config *cfg) { Ssh ssh = (Ssh) handle; + pinger_reconfig(ssh->pinger, &ssh->cfg, cfg); ssh->cfg = *cfg; /* STRUCTURE COPY */ } diff --git a/telnet.c b/telnet.c index af40c856..8b2c4289 100644 --- a/telnet.c +++ b/telnet.c @@ -244,6 +244,8 @@ typedef struct telnet_tag { } state; Config cfg; + + Pinger pinger; } *Telnet; #define TELNET_MAX_BACKLOG 4096 @@ -644,6 +646,7 @@ static int telnet_closing(Plug plug, const char *error_msg, int error_code, if (telnet->s) { sk_close(telnet->s); telnet->s = NULL; + notify_remote_exit(telnet->frontend); } if (error_msg) { /* A socket error has occurred. */ @@ -704,6 +707,7 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, telnet->term_height = telnet->cfg.height; telnet->state = TOP_LEVEL; telnet->ldisc = NULL; + telnet->pinger = NULL; *backend_handle = telnet; /* @@ -739,6 +743,8 @@ static const char *telnet_init(void *frontend_handle, void **backend_handle, if ((err = sk_socket_error(telnet->s)) != NULL) return err; + telnet->pinger = pinger_new(&telnet->cfg, &telnet_backend, telnet); + /* * Initialise option states. */ @@ -778,6 +784,8 @@ static void telnet_free(void *handle) sfree(telnet->sb_buf); if (telnet->s) sk_close(telnet->s); + if (telnet->pinger) + pinger_free(telnet->pinger); sfree(telnet); } /* @@ -788,6 +796,7 @@ static void telnet_free(void *handle) static void telnet_reconfig(void *handle, Config *cfg) { Telnet telnet = (Telnet) handle; + pinger_reconfig(telnet->pinger, &telnet->cfg, cfg); telnet->cfg = *cfg; /* STRUCTURE COPY */ } diff --git a/terminal.c b/terminal.c index f8d92dce..f070579b 100644 --- a/terminal.c +++ b/terminal.c @@ -43,6 +43,11 @@ #define TM_PUTTY (0xFFFF) +#define UPDATE_DELAY ((TICKSPERSEC+49)/50)/* ticks to defer window update */ +#define TBLINK_DELAY ((TICKSPERSEC*9+19)/20)/* ticks between text blinks*/ +#define CBLINK_DELAY (CURSORBLINK) /* ticks between cursor blinks */ +#define VBELL_DELAY (VBELL_TIMEOUT) /* visual bell timeout in ticks */ + #define compatibility(x) \ if ( ((CL_##x)&term->compatibility_level) == 0 ) { \ term->termstate=TOPLEVEL; \ @@ -987,6 +992,117 @@ static termline *lineptr(Terminal *term, int y, int lineno, int screen) #define lineptr(x) (lineptr)(term,x,__LINE__,FALSE) #define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) +static void term_schedule_tblink(Terminal *term); +static void term_schedule_cblink(Terminal *term); + +static void term_timer(void *ctx, long now) +{ + Terminal *term = (Terminal *)ctx; + int update = FALSE; + + if (term->tblink_pending && now - term->next_tblink >= 0) { + term->tblinker = !term->tblinker; + term->tblink_pending = FALSE; + term_schedule_tblink(term); + update = TRUE; + } + + if (term->cblink_pending && now - term->next_cblink >= 0) { + term->cblinker = !term->cblinker; + term->cblink_pending = FALSE; + term_schedule_cblink(term); + update = TRUE; + } + + if (term->in_vbell && now - term->vbell_end >= 0) { + term->in_vbell = FALSE; + update = TRUE; + } + + if (update || + (term->window_update_pending && now - term->next_update >= 0)) + term_update(term); +} + +/* + * Call this whenever the terminal window state changes, to queue + * an update. + */ +static void seen_disp_event(Terminal *term) +{ + term->seen_disp_event = TRUE; /* for scrollback-reset-on-activity */ + if (!term->window_update_pending) { + term->window_update_pending = TRUE; + term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term); + } +} + +/* + * Call when the terminal's blinking-text settings change, or when + * a text blink has just occurred. + */ +static void term_schedule_tblink(Terminal *term) +{ + if (term->tblink_pending) + return; /* already well in hand */ + if (term->blink_is_real) { + term->next_tblink = schedule_timer(TBLINK_DELAY, term_timer, term); + term->tblink_pending = TRUE; + } else { + term->tblinker = 1; /* reset when not in use */ + term->tblink_pending = FALSE; + } +} + +/* + * Likewise with cursor blinks. + */ +static void term_schedule_cblink(Terminal *term) +{ + if (term->cblink_pending) + return; /* already well in hand */ + if (term->cfg.blink_cur) { + term->next_cblink = schedule_timer(CBLINK_DELAY, term_timer, term); + term->cblink_pending = TRUE; + } else { + term->cblinker = 1; /* reset when not in use */ + term->cblink_pending = FALSE; + } +} + +/* + * Call to reset cursor blinking on new output. + */ +static void term_reset_cblink(Terminal *term) +{ + seen_disp_event(term); + term->cblinker = 1; + term->cblink_pending = FALSE; + term_schedule_cblink(term); +} + +/* + * Call to begin a visual bell. + */ +static void term_schedule_vbell(Terminal *term, int already_started, + long startpoint) +{ + long ticks_already_gone; + + if (already_started) + ticks_already_gone = GETTICKCOUNT() - startpoint; + else + ticks_already_gone = 0; + + if (ticks_already_gone < VBELL_DELAY) { + term->in_vbell = TRUE; + term->vbell_end = schedule_timer(VBELL_DELAY - ticks_already_gone, + term_timer, term); + } else { + term->in_vbell = FALSE; + } +} + /* * Set up power-on settings for the terminal. */ @@ -1038,6 +1154,8 @@ static void power_on(Terminal *term) swap_screen(term, 0, FALSE, FALSE); erase_lots(term, FALSE, TRUE, TRUE); } + term_schedule_tblink(term); + term_schedule_cblink(term); } /* @@ -1046,6 +1164,9 @@ static void power_on(Terminal *term) void term_update(Terminal *term) { Context ctx; + + term->window_update_pending = FALSE; + ctx = get_ctx(term->frontend); if (ctx) { int need_sbar_update = term->seen_disp_event; @@ -1090,7 +1211,7 @@ void term_seen_key_event(Terminal *term) */ if (term->cfg.scroll_on_key) { term->disptop = 0; /* return to main screen */ - term->seen_disp_event = 1; + seen_disp_event(term); } } @@ -1130,13 +1251,13 @@ void term_reconfig(Terminal *term, Config *cfg) * default one. The full list is: Auto wrap mode, DEC Origin * Mode, BCE, blinking text, character classes. */ - int reset_wrap, reset_decom, reset_bce, reset_blink, reset_charclass; + int reset_wrap, reset_decom, reset_bce, reset_tblink, reset_charclass; int i; reset_wrap = (term->cfg.wrap_mode != cfg->wrap_mode); reset_decom = (term->cfg.dec_om != cfg->dec_om); reset_bce = (term->cfg.bce != cfg->bce); - reset_blink = (term->cfg.blinktext != cfg->blinktext); + reset_tblink = (term->cfg.blinktext != cfg->blinktext); reset_charclass = 0; for (i = 0; i < lenof(term->cfg.wordness); i++) if (term->cfg.wordness[i] != cfg->wordness[i]) @@ -1168,8 +1289,9 @@ void term_reconfig(Terminal *term, Config *cfg) term->use_bce = term->cfg.bce; set_erase_char(term); } - if (reset_blink) + if (reset_tblink) { term->blink_is_real = term->cfg.blinktext; + } if (reset_charclass) for (i = 0; i < 256; i++) term->wordness[i] = term->cfg.wordness[i]; @@ -1188,6 +1310,8 @@ void term_reconfig(Terminal *term, Config *cfg) if (!*term->cfg.printer) { term_print_finish(term); } + term_schedule_tblink(term); + term_schedule_cblink(term); } /* @@ -1224,7 +1348,7 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, term->logctx = NULL; term->compatibility_level = TM_PUTTY; strcpy(term->id_string, "\033[?6c"); - term->last_blink = term->last_tblink = 0; + term->cblink_pending = term->tblink_pending = FALSE; term->paste_buffer = NULL; term->paste_len = 0; term->last_paste = 0; @@ -1237,7 +1361,7 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, term->seen_disp_event = FALSE; term->xterm_mouse = term->mouse_is_down = FALSE; term->reset_132 = FALSE; - term->blinker = term->tblinker = 0; + term->cblinker = term->tblinker = 0; term->has_focus = 1; term->repeat_off = FALSE; term->termstate = TOPLEVEL; @@ -1271,6 +1395,8 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata, term->wcTo = NULL; term->wcFromTo_size = 0; + term->window_update_pending = FALSE; + term->bidi_cache_size = 0; term->pre_bidi_cache = term->post_bidi_cache = NULL; @@ -1324,6 +1450,8 @@ void term_free(Terminal *term) sfree(term->pre_bidi_cache); sfree(term->post_bidi_cache); + expire_timer_context(term); + sfree(term); } @@ -2044,8 +2172,6 @@ static void insch(Terminal *term, int n) */ static void toggle_mode(Terminal *term, int mode, int query, int state) { - unsigned long ticks; - if (query) switch (mode) { case 1: /* DECCKM: application cursor keys */ @@ -2059,6 +2185,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) } else { term->blink_is_real = term->cfg.blinktext; } + term_schedule_tblink(term); break; case 3: /* DECCOLM: 80/132 columns */ deselect(term); @@ -2077,27 +2204,15 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) * effective visual bell, so that ESC[?5hESC[?5l will * always be an actually _visible_ visual bell. */ - ticks = GETTICKCOUNT(); - /* turn off a previous vbell to avoid inconsistencies */ - if (ticks - term->vbell_startpoint >= VBELL_TIMEOUT) - term->in_vbell = FALSE; - if (term->rvideo && !state && /* we're turning it off... */ - (ticks - term->rvbell_startpoint) < VBELL_TIMEOUT) {/*...soon*/ - /* If there's no vbell timeout already, or this one lasts - * longer, replace vbell_timeout with ours. */ - if (!term->in_vbell || - (term->rvbell_startpoint - term->vbell_startpoint < - VBELL_TIMEOUT)) - term->vbell_startpoint = term->rvbell_startpoint; - term->in_vbell = TRUE; /* may clear rvideo but set in_vbell */ + if (term->rvideo && !state) { + /* This is an OFF, so set up a vbell */ + term_schedule_vbell(term, TRUE, term->rvbell_startpoint); } else if (!term->rvideo && state) { /* This is an ON, so we notice the time and save it. */ - term->rvbell_startpoint = ticks; + term->rvbell_startpoint = GETTICKCOUNT(); } term->rvideo = state; - term->seen_disp_event = TRUE; - if (state) - term_update(term); + seen_disp_event(term); break; case 6: /* DECOM: DEC origin mode */ term->dec_om = state; @@ -2116,7 +2231,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 25: /* DECTCEM: enable/disable cursor */ compatibility2(OTHER, VT220); term->cursor_on = state; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 47: /* alternate screen */ compatibility(OTHER); @@ -2141,12 +2256,12 @@ static void toggle_mode(Terminal *term, int mode, int query, int state) case 1048: /* save/restore cursor */ if (!term->cfg.no_alt_screen) save_cursor(term, state); - if (!state) term->seen_disp_event = TRUE; + if (!state) seen_disp_event(term); break; case 1049: /* cursor & alternate screen */ if (state && !term->cfg.no_alt_screen) save_cursor(term, state); - if (!state) term->seen_disp_event = TRUE; + if (!state) seen_disp_event(term); compatibility(OTHER); deselect(term); swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, FALSE); @@ -2254,7 +2369,7 @@ static void term_print_finish(Terminal *term) * in-memory display. There's a big state machine in here to * process escape sequences... */ -void term_out(Terminal *term) +static void term_out(Terminal *term) { unsigned long c; int unget; @@ -2567,13 +2682,12 @@ void term_out(Terminal *term) */ if (!term->cfg.bellovl || !term->beep_overloaded) { beep(term->frontend, term->cfg.beep); + if (term->cfg.beep == BELL_VISUAL) { - term->in_vbell = TRUE; - term->vbell_startpoint = ticks; - term_update(term); + term_schedule_vbell(term, FALSE, 0); } } - term->seen_disp_event = TRUE; + seen_disp_event(term); } break; case '\b': /* BS: Back space */ @@ -2586,7 +2700,7 @@ void term_out(Terminal *term) term->wrapnext = FALSE; else term->curs.x--; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case '\016': /* LS1: Locking-shift one */ compatibility(VT100); @@ -2608,7 +2722,7 @@ void term_out(Terminal *term) case '\015': /* CR: Carriage return */ term->curs.x = 0; term->wrapnext = FALSE; - term->seen_disp_event = TRUE; + seen_disp_event(term); term->paste_hold = 0; if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); @@ -2619,7 +2733,7 @@ void term_out(Terminal *term) erase_lots(term, FALSE, FALSE, TRUE); term->disptop = 0; term->wrapnext = FALSE; - term->seen_disp_event = 1; + seen_disp_event(term); break; } case '\013': /* VT: Line tabulation */ @@ -2632,7 +2746,7 @@ void term_out(Terminal *term) if (term->cfg.lfhascr) term->curs.x = 0; term->wrapnext = FALSE; - term->seen_disp_event = 1; + seen_disp_event(term); term->paste_hold = 0; if (term->logctx) logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); @@ -2657,7 +2771,7 @@ void term_out(Terminal *term) check_selection(term, old_curs, term->curs); } - term->seen_disp_event = TRUE; + seen_disp_event(term); break; } } else @@ -2776,7 +2890,7 @@ void term_out(Terminal *term) } add_cc(cline, x, c); - term->seen_disp_event = 1; + seen_disp_event(term); } continue; default: @@ -2796,7 +2910,7 @@ void term_out(Terminal *term) term->wrapnext = FALSE; } } - term->seen_disp_event = 1; + seen_disp_event(term); } break; @@ -2841,7 +2955,7 @@ void term_out(Terminal *term) case '8': /* DECRC: restore cursor */ compatibility(VT100); save_cursor(term, FALSE); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case '=': /* DECKPAM: Keypad application mode */ compatibility(VT100); @@ -2858,7 +2972,7 @@ void term_out(Terminal *term) else if (term->curs.y < term->rows - 1) term->curs.y++; term->wrapnext = FALSE; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'E': /* NEL: exactly equivalent to CR-LF */ compatibility(VT100); @@ -2868,7 +2982,7 @@ void term_out(Terminal *term) else if (term->curs.y < term->rows - 1) term->curs.y++; term->wrapnext = FALSE; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'M': /* RI: reverse index - backwards LF */ compatibility(VT100); @@ -2877,7 +2991,7 @@ void term_out(Terminal *term) else if (term->curs.y > 0) term->curs.y--; term->wrapnext = FALSE; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'Z': /* DECID: terminal type query */ compatibility(VT100); @@ -2896,7 +3010,7 @@ void term_out(Terminal *term) term->reset_132 = 0; } term->disptop = 0; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'H': /* HTS: set a tab */ compatibility(VT100); @@ -2920,7 +3034,7 @@ void term_out(Terminal *term) ldata->lattr = LATTR_NORM; } term->disptop = 0; - term->seen_disp_event = TRUE; + seen_disp_event(term); scrtop.x = scrtop.y = 0; scrbot.x = 0; scrbot.y = term->rows; @@ -3036,7 +3150,7 @@ void term_out(Terminal *term) case 'A': /* CUU: move up N lines */ move(term, term->curs.x, term->curs.y - def(term->esc_args[0], 1), 1); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'e': /* VPR: move down N lines */ compatibility(ANSI); @@ -3044,7 +3158,7 @@ void term_out(Terminal *term) case 'B': /* CUD: Cursor down */ move(term, term->curs.x, term->curs.y + def(term->esc_args[0], 1), 1); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case ANSI('c', '>'): /* DA: report xterm version */ compatibility(OTHER); @@ -3059,31 +3173,31 @@ void term_out(Terminal *term) case 'C': /* CUF: Cursor right */ move(term, term->curs.x + def(term->esc_args[0], 1), term->curs.y, 1); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'D': /* CUB: move left N cols */ move(term, term->curs.x - def(term->esc_args[0], 1), term->curs.y, 1); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'E': /* CNL: move down N lines and CR */ compatibility(ANSI); move(term, 0, term->curs.y + def(term->esc_args[0], 1), 1); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'F': /* CPL: move up N lines and CR */ compatibility(ANSI); move(term, 0, term->curs.y - def(term->esc_args[0], 1), 1); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'G': /* CHA */ case '`': /* HPA: set horizontal posn */ compatibility(ANSI); move(term, def(term->esc_args[0], 1) - 1, term->curs.y, 0); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'd': /* VPA: set vertical posn */ compatibility(ANSI); @@ -3091,7 +3205,7 @@ void term_out(Terminal *term) ((term->dec_om ? term->marg_t : 0) + def(term->esc_args[0], 1) - 1), (term->dec_om ? 2 : 0)); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'H': /* CUP */ case 'f': /* HVP: set horz and vert posns at once */ @@ -3101,7 +3215,7 @@ void term_out(Terminal *term) ((term->dec_om ? term->marg_t : 0) + def(term->esc_args[0], 1) - 1), (term->dec_om ? 2 : 0)); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'J': /* ED: erase screen or parts of it */ { @@ -3111,7 +3225,7 @@ void term_out(Terminal *term) erase_lots(term, FALSE, !!(i & 2), !!(i & 1)); } term->disptop = 0; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'K': /* EL: erase line or parts of it */ { @@ -3120,14 +3234,14 @@ void term_out(Terminal *term) i = 0; erase_lots(term, TRUE, !!(i & 2), !!(i & 1)); } - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'L': /* IL: insert lines */ compatibility(VT102); if (term->curs.y <= term->marg_b) scroll(term, term->curs.y, term->marg_b, -def(term->esc_args[0], 1), FALSE); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'M': /* DL: delete lines */ compatibility(VT102); @@ -3135,18 +3249,18 @@ void term_out(Terminal *term) scroll(term, term->curs.y, term->marg_b, def(term->esc_args[0], 1), TRUE); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case '@': /* ICH: insert chars */ /* XXX VTTEST says this is vt220, vt510 manual says vt102 */ compatibility(VT102); insch(term, def(term->esc_args[0], 1)); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'P': /* DCH: delete chars */ compatibility(VT102); insch(term, -def(term->esc_args[0], 1)); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'c': /* DA: terminal type query */ compatibility(VT100); @@ -3244,7 +3358,7 @@ void term_out(Terminal *term) */ term->curs.y = (term->dec_om ? term->marg_t : 0); - term->seen_disp_event = TRUE; + seen_disp_event(term); } } break; @@ -3302,6 +3416,7 @@ void term_out(Terminal *term) compatibility(SCOANSI); term->blink_is_real = FALSE; term->curr_attr |= ATTR_BLINK; + term_schedule_tblink(term); break; case 7: /* enable reverse video */ term->curr_attr |= ATTR_REVERSE; @@ -3406,7 +3521,7 @@ void term_out(Terminal *term) break; case 'u': /* restore cursor */ save_cursor(term, FALSE); - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 't': /* DECSLPP: set page size - ie window height */ /* @@ -3548,14 +3663,14 @@ void term_out(Terminal *term) scroll(term, term->marg_t, term->marg_b, def(term->esc_args[0], 1), TRUE); term->wrapnext = FALSE; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case 'T': /* SD: Scroll down */ compatibility(SCOANSI); scroll(term, term->marg_t, term->marg_b, -def(term->esc_args[0], 1), TRUE); term->wrapnext = FALSE; - term->seen_disp_event = TRUE; + seen_disp_event(term); break; case ANSI('|', '*'): /* DECSNLS */ /* @@ -3608,7 +3723,7 @@ void term_out(Terminal *term) while (n--) copy_termchar(cline, p++, &term->erase_char); - term->seen_disp_event = TRUE; + seen_disp_event(term); } break; case 'x': /* DECREQTPARM: report terminal characteristics */ @@ -3672,6 +3787,7 @@ void term_out(Terminal *term) case ANSI('D', '='): compatibility(SCOANSI); term->blink_is_real = FALSE; + term_schedule_tblink(term); if (term->esc_args[0]>=1) term->curr_attr |= ATTR_BLINK; else @@ -3680,6 +3796,7 @@ void term_out(Terminal *term) case ANSI('E', '='): compatibility(SCOANSI); term->blink_is_real = (term->esc_args[0] >= 1); + term_schedule_tblink(term); break; case ANSI('F', '='): /* set normal foreground */ compatibility(SCOANSI); @@ -3910,7 +4027,7 @@ void term_out(Terminal *term) break; case VT52_ESC: term->termstate = TOPLEVEL; - term->seen_disp_event = TRUE; + seen_disp_event(term); switch (c) { case 'A': move(term, term->curs.x, term->curs.y - 1, 1); @@ -4018,6 +4135,7 @@ void term_out(Terminal *term) */ term->vt52_mode = FALSE; term->blink_is_real = term->cfg.blinktext; + term_schedule_tblink(term); break; #if 0 case '^': @@ -4245,7 +4363,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) wchar_t *ch; int chlen; termchar cursor_background; - unsigned long ticks; #ifdef OPTIMISE_SCROLL struct scrollregion *sr; #endif /* OPTIMISE_SCROLL */ @@ -4255,28 +4372,19 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) chlen = 1024; ch = snewn(chlen, wchar_t); - /* - * Check the visual bell state. - */ - if (term->in_vbell) { - ticks = GETTICKCOUNT(); - if (ticks - term->vbell_startpoint >= VBELL_TIMEOUT) - term->in_vbell = FALSE; - } - rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0); /* Depends on: * screen array, disptop, scrtop, * selection, rv, * cfg.blinkpc, blink_is_real, tblinker, - * curs.y, curs.x, blinker, cfg.blink_cur, cursor_on, has_focus, wrapnext + * curs.y, curs.x, cblinker, cfg.blink_cur, cursor_on, has_focus, wrapnext */ /* Has the cursor position or type changed ? */ if (term->cursor_on) { if (term->has_focus) { - if (term->blinker || !term->cfg.blink_cur) + if (term->cblinker || !term->cfg.blink_cur) cursor = TATTR_ACTCURS; else cursor = 0; @@ -4679,39 +4787,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) } /* - * Flick the switch that says if blinking things should be shown or hidden. - */ - -void term_blink(Terminal *term, int flg) -{ - long now, blink_diff; - - now = GETTICKCOUNT(); - blink_diff = now - term->last_tblink; - - /* Make sure the text blinks no more than 2Hz; we'll use 0.45 s period. */ - if (blink_diff < 0 || blink_diff > (TICKSPERSEC * 9 / 20)) { - term->last_tblink = now; - term->tblinker = !term->tblinker; - } - - if (flg) { - term->blinker = 1; - term->last_blink = now; - return; - } - - blink_diff = now - term->last_blink; - - /* Make sure the cursor blinks no faster than system blink rate */ - if (blink_diff >= 0 && blink_diff < (long) CURSORBLINK) - return; - - term->last_blink = now; - term->blinker = !term->blinker; -} - -/* * Invalidate the whole screen so it will be repainted in full. */ void term_invalidate(Terminal *term) @@ -4744,12 +4819,14 @@ void term_paint(Terminal *term, Context ctx, term->disptext[i]->chars[j].attr = ATTR_INVALID; } - /* This should happen soon enough, also for some reason it sometimes - * fails to actually do anything when re-sizing ... painting the wrong - * window perhaps ? - */ - if (immediately) + if (immediately) { do_paint (term, ctx, FALSE); + } else { + if (!term->window_update_pending) { + term->window_update_pending = TRUE; + term->next_update = schedule_timer(UPDATE_DELAY, term_timer, term); + } + } } /* @@ -5960,8 +6037,14 @@ int term_data(Terminal *term, int is_stderr, const char *data, int len) if (!term->in_term_out) { term->in_term_out = TRUE; - term_blink(term, 1); - term_out(term); + term_reset_cblink(term); + /* + * During drag-selects, we do not process terminal input, + * because the user will want the screen to hold still to + * be selected. + */ + if (term->selstate != DRAGGING) + term_out(term); term->in_term_out = FALSE; } diff --git a/terminal.h b/terminal.h index 66304117..924c8204 100644 --- a/terminal.h +++ b/terminal.h @@ -117,7 +117,7 @@ struct terminal_tag { int cursor_on; /* cursor enabled flag */ int reset_132; /* Flag ESC c resets to 80 cols */ int use_bce; /* Use Background coloured erase */ - int blinker; /* When blinking is the cursor on ? */ + int cblinker; /* When blinking is the cursor on ? */ int tblinker; /* When the blinking text is on */ int blink_is_real; /* Actually blink blinking text */ int term_echoing; /* Does terminal want local echo? */ @@ -136,15 +136,12 @@ struct terminal_tag { int rows, cols, savelines; int has_focus; int in_vbell; - unsigned long vbell_startpoint; + long vbell_end; int app_cursor_keys, app_keypad_keys, vt52_mode; int repeat_off, cr_lf_return; int seen_disp_event; int big_cursor; - long last_blink; /* used for real blinking control */ - long last_tblink; - int xterm_mouse; /* send mouse messages to app */ int mouse_is_down; /* used while tracking mouse buttons */ @@ -246,6 +243,19 @@ struct terminal_tag { int in_term_out; /* + * We schedule a window update shortly after receiving terminal + * data. This tracks whether one is currently pending. + */ + int window_update_pending; + long next_update; + + /* + * Track pending blinks and tblinks. + */ + int tblink_pending, cblink_pending; + long next_tblink, next_cblink; + + /* * These are buffers used by the bidi and Arabic shaping code. */ termchar *ltemp; diff --git a/timing.c b/timing.c new file mode 100644 index 00000000..a87be897 --- /dev/null +++ b/timing.c @@ -0,0 +1,173 @@ +/* + * timing.c + * + * This module tracks any timers set up by schedule_timer(). It + * keeps all the currently active timers in a list; it informs the + * front end of when the next timer is due to go off if that + * changes; and, very importantly, it tracks the context pointers + * passed to schedule_timer(), so that if a context is freed all + * the timers associated with it can be immediately annulled. + */ + +#include +#include + +#include "putty.h" +#include "tree234.h" + +struct timer { + timer_fn_t fn; + void *ctx; + long now; +}; + +static tree234 *timers = NULL; +static tree234 *timer_contexts = NULL; +static long now = 0L; + +static int compare_timers(void *av, void *bv) +{ + struct timer *a = (struct timer *)av; + struct timer *b = (struct timer *)bv; + long at = a->now - now; + long bt = b->now - now; + + if (at < bt) + return -1; + else if (at > bt) + return +1; + + /* + * Failing that, compare on the other two fields, just so that + * we don't get unwanted equality. + */ + if (a->fn < b->fn) + return -1; + else if (a->fn > b->fn) + return +1; + + if (a->ctx < b->ctx) + return -1; + else if (a->ctx > b->ctx) + return +1; + + /* + * Failing _that_, the two entries genuinely are equal, and we + * never have a need to store them separately in the tree. + */ + return 0; +} + +static int compare_timer_contexts(void *av, void *bv) +{ + char *a = (char *)av; + char *b = (char *)bv; + if (a < b) + return -1; + else if (a > b) + return +1; + return 0; +} + +static void init_timers(void) +{ + if (!timers) { + timers = newtree234(compare_timers); + timer_contexts = newtree234(compare_timer_contexts); + now = GETTICKCOUNT(); + } +} + +long schedule_timer(int ticks, timer_fn_t fn, void *ctx) +{ + long when; + struct timer *t, *first; + + init_timers(); + + when = ticks + GETTICKCOUNT(); + assert(when - now > 0); + + t = snew(struct timer); + t->fn = fn; + t->ctx = ctx; + t->now = when; + + if (t != add234(timers, t)) { + sfree(t); /* identical timer already exists */ + } else { + add234(timer_contexts, t->ctx);/* don't care if this fails */ + } + + first = (struct timer *)index234(timers, 0); + if (first == t) { + /* + * This timer is the very first on the list, so we must + * notify the front end. + */ + timer_change_notify(first->now); + } + + return when; +} + +/* + * Call to run any timers whose time has reached the present. + * Returns the time (in ticks) expected until the next timer after + * that triggers. + */ +int run_timers(long anow, long *next) +{ + struct timer *first; + + init_timers(); + + now = anow; + + while (1) { + first = (struct timer *)index234(timers, 0); + + if (!first) + return FALSE; /* no timers remaining */ + + if (find234(timer_contexts, first->ctx, NULL) == NULL) { + /* + * This timer belongs to a context that has been + * expired. Delete it without running. + */ + delpos234(timers, 0); + sfree(first); + } else if (first->now - now <= 0) { + /* + * This timer is active and has reached its running + * time. Run it. + */ + delpos234(timers, 0); + first->fn(first->ctx, first->now); + sfree(first); + } else { + /* + * This is the first still-active timer that is in the + * future. Return how long it has yet to go. + */ + *next = first->now; + return TRUE; + } + } +} + +/* + * Call to expire all timers associated with a given context. + */ +void expire_timer_context(void *ctx) +{ + init_timers(); + + /* + * We don't bother to check the return value; if the context + * already wasn't in the tree (presumably because no timers + * ever actually got scheduled for it) then that's fine and we + * simply don't need to do anything. + */ + del234(timer_contexts, ctx); +} diff --git a/unix/pterm.c b/unix/pterm.c index ca2dab88..a374d506 100644 --- a/unix/pterm.c +++ b/unix/pterm.c @@ -40,6 +40,13 @@ GdkAtom compound_text_atom, utf8_string_atom; extern char **pty_argv; /* declared in pty.c */ extern int use_pty_argv; +/* + * Timers are global across all sessions (even if we were handling + * multiple sessions, which we aren't), so the current timer ID is + * a global variable. + */ +static guint timer_id = 0; + struct gui_data { GtkWidget *window, *area, *sbar; GtkBox *hbox; @@ -1022,7 +1029,6 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) show_mouseptr(inst, 0); term_seen_key_event(inst->term); - term_out(inst->term); } return TRUE; @@ -1130,9 +1136,9 @@ void frontend_keypress(void *handle) exit(0); } -gint timer_func(gpointer data) +void notify_remote_exit(void *frontend) { - struct gui_data *inst = (struct gui_data *)data; + struct gui_data *inst = (struct gui_data *)frontend; int exitcode; if (!inst->exited && @@ -1153,10 +1159,40 @@ gint timer_func(gpointer data) } gtk_widget_show(inst->restartitem); } +} - term_update(inst->term); - term_blink(inst->term, 0); - return TRUE; +static gint timer_trigger(gpointer data) +{ + long now = GPOINTER_TO_INT(data); + long next; + long ticks; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + timer_id = gtk_timeout_add(ticks > 0 ? ticks : 1, timer_trigger, + GINT_TO_POINTER(next)); + } + + /* + * Never let a timer resume. If we need another one, we've + * asked for it explicitly above. + */ + return FALSE; +} + +void timer_change_notify(long next) +{ + long ticks; + + if (timer_id) + gtk_timeout_remove(timer_id); + + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + + timer_id = gtk_timeout_add(ticks, timer_trigger, + GINT_TO_POINTER(next)); } void fd_input_func(gpointer data, gint sourcefd, GdkInputCondition condition) @@ -1183,7 +1219,6 @@ gint focus_event(GtkWidget *widget, GdkEventFocus *event, gpointer data) { struct gui_data *inst = (struct gui_data *)data; inst->term->has_focus = event->in; - term_out(inst->term); term_update(inst->term); show_mouseptr(inst, 1); return FALSE; @@ -3392,7 +3427,6 @@ int pt_main(int argc, char **argv) if (inst->cfg.scrollbar) gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed", GTK_SIGNAL_FUNC(scrollbar_moved), inst); - gtk_timeout_add(20, timer_func, inst); gtk_widget_add_events(GTK_WIDGET(inst->area), GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | diff --git a/unix/pty.c b/unix/pty.c index b621d8be..50b548ce 100644 --- a/unix/pty.c +++ b/unix/pty.c @@ -474,6 +474,8 @@ int pty_select_result(int fd, int event) #endif from_backend(pty_frontend, 0, message, strlen(message)); } + + notify_remote_exit(pty_frontend); } return !finished; } diff --git a/unix/unix.h b/unix/unix.h index 384789aa..90c6ec2f 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -45,8 +45,8 @@ extern Backend pty_backend; /* Simple wraparound timer function */ unsigned long getticks(void); /* based on gettimeofday(2) */ #define GETTICKCOUNT getticks -#define TICKSPERSEC 1000000 /* gettimeofday returns microseconds */ -#define CURSORBLINK 450000 /* no standard way to set this */ +#define TICKSPERSEC 1000 /* we choose to use milliseconds */ +#define CURSORBLINK 450 /* no standard way to set this */ #define WCHAR wchar_t #define BYTE unsigned char diff --git a/unix/uxcons.c b/unix/uxcons.c index 00a6835f..a8effe90 100644 --- a/unix/uxcons.c +++ b/unix/uxcons.c @@ -35,6 +35,14 @@ void update_specials_menu(void *frontend) { } +void notify_remote_exit(void *frontend) +{ +} + +void timer_change_notify(long next) +{ +} + void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, char *keystr, char *fingerprint) { diff --git a/unix/uxmisc.c b/unix/uxmisc.c index d1afa44a..505d43d3 100644 --- a/unix/uxmisc.c +++ b/unix/uxmisc.c @@ -15,14 +15,13 @@ unsigned long getticks(void) struct timeval tv; gettimeofday(&tv, NULL); /* - * This will wrap around approximately every 4000 seconds, i.e. - * just over an hour, which is more than enough. + * We want to use milliseconds rather than microseconds, + * because we need a decent number of them to fit into a 32-bit + * word so it can be used for keepalives. */ - return tv.tv_sec * 1000000 + tv.tv_usec; + return tv.tv_sec * 1000 + tv.tv_usec / 1000; } - - Filename filename_from_str(const char *str) { Filename ret; diff --git a/unix/uxplink.c b/unix/uxplink.c index e2b4d3d9..6f17b5a2 100644 --- a/unix/uxplink.c +++ b/unix/uxplink.c @@ -252,6 +252,7 @@ int main(int argc, char **argv) int errors; int use_subsystem = 0; void *ldisc, *logctx; + long now; ssh_get_line = console_get_line; @@ -584,6 +585,7 @@ int main(int argc, char **argv) atexit(cleanup_termios); ldisc_update(NULL, 1, 1); sending = FALSE; + now = GETTICKCOUNT(); while (1) { fd_set rset, wset, xset; @@ -644,7 +646,23 @@ int main(int argc, char **argv) } do { - ret = select(maxfd, &rset, &wset, &xset, NULL); + long next, ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks < 0) ticks = 0; /* just in case */ + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + ret = select(maxfd, &rset, &wset, &xset, ptv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); } while (ret < 0 && errno == EINTR); if (ret < 0) { diff --git a/unix/uxsftp.c b/unix/uxsftp.c index c86b8ea7..198dbee9 100644 --- a/unix/uxsftp.c +++ b/unix/uxsftp.c @@ -321,55 +321,80 @@ char *dir_file_cat(char *dir, char *file) } /* - * Wait for some network data and process it. + * Do a select() between all currently active network fds and + * optionally stdin. */ -int ssh_sftp_loop_iteration(void) +static int ssh_sftp_do_select(int include_stdin) { fd_set rset, wset, xset; int i, fdcount, fdsize, *fdlist; int fd, fdstate, rwx, ret, maxfd; + long now = GETTICKCOUNT(); fdlist = NULL; fdcount = fdsize = 0; - /* Count the currently active fds. */ - i = 0; - for (fd = first_fd(&fdstate, &rwx); fd >= 0; - fd = next_fd(&fdstate, &rwx)) i++; + do { - if (i < 1) - return -1; /* doom */ + /* Count the currently active fds. */ + i = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) i++; - /* Expand the fdlist buffer if necessary. */ - if (i > fdsize) { - fdsize = i + 16; - fdlist = sresize(fdlist, fdsize, int); - } + if (i < 1) + return -1; /* doom */ - FD_ZERO(&rset); - FD_ZERO(&wset); - FD_ZERO(&xset); - maxfd = 0; + /* Expand the fdlist buffer if necessary. */ + if (i > fdsize) { + fdsize = i + 16; + fdlist = sresize(fdlist, fdsize, int); + } - /* - * Add all currently open fds to the select sets, and store - * them in fdlist as well. - */ - fdcount = 0; - for (fd = first_fd(&fdstate, &rwx); fd >= 0; - fd = next_fd(&fdstate, &rwx)) { - fdlist[fdcount++] = fd; - if (rwx & 1) - FD_SET_MAX(fd, maxfd, rset); - if (rwx & 2) - FD_SET_MAX(fd, maxfd, wset); - if (rwx & 4) - FD_SET_MAX(fd, maxfd, xset); - } + FD_ZERO(&rset); + FD_ZERO(&wset); + FD_ZERO(&xset); + maxfd = 0; - do { - ret = select(maxfd, &rset, &wset, &xset, NULL); - } while (ret < 0 && errno == EINTR); + /* + * Add all currently open fds to the select sets, and store + * them in fdlist as well. + */ + fdcount = 0; + for (fd = first_fd(&fdstate, &rwx); fd >= 0; + fd = next_fd(&fdstate, &rwx)) { + fdlist[fdcount++] = fd; + if (rwx & 1) + FD_SET_MAX(fd, maxfd, rset); + if (rwx & 2) + FD_SET_MAX(fd, maxfd, wset); + if (rwx & 4) + FD_SET_MAX(fd, maxfd, xset); + } + + if (include_stdin) + FD_SET_MAX(0, maxfd, rset); + + do { + long next, ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + ret = select(maxfd, &rset, &wset, &xset, ptv); + if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + } while (ret < 0 && errno != EINTR); + } while (ret == 0); if (ret < 0) { perror("select"); @@ -393,7 +418,58 @@ int ssh_sftp_loop_iteration(void) sfree(fdlist); - return 0; + return FD_ISSET(0, &rset) ? 1 : 0; +} + +/* + * Wait for some network data and process it. + */ +int ssh_sftp_loop_iteration(void) +{ + return ssh_sftp_do_select(FALSE); +} + +/* + * Read a PSFTP command line from stdin. + */ +char *ssh_sftp_get_cmdline(char *prompt) +{ + char *buf; + int buflen, bufsize, ret; + + fputs(prompt, stdout); + fflush(stdout); + + buf = NULL; + buflen = bufsize = 0; + + while (1) { + ret = ssh_sftp_do_select(TRUE); + if (ret < 0) { + printf("connection died\n"); + return NULL; /* woop woop */ + } + if (ret > 0) { + if (buflen >= bufsize) { + bufsize = buflen + 512; + buf = sresize(buf, bufsize, char); + } + ret = read(0, buf+buflen, 1); + if (ret < 0) { + perror("read"); + return NULL; + } + if (ret == 0) { + /* eof on stdin; no error, but no answer either */ + return NULL; + } + + if (buf[buflen++] == '\n') { + /* we have a full line */ + return buf; + } + } + } } /* diff --git a/unix/uxstore.c b/unix/uxstore.c index 922462fa..c46b9a29 100644 --- a/unix/uxstore.c +++ b/unix/uxstore.c @@ -105,29 +105,6 @@ static void make_filename(char *filename, int index, const char *subname) filename[FILENAME_MAX-1] = '\0'; } -/* - * Read an entire line of text from a file. Return a buffer - * malloced to be as big as necessary (caller must free). - */ -static char *fgetline(FILE *fp) -{ - char *ret = snewn(512, char); - int size = 512, len = 0; - while (fgets(ret + len, size - len, fp)) { - len += strlen(ret + len); - if (ret[len-1] == '\n') - break; /* got a newline, we're done */ - size = len + 512; - ret = sresize(ret, size, char); - } - if (len == 0) { /* first fgets returned NULL */ - sfree(ret); - return NULL; - } - ret[len] = '\0'; - return ret; -} - void *open_settings_w(const char *sessionname, char **errmsg) { char filename[FILENAME_MAX]; diff --git a/windows/wincons.c b/windows/wincons.c index 757f753b..347f99f1 100644 --- a/windows/wincons.c +++ b/windows/wincons.c @@ -33,6 +33,14 @@ void cleanup_exit(int code) exit(code); } +void notify_remote_exit(void *frontend) +{ +} + +void timer_change_notify(long next) +{ +} + void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, char *keystr, char *fingerprint) { diff --git a/windows/window.c b/windows/window.c index ccebea77..b460b534 100644 --- a/windows/window.c +++ b/windows/window.c @@ -92,17 +92,12 @@ static int offset_width, offset_height; static int was_zoomed = 0; static int prev_rows, prev_cols; -static int pending_netevent = 0; -static WPARAM pend_netevent_wParam = 0; -static LPARAM pend_netevent_lParam = 0; -static void enact_pending_netevent(void); +static void enact_netevent(WPARAM, LPARAM); static void flash_window(int mode); static void sys_cursor_update(void); static int is_shift_pressed(void); static int get_fullscreen_rect(RECT * ss); -static time_t last_movement = 0; - static int caret_x = -1, caret_y = -1; static int kbd_codepage; @@ -117,6 +112,9 @@ static int session_closed; static const struct telnet_special *specials; static int n_specials; +#define TIMING_TIMER_ID 1234 +static long timing_next_time; + static struct { HMENU menu; int specials_submenu_pos; @@ -776,30 +774,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) UpdateWindow(hwnd); if (GetMessage(&msg, NULL, 0, 0) == 1) { - int timer_id = 0, long_timer = 0; - while (msg.message != WM_QUIT) { - /* Sometimes DispatchMessage calls routines that use their own - * GetMessage loop, setup this timer so we get some control back. - * - * Also call term_update() from the timer so that if the host - * is sending data flat out we still do redraws. - */ - if (timer_id && long_timer) { - KillTimer(hwnd, timer_id); - long_timer = timer_id = 0; - } - if (!timer_id) - timer_id = SetTimer(hwnd, 1, 20, NULL); if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg))) DispatchMessage(&msg); - - /* Make sure we blink everything that needs it. */ - term_blink(term, 0); - /* Send the paste buffer if there's anything to send */ term_paste(term); - /* If there's nothing new in the queue then we can do everything * we've delayed, reading the socket, writing, and repainting * the window. @@ -807,42 +786,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) continue; - if (pending_netevent) { - enact_pending_netevent(); - - if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - continue; - } - - /* Okay there is now nothing to do so we make sure the screen is - * completely up to date then tell windows to call us in a little - * while. - */ - if (timer_id) { - KillTimer(hwnd, timer_id); - timer_id = 0; - } - HideCaret(hwnd); - if (GetCapture() != hwnd || - (send_raw_mouse && - !(cfg.mouse_override && is_shift_pressed()))) - term_out(term); - term_update(term); - ShowCaret(hwnd); - - flash_window(1); /* maintain */ - /* The messages seem unreliable; especially if we're being tricky */ term->has_focus = (GetForegroundWindow() == hwnd); - if (term->in_vbell) - /* Hmm, term_update didn't want to do an update too soon ... */ - timer_id = SetTimer(hwnd, 1, 50, NULL); - else if (!term->has_focus) - timer_id = SetTimer(hwnd, 1, 500, NULL); - else - timer_id = SetTimer(hwnd, 1, 100, NULL); - long_timer = 1; + net_pending_errors(); /* There's no point rescanning everything in the message queue * so we do an apparently unnecessary wait here @@ -1035,7 +982,7 @@ void cmdline_error(char *fmt, ...) /* * Actually do the job requested by a WM_NETEVENT */ -static void enact_pending_netevent(void) +static void enact_netevent(WPARAM wParam, LPARAM lParam) { static int reentering = 0; extern int select_result(WPARAM, LPARAM); @@ -1044,10 +991,8 @@ static void enact_pending_netevent(void) if (reentering) return; /* don't unpend the pending */ - pending_netevent = FALSE; - reentering = 1; - ret = select_result(pend_netevent_wParam, pend_netevent_lParam); + ret = select_result(wParam, lParam); reentering = 0; if (ret == 0 && !session_closed) { @@ -1795,6 +1740,17 @@ static int is_shift_pressed(void) static int resizing; +void notify_remote_exit(void *fe) { /* stub not needed in this frontend */ } + +void timer_change_notify(long next) +{ + long ticks = next - GETTICKCOUNT(); + if (ticks <= 0) ticks = 1; /* just in case */ + KillTimer(hwnd, TIMING_TIMER_ID); + SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL); + timing_next_time = next; +} + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -1806,25 +1762,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, switch (message) { case WM_TIMER: - if (pending_netevent) - enact_pending_netevent(); - if (GetCapture() != hwnd || - (send_raw_mouse && !(cfg.mouse_override && is_shift_pressed()))) - term_out(term); - noise_regular(); - HideCaret(hwnd); - term_update(term); - ShowCaret(hwnd); - if (cfg.ping_interval > 0) { - time_t now; - time(&now); - if (now - last_movement > cfg.ping_interval) { - if (back) - back->special(backhandle, TS_PING); - last_movement = now; + if ((UINT_PTR)wParam == TIMING_TIMER_ID) { + long next; + + KillTimer(hwnd, TIMING_TIMER_ID); + if (run_timers(timing_next_time, &next)) { + timer_change_notify(next); + } else { } } - net_pending_errors(); return 0; case WM_CREATE: break; @@ -2304,18 +2250,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case WM_PAINT: { PAINTSTRUCT p; + HideCaret(hwnd); hdc = BeginPaint(hwnd, &p); if (pal) { SelectPalette(hdc, pal, TRUE); RealizePalette(hdc); } + term_paint(term, hdc, (p.rcPaint.left-offset_width)/font_width, (p.rcPaint.top-offset_height)/font_height, (p.rcPaint.right-offset_width-1)/font_width, (p.rcPaint.bottom-offset_height-1)/font_height, - is_alt_pressed()); + TRUE); if (p.fErase || p.rcPaint.left < offset_width || @@ -2365,20 +2313,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, } return 0; case WM_NETEVENT: - /* Notice we can get multiple netevents, FD_READ, FD_WRITE etc - * but the only one that's likely to try to overload us is FD_READ. - * This means buffering just one is fine. - */ - if (pending_netevent) - enact_pending_netevent(); - - pending_netevent = TRUE; - pend_netevent_wParam = wParam; - pend_netevent_lParam = lParam; - if (WSAGETSELECTEVENT(lParam) != FD_READ) - enact_pending_netevent(); - - time(&last_movement); + enact_netevent(wParam, lParam); + net_pending_errors(); return 0; case WM_SETFOCUS: term->has_focus = TRUE; @@ -2386,7 +2322,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, ShowCaret(hwnd); flash_window(0); /* stop */ compose_state = 0; - term_out(term); term_update(term); break; case WM_KILLFOCUS: @@ -2394,7 +2329,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term->has_focus = FALSE; DestroyCaret(); caret_x = caret_y = -1; /* ensure caret is replaced next time */ - term_out(term); term_update(term); break; case WM_ENTERSIZEMOVE: @@ -4624,14 +4558,23 @@ void modalfatalbox(char *fmt, ...) cleanup_exit(1); } +static void flash_window(int mode); +static long next_flash; +static int flashing = 0; + +static void flash_window_timer(void *ctx, long now) +{ + if (flashing && now - next_flash >= 0) { + flash_window(1); + } +} + /* * Manage window caption / taskbar flashing, if enabled. * 0 = stop, 1 = maintain, 2 = start */ static void flash_window(int mode) { - static long last_flash = 0; - static int flashing = 0; if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) { /* stop */ if (flashing) { @@ -4642,20 +4585,16 @@ static void flash_window(int mode) } else if (mode == 2) { /* start */ if (!flashing) { - last_flash = GetTickCount(); flashing = 1; FlashWindow(hwnd, TRUE); + next_flash = schedule_timer(450, flash_window_timer, hwnd); } } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) { /* maintain */ if (flashing) { - long now = GetTickCount(); - long fdiff = now - last_flash; - if (fdiff < 0 || fdiff > 450) { - last_flash = now; - FlashWindow(hwnd, TRUE); /* toggle */ - } + FlashWindow(hwnd, TRUE); /* toggle */ + next_flash = schedule_timer(450, flash_window_timer, hwnd); } } } diff --git a/windows/winnet.c b/windows/winnet.c index bcb22bf4..f2b2aaa4 100644 --- a/windows/winnet.c +++ b/windows/winnet.c @@ -1353,6 +1353,16 @@ SOCKET next_socket(int *state) return s ? s->s : INVALID_SOCKET; } +extern int socket_writable(SOCKET skt) +{ + Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch); + + if (s) + return bufchain_size(&s->output_data) > 0; + else + return 0; +} + int net_service_lookup(char *service) { struct servent *se; diff --git a/windows/winplink.c b/windows/winplink.c index b99646af..497ac927 100644 --- a/windows/winplink.c +++ b/windows/winplink.c @@ -279,6 +279,7 @@ int main(int argc, char **argv) int exitcode; int errors; int use_subsystem = 0; + long now, next; ssh_get_line = console_get_line; @@ -631,8 +632,11 @@ int main(int argc, char **argv) cleanup_exit(1); } + now = GETTICKCOUNT(); + while (1) { int n; + DWORD ticks; if (!sending && back->sendok(backhandle)) { /* @@ -661,9 +665,16 @@ int main(int argc, char **argv) sending = TRUE; } - n = MsgWaitForMultipleObjects(4, handles, FALSE, INFINITE, + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks < 0) ticks = 0; /* just in case */ + } else { + ticks = INFINITE; + } + + n = MsgWaitForMultipleObjects(4, handles, FALSE, ticks, QS_POSTMESSAGE); - if (n == 0) { + if (n == WAIT_OBJECT_0 + 0) { WSANETWORKEVENTS things; SOCKET socket; extern SOCKET first_socket(int *), next_socket(int *); @@ -724,7 +735,7 @@ int main(int argc, char **argv) } } } - } else if (n == 1) { + } else if (n == WAIT_OBJECT_0 + 1) { reading = 0; noise_ultralight(idata.len); if (connopen && back->socket(backhandle) != NULL) { @@ -734,7 +745,7 @@ int main(int argc, char **argv) back->special(backhandle, TS_EOF); } } - } else if (n == 2) { + } else if (n == WAIT_OBJECT_0 + 2) { odata.busy = 0; if (!odata.writeret) { fprintf(stderr, "Unable to write to standard output\n"); @@ -747,7 +758,7 @@ int main(int argc, char **argv) back->unthrottle(backhandle, bufchain_size(&stdout_data) + bufchain_size(&stderr_data)); } - } else if (n == 3) { + } else if (n == WAIT_OBJECT_0 + 3) { edata.busy = 0; if (!edata.writeret) { fprintf(stderr, "Unable to write to standard output\n"); @@ -760,7 +771,7 @@ int main(int argc, char **argv) back->unthrottle(backhandle, bufchain_size(&stdout_data) + bufchain_size(&stderr_data)); } - } else if (n == 4) { + } else if (n == WAIT_OBJECT_0 + 4) { MSG msg; while (PeekMessage(&msg, INVALID_HANDLE_VALUE, WM_AGENT_CALLBACK, WM_AGENT_CALLBACK, @@ -770,6 +781,13 @@ int main(int argc, char **argv) sfree(c); } } + + if (n == WAIT_TIMEOUT) { + now = next; + } else { + now = GETTICKCOUNT(); + } + if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) { SetEvent(idata.eventback); reading = 1; diff --git a/windows/winsftp.c b/windows/winsftp.c index 52c827a2..d9ea3cac 100644 --- a/windows/winsftp.c +++ b/windows/winsftp.c @@ -2,6 +2,8 @@ * winsftp.c: the Windows-specific parts of PSFTP and PSCP. */ +#include + #include "putty.h" #include "psftp.h" @@ -461,35 +463,255 @@ char *dir_file_cat(char *dir, char *file) * Be told what socket we're supposed to be using. */ static SOCKET sftp_ssh_socket; +static HANDLE netevent = NULL; char *do_select(SOCKET skt, int startup) { + int events; if (startup) sftp_ssh_socket = skt; else sftp_ssh_socket = INVALID_SOCKET; + + if (p_WSAEventSelect) { + if (startup) { + events = (FD_CONNECT | FD_READ | FD_WRITE | + FD_OOB | FD_CLOSE | FD_ACCEPT); + netevent = CreateEvent(NULL, FALSE, FALSE, NULL); + } else { + events = 0; + } + if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) { + switch (p_WSAGetLastError()) { + case WSAENETDOWN: + return "Network is down"; + default: + return "WSAEventSelect(): unknown error"; + } + } + } return NULL; } extern int select_result(WPARAM, LPARAM); +int do_eventsel_loop(HANDLE other_event) +{ + int n; + long next, ticks; + HANDLE handles[2]; + SOCKET *sklist; + int skcount; + long now = GETTICKCOUNT(); + + if (!netevent) { + return -1; /* doom */ + } + + handles[0] = netevent; + handles[1] = other_event; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks < 0) ticks = 0; /* just in case */ + } else { + ticks = INFINITE; + } + + n = MsgWaitForMultipleObjects(other_event ? 2 : 1, handles, FALSE, ticks, + QS_POSTMESSAGE); + + if (n == WAIT_OBJECT_0 + 0) { + WSANETWORKEVENTS things; + SOCKET socket; + extern SOCKET first_socket(int *), next_socket(int *); + extern int select_result(WPARAM, LPARAM); + int i, socketstate; + + /* + * We must not call select_result() for any socket + * until we have finished enumerating within the + * tree. This is because select_result() may close + * the socket and modify the tree. + */ + /* Count the active sockets. */ + i = 0; + for (socket = first_socket(&socketstate); + socket != INVALID_SOCKET; + socket = next_socket(&socketstate)) i++; + + /* Expand the buffer if necessary. */ + sklist = snewn(i, SOCKET); + + /* Retrieve the sockets into sklist. */ + skcount = 0; + for (socket = first_socket(&socketstate); + socket != INVALID_SOCKET; + socket = next_socket(&socketstate)) { + sklist[skcount++] = socket; + } + + /* Now we're done enumerating; go through the list. */ + for (i = 0; i < skcount; i++) { + WPARAM wp; + socket = sklist[i]; + wp = (WPARAM) socket; + if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) { + static const struct { int bit, mask; } eventtypes[] = { + {FD_CONNECT_BIT, FD_CONNECT}, + {FD_READ_BIT, FD_READ}, + {FD_CLOSE_BIT, FD_CLOSE}, + {FD_OOB_BIT, FD_OOB}, + {FD_WRITE_BIT, FD_WRITE}, + {FD_ACCEPT_BIT, FD_ACCEPT}, + }; + int e; + + noise_ultralight(socket); + noise_ultralight(things.lNetworkEvents); + + for (e = 0; e < lenof(eventtypes); e++) + if (things.lNetworkEvents & eventtypes[e].mask) { + LPARAM lp; + int err = things.iErrorCode[eventtypes[e].bit]; + lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err); + select_result(wp, lp); + } + } + } + + sfree(sklist); + } + + if (n == WAIT_TIMEOUT) { + now = next; + } else { + now = GETTICKCOUNT(); + } + + if (other_event && n == WAIT_OBJECT_0 + 1) + return 1; + + return 0; +} + /* * Wait for some network data and process it. + * + * We have two variants of this function. One uses select() so that + * it's compatible with WinSock 1. The other uses WSAEventSelect + * and MsgWaitForMultipleObjects, so that we can consistently use + * WSAEventSelect throughout; this enables us to also implement + * ssh_sftp_get_cmdline() using a parallel mechanism. */ int ssh_sftp_loop_iteration(void) { - fd_set readfds; - if (sftp_ssh_socket == INVALID_SOCKET) return -1; /* doom */ - FD_ZERO(&readfds); - FD_SET(sftp_ssh_socket, &readfds); - if (p_select(1, &readfds, NULL, NULL, NULL) < 0) - return -1; /* doom */ + if (p_WSAEventSelect == NULL) { + fd_set readfds; + int ret; + long now = GETTICKCOUNT(); + + if (socket_writable(sftp_ssh_socket)) + select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE); + + do { + long next, ticks; + struct timeval tv, *ptv; + + if (run_timers(now, &next)) { + ticks = next - GETTICKCOUNT(); + if (ticks <= 0) + ticks = 1; /* just in case */ + tv.tv_sec = ticks / 1000; + tv.tv_usec = ticks % 1000 * 1000; + ptv = &tv; + } else { + ptv = NULL; + } + + FD_ZERO(&readfds); + FD_SET(sftp_ssh_socket, &readfds); + ret = p_select(1, &readfds, NULL, NULL, ptv); + + if (ret < 0) + return -1; /* doom */ + else if (ret == 0) + now = next; + else + now = GETTICKCOUNT(); + + } while (ret == 0); + + select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ); + + return 0; + } else { + return do_eventsel_loop(NULL); + } +} + +/* + * Read a command line from standard input. + * + * In the presence of WinSock 2, we can use WSAEventSelect to + * mediate between the socket and stdin, meaning we can send + * keepalives and respond to server events even while waiting at + * the PSFTP command prompt. Without WS2, we fall back to a simple + * fgets. + */ +struct command_read_ctx { + HANDLE event; + char *line; +}; + +static DWORD WINAPI command_read_thread(void *param) +{ + struct command_read_ctx *ctx = (struct command_read_ctx *) param; + + ctx->line = fgetline(stdin); + + SetEvent(ctx->event); - select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ); return 0; } +char *ssh_sftp_get_cmdline(char *prompt) +{ + int ret; + struct command_read_ctx actx, *ctx = &actx; + DWORD threadid; + + fputs(prompt, stdout); + fflush(stdout); + + if (sftp_ssh_socket == INVALID_SOCKET || p_WSAEventSelect == NULL) { + return fgetline(stdin); /* very simple */ + } + + /* + * Create a second thread to read from stdin. Process network + * and timing events until it terminates. + */ + ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL); + ctx->line = NULL; + + if (!CreateThread(NULL, 0, command_read_thread, + ctx, 0, &threadid)) { + fprintf(stderr, "Unable to create command input thread\n"); + cleanup_exit(1); + } + + do { + ret = do_eventsel_loop(ctx->event); + + /* Error return can only occur if netevent==NULL, and it ain't. */ + assert(ret >= 0); + } while (ret == 0); + + return ctx->line; +} + /* ---------------------------------------------------------------------- * Main program. Parse arguments etc. */ diff --git a/windows/winstuff.h b/windows/winstuff.h index 693fd42e..84db7b04 100644 --- a/windows/winstuff.h +++ b/windows/winstuff.h @@ -150,6 +150,8 @@ extern int (WINAPI *p_WSAGetLastError)(void); extern int (WINAPI *p_WSAEnumNetworkEvents) (SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents); +extern int socket_writable(SOCKET skt); + /* * Exports from winctrls.c. */ -- 2.11.0