1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-07-01 11:32:48 -05: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

View File

@ -33,6 +33,14 @@ void cleanup_exit(int code)
exit(code);
}
void notify_remote_exit(void *frontend)
{
}
void timer_change_notify(long next)
{
}
void verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
char *keystr, char *fingerprint)
{

View File

@ -92,17 +92,12 @@ static int offset_width, offset_height;
static int was_zoomed = 0;
static int prev_rows, prev_cols;
static int pending_netevent = 0;
static WPARAM pend_netevent_wParam = 0;
static LPARAM pend_netevent_lParam = 0;
static void enact_pending_netevent(void);
static void enact_netevent(WPARAM, LPARAM);
static void flash_window(int mode);
static void sys_cursor_update(void);
static int is_shift_pressed(void);
static int get_fullscreen_rect(RECT * ss);
static time_t last_movement = 0;
static int caret_x = -1, caret_y = -1;
static int kbd_codepage;
@ -117,6 +112,9 @@ static int session_closed;
static const struct telnet_special *specials;
static int n_specials;
#define TIMING_TIMER_ID 1234
static long timing_next_time;
static struct {
HMENU menu;
int specials_submenu_pos;
@ -776,30 +774,11 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
UpdateWindow(hwnd);
if (GetMessage(&msg, NULL, 0, 0) == 1) {
int timer_id = 0, long_timer = 0;
while (msg.message != WM_QUIT) {
/* Sometimes DispatchMessage calls routines that use their own
* GetMessage loop, setup this timer so we get some control back.
*
* Also call term_update() from the timer so that if the host
* is sending data flat out we still do redraws.
*/
if (timer_id && long_timer) {
KillTimer(hwnd, timer_id);
long_timer = timer_id = 0;
}
if (!timer_id)
timer_id = SetTimer(hwnd, 1, 20, NULL);
if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
DispatchMessage(&msg);
/* Make sure we blink everything that needs it. */
term_blink(term, 0);
/* Send the paste buffer if there's anything to send */
term_paste(term);
/* If there's nothing new in the queue then we can do everything
* we've delayed, reading the socket, writing, and repainting
* the window.
@ -807,42 +786,10 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
continue;
if (pending_netevent) {
enact_pending_netevent();
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
continue;
}
/* Okay there is now nothing to do so we make sure the screen is
* completely up to date then tell windows to call us in a little
* while.
*/
if (timer_id) {
KillTimer(hwnd, timer_id);
timer_id = 0;
}
HideCaret(hwnd);
if (GetCapture() != hwnd ||
(send_raw_mouse &&
!(cfg.mouse_override && is_shift_pressed())))
term_out(term);
term_update(term);
ShowCaret(hwnd);
flash_window(1); /* maintain */
/* The messages seem unreliable; especially if we're being tricky */
term->has_focus = (GetForegroundWindow() == hwnd);
if (term->in_vbell)
/* Hmm, term_update didn't want to do an update too soon ... */
timer_id = SetTimer(hwnd, 1, 50, NULL);
else if (!term->has_focus)
timer_id = SetTimer(hwnd, 1, 500, NULL);
else
timer_id = SetTimer(hwnd, 1, 100, NULL);
long_timer = 1;
net_pending_errors();
/* There's no point rescanning everything in the message queue
* so we do an apparently unnecessary wait here
@ -1035,7 +982,7 @@ void cmdline_error(char *fmt, ...)
/*
* Actually do the job requested by a WM_NETEVENT
*/
static void enact_pending_netevent(void)
static void enact_netevent(WPARAM wParam, LPARAM lParam)
{
static int reentering = 0;
extern int select_result(WPARAM, LPARAM);
@ -1044,10 +991,8 @@ static void enact_pending_netevent(void)
if (reentering)
return; /* don't unpend the pending */
pending_netevent = FALSE;
reentering = 1;
ret = select_result(pend_netevent_wParam, pend_netevent_lParam);
ret = select_result(wParam, lParam);
reentering = 0;
if (ret == 0 && !session_closed) {
@ -1795,6 +1740,17 @@ static int is_shift_pressed(void)
static int resizing;
void notify_remote_exit(void *fe) { /* stub not needed in this frontend */ }
void timer_change_notify(long next)
{
long ticks = next - GETTICKCOUNT();
if (ticks <= 0) ticks = 1; /* just in case */
KillTimer(hwnd, TIMING_TIMER_ID);
SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL);
timing_next_time = next;
}
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
@ -1806,25 +1762,15 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
switch (message) {
case WM_TIMER:
if (pending_netevent)
enact_pending_netevent();
if (GetCapture() != hwnd ||
(send_raw_mouse && !(cfg.mouse_override && is_shift_pressed())))
term_out(term);
noise_regular();
HideCaret(hwnd);
term_update(term);
ShowCaret(hwnd);
if (cfg.ping_interval > 0) {
time_t now;
time(&now);
if (now - last_movement > cfg.ping_interval) {
if (back)
back->special(backhandle, TS_PING);
last_movement = now;
if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
long next;
KillTimer(hwnd, TIMING_TIMER_ID);
if (run_timers(timing_next_time, &next)) {
timer_change_notify(next);
} else {
}
}
net_pending_errors();
return 0;
case WM_CREATE:
break;
@ -2304,18 +2250,20 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
case WM_PAINT:
{
PAINTSTRUCT p;
HideCaret(hwnd);
hdc = BeginPaint(hwnd, &p);
if (pal) {
SelectPalette(hdc, pal, TRUE);
RealizePalette(hdc);
}
term_paint(term, hdc,
(p.rcPaint.left-offset_width)/font_width,
(p.rcPaint.top-offset_height)/font_height,
(p.rcPaint.right-offset_width-1)/font_width,
(p.rcPaint.bottom-offset_height-1)/font_height,
is_alt_pressed());
TRUE);
if (p.fErase ||
p.rcPaint.left < offset_width ||
@ -2365,20 +2313,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
}
return 0;
case WM_NETEVENT:
/* Notice we can get multiple netevents, FD_READ, FD_WRITE etc
* but the only one that's likely to try to overload us is FD_READ.
* This means buffering just one is fine.
*/
if (pending_netevent)
enact_pending_netevent();
pending_netevent = TRUE;
pend_netevent_wParam = wParam;
pend_netevent_lParam = lParam;
if (WSAGETSELECTEVENT(lParam) != FD_READ)
enact_pending_netevent();
time(&last_movement);
enact_netevent(wParam, lParam);
net_pending_errors();
return 0;
case WM_SETFOCUS:
term->has_focus = TRUE;
@ -2386,7 +2322,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
ShowCaret(hwnd);
flash_window(0); /* stop */
compose_state = 0;
term_out(term);
term_update(term);
break;
case WM_KILLFOCUS:
@ -2394,7 +2329,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
term->has_focus = FALSE;
DestroyCaret();
caret_x = caret_y = -1; /* ensure caret is replaced next time */
term_out(term);
term_update(term);
break;
case WM_ENTERSIZEMOVE:
@ -4624,14 +4558,23 @@ void modalfatalbox(char *fmt, ...)
cleanup_exit(1);
}
static void flash_window(int mode);
static long next_flash;
static int flashing = 0;
static void flash_window_timer(void *ctx, long now)
{
if (flashing && now - next_flash >= 0) {
flash_window(1);
}
}
/*
* Manage window caption / taskbar flashing, if enabled.
* 0 = stop, 1 = maintain, 2 = start
*/
static void flash_window(int mode)
{
static long last_flash = 0;
static int flashing = 0;
if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
/* stop */
if (flashing) {
@ -4642,20 +4585,16 @@ static void flash_window(int mode)
} else if (mode == 2) {
/* start */
if (!flashing) {
last_flash = GetTickCount();
flashing = 1;
FlashWindow(hwnd, TRUE);
next_flash = schedule_timer(450, flash_window_timer, hwnd);
}
} else if ((mode == 1) && (cfg.beep_ind == B_IND_FLASH)) {
/* maintain */
if (flashing) {
long now = GetTickCount();
long fdiff = now - last_flash;
if (fdiff < 0 || fdiff > 450) {
last_flash = now;
FlashWindow(hwnd, TRUE); /* toggle */
}
FlashWindow(hwnd, TRUE); /* toggle */
next_flash = schedule_timer(450, flash_window_timer, hwnd);
}
}
}

View File

@ -1353,6 +1353,16 @@ SOCKET next_socket(int *state)
return s ? s->s : INVALID_SOCKET;
}
extern int socket_writable(SOCKET skt)
{
Actual_Socket s = find234(sktree, (void *)skt, cmpforsearch);
if (s)
return bufchain_size(&s->output_data) > 0;
else
return 0;
}
int net_service_lookup(char *service)
{
struct servent *se;

View File

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

View File

@ -2,6 +2,8 @@
* winsftp.c: the Windows-specific parts of PSFTP and PSCP.
*/
#include <assert.h>
#include "putty.h"
#include "psftp.h"
@ -461,35 +463,255 @@ char *dir_file_cat(char *dir, char *file)
* Be told what socket we're supposed to be using.
*/
static SOCKET sftp_ssh_socket;
static HANDLE netevent = NULL;
char *do_select(SOCKET skt, int startup)
{
int events;
if (startup)
sftp_ssh_socket = skt;
else
sftp_ssh_socket = INVALID_SOCKET;
if (p_WSAEventSelect) {
if (startup) {
events = (FD_CONNECT | FD_READ | FD_WRITE |
FD_OOB | FD_CLOSE | FD_ACCEPT);
netevent = CreateEvent(NULL, FALSE, FALSE, NULL);
} else {
events = 0;
}
if (p_WSAEventSelect(skt, netevent, events) == SOCKET_ERROR) {
switch (p_WSAGetLastError()) {
case WSAENETDOWN:
return "Network is down";
default:
return "WSAEventSelect(): unknown error";
}
}
}
return NULL;
}
extern int select_result(WPARAM, LPARAM);
int do_eventsel_loop(HANDLE other_event)
{
int n;
long next, ticks;
HANDLE handles[2];
SOCKET *sklist;
int skcount;
long now = GETTICKCOUNT();
if (!netevent) {
return -1; /* doom */
}
handles[0] = netevent;
handles[1] = other_event;
if (run_timers(now, &next)) {
ticks = next - GETTICKCOUNT();
if (ticks < 0) ticks = 0; /* just in case */
} else {
ticks = INFINITE;
}
n = MsgWaitForMultipleObjects(other_event ? 2 : 1, handles, FALSE, ticks,
QS_POSTMESSAGE);
if (n == WAIT_OBJECT_0 + 0) {
WSANETWORKEVENTS things;
SOCKET socket;
extern SOCKET first_socket(int *), next_socket(int *);
extern int select_result(WPARAM, LPARAM);
int i, socketstate;
/*
* We must not call select_result() for any socket
* until we have finished enumerating within the
* tree. This is because select_result() may close
* the socket and modify the tree.
*/
/* Count the active sockets. */
i = 0;
for (socket = first_socket(&socketstate);
socket != INVALID_SOCKET;
socket = next_socket(&socketstate)) i++;
/* Expand the buffer if necessary. */
sklist = snewn(i, SOCKET);
/* Retrieve the sockets into sklist. */
skcount = 0;
for (socket = first_socket(&socketstate);
socket != INVALID_SOCKET;
socket = next_socket(&socketstate)) {
sklist[skcount++] = socket;
}
/* Now we're done enumerating; go through the list. */
for (i = 0; i < skcount; i++) {
WPARAM wp;
socket = sklist[i];
wp = (WPARAM) socket;
if (!p_WSAEnumNetworkEvents(socket, NULL, &things)) {
static const struct { int bit, mask; } eventtypes[] = {
{FD_CONNECT_BIT, FD_CONNECT},
{FD_READ_BIT, FD_READ},
{FD_CLOSE_BIT, FD_CLOSE},
{FD_OOB_BIT, FD_OOB},
{FD_WRITE_BIT, FD_WRITE},
{FD_ACCEPT_BIT, FD_ACCEPT},
};
int e;
noise_ultralight(socket);
noise_ultralight(things.lNetworkEvents);
for (e = 0; e < lenof(eventtypes); e++)
if (things.lNetworkEvents & eventtypes[e].mask) {
LPARAM lp;
int err = things.iErrorCode[eventtypes[e].bit];
lp = WSAMAKESELECTREPLY(eventtypes[e].mask, err);
select_result(wp, lp);
}
}
}
sfree(sklist);
}
if (n == WAIT_TIMEOUT) {
now = next;
} else {
now = GETTICKCOUNT();
}
if (other_event && n == WAIT_OBJECT_0 + 1)
return 1;
return 0;
}
/*
* Wait for some network data and process it.
*
* We have two variants of this function. One uses select() so that
* it's compatible with WinSock 1. The other uses WSAEventSelect
* and MsgWaitForMultipleObjects, so that we can consistently use
* WSAEventSelect throughout; this enables us to also implement
* ssh_sftp_get_cmdline() using a parallel mechanism.
*/
int ssh_sftp_loop_iteration(void)
{
fd_set readfds;
if (sftp_ssh_socket == INVALID_SOCKET)
return -1; /* doom */
FD_ZERO(&readfds);
FD_SET(sftp_ssh_socket, &readfds);
if (p_select(1, &readfds, NULL, NULL, NULL) < 0)
return -1; /* doom */
if (p_WSAEventSelect == NULL) {
fd_set readfds;
int ret;
long now = GETTICKCOUNT();
if (socket_writable(sftp_ssh_socket))
select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_WRITE);
do {
long next, ticks;
struct timeval tv, *ptv;
if (run_timers(now, &next)) {
ticks = next - GETTICKCOUNT();
if (ticks <= 0)
ticks = 1; /* just in case */
tv.tv_sec = ticks / 1000;
tv.tv_usec = ticks % 1000 * 1000;
ptv = &tv;
} else {
ptv = NULL;
}
FD_ZERO(&readfds);
FD_SET(sftp_ssh_socket, &readfds);
ret = p_select(1, &readfds, NULL, NULL, ptv);
if (ret < 0)
return -1; /* doom */
else if (ret == 0)
now = next;
else
now = GETTICKCOUNT();
} while (ret == 0);
select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
return 0;
} else {
return do_eventsel_loop(NULL);
}
}
/*
* Read a command line from standard input.
*
* In the presence of WinSock 2, we can use WSAEventSelect to
* mediate between the socket and stdin, meaning we can send
* keepalives and respond to server events even while waiting at
* the PSFTP command prompt. Without WS2, we fall back to a simple
* fgets.
*/
struct command_read_ctx {
HANDLE event;
char *line;
};
static DWORD WINAPI command_read_thread(void *param)
{
struct command_read_ctx *ctx = (struct command_read_ctx *) param;
ctx->line = fgetline(stdin);
SetEvent(ctx->event);
select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
return 0;
}
char *ssh_sftp_get_cmdline(char *prompt)
{
int ret;
struct command_read_ctx actx, *ctx = &actx;
DWORD threadid;
fputs(prompt, stdout);
fflush(stdout);
if (sftp_ssh_socket == INVALID_SOCKET || p_WSAEventSelect == NULL) {
return fgetline(stdin); /* very simple */
}
/*
* Create a second thread to read from stdin. Process network
* and timing events until it terminates.
*/
ctx->event = CreateEvent(NULL, FALSE, FALSE, NULL);
ctx->line = NULL;
if (!CreateThread(NULL, 0, command_read_thread,
ctx, 0, &threadid)) {
fprintf(stderr, "Unable to create command input thread\n");
cleanup_exit(1);
}
do {
ret = do_eventsel_loop(ctx->event);
/* Error return can only occur if netevent==NULL, and it ain't. */
assert(ret >= 0);
} while (ret == 0);
return ctx->line;
}
/* ----------------------------------------------------------------------
* Main program. Parse arguments etc.
*/

View File

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