1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00

New timing infrastructure. There's a new function schedule_timer()

which pretty much any module can call to request a call-back in the
future. So terminal.c can do its own handling of blinking, visual
bells and deferred screen updates, without having to rely on
term_update() being called 50 times a second (fixes: pterm-timer);
and ssh.c and telnet.c both invoke a new module pinger.c which takes
care of sending keepalives, so they get sent uniformly in all front
ends (fixes: plink-keepalives, unix-keepalives).

[originally from svn r4906]
[this svn revision also touched putty-wishlist]
This commit is contained in:
Simon Tatham 2004-11-27 13:20:21 +00:00
parent d609e1f7f8
commit 7ecf13564a
30 changed files with 1109 additions and 369 deletions

15
Recipe
View File

@ -182,14 +182,15 @@ GUITERM = TERMINAL window windlg winctrls sizetip winucs winprint
# Same thing on Unix. # Same thing on Unix.
UXTERM = TERMINAL pterm uxcfg gtkdlg gtkcols gtkpanel uxucs uxprint xkeysym UXTERM = TERMINAL pterm uxcfg gtkdlg gtkcols gtkpanel uxucs uxprint xkeysym
+ timing
# Non-SSH back ends (putty, puttytel, plink). # 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 back end (putty, plink, pscp, psftp).
SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
+ sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd + sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
+ sshaes sshsh512 sshbn wildcard + sshaes sshsh512 sshbn wildcard pinger
WINSSH = SSH winnoise winpgntc WINSSH = SSH winnoise winpgntc
UXSSH = SSH uxnoise uxagentc UXSSH = SSH uxnoise uxagentc
MACSSH = SSH macnoise MACSSH = SSH macnoise
@ -199,12 +200,10 @@ SFTP = sftp int64 logging
# Miscellaneous objects appearing in all the network utilities (not # Miscellaneous objects appearing in all the network utilities (not
# Pageant or PuTTYgen). # Pageant or PuTTYgen).
WINMISC = misc version winstore settings tree234 winnet proxy cmdline MISC = timing misc version settings tree234 proxy
+ windefs winmisc pproxy WINMISC = MISC winstore winnet cmdline windefs winmisc pproxy
UXMISC = misc version uxstore settings tree234 uxsel uxnet proxy cmdline UXMISC = MISC uxstore uxsel uxnet cmdline uxmisc uxproxy
+ uxmisc uxproxy MACMISC = MISC macstore macnet mtcpnet otnet macmisc macabout pproxy
MACMISC = misc version macstore settings tree234 macnet mtcpnet otnet proxy
+ macmisc macabout pproxy
# Character set library, for use in pterm. # Character set library, for use in pterm.
CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc CHARSET = sbcsdat slookup sbcs utf8 toucs fromucs xenc mimeenc macenc localenc

View 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 constrained by the operating system to run as a single process
potentially managing multiple sessions. potentially managing multiple sessions.
Therefore, the platform-independent parts of PuTTY use \e{hardly Therefore, the platform-independent parts of PuTTY never use global
any} global variables. The very few that do exist, such as variables to store per-session data. The global variables that do
\c{flags}, are tolerated because they are not specific to a exist are tolerated because they are not specific to a particular
particular login session: instead, they define properties that are login session: \c{flags} defines properties that are expected to
expected to apply equally to \e{all} the sessions run by a single apply equally to \e{all} the sessions run by a single PuTTY process,
PuTTY process. Any data that is specific to a particular network the random number state in \cw{sshrand.c} and the timer list in
session is stored in dynamically allocated data structures, and \cw{timing.c} serve all sessions equally, and so on. But most data
pointers to these structures are passed around between functions. 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 Platform-specific code can reverse this decision if it likes. The
Windows code, for historical reasons, stores most of its data as Windows code, for historical reasons, stores most of its data as
global variables. That's OK, because \e{on Windows} we know there is 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 only one session per PuTTY process, so it's safe to do that. But
changes to the platform-independent code should avoid introducing 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++ \H{udp-pure-c} C, not C++

View 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 Simon Tatham
* Copyright (c) 1999, 2002 Ben Harris * Copyright (c) 1999, 2002 Ben Harris
@ -314,7 +314,6 @@ void mac_pollterm(void)
Session *s; Session *s;
for (s = sesslist; s != NULL; s = s->next) { for (s = sesslist; s != NULL; s = s->next) {
term_out(s->term);
term_update(s->term); term_update(s->term);
} }
} }

23
misc.c
View File

@ -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 * Base64 encoding routine. This is required in public-key writing
* but also in HTTP proxy handling, so it's centralised here. * but also in HTTP proxy handling, so it's centralised here.

3
misc.h
View File

@ -3,6 +3,7 @@
#include "puttymem.h" #include "puttymem.h"
#include <stdio.h> /* for FILE * */
#include <stdarg.h> /* for va_list */ #include <stdarg.h> /* for va_list */
#ifndef FALSE #ifndef FALSE
@ -20,6 +21,8 @@ char *dupcat(const char *s1, ...);
char *dupprintf(const char *fmt, ...); char *dupprintf(const char *fmt, ...);
char *dupvprintf(const char *fmt, va_list ap); char *dupvprintf(const char *fmt, va_list ap);
char *fgetline(FILE *fp);
void base64_encode_atom(unsigned char *data, int n, char *out); void base64_encode_atom(unsigned char *data, int n, char *out);
struct bufchain_granule; struct bufchain_granule;

71
pinger.c Normal file
View File

@ -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);
}

48
psftp.c
View File

