#include <stdlib.h>
#include <ctype.h>
#include <time.h>
+#include <limits.h>
#include <assert.h>
+#ifndef NO_MULTIMON
+#define COMPILE_MULTIMON_STUBS
+#endif
+
#define PUTTY_DO_GLOBALS /* actually _define_ globals */
#include "putty.h"
#include "terminal.h"
#include "win_res.h"
#ifndef NO_MULTIMON
-#if WINVER < 0x0500
-#define COMPILE_MULTIMON_STUBS
#include <multimon.h>
#endif
-#endif
#include <imm.h>
#include <commctrl.h>
static void deinit_fonts(void);
static void set_input_locale(HKL);
static void update_savedsess_menu(void);
+static void init_flashwindow(void);
static int is_full_screen(void);
static void make_full_screen(void);
static int was_zoomed = 0;
static int prev_rows, prev_cols;
-static void enact_netevent(WPARAM, LPARAM);
+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 flash_window(int mode);
static void sys_cursor_update(void);
static int is_shift_pressed(void);
static void *backhandle;
static struct unicode_data ucsdata;
-static int session_closed;
+static int must_close_session, session_closed;
static int reconfiguring = FALSE;
static const struct telnet_special *specials = NULL;
* Select protocol. This is farmed out into a table in a
* separate file to enable an ssh-free variant.
*/
- back = NULL;
- for (i = 0; backends[i].backend != NULL; i++)
- if (backends[i].protocol == cfg.protocol) {
- back = backends[i].backend;
- break;
- }
+ back = backend_from_proto(cfg.protocol);
if (back == NULL) {
char *str = dupprintf("%s Internal Error", appname);
MessageBox(NULL, "Unsupported protocol number found",
if (error) {
char *str = dupprintf("%s Error", appname);
sprintf(msg, "Unable to open connection to\n"
- "%.800s\n" "%s", cfg.host, error);
+ "%.800s\n" "%s", cfg_dest(&cfg), error);
MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
sfree(str);
exit(0);
DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
}
+ must_close_session = FALSE;
session_closed = FALSE;
}
back->free(backhandle);
backhandle = NULL;
back = NULL;
+ term_provide_resize_fn(term, NULL, NULL);
update_specials_menu(NULL);
}
osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
- /*
- * See if we can find our Help file.
- */
- {
- char b[2048], *p, *q, *r;
- FILE *fp;
- GetModuleFileName(NULL, b, sizeof(b) - 1);
- r = b;
- p = strrchr(b, '\\');
- if (p && p >= r) r = p+1;
- q = strrchr(b, ':');
- if (q && q >= r) r = q+1;
- strcpy(r, PUTTY_HELP_FILE);
- if ( (fp = fopen(b, "r")) != NULL) {
- help_path = dupstr(b);
- fclose(fp);
- } else
- help_path = NULL;
- strcpy(r, PUTTY_HELP_CONTENTS);
- if ( (fp = fopen(b, "r")) != NULL) {
- help_has_contents = TRUE;
- fclose(fp);
- } else
- help_has_contents = FALSE;
- }
+ init_help();
+
+ init_flashwindow();
/*
* Process the command line.
{
char *p;
int got_host = 0;
+ /* By default, we bring up the config dialog, rather than launching
+ * a session. This gets set to TRUE if something happens to change
+ * that (e.g., a hostname is specified on the command-line). */
+ int allow_launch = FALSE;
default_protocol = be_default_protocol;
/* Find the appropriate default port. */
{
- int i;
+ Backend *b = backend_from_proto(default_protocol);
default_port = 0; /* illegal */
- for (i = 0; backends[i].backend != NULL; i++)
- if (backends[i].protocol == default_protocol) {
- default_port = backends[i].backend->default_port;
- break;
- }
+ if (b)
+ default_port = b->default_port;
}
cfg.logtype = LGTYP_NONE;
i--;
p[i] = '\0';
do_defaults(p + 1, &cfg);
- if (!*cfg.host && !do_config()) {
+ if (!cfg_launchable(&cfg) && !do_config()) {
cleanup_exit(0);
}
+ allow_launch = TRUE; /* allow it to be launched directly */
} else if (*p == '&') {
/*
* An initial & means we've been given a command line
} else if (!do_config()) {
cleanup_exit(0);
}
+ allow_launch = TRUE;
} else {
/*
* Otherwise, break up the command line and deal with
cmdline_run_saved(&cfg);
- if (!*cfg.host && !do_config()) {
+ if (loaded_session || got_host)
+ allow_launch = TRUE;
+
+ if ((!allow_launch || !cfg_launchable(&cfg)) && !do_config()) {
cleanup_exit(0);
}
}
}
- /* Check for invalid Port number (i.e. zero) */
- if (cfg.port == 0) {
- char *str = dupprintf("%s Internal Error", appname);
- MessageBox(NULL, "Invalid Port Number",
- str, MB_OK | MB_ICONEXCLAMATION);
- sfree(str);
- cleanup_exit(1);
- }
-
if (!prev) {
wndclass.style = 0;
wndclass.lpfnWndProc = WndProc;
AppendMenu(m, (cfg.resize_action == RESIZE_DISABLED) ?
MF_GRAYED : MF_ENABLED, IDM_FULLSCREEN, "&Full Screen");
AppendMenu(m, MF_SEPARATOR, 0, 0);
- if (help_path)
+ if (has_help())
AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
str = dupprintf("&About %s", appname);
AppendMenu(m, MF_ENABLED, IDM_ABOUT, str);
if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
handle_got_event(handles[n - WAIT_OBJECT_0]);
sfree(handles);
- continue;
- }
-
- sfree(handles);
+ if (must_close_session)
+ close_session();
+ } else
+ sfree(handles);
- if (GetMessage(&msg, NULL, 0, 0) != 1)
- break;
- do {
+ while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
goto finished; /* two-level break */
* we've delayed, reading the socket, writing, and repainting
* the window.
*/
- } while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
+ if (must_close_session)
+ close_session();
+ }
/* The messages seem unreliable; especially if we're being tricky */
term_set_focus(term, GetForegroundWindow() == hwnd);
+ if (pending_netevent)
+ enact_pending_netevent();
+
net_pending_errors();
}
crypto_wrapup();
#endif
}
+ shutdown_help();
exit(code);
}
for (j = 0; j < lenof(popup_menus); j++) {
if (specials_menu) {
/* XXX does this free up all submenus? */
- DeleteMenu(popup_menus[j].menu, specials_menu, MF_BYCOMMAND);
+ DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND);
DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
}
if (new_menu) {
if (cfg.close_on_exit == FORCE_ON)
PostQuitMessage(1);
else {
- close_session();
+ must_close_session = TRUE;
}
}
/*
* Actually do the job requested by a WM_NETEVENT
*/
-static void enact_netevent(WPARAM wParam, LPARAM lParam)
+static void enact_pending_netevent(void)
{
static int reentering = 0;
extern int select_result(WPARAM, LPARAM);
- int ret;
if (reentering)
return; /* don't unpend the pending */
+ pending_netevent = FALSE;
+
reentering = 1;
- ret = select_result(wParam, lParam);
+ select_result(pend_netevent_wParam, pend_netevent_lParam);
reentering = 0;
-
- if (ret == 0 && !session_closed) {
- /* Abnormal exits will already have set session_closed and taken
- * appropriate action. */
- if (cfg.close_on_exit == FORCE_ON ||
- cfg.close_on_exit == AUTO) PostQuitMessage(0);
- else {
- close_session();
- session_closed = TRUE;
- MessageBox(hwnd, "Connection closed by remote host",
- appname, MB_OK | MB_ICONINFORMATION);
- }
- }
}
/*
gcpr.lStructSize = sizeof(gcpr);
gcpr.lpGlyphs = (void *)buffer;
- gcpr.lpClass = classbuffer;
+ gcpr.lpClass = (void *)classbuffer;
gcpr.nGlyphs = cbCount;
GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
}
/*
+ * The exact_textout() wrapper, unfortunately, destroys the useful
+ * Windows `font linking' behaviour: automatic handling of Unicode
+ * code points not supported in this font by falling back to a font
+ * which does contain them. Therefore, we adopt a multi-layered
+ * approach: for any potentially-bidi text, we use exact_textout(),
+ * and for everything else we use a simple ExtTextOut as we did
+ * before exact_textout() was introduced.
+ */
+static void general_textout(HDC hdc, int x, int y, CONST RECT *lprc,
+ unsigned short *lpString, UINT cbCount,
+ CONST INT *lpDx, int opaque)
+{
+ int i, j, xp, xn;
+ RECT newrc;
+
+#ifdef FIXME_REMOVE_BEFORE_CHECKIN
+int k;
+debug(("general_textout: %d,%d", x, y));
+for(k=0;k<cbCount;k++)debug((" U+%04X", lpString[k]));
+debug(("\n rect: [%d,%d %d,%d]", lprc->left, lprc->top, lprc->right, lprc->bottom));
+debug(("\n"));
+#endif
+
+ xp = xn = x;
+
+ for (i = 0; i < (int)cbCount ;) {
+ int rtl = is_rtl(lpString[i]);
+
+ xn += lpDx[i];
+
+ for (j = i+1; j < (int)cbCount; j++) {
+ if (rtl != is_rtl(lpString[j]))
+ break;
+ xn += lpDx[j];
+ }
+
+ /*
+ * Now [i,j) indicates a maximal substring of lpString
+ * which should be displayed using the same textout
+ * function.
+ */
+ if (rtl) {
+ newrc.left = lprc->left + xp - x;
+ newrc.right = lprc->left + xn - x;
+ newrc.top = lprc->top;
+ newrc.bottom = lprc->bottom;
+#ifdef FIXME_REMOVE_BEFORE_CHECKIN
+{
+int k;
+debug((" exact_textout: %d,%d", xp, y));
+for(k=0;k<j-i;k++)debug((" U+%04X", lpString[i+k]));
+debug(("\n rect: [%d,%d %d,%d]\n", newrc.left, newrc.top, newrc.right, newrc.bottom));
+}
+#endif
+ exact_textout(hdc, xp, y, &newrc, lpString+i, j-i, lpDx+i, opaque);
+ } else {
+#ifdef FIXME_REMOVE_BEFORE_CHECKIN
+{
+int k;
+debug((" ExtTextOut : %d,%d", xp, y));
+for(k=0;k<j-i;k++)debug((" U+%04X", lpString[i+k]));
+debug(("\n rect: [%d,%d %d,%d]\n", newrc.left, newrc.top, newrc.right, newrc.bottom));
+}
+#endif
+ newrc.left = lprc->left + xp - x;
+ newrc.right = lprc->left + xn - x;
+ newrc.top = lprc->top;
+ newrc.bottom = lprc->bottom;
+ ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+ &newrc, lpString+i, j-i, lpDx+i);
+ }
+
+ i = j;
+ xp = xn;
+ }
+
+#ifdef FIXME_REMOVE_BEFORE_CHECKIN
+debug(("general_textout: done, xn=%d\n", xn));
+#endif
+ assert(xn - x == lprc->right - lprc->left);
+}
+
+/*
* Initialise all the fonts we will need initially. There may be as many as
* three or as few as one. The other (poentially) twentyone fonts are done
* if/when they are needed.
static int resizing;
-void notify_remote_exit(void *fe) { /* stub not needed in this frontend */ }
+void notify_remote_exit(void *fe)
+{
+ int exitcode;
+
+ if (!session_closed &&
+ (exitcode = back->exitcode(backhandle)) >= 0) {
+ /* Abnormal exits will already have set session_closed and taken
+ * appropriate action. */
+ if (cfg.close_on_exit == FORCE_ON ||
+ (cfg.close_on_exit == AUTO && exitcode != INT_MAX)) {
+ PostQuitMessage(0);
+ } else {
+ must_close_session = TRUE;
+ session_closed = TRUE;
+ /* exitcode == INT_MAX indicates that the connection was closed
+ * by a fatal error, so an error box will be coming our way and
+ * we should not generate this informational one. */
+ if (exitcode != INT_MAX)
+ MessageBox(hwnd, "Connection closed by remote host",
+ appname, MB_OK | MB_ICONINFORMATION);
+ }
+ }
+}
void timer_change_notify(long next)
{
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
- filemap = CreateFileMapping((HANDLE) 0xFFFFFFFF,
+ filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
&sa,
PAGE_READWRITE,
0, sizeof(Config), NULL);
- if (filemap) {
+ if (filemap && filemap != INVALID_HANDLE_VALUE) {
p = (Config *) MapViewOfFile(filemap,
FILE_MAP_WRITE,
0, 0, sizeof(Config));
} else if (wParam == IDM_SAVEDSESS) {
unsigned int sessno = ((lParam - IDM_SAVED_MIN)
/ MENU_SAVED_STEP) + 1;
- if (sessno < sesslist.nsessions) {
+ if (sessno < (unsigned)sesslist.nsessions) {
char *session = sesslist.sessions[sessno];
/* XXX spaces? quotes? "-load"? */
cl = dupprintf("putty @%s", session);
showabout(hwnd);
break;
case IDM_HELP:
- WinHelp(hwnd, help_path,
- help_has_contents ? HELP_FINDER : HELP_CONTENTS, 0);
+ launch_help(hwnd, NULL);
break;
case SC_MOUSEMENU:
/*
}
return 0;
case WM_NETEVENT:
- enact_netevent(wParam, lParam);
+ /* 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();
+
net_pending_errors();
return 0;
case WM_SETFOCUS:
wbuf[i] = text[i];
/* print Glyphs as they are, without Windows' Shaping*/
- exact_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust,
- &line_box, wbuf, len, IpDx, !(attr & TATTR_COMBINING));
-/* ExtTextOutW(hdc, x,
- y - font_height * (lattr == LATTR_BOT) + text_adjust,
- ETO_CLIPPED | ETO_OPAQUE, &line_box, wbuf, len, IpDx);
- */
+ general_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust,
+ &line_box, wbuf, len, IpDx, !(attr & TATTR_COMBINING));
/* And the shadow bold hack. */
if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
/*
* Translate a WM_(SYS)?KEY(UP|DOWN) message into a string of ASCII
- * codes. Returns number of bytes used or zero to drop the message
- * or -1 to forward the message to windows.
+ * codes. Returns number of bytes used, zero to drop the message,
+ * -1 to forward the message to Windows, or another negative number
+ * to indicate a NUL-terminated "special" string.
*/
static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
unsigned char *output)
return p - output;
}
if (wParam == VK_CANCEL && shift_state == 2) { /* Ctrl-Break */
- *p++ = 3;
- *p++ = 0;
- return -2;
+ if (back)
+ back->special(backhandle, TS_BRK);
+ return 0;
}
if (wParam == VK_PAUSE) { /* Break/Pause */
*p++ = 26;
cleanup_exit(1);
}
+typedef BOOL (WINAPI *p_FlashWindowEx_t)(PFLASHWINFO);
+static p_FlashWindowEx_t p_FlashWindowEx = NULL;
+
+static void init_flashwindow(void)
+{
+ HMODULE user32_module = LoadLibrary("USER32.DLL");
+ if (user32_module) {
+ p_FlashWindowEx = (p_FlashWindowEx_t)
+ GetProcAddress(user32_module, "FlashWindowEx");
+ }
+}
+
+static BOOL flash_window_ex(DWORD dwFlags, UINT uCount, DWORD dwTimeout)
+{
+ if (p_FlashWindowEx) {
+ FLASHWINFO fi;
+ fi.cbSize = sizeof(fi);
+ fi.hwnd = hwnd;
+ fi.dwFlags = dwFlags;
+ fi.uCount = uCount;
+ fi.dwTimeout = dwTimeout;
+ return (*p_FlashWindowEx)(&fi);
+ }
+ else
+ return FALSE; /* shrug */
+}
+
static void flash_window(int mode);
static long next_flash;
static int flashing = 0;
+/*
+ * Timer for platforms where we must maintain window flashing manually
+ * (e.g., Win95).
+ */
static void flash_window_timer(void *ctx, long now)
{
if (flashing && now - next_flash >= 0) {
if ((mode == 0) || (cfg.beep_ind == B_IND_DISABLED)) {
/* stop */
if (flashing) {
- FlashWindow(hwnd, FALSE);
flashing = 0;
+ if (p_FlashWindowEx)
+ flash_window_ex(FLASHW_STOP, 0, 0);
+ else
+ FlashWindow(hwnd, FALSE);
}
} else if (mode == 2) {
/* start */
if (!flashing) {
flashing = 1;
- FlashWindow(hwnd, TRUE);
- next_flash = schedule_timer(450, flash_window_timer, hwnd);
+ if (p_FlashWindowEx) {
+ /* For so-called "steady" mode, we use uCount=2, which
+ * seems to be the traditional number of flashes used
+ * by user notifications (e.g., by Explorer).
+ * uCount=0 appears to enable continuous flashing, per
+ * "flashing" mode, although I haven't seen this
+ * documented. */
+ flash_window_ex(FLASHW_ALL | FLASHW_TIMER,
+ (cfg.beep_ind == B_IND_FLASH ? 0 : 2),
+ 0 /* system cursor blink rate */);
+ /* No need to schedule timer */
+ } else {
+ 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) {
+ if (flashing && !p_FlashWindowEx) {
FlashWindow(hwnd, TRUE); /* toggle */
next_flash = schedule_timer(450, flash_window_timer, hwnd);
}