New timing infrastructure. There's a new function schedule_timer()
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 27 Nov 2004 13:20:21 +0000 (13:20 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 27 Nov 2004 13:20:21 +0000 (13:20 +0000)
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

30 files changed:
Recipe
doc/udp.but
mac/macterm.c
misc.c
misc.h
pinger.c [new file with mode: 0644]
psftp.c
psftp.h
putty.h
raw.c
rlogin.c
ssh.c
telnet.c
terminal.c
terminal.h
timing.c [new file with mode: 0644]
unix/pterm.c
unix/pty.c
unix/unix.h
unix/uxcons.c
unix/uxmisc.c
unix/uxplink.c
unix/uxsftp.c
unix/uxstore.c
windows/wincons.c
windows/window.c
windows/winnet.c
windows/winplink.c
windows/winsftp.c
windows/winstuff.h

diff --git a/Recipe b/Recipe
index ac7171c..18f5637 100644 (file)
--- 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
index 8e42561..796b2c6 100644 (file)
@@ -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++
 
index 46c30f6..5831cd2 100644 (file)
@@ -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 50c38d5..474bf02 100644 (file)
--- 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 7d0ee4d..cd144b5 100644 (file)
--- a/misc.h
+++ b/misc.h
@@ -3,6 +3,7 @@
 
 #include "puttymem.h"
 
+#include <stdio.h>                    /* for FILE * */
 #include <stdarg.h>                   /* 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 (file)
index 0000000..c9b7503
--- /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 66c5994..1def80e 100644 (file)
--- 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 0034f84..6ce24e8 100644 (file)
--- 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 061f627..d4deb62 100644 (file)
--- 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 8c1f97f..8b1b1e9 100644 (file)
--- 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. */
index f2b61d2..06c8b12 100644 (file)
--- 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 3426098..1d8e6b8 100644 (file)
--- 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 */
 }
 
index af40c85..8b2c428 100644 (file)
--- 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 */
 }
 
index f8d92dc..f070579 100644 (file)
 
 #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;
     }
 
index 6630411..924c820 100644 (file)
@@ -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 (file)
index 0000000..a87be89
--- /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 <assert.h>
+#include <stdio.h>
+
+#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);
+}
index ca2dab8..a374d50 100644 (file)
@@ -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 |
index b621d8b..50b548c 100644 (file)
@@ -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;
 }
index 384789a..90c6ec2 100644 (file)
@@ -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
index 00a6835..a8effe9 100644 (file)
@@ -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)
 {
index d1afa44..505d43d 100644 (file)
@@ -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;
index e2b4d3d..6f17b5a 100644 (file)
@@ -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) {
index c86b8ea..198dbee 100644 (file)
@@ -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;
+           }
+       }
+    }
 }
 
 /*
index 922462f..c46b9a2 100644 (file)
@@ -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];
index 757f753..347f99f 100644 (file)
@@ -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)
 {
index ccebea7..b460b53 100644 (file)
@@ -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);
        }
     }
 }
index bcb22bf..f2b2aaa 100644 (file)
@@ -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;
index b99646a..497ac92 100644 (file)
@@ -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;
index 52c827a..d9ea3ca 100644 (file)
@@ -2,6 +2,8 @@
  * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
  */
 
+#include <assert.h>
+
 #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.
  */
index 693fd42..84db7b0 100644 (file)
@@ -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.
  */