@ -1361,45 +1361,34 @@ static int sftp_cmd_help(struct sftp_command *cmd)
struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags) struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
{ {
char *line; char *line;
int linelen, linesize;
struct sftp_command *cmd; struct sftp_command *cmd;
char *p, *q, *r; char *p, *q, *r;
int quoting; int quoting;
if ((mode == 0) || (modeflags & 1)) {
printf("psftp> ");
}
fflush(stdout);
cmd = snew(struct sftp_command); cmd = snew(struct sftp_command);
cmd->words = NULL; cmd->words = NULL;
cmd->nwords = 0; cmd->nwords = 0;
cmd->wordssize = 0; cmd->wordssize = 0;
line = NULL; line = NULL;
linesize = linelen = 0;
while (1) {
int len;
char *ret;
linesize += 512; if (fp) {
line = sresize(line, linesize, char); if (modeflags & 1)
ret = fgets(line + linelen, linesize - linelen, fp); printf("psftp> ");
line = fgetline(fp);
if (!ret || (linelen == 0 && line[0] == '\0')) { } else {
cmd->obey = sftp_cmd_quit; line = ssh_sftp_get_cmdline("psftp> ");
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 (!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) { if (modeflags & 1) {
printf("%s\n", line); 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. * Now parse the first word and assign a function.
*/ */
@ -1551,7 +1541,7 @@ void do_sftp(int mode, int modeflags, char *batchfile)
*/ */
while (1) { while (1) {
struct sftp_command *cmd; struct sftp_command *cmd;
cmd = sftp_getcmd(stdin, 0, 0); cmd = sftp_getcmd(NULL, 0, 0);
if (!cmd) if (!cmd)
break; break;
ret = cmd->obey(cmd); ret = cmd->obey(cmd);

View File

@ -32,6 +32,12 @@ void get_file_times(char *filename, unsigned long *mtime,
*/ */
int ssh_sftp_loop_iteration(void); 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- * The main program in psftp.c. Called from main() in the platform-
* specific code, after doing any platform-specific initialisation. * specific code, after doing any platform-specific initialisation.

44
putty.h
View File

@ -578,6 +578,7 @@ void ldisc_update(void *frontend, int echo, int edit);
* shutdown. */ * shutdown. */
void update_specials_menu(void *frontend); void update_specials_menu(void *frontend);
int from_backend(void *frontend, int is_stderr, const char *data, int len); int from_backend(void *frontend, int is_stderr, const char *data, int len);
void notify_remote_exit(void *frontend);
#define OPTIMISE_IS_SCROLL 1 #define OPTIMISE_IS_SCROLL 1
void set_iconic(void *frontend, int iconic); void set_iconic(void *frontend, int iconic);
@ -744,6 +745,14 @@ int random_byte(void);
void random_get_savedata(void **data, int *len); void random_get_savedata(void **data, int *len);
extern int random_active; 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. * Exports from misc.c.
*/ */
@ -895,4 +904,39 @@ int filename_is_null(Filename fn);
char *get_username(void); /* return value needs freeing */ char *get_username(void); /* return value needs freeing */
char *get_random_data(int bytes); /* used in cmdgen.c */ 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 #endif

1
raw.c
View File

@ -37,6 +37,7 @@ static int raw_closing(Plug plug, const char *error_msg, int error_code,
if (raw->s) { if (raw->s) {
sk_close(raw->s); sk_close(raw->s);
raw->s = NULL; raw->s = NULL;
notify_remote_exit(raw->frontend);
} }
if (error_msg) { if (error_msg) {
/* A socket error has occurred. */ /* A socket error has occurred. */

View File

@ -39,6 +39,7 @@ static int rlogin_closing(Plug plug, const char *error_msg, int error_code,
if (rlogin->s) { if (rlogin->s) {
sk_close(rlogin->s); sk_close(rlogin->s);
rlogin->s = NULL; rlogin->s = NULL;
notify_remote_exit(rlogin->frontend);
} }
if (error_msg) { if (error_msg) {
/* A socket error has occurred. */ /* A socket error has occurred. */

13
ssh.c
View File

@ -698,6 +698,11 @@ struct ssh_tag {
* with at any time. * with at any time.
*/ */
handler_fn_t packet_dispatch[256]; handler_fn_t packet_dispatch[256];
/*
* This module deals with sending keepalives.
*/
Pinger pinger;
}; };
#define logevent(s) logevent(ssh->frontend, s) #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); update_specials_menu(ssh->frontend);
ssh->state = SSH_STATE_BEFORE_SIZE; ssh->state = SSH_STATE_BEFORE_SIZE;
ssh->pinger = pinger_new(&ssh->cfg, &ssh_backend, ssh);
sfree(s->vstring); sfree(s->vstring);
@ -2216,6 +2222,7 @@ static void ssh_do_close(Ssh ssh)
if (ssh->s) { if (ssh->s) {
sk_close(ssh->s); sk_close(ssh->s);
ssh->s = NULL; ssh->s = NULL;
notify_remote_exit(ssh->frontend);
} }
/* /*
* Now we must shut down any port and X forwardings going * 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); 0, 1, nodelay, keepalive, (Plug) ssh, &ssh->cfg);
if ((err = sk_socket_error(ssh->s)) != NULL) { if ((err = sk_socket_error(ssh->s)) != NULL) {
ssh->s = NULL; ssh->s = NULL;
notify_remote_exit(ssh->frontend);
return err; return err;
} }
@ -6933,6 +6941,8 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
ssh->protocol_initial_phase_done = FALSE; ssh->protocol_initial_phase_done = FALSE;
ssh->pinger = NULL;
p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive); p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
if (p != NULL) if (p != NULL)
return p; return p;
@ -7012,6 +7022,8 @@ static void ssh_free(void *handle)
if (ssh->s) if (ssh->s)
ssh_do_close(ssh); ssh_do_close(ssh);
sfree(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) static void ssh_reconfig(void *handle, Config *cfg)
{ {
Ssh ssh = (Ssh) handle; Ssh ssh = (Ssh) handle;
pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
ssh->cfg = *cfg; /* STRUCTURE COPY */ ssh->cfg = *cfg; /* STRUCTURE COPY */
} }

View File

@ -244,6 +244,8 @@ typedef struct telnet_tag {
} state; } state;
Config cfg; Config cfg;
Pinger pinger;
} *Telnet; } *Telnet;
#define TELNET_MAX_BACKLOG 4096 #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) { if (telnet->s) {
sk_close(telnet->s); sk_close(telnet->s);
telnet->s = NULL; telnet->s = NULL;
notify_remote_exit(telnet->frontend);
} }
if (error_msg) { if (error_msg) {
/* A socket error has occurred. */ /* 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->term_height = telnet->cfg.height;
telnet->state = TOP_LEVEL; telnet->state = TOP_LEVEL;
telnet->ldisc = NULL; telnet->ldisc = NULL;
telnet->pinger = NULL;
*backend_handle = telnet; *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) if ((err = sk_socket_error(telnet->s)) != NULL)
return err; return err;
telnet->pinger = pinger_new(&telnet->cfg, &telnet_backend, telnet);
/* /*
* Initialise option states. * Initialise option states.
*/ */
@ -778,6 +784,8 @@ static void telnet_free(void *handle)
sfree(telnet->sb_buf); sfree(telnet->sb_buf);
if (telnet->s) if (telnet->s)
sk_close(telnet->s); sk_close(telnet->s);
if (telnet->pinger)
pinger_free(telnet->pinger);
sfree(telnet); sfree(telnet);
} }
/* /*
@ -788,6 +796,7 @@ static void telnet_free(void *handle)
static void telnet_reconfig(void *handle, Config *cfg) static void telnet_reconfig(void *handle, Config *cfg)
{ {
Telnet telnet = (Telnet) handle; Telnet telnet = (Telnet) handle;
pinger_reconfig(telnet->pinger, &telnet->cfg, cfg);
telnet->cfg = *cfg; /* STRUCTURE COPY */ telnet->cfg = *cfg; /* STRUCTURE COPY */
} }

View File

@ -43,6 +43,11 @@
#define TM_PUTTY (0xFFFF) #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) \ #define compatibility(x) \
if ( ((CL_##x)&term->compatibility_level) == 0 ) { \ if ( ((CL_##x)&term->compatibility_level) == 0 ) { \
term->termstate=TOPLEVEL; \ 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 lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE) #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. * Set up power-on settings for the terminal.
*/ */
@ -1038,6 +1154,8 @@ static void power_on(Terminal *term)
swap_screen(term, 0, FALSE, FALSE); swap_screen(term, 0, FALSE, FALSE);
erase_lots(term, FALSE, TRUE, TRUE); 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) void term_update(Terminal *term)
{ {
Context ctx; Context ctx;
term->window_update_pending = FALSE;
ctx = get_ctx(term->frontend); ctx = get_ctx(term->frontend);
if (ctx) { if (ctx) {
int need_sbar_update = term->seen_disp_event; 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) { if (term->cfg.scroll_on_key) {
term->disptop = 0; /* return to main screen */ 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 * default one. The full list is: Auto wrap mode, DEC Origin
* Mode, BCE, blinking text, character classes. * 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; int i;
reset_wrap = (term->cfg.wrap_mode != cfg->wrap_mode); reset_wrap = (term->cfg.wrap_mode != cfg->wrap_mode);
reset_decom = (term->cfg.dec_om != cfg->dec_om); reset_decom = (term->cfg.dec_om != cfg->dec_om);
reset_bce = (term->cfg.bce != cfg->bce); reset_bce = (term->cfg.bce != cfg->bce);
reset_blink = (term->cfg.blinktext != cfg->blinktext); reset_tblink = (term->cfg.blinktext != cfg->blinktext);
reset_charclass = 0; reset_charclass = 0;
for (i = 0; i < lenof(term->cfg.wordness); i++) for (i = 0; i < lenof(term->cfg.wordness); i++)
if (term->cfg.wordness[i] != 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; term->use_bce = term->cfg.bce;
set_erase_char(term); set_erase_char(term);
} }
if (reset_blink) if (reset_tblink) {
term->blink_is_real = term->cfg.blinktext; term->blink_is_real = term->cfg.blinktext;
}
if (reset_charclass) if (reset_charclass)
for (i = 0; i < 256; i++) for (i = 0; i < 256; i++)
term->wordness[i] = term->cfg.wordness[i]; term->wordness[i] = term->cfg.wordness[i];
@ -1188,6 +1310,8 @@ void term_reconfig(Terminal *term, Config *cfg)
if (!*term->cfg.printer) { if (!*term->cfg.printer) {
term_print_finish(term); 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->logctx = NULL;
term->compatibility_level = TM_PUTTY; term->compatibility_level = TM_PUTTY;
strcpy(term->id_string, "\033[?6c"); 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_buffer = NULL;
term->paste_len = 0; term->paste_len = 0;
term->last_paste = 0; term->last_paste = 0;
@ -1237,7 +1361,7 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata,
term->seen_disp_event = FALSE; term->seen_disp_event = FALSE;
term->xterm_mouse = term->mouse_is_down = FALSE; term->xterm_mouse = term->mouse_is_down = FALSE;
term->reset_132 = FALSE; term->reset_132 = FALSE;
term->blinker = term->tblinker = 0; term->cblinker = term->tblinker = 0;
term->has_focus = 1; term->has_focus = 1;
term->repeat_off = FALSE; term->repeat_off = FALSE;
term->termstate = TOPLEVEL; term->termstate = TOPLEVEL;
@ -1271,6 +1395,8 @@ Terminal *term_init(Config *mycfg, struct unicode_data *ucsdata,
term->wcTo = NULL; term->wcTo = NULL;
term->wcFromTo_size = 0; term->wcFromTo_size = 0;
term->window_update_pending = FALSE;
term->bidi_cache_size = 0; term->bidi_cache_size = 0;
term->pre_bidi_cache = term->post_bidi_cache = NULL; 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->pre_bidi_cache);
sfree(term->post_bidi_cache); sfree(term->post_bidi_cache);
expire_timer_context(term);
sfree(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) static void toggle_mode(Terminal *term, int mode, int query, int state)
{ {
unsigned long ticks;
if (query) if (query)
switch (mode) { switch (mode) {
case 1: /* DECCKM: application cursor keys */ case 1: /* DECCKM: application cursor keys */
@ -2059,6 +2185,7 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
} else { } else {
term->blink_is_real = term->cfg.blinktext; term->blink_is_real = term->cfg.blinktext;
} }
term_schedule_tblink(term);
break; break;
case 3: /* DECCOLM: 80/132 columns */ case 3: /* DECCOLM: 80/132 columns */
deselect(term); 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 * effective visual bell, so that ESC[?5hESC[?5l will
* always be an actually _visible_ visual bell. * always be an actually _visible_ visual bell.
*/ */
ticks = GETTICKCOUNT(); if (term->rvideo && !state) {
/* turn off a previous vbell to avoid inconsistencies */ /* This is an OFF, so set up a vbell */
if (ticks - term->vbell_startpoint >= VBELL_TIMEOUT) term_schedule_vbell(term, TRUE, term->rvbell_startpoint);
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 */
} else if (!term->rvideo && state) { } else if (!term->rvideo && state) {
/* This is an ON, so we notice the time and save it. */ /* This is an ON, so we notice the time and save it. */
term->rvbell_startpoint = ticks; term->rvbell_startpoint = GETTICKCOUNT();
} }
term->rvideo = state; term->rvideo = state;
term->seen_disp_event = TRUE; seen_disp_event(term);
if (state)
term_update(term);
break; break;
case 6: /* DECOM: DEC origin mode */ case 6: /* DECOM: DEC origin mode */
term->dec_om = state; 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 */ case 25: /* DECTCEM: enable/disable cursor */
compatibility2(OTHER, VT220); compatibility2(OTHER, VT220);
term->cursor_on = state; term->cursor_on = state;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 47: /* alternate screen */ case 47: /* alternate screen */
compatibility(OTHER); compatibility(OTHER);
@ -2141,12 +2256,12 @@ static void toggle_mode(Terminal *term, int mode, int query, int state)
case 1048: /* save/restore cursor */ case 1048: /* save/restore cursor */
if (!term->cfg.no_alt_screen) if (!term->cfg.no_alt_screen)
save_cursor(term, state); save_cursor(term, state);
if (!state) term->seen_disp_event = TRUE; if (!state) seen_disp_event(term);
break; break;
case 1049: /* cursor & alternate screen */ case 1049: /* cursor & alternate screen */
if (state && !term->cfg.no_alt_screen) if (state && !term->cfg.no_alt_screen)
save_cursor(term, state); save_cursor(term, state);
if (!state) term->seen_disp_event = TRUE; if (!state) seen_disp_event(term);
compatibility(OTHER); compatibility(OTHER);
deselect(term); deselect(term);
swap_screen(term, term->cfg.no_alt_screen ? 0 : state, TRUE, FALSE); 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 * in-memory display. There's a big state machine in here to
* process escape sequences... * process escape sequences...
*/ */
void term_out(Terminal *term) static void term_out(Terminal *term)
{ {
unsigned long c; unsigned long c;
int unget; int unget;
@ -2567,13 +2682,12 @@ void term_out(Terminal *term)
*/ */
if (!term->cfg.bellovl || !term->beep_overloaded) { if (!term->cfg.bellovl || !term->beep_overloaded) {
beep(term->frontend, term->cfg.beep); beep(term->frontend, term->cfg.beep);
if (term->cfg.beep == BELL_VISUAL) { if (term->cfg.beep == BELL_VISUAL) {
term->in_vbell = TRUE; term_schedule_vbell(term, FALSE, 0);
term->vbell_startpoint = ticks;
term_update(term);
} }
} }
term->seen_disp_event = TRUE; seen_disp_event(term);
} }
break; break;
case '\b': /* BS: Back space */ case '\b': /* BS: Back space */
@ -2586,7 +2700,7 @@ void term_out(Terminal *term)
term->wrapnext = FALSE; term->wrapnext = FALSE;
else else
term->curs.x--; term->curs.x--;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case '\016': /* LS1: Locking-shift one */ case '\016': /* LS1: Locking-shift one */
compatibility(VT100); compatibility(VT100);
@ -2608,7 +2722,7 @@ void term_out(Terminal *term)
case '\015': /* CR: Carriage return */ case '\015': /* CR: Carriage return */
term->curs.x = 0; term->curs.x = 0;
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = TRUE; seen_disp_event(term);
term->paste_hold = 0; term->paste_hold = 0;
if (term->logctx) if (term->logctx)
logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
@ -2619,7 +2733,7 @@ void term_out(Terminal *term)
erase_lots(term, FALSE, FALSE, TRUE); erase_lots(term, FALSE, FALSE, TRUE);
term->disptop = 0; term->disptop = 0;
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = 1; seen_disp_event(term);
break; break;
} }
case '\013': /* VT: Line tabulation */ case '\013': /* VT: Line tabulation */
@ -2632,7 +2746,7 @@ void term_out(Terminal *term)
if (term->cfg.lfhascr) if (term->cfg.lfhascr)
term->curs.x = 0; term->curs.x = 0;
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = 1; seen_disp_event(term);
term->paste_hold = 0; term->paste_hold = 0;
if (term->logctx) if (term->logctx)
logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII); logtraffic(term->logctx, (unsigned char) c, LGTYP_ASCII);
@ -2657,7 +2771,7 @@ void term_out(Terminal *term)
check_selection(term, old_curs, term->curs); check_selection(term, old_curs, term->curs);
} }
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
} }
} else } else
@ -2776,7 +2890,7 @@ void term_out(Terminal *term)
} }
add_cc(cline, x, c); add_cc(cline, x, c);
term->seen_disp_event = 1; seen_disp_event(term);
} }
continue; continue;
default: default:
@ -2796,7 +2910,7 @@ void term_out(Terminal *term)
term->wrapnext = FALSE; term->wrapnext = FALSE;
} }
} }
term->seen_disp_event = 1; seen_disp_event(term);
} }
break; break;
@ -2841,7 +2955,7 @@ void term_out(Terminal *term)
case '8': /* DECRC: restore cursor */ case '8': /* DECRC: restore cursor */
compatibility(VT100); compatibility(VT100);
save_cursor(term, FALSE); save_cursor(term, FALSE);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case '=': /* DECKPAM: Keypad application mode */ case '=': /* DECKPAM: Keypad application mode */
compatibility(VT100); compatibility(VT100);
@ -2858,7 +2972,7 @@ void term_out(Terminal *term)
else if (term->curs.y < term->rows - 1) else if (term->curs.y < term->rows - 1)
term->curs.y++; term->curs.y++;
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'E': /* NEL: exactly equivalent to CR-LF */ case 'E': /* NEL: exactly equivalent to CR-LF */
compatibility(VT100); compatibility(VT100);
@ -2868,7 +2982,7 @@ void term_out(Terminal *term)
else if (term->curs.y < term->rows - 1) else if (term->curs.y < term->rows - 1)
term->curs.y++; term->curs.y++;
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'M': /* RI: reverse index - backwards LF */ case 'M': /* RI: reverse index - backwards LF */
compatibility(VT100); compatibility(VT100);
@ -2877,7 +2991,7 @@ void term_out(Terminal *term)
else if (term->curs.y > 0) else if (term->curs.y > 0)
term->curs.y--; term->curs.y--;
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'Z': /* DECID: terminal type query */ case 'Z': /* DECID: terminal type query */
compatibility(VT100); compatibility(VT100);
@ -2896,7 +3010,7 @@ void term_out(Terminal *term)
term->reset_132 = 0; term->reset_132 = 0;
} }
term->disptop = 0; term->disptop = 0;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'H': /* HTS: set a tab */ case 'H': /* HTS: set a tab */
compatibility(VT100); compatibility(VT100);
@ -2920,7 +3034,7 @@ void term_out(Terminal *term)
ldata->lattr = LATTR_NORM; ldata->lattr = LATTR_NORM;
} }
term->disptop = 0; term->disptop = 0;
term->seen_disp_event = TRUE; seen_disp_event(term);
scrtop.x = scrtop.y = 0; scrtop.x = scrtop.y = 0;
scrbot.x = 0; scrbot.x = 0;
scrbot.y = term->rows; scrbot.y = term->rows;
@ -3036,7 +3150,7 @@ void term_out(Terminal *term)
case 'A': /* CUU: move up N lines */ case 'A': /* CUU: move up N lines */
move(term, term->curs.x, move(term, term->curs.x,
term->curs.y - def(term->esc_args[0], 1), 1); term->curs.y - def(term->esc_args[0], 1), 1);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'e': /* VPR: move down N lines */ case 'e': /* VPR: move down N lines */
compatibility(ANSI); compatibility(ANSI);
@ -3044,7 +3158,7 @@ void term_out(Terminal *term)
case 'B': /* CUD: Cursor down */ case 'B': /* CUD: Cursor down */
move(term, term->curs.x, move(term, term->curs.x,
term->curs.y + def(term->esc_args[0], 1), 1); term->curs.y + def(term->esc_args[0], 1), 1);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case ANSI('c', '>'): /* DA: report xterm version */ case ANSI('c', '>'): /* DA: report xterm version */
compatibility(OTHER); compatibility(OTHER);
@ -3059,31 +3173,31 @@ void term_out(Terminal *term)
case 'C': /* CUF: Cursor right */ case 'C': /* CUF: Cursor right */
move(term, term->curs.x + def(term->esc_args[0], 1), move(term, term->curs.x + def(term->esc_args[0], 1),
term->curs.y, 1); term->curs.y, 1);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'D': /* CUB: move left N cols */ case 'D': /* CUB: move left N cols */
move(term, term->curs.x - def(term->esc_args[0], 1), move(term, term->curs.x - def(term->esc_args[0], 1),
term->curs.y, 1); term->curs.y, 1);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'E': /* CNL: move down N lines and CR */ case 'E': /* CNL: move down N lines and CR */
compatibility(ANSI); compatibility(ANSI);
move(term, 0, move(term, 0,
term->curs.y + def(term->esc_args[0], 1), 1); term->curs.y + def(term->esc_args[0], 1), 1);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'F': /* CPL: move up N lines and CR */ case 'F': /* CPL: move up N lines and CR */
compatibility(ANSI); compatibility(ANSI);
move(term, 0, move(term, 0,
term->curs.y - def(term->esc_args[0], 1), 1); term->curs.y - def(term->esc_args[0], 1), 1);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'G': /* CHA */ case 'G': /* CHA */
case '`': /* HPA: set horizontal posn */ case '`': /* HPA: set horizontal posn */
compatibility(ANSI); compatibility(ANSI);
move(term, def(term->esc_args[0], 1) - 1, move(term, def(term->esc_args[0], 1) - 1,
term->curs.y, 0); term->curs.y, 0);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'd': /* VPA: set vertical posn */ case 'd': /* VPA: set vertical posn */
compatibility(ANSI); compatibility(ANSI);
@ -3091,7 +3205,7 @@ void term_out(Terminal *term)
((term->dec_om ? term->marg_t : 0) + ((term->dec_om ? term->marg_t : 0) +
def(term->esc_args[0], 1) - 1), def(term->esc_args[0], 1) - 1),
(term->dec_om ? 2 : 0)); (term->dec_om ? 2 : 0));
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'H': /* CUP */ case 'H': /* CUP */
case 'f': /* HVP: set horz and vert posns at once */ 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) + ((term->dec_om ? term->marg_t : 0) +
def(term->esc_args[0], 1) - 1), def(term->esc_args[0], 1) - 1),
(term->dec_om ? 2 : 0)); (term->dec_om ? 2 : 0));
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'J': /* ED: erase screen or parts of it */ 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)); erase_lots(term, FALSE, !!(i & 2), !!(i & 1));
} }
term->disptop = 0; term->disptop = 0;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'K': /* EL: erase line or parts of it */ case 'K': /* EL: erase line or parts of it */
{ {
@ -3120,14 +3234,14 @@ void term_out(Terminal *term)
i = 0; i = 0;
erase_lots(term, TRUE, !!(i & 2), !!(i & 1)); erase_lots(term, TRUE, !!(i & 2), !!(i & 1));
} }
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'L': /* IL: insert lines */ case 'L': /* IL: insert lines */
compatibility(VT102); compatibility(VT102);
if (term->curs.y <= term->marg_b) if (term->curs.y <= term->marg_b)
scroll(term, term->curs.y, term->marg_b, scroll(term, term->curs.y, term->marg_b,
-def(term->esc_args[0], 1), FALSE); -def(term->esc_args[0], 1), FALSE);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'M': /* DL: delete lines */ case 'M': /* DL: delete lines */
compatibility(VT102); compatibility(VT102);
@ -3135,18 +3249,18 @@ void term_out(Terminal *term)
scroll(term, term->curs.y, term->marg_b, scroll(term, term->curs.y, term->marg_b,
def(term->esc_args[0], 1), def(term->esc_args[0], 1),
TRUE); TRUE);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case '@': /* ICH: insert chars */ case '@': /* ICH: insert chars */
/* XXX VTTEST says this is vt220, vt510 manual says vt102 */ /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
compatibility(VT102); compatibility(VT102);
insch(term, def(term->esc_args[0], 1)); insch(term, def(term->esc_args[0], 1));
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'P': /* DCH: delete chars */ case 'P': /* DCH: delete chars */
compatibility(VT102); compatibility(VT102);
insch(term, -def(term->esc_args[0], 1)); insch(term, -def(term->esc_args[0], 1));
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'c': /* DA: terminal type query */ case 'c': /* DA: terminal type query */
compatibility(VT100); compatibility(VT100);
@ -3244,7 +3358,7 @@ void term_out(Terminal *term)
*/ */
term->curs.y = (term->dec_om ? term->curs.y = (term->dec_om ?
term->marg_t : 0); term->marg_t : 0);
term->seen_disp_event = TRUE; seen_disp_event(term);
} }
} }
break; break;
@ -3302,6 +3416,7 @@ void term_out(Terminal *term)
compatibility(SCOANSI); compatibility(SCOANSI);
term->blink_is_real = FALSE; term->blink_is_real = FALSE;
term->curr_attr |= ATTR_BLINK; term->curr_attr |= ATTR_BLINK;
term_schedule_tblink(term);
break; break;
case 7: /* enable reverse video */ case 7: /* enable reverse video */
term->curr_attr |= ATTR_REVERSE; term->curr_attr |= ATTR_REVERSE;
@ -3406,7 +3521,7 @@ void term_out(Terminal *term)
break; break;
case 'u': /* restore cursor */ case 'u': /* restore cursor */
save_cursor(term, FALSE); save_cursor(term, FALSE);
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 't': /* DECSLPP: set page size - ie window height */ 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, scroll(term, term->marg_t, term->marg_b,
def(term->esc_args[0], 1), TRUE); def(term->esc_args[0], 1), TRUE);
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case 'T': /* SD: Scroll down */ case 'T': /* SD: Scroll down */
compatibility(SCOANSI); compatibility(SCOANSI);
scroll(term, term->marg_t, term->marg_b, scroll(term, term->marg_t, term->marg_b,
-def(term->esc_args[0], 1), TRUE); -def(term->esc_args[0], 1), TRUE);
term->wrapnext = FALSE; term->wrapnext = FALSE;
term->seen_disp_event = TRUE; seen_disp_event(term);
break; break;
case ANSI('|', '*'): /* DECSNLS */ case ANSI('|', '*'): /* DECSNLS */
/* /*
@ -3608,7 +3723,7 @@ void term_out(Terminal *term)
while (n--) while (n--)
copy_termchar(cline, p++, copy_termchar(cline, p++,
&term->erase_char); &term->erase_char);
term->seen_disp_event = TRUE; seen_disp_event(term);
} }
break; break;
case 'x': /* DECREQTPARM: report terminal characteristics */ case 'x': /* DECREQTPARM: report terminal characteristics */
@ -3672,6 +3787,7 @@ void term_out(Terminal *term)
case ANSI('D', '='): case ANSI('D', '='):
compatibility(SCOANSI); compatibility(SCOANSI);
term->blink_is_real = FALSE; term->blink_is_real = FALSE;
term_schedule_tblink(term);
if (term->esc_args[0]>=1) if (term->esc_args[0]>=1)
term->curr_attr |= ATTR_BLINK; term->curr_attr |= ATTR_BLINK;
else else
@ -3680,6 +3796,7 @@ void term_out(Terminal *term)
case ANSI('E', '='): case ANSI('E', '='):
compatibility(SCOANSI); compatibility(SCOANSI);
term->blink_is_real = (term->esc_args[0] >= 1); term->blink_is_real = (term->esc_args[0] >= 1);
term_schedule_tblink(term);
break; break;
case ANSI('F', '='): /* set normal foreground */ case ANSI('F', '='): /* set normal foreground */
compatibility(SCOANSI); compatibility(SCOANSI);
@ -3910,7 +4027,7 @@ void term_out(Terminal *term)
break; break;
case VT52_ESC: case VT52_ESC:
term->termstate = TOPLEVEL; term->termstate = TOPLEVEL;
term->seen_disp_event = TRUE; seen_disp_event(term);
switch (c) { switch (c) {
case 'A': case 'A':
move(term, term->curs.x, term->curs.y - 1, 1); move(term, term->curs.x, term->curs.y - 1, 1);
@ -4018,6 +4135,7 @@ void term_out(Terminal *term)
*/ */
term->vt52_mode = FALSE; term->vt52_mode = FALSE;
term->blink_is_real = term->cfg.blinktext; term->blink_is_real = term->cfg.blinktext;
term_schedule_tblink(term);
break; break;
#if 0 #if 0
case '^': case '^':
@ -4245,7 +4363,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
wchar_t *ch; wchar_t *ch;
int chlen; int chlen;
termchar cursor_background; termchar cursor_background;
unsigned long ticks;
#ifdef OPTIMISE_SCROLL #ifdef OPTIMISE_SCROLL
struct scrollregion *sr; struct scrollregion *sr;
#endif /* OPTIMISE_SCROLL */ #endif /* OPTIMISE_SCROLL */
@ -4255,28 +4372,19 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
chlen = 1024; chlen = 1024;
ch = snewn(chlen, wchar_t); 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); rv = (!term->rvideo ^ !term->in_vbell ? ATTR_REVERSE : 0);
/* Depends on: /* Depends on:
* screen array, disptop, scrtop, * screen array, disptop, scrtop,
* selection, rv, * selection, rv,
* cfg.blinkpc, blink_is_real, tblinker, * 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 ? */ /* Has the cursor position or type changed ? */
if (term->cursor_on) { if (term->cursor_on) {
if (term->has_focus) { if (term->has_focus) {
if (term->blinker || !term->cfg.blink_cur) if (term->cblinker || !term->cfg.blink_cur)
cursor = TATTR_ACTCURS; cursor = TATTR_ACTCURS;
else else
cursor = 0; cursor = 0;
@ -4678,39 +4786,6 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise)
sfree(ch); 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. * Invalidate the whole screen so it will be repainted in full.
*/ */
@ -4744,12 +4819,14 @@ void term_paint(Terminal *term, Context ctx,
term->disptext[i]->chars[j].attr = ATTR_INVALID; term->disptext[i]->chars[j].attr = ATTR_INVALID;
} }
/* This should happen soon enough, also for some reason it sometimes if (immediately) {
* fails to actually do anything when re-sizing ... painting the wrong
* window perhaps ?
*/
if (immediately)
do_paint (term, ctx, FALSE); 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) { if (!term->in_term_out) {
term->in_term_out = TRUE; term->in_term_out = TRUE;
term_blink(term, 1); term_reset_cblink(term);
term_out(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; term->in_term_out = FALSE;
} }

View File

@ -117,7 +117,7 @@ struct terminal_tag {
int cursor_on; /* cursor enabled flag */ int cursor_on; /* cursor enabled flag */
int reset_132; /* Flag ESC c resets to 80 cols */ int reset_132; /* Flag ESC c resets to 80 cols */
int use_bce; /* Use Background coloured erase */ 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 tblinker; /* When the blinking text is on */
int blink_is_real; /* Actually blink blinking text */ int blink_is_real; /* Actually blink blinking text */
int term_echoing; /* Does terminal want local echo? */ int term_echoing; /* Does terminal want local echo? */
@ -136,15 +136,12 @@ struct terminal_tag {
int rows, cols, savelines; int rows, cols, savelines;
int has_focus; int has_focus;
int in_vbell; int in_vbell;
unsigned long vbell_startpoint; long vbell_end;
int app_cursor_keys, app_keypad_keys, vt52_mode; int app_cursor_keys, app_keypad_keys, vt52_mode;
int repeat_off, cr_lf_return; int repeat_off, cr_lf_return;
int seen_disp_event; int seen_disp_event;
int big_cursor; int big_cursor;
long last_blink; /* used for real blinking control */
long last_tblink;
int xterm_mouse; /* send mouse messages to app */ int xterm_mouse; /* send mouse messages to app */
int mouse_is_down; /* used while tracking mouse buttons */ int mouse_is_down; /* used while tracking mouse buttons */
@ -245,6 +242,19 @@ struct terminal_tag {
*/ */
int in_term_out; 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. * These are buffers used by the bidi and Arabic shaping code.
*/ */

173
timing.c Normal file
View File

@ -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);
}

View File

@ -40,6 +40,13 @@ GdkAtom compound_text_atom, utf8_string_atom;
extern char **pty_argv; /* declared in pty.c */ extern char **pty_argv; /* declared in pty.c */
extern int use_pty_argv; 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 { struct gui_data {
GtkWidget *window, *area, *sbar; GtkWidget *window, *area, *sbar;
GtkBox *hbox; GtkBox *hbox;
@ -1022,7 +1029,6 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data)
show_mouseptr(inst, 0); show_mouseptr(inst, 0);
term_seen_key_event(inst->term); term_seen_key_event(inst->term);
term_out(inst->term);
} }
return TRUE; return TRUE;
@ -1130,9 +1136,9 @@ void frontend_keypress(void *handle)
exit(0); 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; int exitcode;
if (!inst->exited && if (!inst->exited &&
@ -1153,10 +1159,40 @@ gint timer_func(gpointer data)
} }
gtk_widget_show(inst->restartitem); gtk_widget_show(inst->restartitem);
} }
}
term_update(inst->term); static gint timer_trigger(gpointer data)
term_blink(inst->term, 0); {
return TRUE; 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) 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; struct gui_data *inst = (struct gui_data *)data;
inst->term->has_focus = event->in; inst->term->has_focus = event->in;
term_out(inst->term);
term_update(inst->term); term_update(inst->term);
show_mouseptr(inst, 1); show_mouseptr(inst, 1);
return FALSE; return FALSE;
@ -3392,7 +3427,6 @@ int pt_main(int argc, char **argv)
if (inst->cfg.scrollbar) if (inst->cfg.scrollbar)
gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed", gtk_signal_connect(GTK_OBJECT(inst->sbar_adjust), "value_changed",
GTK_SIGNAL_FUNC(scrollbar_moved), inst); GTK_SIGNAL_FUNC(scrollbar_moved), inst);
gtk_timeout_add(20, timer_func, inst);
gtk_widget_add_events(GTK_WIDGET(inst->area), gtk_widget_add_events(GTK_WIDGET(inst->area),
GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |

View File

@ -474,6 +474,8 @@ int pty_select_result(int fd, int event)
#endif #endif
from_backend(pty_frontend, 0, message, strlen(message)); from_backend(pty_frontend, 0, message, strlen(message));
} }
notify_remote_exit(pty_frontend);
} }
return !finished; return !finished;
} }

