+/*
+ * window.c - the PuTTY(tel) main program, which runs a PuTTY terminal
+ * emulator and backend in a window.
+ */
+
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
+#include <limits.h>
#include <assert.h>
#define PUTTY_DO_GLOBALS /* actually _define_ globals */
#define IDM_COPYALL 0x0170
#define IDM_FULLSCREEN 0x0180
#define IDM_PASTE 0x0190
+#define IDM_SPECIALSEP 0x0200
#define IDM_SPECIAL_MIN 0x0400
#define IDM_SPECIAL_MAX 0x0800
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;
-static int n_specials;
+static const struct telnet_special *specials = NULL;
+static HMENU specials_menu = NULL;
+static int n_specials = 0;
#define TIMING_TIMER_ID 1234
static long timing_next_time;
static struct {
HMENU menu;
- int specials_submenu_pos;
} popup_menus[2];
enum { SYSMENU, CTXMENU };
static HMENU savedsess_menu;
m = popup_menus[j].menu;
AppendMenu(m, MF_SEPARATOR, 0, 0);
- popup_menus[j].specials_submenu_pos = GetMenuItemCount(m);
AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
AppendMenu(m, MF_SEPARATOR, 0, 0);
AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
set_input_locale(GetKeyboardLayout(0));
/*
- * Open the initial log file if there is one.
- */
- logfopen(logctx);
-
- /*
* Finally show the window!
*/
ShowWindow(hwnd, show);
term_set_focus(term, GetForegroundWindow() == hwnd);
UpdateWindow(hwnd);
- if (GetMessage(&msg, NULL, 0, 0) == 1) {
- while (msg.message != WM_QUIT) {
+ while (1) {
+ HANDLE *handles;
+ int nhandles, n;
+
+ handles = handle_get_events(&nhandles);
+
+ n = MsgWaitForMultipleObjects(nhandles, handles, FALSE, INFINITE,
+ QS_ALLINPUT);
+
+ if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
+ handle_got_event(handles[n - WAIT_OBJECT_0]);
+ sfree(handles);
+ if (must_close_session)
+ close_session();
+ continue;
+ }
+
+ sfree(handles);
+
+ if (GetMessage(&msg, NULL, 0, 0) != 1)
+ break;
+ do {
+ if (msg.message == WM_QUIT)
+ goto finished; /* two-level break */
+
if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
DispatchMessage(&msg);
/* Send the paste buffer if there's anything to send */
* we've delayed, reading the socket, writing, and repainting
* the window.
*/
- if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- continue;
+ if (must_close_session)
+ close_session();
+ } while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
- /* The messages seem unreliable; especially if we're being tricky */
- term_set_focus(term, GetForegroundWindow() == hwnd);
+ /* The messages seem unreliable; especially if we're being tricky */
+ term_set_focus(term, GetForegroundWindow() == hwnd);
- net_pending_errors();
-
- /* There's no point rescanning everything in the message queue
- * so we do an apparently unnecessary wait here
- */
- WaitMessage();
- if (GetMessage(&msg, NULL, 0, 0) != 1)
- break;
- }
+ net_pending_errors();
}
+ finished:
cleanup_exit(msg.wParam); /* this doesn't return... */
return msg.wParam; /* ... but optimiser doesn't know */
}
*/
void update_specials_menu(void *frontend)
{
- HMENU p;
- int menu_already_exists = (specials != NULL);
+ HMENU new_menu;
int i, j;
if (back)
* here's a lame "stack" that will do for now. */
HMENU saved_menu = NULL;
int nesting = 1;
- p = CreatePopupMenu();
+ new_menu = CreatePopupMenu();
for (i = 0; nesting > 0; i++) {
assert(IDM_SPECIAL_MIN + 0x10 * i < IDM_SPECIAL_MAX);
switch (specials[i].code) {
case TS_SEP:
- AppendMenu(p, MF_SEPARATOR, 0, 0);
+ AppendMenu(new_menu, MF_SEPARATOR, 0, 0);
break;
case TS_SUBMENU:
assert(nesting < 2);
nesting++;
- saved_menu = p; /* XXX lame stacking */
- p = CreatePopupMenu();
+ saved_menu = new_menu; /* XXX lame stacking */
+ new_menu = CreatePopupMenu();
AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
- (UINT) p, specials[i].name);
+ (UINT) new_menu, specials[i].name);
break;
case TS_EXITMENU:
nesting--;
if (nesting) {
- p = saved_menu; /* XXX lame stacking */
+ new_menu = saved_menu; /* XXX lame stacking */
saved_menu = NULL;
}
break;
default:
- AppendMenu(p, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
+ AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
specials[i].name);
break;
}
/* Squirrel the highest special. */
n_specials = i - 1;
} else {
- p = NULL;
+ new_menu = NULL;
n_specials = 0;
}
for (j = 0; j < lenof(popup_menus); j++) {
- if (menu_already_exists) {
+ if (specials_menu) {
/* XXX does this free up all submenus? */
- DeleteMenu(popup_menus[j].menu,
- popup_menus[j].specials_submenu_pos,
- MF_BYPOSITION);
- DeleteMenu(popup_menus[j].menu,
- popup_menus[j].specials_submenu_pos,
- MF_BYPOSITION);
+ DeleteMenu(popup_menus[j].menu, specials_menu, MF_BYCOMMAND);
+ DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
}
- if (specials) {
- InsertMenu(popup_menus[j].menu,
- popup_menus[j].specials_submenu_pos,
- MF_BYPOSITION | MF_SEPARATOR, 0, 0);
- InsertMenu(popup_menus[j].menu,
- popup_menus[j].specials_submenu_pos,
- MF_BYPOSITION | MF_POPUP | MF_ENABLED,
- (UINT) p, "S&pecial Command");
+ if (new_menu) {
+ InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
+ MF_BYCOMMAND | MF_POPUP | MF_ENABLED,
+ (UINT) new_menu, "S&pecial Command");
+ InsertMenu(popup_menus[j].menu, IDM_SHOWLOG,
+ MF_BYCOMMAND | MF_SEPARATOR, IDM_SPECIALSEP, 0);
}
}
+ specials_menu = new_menu;
}
static void update_mouse_pointer(void)
}
{
HCURSOR cursor = LoadCursor(NULL, curstype);
- SetClassLong(hwnd, GCL_HCURSOR, (LONG)cursor);
+ SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor);
SetCursor(cursor); /* force redraw of cursor at current posn */
}
if (force_visible != forced_visible) {
if (cfg.close_on_exit == FORCE_ON)
PostQuitMessage(1);
else {
- close_session();
+ must_close_session = TRUE;
}
}
{
static int reentering = 0;
extern int select_result(WPARAM, LPARAM);
- int ret;
if (reentering)
return; /* don't unpend the pending */
reentering = 1;
- ret = select_result(wParam, lParam);
+ select_result(wParam, 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);
- }
- }
}
/*
#define f(i,c,w,u) \
fonts[i] = CreateFont (font_height, font_width, 0, 0, w, FALSE, u, FALSE, \
c, OUT_DEFAULT_PRECIS, \
- CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, \
+ CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality), \
FIXED_PITCH | FF_DONTCARE, cfg.font.name)
f(FONT_NORMAL, cfg.font.charset, fw_dontcare, FALSE);
fonts[fontno] =
CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
- CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
+ CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality),
FIXED_PITCH | FF_DONTCARE, s);
fontflag[fontno] = 1;
#endif
}
} else {
- if ( font_width != win_width/term->cols ||
- font_height != win_height/term->rows) {
+ if ( font_width * term->cols != win_width ||
+ font_height * term->rows != win_height) {
/* Our only choice at this point is to change the
* size of the terminal; Oh well.
*/
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)
{
case IDM_RESTART:
if (!back) {
logevent(NULL, "----- Session restarted -----");
+ term_pwron(term, FALSE);
start_backend();
}
cfg.font.isbold != prev_cfg.font.isbold ||
cfg.font.height != prev_cfg.font.height ||
cfg.font.charset != prev_cfg.font.charset ||
+ cfg.font_quality != prev_cfg.font_quality ||
cfg.vtmode != prev_cfg.vtmode ||
cfg.bold_colour != prev_cfg.bold_colour ||
cfg.resize_action == RESIZE_DISABLED ||
term_clrsb(term);
break;
case IDM_RESET:
- term_pwron(term);
+ term_pwron(term, TRUE);
if (ldisc)
ldisc_send(ldisc, NULL, 0, 0);
break;
if (cfg.nethack_keypad && !left_alt) {
switch (wParam) {
case VK_NUMPAD1:
- *p++ = shift_state ? 'B' : 'b';
+ *p++ = "bB\002\002"[shift_state & 3];
return p - output;
case VK_NUMPAD2:
- *p++ = shift_state ? 'J' : 'j';
+ *p++ = "jJ\012\012"[shift_state & 3];
return p - output;
case VK_NUMPAD3:
- *p++ = shift_state ? 'N' : 'n';
+ *p++ = "nN\016\016"[shift_state & 3];
return p - output;
case VK_NUMPAD4:
- *p++ = shift_state ? 'H' : 'h';
+ *p++ = "hH\010\010"[shift_state & 3];
return p - output;
case VK_NUMPAD5:
*p++ = shift_state ? '.' : '.';
return p - output;
case VK_NUMPAD6:
- *p++ = shift_state ? 'L' : 'l';
+ *p++ = "lL\014\014"[shift_state & 3];
return p - output;
case VK_NUMPAD7:
- *p++ = shift_state ? 'Y' : 'y';
+ *p++ = "yY\031\031"[shift_state & 3];
return p - output;
case VK_NUMPAD8:
- *p++ = shift_state ? 'K' : 'k';
+ *p++ = "kK\013\013"[shift_state & 3];
return p - output;
case VK_NUMPAD9:
- *p++ = shift_state ? 'U' : 'u';
+ *p++ = "uU\025\025"[shift_state & 3];
return p - output;
}
}
UnrealizeObject(pal);
RealizePalette(hdc);
free_ctx(hdc);
+ } else {
+ if (n == (ATTR_DEFBG>>ATTR_BGSHIFT))
+ /* If Default Background changes, we need to ensure any
+ * space between the text area and the window border is
+ * redrawn. */
+ InvalidateRect(hwnd, NULL, TRUE);
}
}
hdc = get_ctx(frontend);
RealizePalette(hdc);
free_ctx(hdc);
+ } else {
+ /* Default Background may have changed. Ensure any space between
+ * text area and window border is redrawn. */
+ InvalidateRect(hwnd, NULL, TRUE);
}
}
/*
* Note: unlike write_aclip() this will not append a nul.
*/
-void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
+void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect)
{
HGLOBAL clipdata, clipdata2, clipdata3;
int len2;
int rtfsize = 0;
int multilen, blen, alen, totallen, i;
char before[16], after[4];
+ int fgcolour, lastfgcolour = 0;
+ int bgcolour, lastbgcolour = 0;
+ int attrBold, lastAttrBold = 0;
+ int attrUnder, lastAttrUnder = 0;
+ int palette[NALLCOLOURS];
+ int numcolours;
get_unitab(CP_ACP, unitab, 0);
rtfsize = 100 + strlen(cfg.font.name);
rtf = snewn(rtfsize, char);
- sprintf(rtf, "{\\rtf1\\ansi%d{\\fonttbl\\f0\\fmodern %s;}\\f0 ",
- GetACP(), cfg.font.name);
- rtflen = strlen(rtf);
+ rtflen = sprintf(rtf, "{\\rtf1\\ansi\\deff0{\\fonttbl\\f0\\fmodern %s;}\\f0\\fs%d",
+ cfg.font.name, cfg.font.height*2);
+
+ /*
+ * Add colour palette
+ * {\colortbl ;\red255\green0\blue0;\red0\green0\blue128;}
+ */
+
+ /*
+ * First - Determine all colours in use
+ * o Foregound and background colours share the same palette
+ */
+ if (attr) {
+ memset(palette, 0, sizeof(palette));
+ for (i = 0; i < (len-1); i++) {
+ fgcolour = ((attr[i] & ATTR_FGMASK) >> ATTR_FGSHIFT);
+ bgcolour = ((attr[i] & ATTR_BGMASK) >> ATTR_BGSHIFT);
+
+ if (attr[i] & ATTR_REVERSE) {
+ int tmpcolour = fgcolour; /* Swap foreground and background */
+ fgcolour = bgcolour;
+ bgcolour = tmpcolour;
+ }
+
+ if (bold_mode == BOLD_COLOURS && (attr[i] & ATTR_BOLD)) {
+ if (fgcolour < 8) /* ANSI colours */
+ fgcolour += 8;
+ else if (fgcolour >= 256) /* Default colours */
+ fgcolour ++;
+ }
+
+ if (attr[i] & ATTR_BLINK) {
+ if (bgcolour < 8) /* ANSI colours */
+ bgcolour += 8;
+ else if (bgcolour >= 256) /* Default colours */
+ bgcolour ++;
+ }
+
+ palette[fgcolour]++;
+ palette[bgcolour]++;
+ }
+
+ /*
+ * Next - Create a reduced palette
+ */
+ numcolours = 0;
+ for (i = 0; i < NALLCOLOURS; i++) {
+ if (palette[i] != 0)
+ palette[i] = ++numcolours;
+ }
+
+ /*
+ * Finally - Write the colour table
+ */
+ rtf = sresize(rtf, rtfsize + (numcolours * 25), char);
+ strcat(rtf, "{\\colortbl ;");
+ rtflen = strlen(rtf);
+
+ for (i = 0; i < NALLCOLOURS; i++) {
+ if (palette[i] != 0) {
+ rtflen += sprintf(&rtf[rtflen], "\\red%d\\green%d\\blue%d;", defpal[i].rgbtRed, defpal[i].rgbtGreen, defpal[i].rgbtBlue);
+ }
+ }
+ strcpy(&rtf[rtflen], "}");
+ rtflen ++;
+ }
/*
* We want to construct a piece of RTF that specifies the
tdata[tindex+1] == '\n') {
tindex++;
uindex++;
+ }
+
+ /*
+ * Set text attributes
+ */
+ if (attr) {
+ if (rtfsize < rtflen + 64) {
+ rtfsize = rtflen + 512;
+ rtf = sresize(rtf, rtfsize, char);
+ }
+
+ /*
+ * Determine foreground and background colours
+ */
+ fgcolour = ((attr[tindex] & ATTR_FGMASK) >> ATTR_FGSHIFT);
+ bgcolour = ((attr[tindex] & ATTR_BGMASK) >> ATTR_BGSHIFT);
+
+ if (attr[tindex] & ATTR_REVERSE) {
+ int tmpcolour = fgcolour; /* Swap foreground and background */
+ fgcolour = bgcolour;
+ bgcolour = tmpcolour;
+ }
+
+ if (bold_mode == BOLD_COLOURS && (attr[tindex] & ATTR_BOLD)) {
+ if (fgcolour < 8) /* ANSI colours */
+ fgcolour += 8;
+ else if (fgcolour >= 256) /* Default colours */
+ fgcolour ++;
+ }
+
+ if (attr[tindex] & ATTR_BLINK) {
+ if (bgcolour < 8) /* ANSI colours */
+ bgcolour += 8;
+ else if (bgcolour >= 256) /* Default colours */
+ bgcolour ++;
+ }
+
+ /*
+ * Collect other attributes
+ */
+ if (bold_mode != BOLD_COLOURS)
+ attrBold = attr[tindex] & ATTR_BOLD;
+ else
+ attrBold = 0;
+
+ attrUnder = attr[tindex] & ATTR_UNDER;
+
+ /*
+ * Reverse video
+ * o If video isn't reversed, ignore colour attributes for default foregound
+ * or background.
+ * o Special case where bolded text is displayed using the default foregound
+ * and background colours - force to bolded RTF.
+ */
+ if (!(attr[tindex] & ATTR_REVERSE)) {
+ if (bgcolour >= 256) /* Default color */
+ bgcolour = -1; /* No coloring */
+
+ if (fgcolour >= 256) { /* Default colour */
+ if (bold_mode == BOLD_COLOURS && (fgcolour & 1) && bgcolour == -1)
+ attrBold = ATTR_BOLD; /* Emphasize text with bold attribute */
+
+ fgcolour = -1; /* No coloring */
+ }
+ }
+
+ /*
+ * Write RTF text attributes
+ */
+ if (lastfgcolour != fgcolour) {
+ lastfgcolour = fgcolour;
+ rtflen += sprintf(&rtf[rtflen], "\\cf%d ", (fgcolour >= 0) ? palette[fgcolour] : 0);
+ }
+
+ if (lastbgcolour != bgcolour) {
+ lastbgcolour = bgcolour;
+ rtflen += sprintf(&rtf[rtflen], "\\highlight%d ", (bgcolour >= 0) ? palette[bgcolour] : 0);
+ }
+
+ if (lastAttrBold != attrBold) {
+ lastAttrBold = attrBold;
+ rtflen += sprintf(&rtf[rtflen], "%s", attrBold ? "\\b " : "\\b0 ");
+ }
+
+ if (lastAttrUnder != attrUnder) {
+ lastAttrUnder = attrUnder;
+ rtflen += sprintf(&rtf[rtflen], "%s", attrUnder ? "\\ul " : "\\ulnone ");
+ }
}
+
if (unitab[tdata[tindex]] == udata[uindex]) {
multilen = 1;
before[0] = '\0';
uindex++;
}
- strcpy(rtf + rtflen, "}");
- rtflen += 2;
+ rtf[rtflen++] = '}'; /* Terminate RTF stream */
+ rtf[rtflen++] = '\0';
+ rtf[rtflen++] = '\0';
clipdata3 = GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, rtflen);
if (clipdata3 && (lock3 = GlobalLock(clipdata3)) != NULL) {
- strcpy(lock3, rtf);
+ memcpy(lock3, rtf, rtflen);
GlobalUnlock(clipdata3);
}
sfree(rtf);
ss.bottom - ss.top,
SWP_FRAMECHANGED);
+ /* We may have changed size as a result */
+
+ reset_window(0);
+
/* Tick the menu item in the System menu. */
CheckMenuItem(GetSystemMenu(hwnd, FALSE), IDM_FULLSCREEN,
MF_CHECKED);