# 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
# 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
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++
-/* $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
Session *s;
for (s = sesslist; s != NULL; s = s->next) {
- term_out(s->term);
term_update(s->term);
}
}
}
}
+/*
+ * 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.
#include "puttymem.h"
+#include <stdio.h> /* for FILE * */
#include <stdarg.h> /* for va_list */
#ifndef FALSE
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;
--- /dev/null
+/*
+ * 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);
+}
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);
}
}
}
- sfree(line);
+ sfree(line);
+
/*
* Now parse the first word and assign a function.
*/
*/
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);
*/
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.
* 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);
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.
*/
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
if (raw->s) {
sk_close(raw->s);
raw->s = NULL;
+ notify_remote_exit(raw->frontend);
}
if (error_msg) {
/* A socket error has occurred. */
if (rlogin->s) {
sk_close(rlogin->s);
rlogin->s = NULL;
+ notify_remote_exit(rlogin->frontend);
}
if (error_msg) {
/* A socket error has occurred. */
* 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)
}
update_specials_menu(ssh->frontend);
ssh->state = SSH_STATE_BEFORE_SIZE;
+ ssh->pinger = pinger_new(&ssh->cfg, &ssh_backend, ssh);
sfree(s->vstring);
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
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;
}
ssh->protocol_initial_phase_done = FALSE;
+ ssh->pinger = NULL;
+
p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
if (p != NULL)
return p;
if (ssh->s)
ssh_do_close(ssh);
sfree(ssh);
+ if (ssh->pinger)
+ pinger_free(ssh->pinger);
}
/*
static void ssh_reconfig(void *handle, Config *cfg)
{
Ssh ssh = (Ssh) handle;
+ pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
ssh->cfg = *cfg; /* STRUCTURE COPY */
}
} state;
Config cfg;
+
+ Pinger pinger;
} *Telnet;
#define TELNET_MAX_BACKLOG 4096
if (telnet->s) {
sk_close(telnet->s);
telnet->s = NULL;
+ notify_remote_exit(telnet->frontend);
}
if (error_msg) {
/* A socket error has occurred. */
telnet->term_height = telnet->cfg.height;
telnet->state = TOP_LEVEL;
telnet->ldisc = NULL;
+ telnet->pinger = NULL;
*backend_handle = telnet;
/*
if ((err = sk_socket_error(telnet->s)) != NULL)
return err;
+ telnet->pinger = pinger_new(&telnet->cfg, &telnet_backend, telnet);
+
/*
* Initialise option states.
*/
sfree(telnet->sb_buf);
if (telnet->s)
sk_close(telnet->s);
+ if (telnet->pinger)
+ pinger_free(telnet->pinger);
sfree(telnet);
}
/*
static void telnet_reconfig(void *handle, Config *cfg)
{
Telnet telnet = (Telnet) handle;
+ pinger_reconfig(telnet->pinger, &telnet->cfg, cfg);
telnet->cfg = *cfg; /* STRUCTURE COPY */
}
#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; \
#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.
*/
swap_screen(term, 0, FALSE, FALSE);
erase_lots(term, FALSE, TRUE, TRUE);
}
+ term_schedule_tblink(term);
+ term_schedule_cblink(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;
*/
if (term->cfg.scroll_on_key) {
term->disptop = 0; /* return to main screen */
- term->seen_disp_event = 1;
+ seen_disp_event(term);
}
}
* 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])
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];
if (!*term->cfg.printer) {
term_print_finish(term);
}
+ term_schedule_tblink(term);
+ term_schedule_cblink(term);
}
/*
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;
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;
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;
sfree(term->pre_bidi_cache);
sfree(term->post_bidi_cache);
+ expire_timer_context(term);
+
sfree(term);
}
*/
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 */
} else {
term->blink_is_real = term->cfg.blinktext;
}
+ term_schedule_tblink(term);
break;
case 3: /* DECCOLM: 80/132 columns */
deselect(term);
* 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;
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);
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);
* 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;
*/
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 */
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);
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);
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 */
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);
check_selection(term, old_curs, term->curs);
}
- term->seen_disp_event = TRUE;
+ seen_disp_event(term);
break;
}
} else
}
add_cc(cline, x, c);
- term->seen_disp_event = 1;
+ seen_disp_event(term);
}
continue;
default:
term->wrapnext = FALSE;
}
}
- term->seen_disp_event = 1;
+ seen_disp_event(term);
}
break;
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);
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);
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);
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);
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);
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;
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);
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);
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);
((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 */
((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 */
{
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 */
{
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);
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);
*/
term->curs.y = (term->dec_om ?
term->marg_t : 0);
- term->seen_disp_event = TRUE;
+ seen_disp_event(term);
}
}
break;
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;
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 */
/*
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 */
/*
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 */
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
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);
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);
*/
term->vt52_mode = FALSE;
term->blink_is_real = term->cfg.blinktext;
+ term_schedule_tblink(term);
break;
#if 0
case '^':
wchar_t *ch;
int chlen;
termchar cursor_background;
- unsigned long ticks;
#ifdef OPTIMISE_SCROLL
struct scrollregion *sr;
#endif /* OPTIMISE_SCROLL */
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;
sfree(ch);
}
-/*
- * 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.
*/
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);
+ }
+ }
}
/*
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;
}
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? */
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 */
*/
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.
*/
--- /dev/null
+/*
+ * 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);
+}
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;
show_mouseptr(inst, 0);
term_seen_key_event(inst->term);
- term_out(inst->term);
}
return TRUE;
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 &&
}
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)
{
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;
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 |
#endif
from_backend(pty_frontend, 0, message, strlen(message));
}
+
+ notify_remote_exit(pty_frontend);
}
return !finished;
}
/* 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
{
}
+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)
{
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;
int errors;
int use_subsystem = 0;
void *ldisc, *logctx;
+ long now;
ssh_get_line = console_get_line;
atexit(cleanup_termios);
ldisc_update(NULL, 1, 1);
sending = FALSE;
+ now = GETTICKCOUNT();
while (1) {
fd_set rset, wset, xset;
}
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) {
}
/*
- * 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");
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;
+ }
+ }
+ }
}
/*
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];
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)
{
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;
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;
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.
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
/*
* 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);
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) {
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)
{
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;
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 ||
}
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;
ShowCaret(hwnd);
flash_window(0); /* stop */
compose_state = 0;
- term_out(term);
term_update(term);
break;
case WM_KILLFOCUS:
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:
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) {
} 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);
}
}
}
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;
int exitcode;
int errors;
int use_subsystem = 0;
+ long now, next;
ssh_get_line = console_get_line;
cleanup_exit(1);
}
+ now = GETTICKCOUNT();
+
while (1) {
int n;
+ DWORD ticks;
if (!sending && back->sendok(backhandle)) {
/*
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 *);
}
}
}
- } else if (n == 1) {
+ } else if (n == WAIT_OBJECT_0 + 1) {
reading = 0;
noise_ultralight(idata.len);
if (connopen && back->socket(backhandle) != NULL) {
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");
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");
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,
sfree(c);
}
}
+
+ if (n == WAIT_TIMEOUT) {
+ now = next;
+ } else {
+ now = GETTICKCOUNT();
+ }
+
if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
SetEvent(idata.eventback);
reading = 1;
* winsftp.c: the Windows-specific parts of PSFTP and PSCP.
*/
+#include <assert.h>
+
#include "putty.h"
#include "psftp.h"
* 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.
*/
extern int (WINAPI *p_WSAEnumNetworkEvents)
(SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);
+extern int socket_writable(SOCKET skt);
+
/*
* Exports from winctrls.c.
*/