View File

@ -45,8 +45,8 @@ extern Backend pty_backend;
/* Simple wraparound timer function */ /* Simple wraparound timer function */
unsigned long getticks(void); /* based on gettimeofday(2) */ unsigned long getticks(void); /* based on gettimeofday(2) */
#define GETTICKCOUNT getticks #define GETTICKCOUNT getticks
#define TICKSPERSEC 1000000 /* gettimeofday returns microseconds */ #define TICKSPERSEC 1000 /* we choose to use milliseconds */
#define CURSORBLINK 450000 /* no standard way to set this */ #define CURSORBLINK 450 /* no standard way to set this */
#define WCHAR wchar_t #define WCHAR wchar_t
#define BYTE unsigned char #define BYTE unsigned char

View 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, void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
char *keystr, char *fingerprint) char *keystr, char *fingerprint)
{ {

View File

@ -15,14 +15,13 @@ unsigned long getticks(void)
struct timeval tv; struct timeval tv;
gettimeofday(&tv, NULL); gettimeofday(&tv, NULL);
/* /*
* This will wrap around approximately every 4000 seconds, i.e. * We want to use milliseconds rather than microseconds,
* just over an hour, which is more than enough. * 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 filename_from_str(const char *str)
{ {
Filename ret; Filename ret;

View File

@ -252,6 +252,7 @@ int main(int argc, char **argv)
int errors; int errors;
int use_subsystem = 0; int use_subsystem = 0;
void *ldisc, *logctx; void *ldisc, *logctx;
long now;
ssh_get_line = console_get_line; ssh_get_line = console_get_line;
@ -584,6 +585,7 @@ int main(int argc, char **argv)
atexit(cleanup_termios); atexit(cleanup_termios);
ldisc_update(NULL, 1, 1); ldisc_update(NULL, 1, 1);
sending = FALSE; sending = FALSE;
now = GETTICKCOUNT();
while (1) { while (1) {
fd_set rset, wset, xset; fd_set rset, wset, xset;
@ -644,7 +646,23 @@ int main(int argc, char **argv)
} }
do { 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); } while (ret < 0 && errno == EINTR);
if (ret < 0) { if (ret < 0) {

View 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; fd_set rset, wset, xset;
int i, fdcount, fdsize, *fdlist; int i, fdcount, fdsize, *fdlist;
int fd, fdstate, rwx, ret, maxfd; int fd, fdstate, rwx, ret, maxfd;
long now = GETTICKCOUNT();
fdlist = NULL; fdlist = NULL;
fdcount = fdsize = 0; fdcount = fdsize = 0;
/* Count the currently active fds. */
i = 0;
for (fd = first_fd(&fdstate, &rwx); fd >= 0;
fd = next_fd(&fdstate, &rwx)) i++;
if (i < 1)
return -1; /* doom */
/* Expand the fdlist buffer if necessary. */
if (i > fdsize) {
fdsize = i + 16;
fdlist = sresize(fdlist, fdsize, int);
}
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&xset);
maxfd = 0;
/*
* 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);
}
do { do {
ret = select(maxfd, &rset, &wset, &xset, NULL);
} while (ret < 0 && errno == EINTR); /* Count the currently active fds. */
i = 0;
for (fd = first_fd(&fdstate, &rwx); fd >= 0;
fd = next_fd(&fdstate, &rwx)) i++;
if (i < 1)
return -1; /* doom */
/* Expand the fdlist buffer if necessary. */
if (i > fdsize) {
fdsize = i + 16;
fdlist = sresize(fdlist, fdsize, int);
}
FD_ZERO(&rset);
FD_ZERO(&wset);
FD_ZERO(&xset);
maxfd = 0;
/*
* 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) { if (ret < 0) {
perror("select"); perror("select");
@ -393,7 +418,58 @@ int ssh_sftp_loop_iteration(void)
sfree(fdlist); 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;
}
}
}
} }
/* /*

View File

@ -105,29 +105,6 @@ static void make_filename(char *filename, int index, const char *subname)
filename[FILENAME_MAX-1] = '\0'; 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) void *open_settings_w(const char *sessionname, char **errmsg)
{ {
char filename[FILENAME_MAX]; char filename[FILENAME_MAX];

View File

@ -33,6 +33,14 @@ void cleanup_exit(int code)
exit(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, void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
char *keystr, char *fingerprint) char *keystr, char *fingerprint)
{ {

View File

@ -92,17 +92,12 @@ static int offset_width, offset_height;
static int was_zoomed = 0; static int was_zoomed = 0;
static int prev_rows, prev_cols; static int prev_rows, prev_cols;
static int pending_netevent = 0; static void enact_netevent(WPARAM, LPARAM);
static WPARAM pend_netevent_wParam = 0;
static LPARAM pend_netevent_lParam = 0;
static void enact_pending_netevent(void);
static void flash_window(int mode); static void flash_window(int mode);
static void sys_cursor_update(void); static void sys_cursor_update(void);
static int is_shift_pressed(void); static int is_shift_pressed(void);
static int get_fullscreen_rect(RECT * ss); static int get_fullscreen_rect(RECT * ss);
static time_t last_movement = 0;
static int caret_x = -1, caret_y = -1; static int caret_x = -1, caret_y = -1;
static int kbd_codepage; static int kbd_codepage;
@ -117,6 +112,9 @@ static int session_closed;
static const struct telnet_special *specials; static const struct telnet_special *specials;
static int n_specials; static int n_specials;
#define TIMING_TIMER_ID 1234
static long timing_next_time;
static struct { static struct {
HMENU menu; HMENU menu;
int specials_submenu_pos; int specials_submenu_pos;
@ -776,30 +774,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
UpdateWindow(hwnd); UpdateWindow(hwnd);
if (GetMessage(&msg, NULL, 0, 0) == 1) { if (GetMessage(&msg, NULL, 0, 0) == 1) {
int timer_id = 0, long_timer = 0;
while (msg.message != WM_QUIT) { 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))) if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
DispatchMessage(&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 */ /* Send the paste buffer if there's anything to send */
term_paste(term); term_paste(term);
/* If there's nothing new in the queue then we can do everything /* If there's nothing new in the queue then we can do everything
* we've delayed, reading the socket, writing, and repainting * we've delayed, reading the socket, writing, and repainting
* the window. * 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)) if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
continue; 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 */ /* The messages seem unreliable; especially if we're being tricky */
term->has_focus = (GetForegroundWindow() == hwnd); term->has_focus = (GetForegroundWindow() == hwnd);
if (term->in_vbell) net_pending_errors();
/* 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;
/* There's no point rescanning everything in the message queue /* There's no point rescanning everything in the message queue
* so we do an apparently unnecessary wait here * 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 * 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; static int reentering = 0;
extern int select_result(WPARAM, LPARAM); extern int select_result(WPARAM, LPARAM);
@ -1044,10 +991,8 @@ static void enact_pending_netevent(void)
if (reentering) if (reentering)
return; /* don't unpend the pending */ return; /* don't unpend the pending */
pending_netevent = FALSE;
reentering = 1; reentering = 1;
ret = select_result(pend_netevent_wParam, pend_netevent_lParam); ret = select_result(wParam, lParam);
reentering = 0; reentering = 0;
if (ret == 0 && !session_closed) { if (ret == 0 && !session_closed) {
@ -1795,6 +1740,17 @@ static int is_shift_pressed(void)
static int resizing; 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, static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam) WPARAM wParam, LPARAM lParam)
{ {
@ -1806,25 +1762,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
switch (message) { switch (message) {
case WM_TIMER: case WM_TIMER:
if (pending_netevent) if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
enact_pending_netevent(); long next;
if (GetCapture() != hwnd ||
(send_raw_mouse && !(cfg.mouse_override && is_shift_pressed()))) KillTimer(hwnd, TIMING_TIMER_ID);
term_out(term); if (run_timers(timing_next_time, &next)) {
noise_regular(); timer_change_notify(next);
HideCaret(hwnd); } else {
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;
} }
} }
net_pending_errors();
return 0; return 0;
case WM_CREATE: case WM_CREATE:
break; break;
@ -2304,18 +2250,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
case WM_PAINT: case WM_PAINT:
{ {
PAINTSTRUCT p; PAINTSTRUCT p;
HideCaret(hwnd); HideCaret(hwnd);
hdc = BeginPaint(hwnd, &p); hdc = BeginPaint(hwnd, &p);
if (pal) { if (pal) {
SelectPalette(hdc, pal, TRUE); SelectPalette(hdc, pal, TRUE);
RealizePalette(hdc); RealizePalette(hdc);
} }
term_paint(term, hdc, term_paint(term, hdc,
(p.rcPaint.left-offset_width)/font_width, (p.rcPaint.left-offset_width)/font_width,
(p.rcPaint.top-offset_height)/font_height, (p.rcPaint.top-offset_height)/font_height,
(p.rcPaint.right-offset_width-1)/font_width, (p.rcPaint.right-offset_width-1)/font_width,
(p.rcPaint.bottom-offset_height-1)/font_height, (p.rcPaint.bottom-offset_height-1)/font_height,
is_alt_pressed()); TRUE);
if (p.fErase || if (p.fErase ||
p.rcPaint.left < offset_width || p.rcPaint.left < offset_width ||
@ -2365,20 +2313,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
} }
return 0; return 0;
case WM_NETEVENT: case WM_NETEVENT:
/* Notice we can get multiple netevents, FD_READ, FD_WRITE etc enact_netevent(wParam, lParam);
* but the only one that's likely to try to overload us is FD_READ. net_pending_errors();
* 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);
return 0; return 0;
case WM_SETFOCUS: case WM_SETFOCUS:
term->has_focus = TRUE; term->has_focus = TRUE;
@ -2386,7 +2322,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
ShowCaret(hwnd); ShowCaret(hwnd);
flash_window(0); /* stop */ flash_window(0); /* stop */
compose_state = 0; compose_state = 0;
term_out(term);
term_update(term); term_update(term);
break; break;
case WM_KILLFOCUS: case WM_KILLFOCUS:
@ -2394,7 +2329,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
term->has_focus = FALSE; term->has_focus = FALSE;
DestroyCaret(); DestroyCaret();
caret_x = caret_y = -1; /* ensure caret is replaced next time */ caret_x = caret_y = -1; /* ensure caret is replaced next time */
term_out(term);
term_update(term); term_update(term);
break; break;
case WM_ENTERSIZEMOVE: case WM_ENTERSIZEMOVE:
@ -4624,14 +4558,23 @@ void modalfatalbox(char *fmt, ...)
cleanup_exit(1); 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. * Manage window caption / taskbar flashing, if enabled.
* 0 = stop, 1 = maintain, 2 = start * 0 = stop, 1 = maintain, 2 = start
*/ */
static void flash_window(int mode) static void flash_window(int mode)
{ {
static long last_flash = 0;
static int flashing = 0;
if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) { if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
/* stop */ /* stop */
if (flashing) { if (flashing) {
@ -4642,20 +4585,16 @@ static void flash_window(int mode)
} else if (mode == 2) { } else if (mode == 2) {
/* start */ /* start */
if (!flashing) { if (!flashing) {
last_flash = GetTickCount();
flashing = 1; flashing = 1;
FlashWindow(hwnd, TRUE); FlashWindow(hwnd, TRUE);
next_flash = schedule_timer(450, flash_window_timer, hwnd);
} }
} else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) { } else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
/* maintain */ /* maintain */
if (flashing) { if (flashing) {
long now = GetTickCount(); FlashWindow(hwnd, TRUE); /* toggle */
long fdiff = now - last_flash; next_flash = schedule_timer(450, flash_window_timer, hwnd);
if (fdiff < 0 || fdiff > 450) {
last_flash = now;
FlashWindow(hwnd, TRUE); /* toggle */
}
} }
} }
} }

View File

@ -1353,6 +1353,16 @@ SOCKET next_socket(int *state)
return s ? s->s : INVALID_SOCKET; 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) int net_service_lookup(char *service)
{ {
struct servent *se; struct servent *se;

View File

@ -279,6 +279,7 @@ int main(int argc, char **argv)
int exitcode; int exitcode;
int errors; int errors;
int use_subsystem = 0; int use_subsystem = 0;
long now, next;
ssh_get_line = console_get_line; ssh_get_line = console_get_line;
@ -631,8 +632,11 @@ int main(int argc, char **argv)
cleanup_exit(1); cleanup_exit(1);
} }
now = GETTICKCOUNT();
while (1) { while (1) {
int n; int n;
DWORD ticks;
if (!sending && back->sendok(backhandle)) { if (!sending && back->sendok(backhandle)) {
/* /*
@ -661,9 +665,16 @@ int main(int argc, char **argv)
sending = TRUE; 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); QS_POSTMESSAGE);
if (n == 0) { if (n == WAIT_OBJECT_0 + 0) {
WSANETWORKEVENTS things; WSANETWORKEVENTS things;
SOCKET socket; SOCKET socket;
extern SOCKET first_socket(int *), next_socket(int *); 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; reading = 0;
noise_ultralight(idata.len); noise_ultralight(idata.len);
if (connopen && back->socket(backhandle) != NULL) { if (connopen && back->socket(backhandle) != NULL) {
@ -734,7 +745,7 @@ int main(int argc, char **argv)
back->special(backhandle, TS_EOF); back->special(backhandle, TS_EOF);
} }
} }
} else if (n == 2) { } else if (n == WAIT_OBJECT_0 + 2) {
odata.busy = 0; odata.busy = 0;
if (!odata.writeret) { if (!odata.writeret) {
fprintf(stderr, "Unable to write to standard output\n"); 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) + back->unthrottle(backhandle, bufchain_size(&stdout_data) +
bufchain_size(&stderr_data)); bufchain_size(&stderr_data));
} }
} else if (n == 3) { } else if (n == WAIT_OBJECT_0 + 3) {
edata.busy = 0; edata.busy = 0;
if (!edata.writeret) { if (!edata.writeret) {
fprintf(stderr, "Unable to write to standard output\n"); 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) + back->unthrottle(backhandle, bufchain_size(&stdout_data) +
bufchain_size(&stderr_data)); bufchain_size(&stderr_data));
} }
} else if (n == 4) { } else if (n == WAIT_OBJECT_0 + 4) {
MSG msg; MSG msg;
while (PeekMessage(&msg, INVALID_HANDLE_VALUE, while (PeekMessage(&msg, INVALID_HANDLE_VALUE,
WM_AGENT_CALLBACK, WM_AGENT_CALLBACK, WM_AGENT_CALLBACK, WM_AGENT_CALLBACK,
@ -770,6 +781,13 @@ int main(int argc, char **argv)
sfree(c); sfree(c);
} }
} }
if (n == WAIT_TIMEOUT) {
now = next;
} else {
now = GETTICKCOUNT();
}
if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) { if (!reading && back->sendbuffer(backhandle) < MAX_STDIN_BACKLOG) {
SetEvent(idata.eventback); SetEvent(idata.eventback);
reading = 1; reading = 1;

View File

@ -2,6 +2,8 @@
* winsftp.c: the Windows-specific parts of PSFTP and PSCP. * winsftp.c: the Windows-specific parts of PSFTP and PSCP.
*/ */
#include <assert.h>
#include "putty.h" #include "putty.h"
#include "psftp.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. * Be told what socket we're supposed to be using.
*/ */
static SOCKET sftp_ssh_socket; static SOCKET sftp_ssh_socket;
static HANDLE netevent = NULL;
char *do_select(SOCKET skt, int startup) char *do_select(SOCKET skt, int startup)
{ {
int events;
if (startup) if (startup)
sftp_ssh_socket = skt; sftp_ssh_socket = skt;
else else
sftp_ssh_socket = INVALID_SOCKET; 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; return NULL;
} }
extern int select_result(WPARAM, LPARAM); 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. * 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) int ssh_sftp_loop_iteration(void)
{ {
fd_set readfds;
if (sftp_ssh_socket == INVALID_SOCKET) if (sftp_ssh_socket == INVALID_SOCKET)
return -1; /* doom */ return -1; /* doom */
FD_ZERO(&readfds); if (p_WSAEventSelect == NULL) {
FD_SET(sftp_ssh_socket, &readfds); fd_set readfds;
if (p_select(1, &readfds, NULL, NULL, NULL) < 0) int ret;
return -1; /* doom */ 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; 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. * Main program. Parse arguments etc.
*/ */

View File

@ -150,6 +150,8 @@ extern int (WINAPI *p_WSAGetLastError)(void);
extern int (WINAPI *p_WSAEnumNetworkEvents) extern int (WINAPI *p_WSAEnumNetworkEvents)
(SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents); (SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents);
extern int socket_writable(SOCKET skt);
/* /*
* Exports from winctrls.c. * Exports from winctrls.c.
*/ */