Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
/*
* 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>
#ifndef NO_MULTIMON
#define COMPILE_MULTIMON_STUBS
#endif
#define PUTTY_DO_GLOBALS /* actually _define_ globals */
#include "putty.h"
#include "terminal.h"
#include "storage.h"
#include "win_res.h"
#include "winsecur.h"
#ifndef NO_MULTIMON
#include <multimon.h>
#endif
#include <imm.h>
#include <commctrl.h>
#include <richedit.h>
#include <mmsystem.h>
/* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of
* wParam are used by Windows, and should be masked off, so we shouldn't
* attempt to store information in them. Hence all these identifiers have
* the low 4 bits clear. Also, identifiers should < 0xF000. */
#define IDM_SHOWLOG 0x0010
#define IDM_NEWSESS 0x0020
#define IDM_DUPSESS 0x0030
#define IDM_RESTART 0x0040
#define IDM_RECONF 0x0050
#define IDM_CLRSB 0x0060
#define IDM_RESET 0x0070
/* One Key Cleaning Screen by Kasper */
#define IDM_CLEAN 0x0080
/* Title change by Kasper */
#define IDM_CHNTITLE 0x0090
/* end */
#define IDM_HELP 0x0140
#define IDM_ABOUT 0x0150
#define IDM_SAVEDSESS 0x0160
#define IDM_COPYALL 0x0170
#define IDM_FULLSCREEN 0x0180
#define IDM_PASTE 0x0190
#define IDM_SPECIALSEP 0x0200
/* Brian Jiang */
#define IDM_LOG_SESSION 0x0230
#define IDM_TURNOFF_LOG 0x0240
#define IDM_FIND 0x0250
#define IDM_SPECIAL_MIN 0x0400
#define IDM_SPECIAL_MAX 0x0800
#define IDM_SAVED_MIN 0x1000
#define IDM_SAVED_MAX 0x5000
#define MENU_SAVED_STEP 16
/* Maximum number of sessions on saved-session submenu */
#define MENU_SAVED_MAX ((IDM_SAVED_MAX-IDM_SAVED_MIN) / MENU_SAVED_STEP)
#define WM_IGNORE_CLIP (WM_APP + 2)
#define WM_FULLSCR_ON_MAX (WM_APP + 3)
#define WM_AGENT_CALLBACK (WM_APP + 4)
#define WM_GOT_CLIPDATA (WM_APP + 6)
/* Needed for Chinese support and apparently not always defined. */
#ifndef VK_PROCESSKEY
#define VK_PROCESSKEY 0xE5
#endif
/* Mouse wheel support. */
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL 0x020A /* not defined in earlier SDKs */
#endif
#ifndef WHEEL_DELTA
#define WHEEL_DELTA 120
#endif
/* VK_PACKET, used to send Unicode characters in WM_KEYDOWNs */
#ifndef VK_PACKET
#define VK_PACKET 0xE7
#endif
static Mouse_Button translate_button(Mouse_Button button);
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam,
unsigned char *output);
static void conftopalette(void);
static void systopalette(void);
static void init_palette(void);
static void init_fonts(int, int);
static void another_font(int);
static void deinit_fonts(void);
static void set_input_locale(HKL);
static void update_savedsess_menu(void);
static void init_winfuncs(void);
static int is_full_screen(void);
static void make_full_screen(void);
static void clear_full_screen(void);
static void flip_full_screen(void);
static int process_clipdata(HGLOBAL clipdata, int unicode);
/* Window layout information */
static void reset_window(int);
static int extra_width, extra_height;
static int font_width, font_height, font_dualwidth, font_varpitch;
static int offset_width, offset_height;
static int was_zoomed = 0;
static int prev_rows, prev_cols;
static void flash_window(int mode);
static void sys_cursor_update(void);
static int get_fullscreen_rect(RECT * ss);
static int caret_x = -1, caret_y = -1;
static int kbd_codepage;
static void *ldisc;
static Backend *back;
static void *backhandle;
static struct unicode_data ucsdata;
static int session_closed;
static int reconfiguring = FALSE;
static const struct telnet_special *specials = NULL;
static HMENU specials_menu = NULL;
static int n_specials = 0;
static wchar_t *clipboard_contents;
static size_t clipboard_length;
#define TIMING_TIMER_ID 1234
static long timing_next_time;
static struct {
HMENU menu;
} popup_menus[2];
enum { SYSMENU, CTXMENU };
static HMENU savedsess_menu;
struct wm_netevent_params {
/* Used to pass data to wm_netevent_callback */
WPARAM wParam;
LPARAM lParam;
};
Conf *conf; /* exported to windlg.c */
static void conf_cache_data(void);
int cursor_type;
int vtmode;
static struct sesslist sesslist; /* for saved-session menu */
struct agent_callback {
void (*callback)(void *, void *, int);
void *callback_ctx;
void *data;
int len;
};
#define FONT_NORMAL 0
#define FONT_BOLD 1
#define FONT_UNDERLINE 2
#define FONT_BOLDUND 3
#define FONT_WIDE 0x04
#define FONT_HIGH 0x08
#define FONT_NARROW 0x10
#define FONT_OEM 0x20
#define FONT_OEMBOLD 0x21
#define FONT_OEMUND 0x22
#define FONT_OEMBOLDUND 0x23
#define FONT_MAXNO 0x40
#define FONT_SHIFT 5
static HFONT fonts[FONT_MAXNO];
static LOGFONT lfont;
static int fontflag[FONT_MAXNO];
static enum {
BOLD_NONE, BOLD_SHADOW, BOLD_FONT
} bold_font_mode;
static int bold_colours;
static enum {
UND_LINE, UND_FONT
} und_mode;
static int descent;
#define NCFGCOLOURS 22
#define NEXTCOLOURS 240
#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
static COLORREF colours[NALLCOLOURS];
static HPALETTE pal;
static LPLOGPALETTE logpal;
static RGBTRIPLE defpal[NALLCOLOURS];
static HBITMAP caretbm;
static int dbltime, lasttime, lastact;
static Mouse_Button lastbtn;
/* this allows xterm-style mouse handling. */
static int send_raw_mouse = 0;
static int wheel_accumulator = 0;
static int busy_status = BUSY_NOT;
static char *window_name, *icon_name;
static int compose_state = 0;
static UINT wm_mousewheel = WM_MOUSEWHEEL;
#define IS_HIGH_VARSEL(wch1, wch2) \
((wch1) == 0xDB40 && ((wch2) >= 0xDD00 && (wch2) <= 0xDDEF))
#define IS_LOW_VARSEL(wch) \
(((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \
((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */
const int share_can_be_downstream = TRUE;
const int share_can_be_upstream = TRUE;
/* Dummy routine, only required in plink. */
void ldisc_update(void *frontend, int echo, int edit)
{
}
char *get_ttymode(void *frontend, const char *mode)
{
return term_get_ttymode(term, mode);
}
static void start_backend(void)
{
const char *error;
char msg[1024], *title;
char *realhost;
int i;
/*
* Select protocol. This is farmed out into a table in a
* separate file to enable an ssh-free variant.
*/
back = backend_from_proto(conf_get_int(conf, CONF_protocol));
if (back == NULL) {
char *str = dupprintf("%s Internal Error", appname);
MessageBox(NULL, "Unsupported protocol number found",
str, MB_OK | MB_ICONEXCLAMATION);
sfree(str);
cleanup_exit(1);
}
error = back->init(NULL, &backhandle, conf,
conf_get_str(conf, CONF_host),
conf_get_int(conf, CONF_port),
&realhost,
conf_get_int(conf, CONF_tcp_nodelay),
conf_get_int(conf, CONF_tcp_keepalives));
back->provide_logctx(backhandle, logctx);
if (error) {
char *str = dupprintf("%s Error", appname);
sprintf(msg, "Unable to open connection to\n"
"%.800s\n" "%s", conf_dest(conf), error);
MessageBox(NULL, msg, str, MB_ICONERROR | MB_OK);
sfree(str);
exit(0);
}
window_name = icon_name = NULL;
title = conf_get_str(conf, CONF_wintitle);
if (!*title) {
sprintf(msg, "%s - %s", realhost, appname);
title = msg;
}
sfree(realhost);
set_title(NULL, title);
set_icon(NULL, title);
/*
* Connect the terminal to the backend for resize purposes.
*/
term_provide_resize_fn(term, back->size, backhandle);
/*
* Set up a line discipline.
*/
ldisc = ldisc_create(conf, term, back, backhandle, NULL);
/*
* Destroy the Restart Session menu item. (This will return
* failure if it's already absent, as it will be the very first
* time we call this function. We ignore that, because as long
* as the menu item ends up not being there, we don't care
* whether it was us who removed it or not!)
*/
for (i = 0; i < lenof(popup_menus); i++) {
DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
}
session_closed = FALSE;
}
static void close_session(void *ignored_context)
{
char morestuff[100];
int i;
session_closed = TRUE;
sprintf(morestuff, "%.70s (inactive)", appname);
set_icon(NULL, morestuff);
set_title(NULL, morestuff);
if (ldisc) {
ldisc_free(ldisc);
ldisc = NULL;
}
if (back) {
back->free(backhandle);
backhandle = NULL;
back = NULL;
term_provide_resize_fn(term, NULL, NULL);
update_specials_menu(NULL);
}
/*
* Show the Restart Session menu item. Do a precautionary
* delete first to ensure we never end up with more than one.
*/
for (i = 0; i < lenof(popup_menus); i++) {
DeleteMenu(popup_menus[i].menu, IDM_RESTART, MF_BYCOMMAND);
InsertMenu(popup_menus[i].menu, IDM_DUPSESS, MF_BYCOMMAND | MF_ENABLED,
IDM_RESTART, "&Restart Session");
}
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
{
MSG msg;
HRESULT hr;
int guess_width, guess_height;
hinst = inst;
hwnd = NULL;
flags = FLAG_VERBOSE | FLAG_INTERACTIVE;
sk_init();
InitCommonControls();
/* Ensure a Maximize setting in Explorer doesn't maximise the
* config box. */
defuse_showwindow();
if (!init_winver())
{
char *str = dupprintf("%s Fatal Error", appname);
MessageBox(NULL, "Windows refuses to report a version",
str, MB_OK | MB_ICONEXCLAMATION);
sfree(str);
return 1;
}
/*
* If we're running a version of Windows that doesn't support
* WM_MOUSEWHEEL, find out what message number we should be
* using instead.
*/
if (osVersion.dwMajorVersion < 4 ||
(osVersion.dwMajorVersion == 4 &&
osVersion.dwPlatformId != VER_PLATFORM_WIN32_NT))
wm_mousewheel = RegisterWindowMessage("MSWHEEL_ROLLMSG");
init_help();
init_winfuncs();
conf = conf_new();
/*
* Initialize COM.
*/
hr = CoInitialize(NULL);
if (hr != S_OK && hr != S_FALSE) {
char *str = dupprintf("%s Fatal Error", appname);
MessageBox(NULL, "Failed to initialize COM subsystem",
str, MB_OK | MB_ICONEXCLAMATION);
sfree(str);
return 1;
}
/*
* Protect our process
*/
{
#ifndef UNPROTECT
char *error = NULL;
if (! setprocessacl(error)) {
char *message = dupprintf("Could not restrict process ACL: %s",
error);
logevent(NULL, message);
sfree(message);
sfree(error);
}
#endif
}
/*
* 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. */
{
Backend *b = backend_from_proto(default_protocol);
default_port = 0; /* illegal */
if (b)
default_port = b->default_port;
}
conf_set_int(conf, CONF_logtype, LGTYP_NONE);
do_defaults(NULL, conf);
p = cmdline;
/*
* Process a couple of command-line options which are more
* easily dealt with before the line is broken up into words.
* These are the old-fashioned but convenient @sessionname and
* the internal-use-only &sharedmemoryhandle, neither of which
* are combined with anything else.
*/
while (*p && isspace(*p))
p++;
if (*p == '@') {
/*
* An initial @ means that the whole of the rest of the
* command line should be treated as the name of a saved
* session, with _no quoting or escaping_. This makes it a
* very convenient means of automated saved-session
* launching, via IDM_SAVEDSESS or Windows 7 jump lists.
*/
int i = strlen(p);
while (i > 1 && isspace(p[i - 1]))
i--;
p[i] = '\0';
do_defaults(p + 1, conf);
if (!conf_launchable(conf) && !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
* containing the hex value of a HANDLE for a file
* mapping object, which we must then interpret as a
* serialised Conf.
*/
HANDLE filemap;
void *cp;
unsigned cpsize;
if (sscanf(p + 1, "%p:%u", &filemap, &cpsize) == 2 &&
(cp = MapViewOfFile(filemap, FILE_MAP_READ,
0, 0, cpsize)) != NULL) {
conf_deserialise(conf, cp, cpsize);
UnmapViewOfFile(cp);
CloseHandle(filemap);
} else if (!do_config()) {
cleanup_exit(0);
}
allow_launch = TRUE;
} else {
/*
* Otherwise, break up the command line and deal with
* it sensibly.
*/
int argc, i;
char **argv;
split_into_argv(cmdline, &argc, &argv, NULL);
for (i = 0; i < argc; i++) {
char *p = argv[i];
int ret;
ret = cmdline_process_param(p, i+1<argc?argv[i+1]:NULL,
1, conf);
if (ret == -2) {
cmdline_error("option \"%s\" requires an argument", p);
} else if (ret == 2) {
i++; /* skip next argument */
} else if (ret == 1) {
continue; /* nothing further needs doing */
} else if (!strcmp(p, "-cleanup") ||
!strcmp(p, "-cleanup-during-uninstall")) {
/*
* `putty -cleanup'. Remove all registry
* entries associated with PuTTY, and also find
* and delete the random seed file.
*/
char *s1, *s2;
/* Are we being invoked from an uninstaller? */
if (!strcmp(p, "-cleanup-during-uninstall")) {
s1 = dupprintf("Remove saved sessions and random seed file?\n"
"\n"
"If you hit Yes, ALL Registry entries associated\n"
"with %s will be removed, as well as the\n"
"random seed file. THIS PROCESS WILL\n"
"DESTROY YOUR SAVED SESSIONS.\n"
"(This only affects the currently logged-in user.)\n"
"\n"
"If you hit No, uninstallation will proceed, but\n"
"saved sessions etc will be left on the machine.",
appname);
s2 = dupprintf("%s Uninstallation", appname);
} else {
s1 = dupprintf("This procedure will remove ALL Registry entries\n"
"associated with %s, and will also remove\n"
"the random seed file. (This only affects the\n"
"currently logged-in user.)\n"
"\n"
"THIS PROCESS WILL DESTROY YOUR SAVED SESSIONS.\n"
"Are you really sure you want to continue?",
appname);
s2 = dupprintf("%s Warning", appname);
}
if (message_box(s1, s2,
MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2,
HELPCTXID(option_cleanup)) == IDYES) {
cleanup_all();
}
sfree(s1);
sfree(s2);
exit(0);
} else if (!strcmp(p, "-pgpfp")) {
pgp_fingerprints();
exit(1);
} else if (*p != '-') {
char *q = p;
if (got_host) {
/*
* If we already have a host name, treat
* this argument as a port number. NB we
* have to treat this as a saved -P
* argument, so that it will be deferred
* until it's a good moment to run it.
*/
int ret = cmdline_process_param("-P", p, 1, conf);
assert(ret == 2);
} else if (!strncmp(q, "telnet:", 7)) {
/*
* If the hostname starts with "telnet:",
* set the protocol to Telnet and process
* the string as a Telnet URL.
*/
char c;
q += 7;
if (q[0] == '/' && q[1] == '/')
q += 2;
conf_set_int(conf, CONF_protocol, PROT_TELNET);
p = q;
p += host_strcspn(p, ":/");
c = *p;
if (*p)
*p++ = '\0';
if (c == ':')
conf_set_int(conf, CONF_port, atoi(p));
else
conf_set_int(conf, CONF_port, -1);
conf_set_str(conf, CONF_host, q);
got_host = 1;
} else {
/*
* Otherwise, treat this argument as a host
* name.
*/
while (*p && !isspace(*p))
p++;
if (*p)
*p++ = '\0';
conf_set_str(conf, CONF_host, q);
got_host = 1;
}
} else {
cmdline_error("unknown option \"%s\"", p);
}
}
}
cmdline_run_saved(conf);
if (loaded_session || got_host)
allow_launch = TRUE;
if ((!allow_launch || !conf_launchable(conf)) && !do_config()) {
cleanup_exit(0);
}
/*
* Muck about with the hostname in various ways.
*/
{
char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
char *host = hostbuf;
char *p, *q;
/*
* Trim leading whitespace.
*/
host += strspn(host, " \t");
/*
* See if host is of the form user@host, and separate
* out the username if so.
*/
if (host[0] != '\0') {
char *atsign = strrchr(host, '@');
if (atsign) {
*atsign = '\0';
conf_set_str(conf, CONF_username, host);
host = atsign + 1;
}
}
/*
* Trim a colon suffix off the hostname if it's there. In
* order to protect unbracketed IPv6 address literals
* against this treatment, we do not do this if there's
* _more_ than one colon.
*/
{
char *c = host_strchr(host, ':');
if (c) {
char *d = host_strchr(c+1, ':');
if (!d)
*c = '\0';
}
}
/*
* Remove any remaining whitespace.
*/
p = hostbuf;
q = host;
while (*q) {
if (*q != ' ' && *q != '\t')
*p++ = *q;
q++;
}
*p = '\0';
conf_set_str(conf, CONF_host, hostbuf);
sfree(hostbuf);
}
}
if (!prev) {
WNDCLASSW wndclass;
wndclass.style = 0;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = inst;
wndclass.hIcon = LoadIcon(inst, MAKEINTRESOURCE(IDI_MAINICON));
wndclass.hCursor = LoadCursor(NULL, IDC_IBEAM);
wndclass.hbrBackground = NULL;
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
RegisterClassW(&wndclass);
}
memset(&ucsdata, 0, sizeof(ucsdata));
conf_cache_data();
conftopalette();
/*
* Guess some defaults for the window size. This all gets
* updated later, so we don't really care too much. However, we
* do want the font width/height guesses to correspond to a
* large font rather than a small one...
*/
font_width = 10;
font_height = 20;
extra_width = 25;
extra_height = 28;
guess_width = extra_width + font_width * conf_get_int(conf, CONF_width);
guess_height = extra_height + font_height*conf_get_int(conf, CONF_height);
{
RECT r;
get_fullscreen_rect(&r);
if (guess_width > r.right - r.left)
guess_width = r.right - r.left;
if (guess_height > r.bottom - r.top)
guess_height = r.bottom - r.top;
}
{
int winmode = WS_OVERLAPPEDWINDOW | WS_VSCROLL;
int exwinmode = 0;
wchar_t *uappname = dup_mb_to_wc(DEFAULT_CODEPAGE, 0, appname);
if (!conf_get_int(conf, CONF_scrollbar))
winmode &= ~(WS_VSCROLL);
if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED)
winmode &= ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
if (conf_get_int(conf, CONF_alwaysontop))
exwinmode |= WS_EX_TOPMOST;
if (conf_get_int(conf, CONF_sunken_edge))
exwinmode |= WS_EX_CLIENTEDGE;
hwnd = CreateWindowExW(exwinmode, uappname, uappname,
winmode, CW_USEDEFAULT, CW_USEDEFAULT,
guess_width, guess_height,
NULL, NULL, inst, NULL);
sfree(uappname);
}
/*
* Initialise the fonts, simultaneously correcting the guesses
* for font_{width,height}.
*/
init_fonts(0,0);
/*
* Initialise the terminal. (We have to do this _after_
* creating the window, since the terminal is the first thing
* which will call schedule_timer(), which will in turn call
* timer_change_notify() which will expect hwnd to exist.)
*/
term = term_init(conf, &ucsdata, NULL);
logctx = log_init(NULL, conf);
term_provide_logctx(term, logctx);
term_size(term, conf_get_int(conf, CONF_height),
conf_get_int(conf, CONF_width),
conf_get_int(conf, CONF_savelines));
/*
* Correct the guesses for extra_{width,height}.
*/
{
RECT cr, wr;
GetWindowRect(hwnd, &wr);
GetClientRect(hwnd, &cr);
offset_width = offset_height = conf_get_int(conf, CONF_window_border);
extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
}
/*
* Resize the window, now we know what size we _really_ want it
* to be.
*/
guess_width = extra_width + font_width * term->cols;
guess_height = extra_height + font_height * term->rows;
SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
/*
* Set up a caret bitmap, with no content.
*/
{
char *bits;
int size = (font_width + 15) / 16 * 2 * font_height;
bits = snewn(size, char);
memset(bits, 0, size);
caretbm = CreateBitmap(font_width, font_height, 1, 1, bits);
sfree(bits);
}
CreateCaret(hwnd, caretbm, font_width, font_height);
/*
* Initialise the scroll bar.
*/
{
SCROLLINFO si;
si.cbSize = sizeof(si);
si.fMask = SIF_ALL | SIF_DISABLENOSCROLL;
si.nMin = 0;
si.nMax = term->rows - 1;
si.nPage = term->rows;
si.nPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, FALSE);
}
/*
* Prepare the mouse handler.
*/
lastact = MA_NOTHING;
lastbtn = MBT_NOTHING;
dbltime = GetDoubleClickTime();
/*
* Set up the session-control options on the system menu.
*/
{
HMENU m;
int j;
char *str;
popup_menus[SYSMENU].menu = GetSystemMenu(hwnd, FALSE);
popup_menus[CTXMENU].menu = CreatePopupMenu();
AppendMenu(popup_menus[CTXMENU].menu, MF_ENABLED, IDM_PASTE, "&Paste");
savedsess_menu = CreateMenu();
get_sesslist(&sesslist, TRUE);
update_savedsess_menu();
for (j = 0; j < lenof(popup_menus); j++) {
m = popup_menus[j].menu;
AppendMenu(m, MF_SEPARATOR, 0, 0);
AppendMenu(m, MF_ENABLED, IDM_SHOWLOG, "&Event Log");
AppendMenu(m, MF_SEPARATOR, 0, 0);
/* brian jiang */
AppendMenu(m, MF_ENABLED, IDM_FIND, "&Find");
AppendMenu(m, MF_SEPARATOR, 0, 0);
AppendMenu(m, MF_ENABLED, IDM_NEWSESS, "Ne&w Session...");
AppendMenu(m, MF_ENABLED, IDM_DUPSESS, "&Duplicate Session");
AppendMenu(m, MF_POPUP | MF_ENABLED, (UINT) savedsess_menu,
"Sa&ved Sessions");
AppendMenu(m, MF_ENABLED, IDM_RECONF, "Chan&ge Settings...");
AppendMenu(m, MF_SEPARATOR, 0, 0);
AppendMenu(m, MF_ENABLED, IDM_COPYALL, "C&opy All to Clipboard");
AppendMenu(m, MF_ENABLED, IDM_CLRSB, "C&lear Scrollback");
AppendMenu(m, MF_ENABLED, IDM_RESET, "Rese&t Terminal");
/* One Key Cleaning Screen by Kasper */
AppendMenu(m, MF_ENABLED, IDM_CLEAN, "Clean Sc&reen");
AppendMenu(m, MF_SEPARATOR, 0, 0);
AppendMenu(m, MF_ENABLED, IDM_CHNTITLE, "Change &Title");
/* end */
AppendMenu(m, MF_SEPARATOR, 0, 0);
AppendMenu(m, (conf_get_int(conf, CONF_resize_action)
== RESIZE_DISABLED) ? MF_GRAYED : MF_ENABLED,
IDM_FULLSCREEN, "&Full Screen");
AppendMenu(m, MF_SEPARATOR, 0, 0);
if (has_help())
AppendMenu(m, MF_ENABLED, IDM_HELP, "&Help");
str = dupprintf("&About %s", appname);
AppendMenu(m, MF_ENABLED, IDM_ABOUT, str);
sfree(str);
}
}
start_backend();
/*
* Set up the initial input locale.
*/
set_input_locale(GetKeyboardLayout(0));
/*
* Finally show the window!
*/
ShowWindow(hwnd, show);
SetForegroundWindow(hwnd);
/*
* Set the palette up.
*/
pal = NULL;
logpal = NULL;
init_palette();
term_set_focus(term, GetForegroundWindow() == hwnd);
UpdateWindow(hwnd);
while (1) {
HANDLE *handles;
int nhandles, n;
DWORD timeout;
if (toplevel_callback_pending() ||
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
/*
* If we have anything we'd like to do immediately, set
* the timeout for MsgWaitForMultipleObjects to zero so
* that we'll only do a quick check of our handles and
* then get on with whatever that was.
*
* One such option is a pending toplevel callback. The
* other is a non-empty Windows message queue, which you'd
* think we could leave to MsgWaitForMultipleObjects to
* check for us along with all the handles, but in fact we
* can't because once PeekMessage in one iteration of this
* loop has removed a message from the queue, the whole
* queue is considered uninteresting by the next
* invocation of MWFMO. So we check ourselves whether the
* message queue is non-empty, and if so, set this timeout
* to zero to ensure MWFMO doesn't block.
*/
timeout = 0;
} else {
timeout = INFINITE;
/* The messages seem unreliable; especially if we're being tricky */
term_set_focus(term, GetForegroundWindow() == hwnd);
}
handles = handle_get_events(&nhandles);
n = MsgWaitForMultipleObjects(nhandles, handles, FALSE,
timeout, QS_ALLINPUT);
if ((unsigned)(n - WAIT_OBJECT_0) < (unsigned)nhandles) {
handle_got_event(handles[n - WAIT_OBJECT_0]);
sfree(handles);
} else
sfree(handles);
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
goto finished; /* two-level break */
if (!(IsWindow(logbox) && IsDialogMessage(logbox, &msg)))
DispatchMessageW(&msg);
/*
* WM_NETEVENT messages seem to jump ahead of others in
* the message queue. I'm not sure why; the docs for
* PeekMessage mention that messages are prioritised in
* some way, but I'm unclear on which priorities go where.
*
* Anyway, in practice I observe that WM_NETEVENT seems to
* jump to the head of the queue, which means that if we
* were to only process one message every time round this
* loop, we'd get nothing but NETEVENTs if the server
* flooded us with data, and stop responding to any other
* kind of window message. So instead, we keep on round
* this loop until we've consumed at least one message
* that _isn't_ a NETEVENT, or run out of messages
* completely (whichever comes first). And we don't go to
* run_toplevel_callbacks (which is where the netevents
* are actually processed, causing fresh NETEVENT messages
* to appear) until we've done this.
*/
if (msg.message != WM_NETEVENT)
break;
}
run_toplevel_callbacks();
}
finished:
cleanup_exit(msg.wParam); /* this doesn't return... */
return msg.wParam; /* ... but optimiser doesn't know */
}
/*
* Clean up and exit.
*/
void cleanup_exit(int code)
{
/*
* Clean up.
*/
deinit_fonts();
sfree(logpal);
if (pal)
DeleteObject(pal);
sk_cleanup();
if (conf_get_int(conf, CONF_protocol) == PROT_SSH) {
random_save_seed();
#ifdef MSCRYPTOAPI
crypto_wrapup();
#endif
}
shutdown_help();
/* Clean up COM. */
CoUninitialize();
exit(code);
}
/*
* Set up, or shut down, an AsyncSelect. Called from winnet.c.
*/
char *do_select(SOCKET skt, int startup)
{
int msg, events;
if (startup) {
msg = WM_NETEVENT;
events = (FD_CONNECT | FD_READ | FD_WRITE |
FD_OOB | FD_CLOSE | FD_ACCEPT);
} else {
msg = events = 0;
}
if (!hwnd)
return "do_select(): internal error (hwnd==NULL)";
if (p_WSAAsyncSelect(skt, hwnd, msg, events) == SOCKET_ERROR) {
switch (p_WSAGetLastError()) {
case WSAENETDOWN:
return "Network is down";
default:
return "WSAAsyncSelect(): unknown error";
}
}
return NULL;
}
/*
* Refresh the saved-session submenu from `sesslist'.
*/
static void update_savedsess_menu(void)
{
int i;
while (DeleteMenu(savedsess_menu, 0, MF_BYPOSITION)) ;
/* skip sesslist.sessions[0] == Default Settings */
for (i = 1;
i < ((sesslist.nsessions <= MENU_SAVED_MAX+1) ? sesslist.nsessions
: MENU_SAVED_MAX+1);
i++)
AppendMenu(savedsess_menu, MF_ENABLED,
IDM_SAVED_MIN + (i-1)*MENU_SAVED_STEP,
sesslist.sessions[i]);
if (sesslist.nsessions <= 1)
AppendMenu(savedsess_menu, MF_GRAYED, IDM_SAVED_MIN, "(No sessions)");
}
/*
* Update the Special Commands submenu.
*/
void update_specials_menu(void *frontend)
{
HMENU new_menu;
int i, j;
if (back)
specials = back->get_specials(backhandle);
else
specials = NULL;
if (specials) {
/* We can't use Windows to provide a stack for submenus, so
* here's a lame "stack" that will do for now. */
HMENU saved_menu = NULL;
int nesting = 1;
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(new_menu, MF_SEPARATOR, 0, 0);
break;
case TS_SUBMENU:
assert(nesting < 2);
nesting++;
saved_menu = new_menu; /* XXX lame stacking */
new_menu = CreatePopupMenu();
AppendMenu(saved_menu, MF_POPUP | MF_ENABLED,
(UINT) new_menu, specials[i].name);
break;
case TS_EXITMENU:
nesting--;
if (nesting) {
new_menu = saved_menu; /* XXX lame stacking */
saved_menu = NULL;
}
break;
default:
AppendMenu(new_menu, MF_ENABLED, IDM_SPECIAL_MIN + 0x10 * i,
specials[i].name);
break;
}
}
/* Squirrel the highest special. */
n_specials = i - 1;
} else {
new_menu = NULL;
n_specials = 0;
}
for (j = 0; j < lenof(popup_menus); j++) {
if (specials_menu) {
/* XXX does this free up all submenus? */
DeleteMenu(popup_menus[j].menu, (UINT)specials_menu, MF_BYCOMMAND);
DeleteMenu(popup_menus[j].menu, IDM_SPECIALSEP, MF_BYCOMMAND);
}
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)
{
LPTSTR curstype;
int force_visible = FALSE;
static int forced_visible = FALSE;
switch (busy_status) {
case BUSY_NOT:
if (send_raw_mouse)
curstype = IDC_ARROW;
else
curstype = IDC_IBEAM;
break;
case BUSY_WAITING:
curstype = IDC_APPSTARTING; /* this may be an abuse */
force_visible = TRUE;
break;
case BUSY_CPU:
curstype = IDC_WAIT;
force_visible = TRUE;
break;
default:
assert(0);
}
{
HCURSOR cursor = LoadCursor(NULL, curstype);
SetClassLongPtr(hwnd, GCLP_HCURSOR, (LONG_PTR)cursor);
SetCursor(cursor); /* force redraw of cursor at current posn */
}
if (force_visible != forced_visible) {
/* We want some cursor shapes to be visible always.
* Along with show_mouseptr(), this manages the ShowCursor()
* counter such that if we switch back to a non-force_visible
* cursor, the previous visibility state is restored. */
ShowCursor(force_visible);
forced_visible = force_visible;
}
}
void set_busy_status(void *frontend, int status)
{
busy_status = status;
update_mouse_pointer();
}
/*
* set or clear the "raw mouse message" mode
*/
void set_raw_mouse_mode(void *frontend, int activate)
{
activate = activate && !conf_get_int(conf, CONF_no_mouse_rep);
send_raw_mouse = activate;
update_mouse_pointer();
}
/*
* Print a message box and close the connection.
*/
void connection_fatal(void *frontend, char *fmt, ...)
{
va_list ap;
char *stuff, morestuff[100];
va_start(ap, fmt);
stuff = dupvprintf(fmt, ap);
va_end(ap);
sprintf(morestuff, "%.70s Fatal Error", appname);
MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
sfree(stuff);
if (conf_get_int(conf, CONF_close_on_exit) == FORCE_ON)
PostQuitMessage(1);
else {
queue_toplevel_callback(close_session, NULL);
}
}
/*
* Report an error at the command-line parsing stage.
*/
void cmdline_error(char *fmt, ...)
{
va_list ap;
char *stuff, morestuff[100];
va_start(ap, fmt);
stuff = dupvprintf(fmt, ap);
va_end(ap);
sprintf(morestuff, "%.70s Command Line Error", appname);
MessageBox(hwnd, stuff, morestuff, MB_ICONERROR | MB_OK);
sfree(stuff);
exit(1);
}
/*
* Actually do the job requested by a WM_NETEVENT
*/
static void wm_netevent_callback(void *vctx)
{
struct wm_netevent_params *params = (struct wm_netevent_params *)vctx;
select_result(params->wParam, params->lParam);
sfree(vctx);
}
/*
* Copy the colour palette from the configuration data into defpal.
* This is non-trivial because the colour indices are different.
*/
static void conftopalette(void)
{
int i;
static const int ww[] = {
256, 257, 258, 259, 260, 261,
0, 8, 1, 9, 2, 10, 3, 11,
4, 12, 5, 13, 6, 14, 7, 15
};
for (i = 0; i < 22; i++) {
int w = ww[i];
defpal[w].rgbtRed = conf_get_int_int(conf, CONF_colours, i*3+0);
defpal[w].rgbtGreen = conf_get_int_int(conf, CONF_colours, i*3+1);
defpal[w].rgbtBlue = conf_get_int_int(conf, CONF_colours, i*3+2);
}
for (i = 0; i < NEXTCOLOURS; i++) {
if (i < 216) {
int r = i / 36, g = (i / 6) % 6, b = i % 6;
defpal[i+16].rgbtRed = r ? r * 40 + 55 : 0;
defpal[i+16].rgbtGreen = g ? g * 40 + 55 : 0;
defpal[i+16].rgbtBlue = b ? b * 40 + 55 : 0;
} else {
int shade = i - 216;
shade = shade * 10 + 8;
defpal[i+16].rgbtRed = defpal[i+16].rgbtGreen =
defpal[i+16].rgbtBlue = shade;
}
}
/* Override with system colours if appropriate */
if (conf_get_int(conf, CONF_system_colour))
systopalette();
}
/*
* Override bit of defpal with colours from the system.
* (NB that this takes a copy the system colours at the time this is called,
* so subsequent colour scheme changes don't take effect. To fix that we'd
* probably want to be using GetSysColorBrush() and the like.)
*/
static void systopalette(void)
{
int i;
static const struct { int nIndex; int norm; int bold; } or[] =
{
{ COLOR_WINDOWTEXT, 256, 257 }, /* Default Foreground */
{ COLOR_WINDOW, 258, 259 }, /* Default Background */
{ COLOR_HIGHLIGHTTEXT, 260, 260 }, /* Cursor Text */
{ COLOR_HIGHLIGHT, 261, 261 }, /* Cursor Colour */
};
for (i = 0; i < (sizeof(or)/sizeof(or[0])); i++) {
COLORREF colour = GetSysColor(or[i].nIndex);
defpal[or[i].norm].rgbtRed =
defpal[or[i].bold].rgbtRed = GetRValue(colour);
defpal[or[i].norm].rgbtGreen =
defpal[or[i].bold].rgbtGreen = GetGValue(colour);
defpal[or[i].norm].rgbtBlue =
defpal[or[i].bold].rgbtBlue = GetBValue(colour);
}
}
/*
* Set up the colour palette.
*/
static void init_palette(void)
{
int i;
HDC hdc = GetDC(hwnd);
if (hdc) {
if (conf_get_int(conf, CONF_try_palette) &&
GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) {
/*
* This is a genuine case where we must use smalloc
* because the snew macros can't cope.
*/
logpal = smalloc(sizeof(*logpal)
- sizeof(logpal->palPalEntry)
+ NALLCOLOURS * sizeof(PALETTEENTRY));
logpal->palVersion = 0x300;
logpal->palNumEntries = NALLCOLOURS;
for (i = 0; i < NALLCOLOURS; i++) {
logpal->palPalEntry[i].peRed = defpal[i].rgbtRed;
logpal->palPalEntry[i].peGreen = defpal[i].rgbtGreen;
logpal->palPalEntry[i].peBlue = defpal[i].rgbtBlue;
logpal->palPalEntry[i].peFlags = PC_NOCOLLAPSE;
}
pal = CreatePalette(logpal);
if (pal) {
SelectPalette(hdc, pal, FALSE);
RealizePalette(hdc);
SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
}
}
ReleaseDC(hwnd, hdc);
}
if (pal)
for (i = 0; i < NALLCOLOURS; i++)
colours[i] = PALETTERGB(defpal[i].rgbtRed,
defpal[i].rgbtGreen,
defpal[i].rgbtBlue);
else
for (i = 0; i < NALLCOLOURS; i++)
colours[i] = RGB(defpal[i].rgbtRed,
defpal[i].rgbtGreen, defpal[i].rgbtBlue);
}
/*
* This is a wrapper to ExtTextOut() to force Windows to display
* the precise glyphs we give it. Otherwise it would do its own
* bidi and Arabic shaping, and we would end up uncertain which
* characters it had put where.
*/
static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc,
unsigned short *lpString, UINT cbCount,
CONST INT *lpDx, int opaque)
{
#ifdef __LCC__
/*
* The LCC include files apparently don't supply the
* GCP_RESULTSW type, but we can make do with GCP_RESULTS
* proper: the differences aren't important to us (the only
* variable-width string parameter is one we don't use anyway).
*/
GCP_RESULTS gcpr;
#else
GCP_RESULTSW gcpr;
#endif
char *buffer = snewn(cbCount*2+2, char);
char *classbuffer = snewn(cbCount, char);
memset(&gcpr, 0, sizeof(gcpr));
memset(buffer, 0, cbCount*2+2);
memset(classbuffer, GCPCLASS_NEUTRAL, cbCount);
gcpr.lStructSize = sizeof(gcpr);
gcpr.lpGlyphs = (void *)buffer;
gcpr.lpClass = (void *)classbuffer;
gcpr.nGlyphs = cbCount;
GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC);
ExtTextOut(hdc, x, y,
ETO_GLYPH_INDEX | ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
lprc, buffer, cbCount, lpDx);
}
/*
* 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;
int bkmode = 0, got_bkmode = FALSE;
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) {
exact_textout(hdc, xp, y, lprc, lpString+i, j-i,
font_varpitch ? NULL : lpDx+i, opaque);
} else {
ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
lprc, lpString+i, j-i,
font_varpitch ? NULL : lpDx+i);
}
i = j;
xp = xn;
bkmode = GetBkMode(hdc);
got_bkmode = TRUE;
SetBkMode(hdc, TRANSPARENT);
opaque = FALSE;
}
if (got_bkmode)
SetBkMode(hdc, bkmode);
}
static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
{
int ret;
/* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) {
ret = tm->tmAveCharWidth;
} else {
#define FIRST '0'
#define LAST '9'
ABCFLOAT widths[LAST-FIRST + 1];
int j;
font_varpitch = TRUE;
font_dualwidth = TRUE;
if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) {
ret = 0;
for (j = 0; j < lenof(widths); j++) {
int width = (int)(0.5 + widths[j].abcfA +
widths[j].abcfB + widths[j].abcfC);
if (ret < width)
ret = width;
}
} else {
ret = tm->tmMaxCharWidth;
}
#undef FIRST
#undef LAST
}
return ret;
}
/*
* Initialise all the fonts we will need initially. There may be as many as
* three or as few as one. The other (potentially) twenty-one fonts are done
* if/when they are needed.
*
* We also:
*
* - check the font width and height, correcting our guesses if
* necessary.
*
* - verify that the bold font is the same width as the ordinary
* one, and engage shadow bolding if not.
*
* - verify that the underlined font is the same width as the
* ordinary one (manual underlining by means of line drawing can
* be done in a pinch).
*/
static void init_fonts(int pick_width, int pick_height)
{
TEXTMETRIC tm;
CPINFO cpinfo;
FontSpec *font;
int fontsize[3];
int i;
int quality;
HDC hdc;
int fw_dontcare, fw_bold;
for (i = 0; i < FONT_MAXNO; i++)
fonts[i] = NULL;
bold_font_mode = conf_get_int(conf, CONF_bold_style) & 1 ?
BOLD_FONT : BOLD_NONE;
bold_colours = conf_get_int(conf, CONF_bold_style) & 2 ? TRUE : FALSE;
und_mode = UND_FONT;
font = conf_get_fontspec(conf, CONF_font);
if (font->isbold) {
fw_dontcare = FW_BOLD;
fw_bold = FW_HEAVY;
} else {
fw_dontcare = FW_DONTCARE;
fw_bold = FW_BOLD;
}
hdc = GetDC(hwnd);
if (pick_height)
font_height = pick_height;
else {
font_height = font->height;
if (font_height > 0) {
font_height =
-MulDiv(font_height, GetDeviceCaps(hdc, LOGPIXELSY), 72);
}
}
font_width = pick_width;
quality = conf_get_int(conf, CONF_font_quality);
#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, FONT_QUALITY(quality), \
FIXED_PITCH | FF_DONTCARE, font->name)
f(FONT_NORMAL, font->charset, fw_dontcare, FALSE);
SelectObject(hdc, fonts[FONT_NORMAL]);
GetTextMetrics(hdc, &tm);
GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont);
/* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
font_varpitch = FALSE;
font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
} else {
font_varpitch = TRUE;
font_dualwidth = TRUE;
}
if (pick_width == 0 || pick_height == 0) {
font_height = tm.tmHeight;
font_width = get_font_width(hdc, &tm);
}
#ifdef RDB_DEBUG_PATCH
debug(23, "Primary font H=%d, AW=%d, MW=%d",
tm.tmHeight, tm.tmAveCharWidth, tm.tmMaxCharWidth);
#endif
{
CHARSETINFO info;
DWORD cset = tm.tmCharSet;
memset(&info, 0xFF, sizeof(info));
/* !!! Yes the next line is right */
if (cset == OEM_CHARSET)
ucsdata.font_codepage = GetOEMCP();
else
if (TranslateCharsetInfo ((DWORD *) cset, &info, TCI_SRCCHARSET))
ucsdata.font_codepage = info.ciACP;
else
ucsdata.font_codepage = -1;
GetCPInfo(ucsdata.font_codepage, &cpinfo);
ucsdata.dbcs_screenfont = (cpinfo.MaxCharSize > 1);
}
f(FONT_UNDERLINE, font->charset, fw_dontcare, TRUE);
/*
* Some fonts, e.g. 9-pt Courier, draw their underlines
* outside their character cell. We successfully prevent
* screen corruption by clipping the text output, but then
* we lose the underline completely. Here we try to work
* out whether this is such a font, and if it is, we set a
* flag that causes underlines to be drawn by hand.
*
* Having tried other more sophisticated approaches (such
* as examining the TEXTMETRIC structure or requesting the
* height of a string), I think we'll do this the brute
* force way: we create a small bitmap, draw an underlined
* space on it, and test to see whether any pixels are
* foreground-coloured. (Since we expect the underline to
* go all the way across the character cell, we only search
* down a single column of the bitmap, half way across.)
*/
{
HDC und_dc;
HBITMAP und_bm, und_oldbm;
int i, gotit;
COLORREF c;
und_dc = CreateCompatibleDC(hdc);
und_bm = CreateCompatibleBitmap(hdc, font_width, font_height);
und_oldbm = SelectObject(und_dc, und_bm);
SelectObject(und_dc, fonts[FONT_UNDERLINE]);
SetTextAlign(und_dc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
SetTextColor(und_dc, RGB(255, 255, 255));
SetBkColor(und_dc, RGB(0, 0, 0));
SetBkMode(und_dc, OPAQUE);
ExtTextOut(und_dc, 0, 0, ETO_OPAQUE, NULL, " ", 1, NULL);
gotit = FALSE;
for (i = 0; i < font_height; i++) {
c = GetPixel(und_dc, font_width / 2, i);
if (c != RGB(0, 0, 0))
gotit = TRUE;
}
SelectObject(und_dc, und_oldbm);
DeleteObject(und_bm);
DeleteDC(und_dc);
if (!gotit) {
und_mode = UND_LINE;
DeleteObject(fonts[FONT_UNDERLINE]);
fonts[FONT_UNDERLINE] = 0;
}
}
if (bold_font_mode == BOLD_FONT) {
f(FONT_BOLD, font->charset, fw_bold, FALSE);
}
#undef f
descent = tm.tmAscent + 1;
if (descent >= font_height)
descent = font_height - 1;
for (i = 0; i < 3; i++) {
if (fonts[i]) {
if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight;
else
fontsize[i] = -i;
} else
fontsize[i] = -i;
}
ReleaseDC(hwnd, hdc);
if (fontsize[FONT_UNDERLINE] != fontsize[FONT_NORMAL]) {
und_mode = UND_LINE;
DeleteObject(fonts[FONT_UNDERLINE]);
fonts[FONT_UNDERLINE] = 0;
}
if (bold_font_mode == BOLD_FONT &&
fontsize[FONT_BOLD] != fontsize[FONT_NORMAL]) {
bold_font_mode = BOLD_SHADOW;
DeleteObject(fonts[FONT_BOLD]);
fonts[FONT_BOLD] = 0;
}
fontflag[0] = fontflag[1] = fontflag[2] = 1;
init_ucs(conf, &ucsdata);
}
static void another_font(int fontno)
{
int basefont;
int fw_dontcare, fw_bold, quality;
int c, u, w, x;
char *s;
FontSpec *font;
if (fontno < 0 || fontno >= FONT_MAXNO || fontflag[fontno])
return;
basefont = (fontno & ~(FONT_BOLDUND));
if (basefont != fontno && !fontflag[basefont])
another_font(basefont);
font = conf_get_fontspec(conf, CONF_font);
if (font->isbold) {
fw_dontcare = FW_BOLD;
fw_bold = FW_HEAVY;
} else {
fw_dontcare = FW_DONTCARE;
fw_bold = FW_BOLD;
}
c = font->charset;
w = fw_dontcare;
u = FALSE;
s = font->name;
x = font_width;
if (fontno & FONT_WIDE)
x *= 2;
if (fontno & FONT_NARROW)
x = (x+1)/2;
if (fontno & FONT_OEM)
c = OEM_CHARSET;
if (fontno & FONT_BOLD)
w = fw_bold;
if (fontno & FONT_UNDERLINE)
u = TRUE;
quality = conf_get_int(conf, CONF_font_quality);
fonts[fontno] =
CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, FONT_QUALITY(quality),
DEFAULT_PITCH | FF_DONTCARE, s);
fontflag[fontno] = 1;
}
static void deinit_fonts(void)
{
int i;
for (i = 0; i < FONT_MAXNO; i++) {
if (fonts[i])
DeleteObject(fonts[i]);
fonts[i] = 0;
fontflag[i] = 0;
}
}
void request_resize(void *frontend, int w, int h)
{
int width, height;
/* If the window is maximized supress resizing attempts */
if (IsZoomed(hwnd)) {
if (conf_get_int(conf, CONF_resize_action) == RESIZE_TERM)
return;
}
if (conf_get_int(conf, CONF_resize_action) == RESIZE_DISABLED) return;
if (h == term->rows && w == term->cols) return;
/* Sanity checks ... */
{
static int first_time = 1;
static RECT ss;
switch (first_time) {
case 1:
/* Get the size of the screen */
if (get_fullscreen_rect(&ss))
/* first_time = 0 */ ;
else {
first_time = 2;
break;
}
case 0:
/* Make sure the values are sane */
width = (ss.right - ss.left - extra_width) / 4;
height = (ss.bottom - ss.top - extra_height) / 6;
if (w > width || h > height)
return;
if (w < 15)
w = 15;
if (h < 1)
h = 1;
}
}
term_size(term, h, w, conf_get_int(conf, CONF_savelines));
if (conf_get_int(conf, CONF_resize_action) != RESIZE_FONT &&
!IsZoomed(hwnd)) {
width = extra_width + font_width * w;
height = extra_height + font_height * h;
SetWindowPos(hwnd, NULL, 0, 0, width, height,
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOMOVE | SWP_NOZORDER);
} else
reset_window(0);
InvalidateRect(hwnd, NULL, TRUE);
}
static void reset_window(int reinit) {
/*
* This function decides how to resize or redraw when the
* user changes something.
*
* This function doesn't like to change the terminal size but if the
* font size is locked that may be it's only soluion.
*/
int win_width, win_height, resize_action, window_border;
RECT cr, wr;
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window()"));
#endif
/* Current window sizes ... */
GetWindowRect(hwnd, &wr);
GetClientRect(hwnd, &cr);
win_width = cr.right - cr.left;
win_height = cr.bottom - cr.top;
resize_action = conf_get_int(conf, CONF_resize_action);
window_border = conf_get_int(conf, CONF_window_border);
if (resize_action == RESIZE_DISABLED)
reinit = 2;
/* Are we being forced to reload the fonts ? */
if (reinit>1) {
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window() -- Forced deinit"));
#endif
deinit_fonts();
init_fonts(0,0);
}
/* Oh, looks like we're minimised */
if (win_width == 0 || win_height == 0)
return;
/* Is the window out of position ? */
if ( !reinit &&
(offset_width != (win_width-font_width*term->cols)/2 ||
offset_height != (win_height-font_height*term->rows)/2) ){
offset_width = (win_width-font_width*term->cols)/2;
offset_height = (win_height-font_height*term->rows)/2;
InvalidateRect(hwnd, NULL, TRUE);
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window() -> Reposition terminal"));
#endif
}
if (IsZoomed(hwnd)) {
/* We're fullscreen, this means we must not change the size of
* the window so it's the font size or the terminal itself.
*/
extra_width = wr.right - wr.left - cr.right + cr.left;
extra_height = wr.bottom - wr.top - cr.bottom + cr.top;
if (resize_action != RESIZE_TERM) {
if (font_width != win_width/term->cols ||
font_height != win_height/term->rows) {
deinit_fonts();
init_fonts(win_width/term->cols, win_height/term->rows);
offset_width = (win_width-font_width*term->cols)/2;
offset_height = (win_height-font_height*term->rows)/2;
InvalidateRect(hwnd, NULL, TRUE);
#ifdef RDB_DEBUG_PATCH
debug((25, "reset_window() -> Z font resize to (%d, %d)",
font_width, font_height));
#endif
}
} else {
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.
*/
term_size(term, win_height/font_height, win_width/font_width,
conf_get_int(conf, CONF_savelines));
offset_width = (win_width-font_width*term->cols)/2;
offset_height = (win_height-font_height*term->rows)/2;
InvalidateRect(hwnd, NULL, TRUE);
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window() -> Zoomed term_size"));
#endif
}
}
return;
}
/* Hmm, a force re-init means we should ignore the current window
* so we resize to the default font size.
*/
if (reinit>0) {
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window() -> Forced re-init"));
#endif
offset_width = offset_height = window_border;
extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
if (win_width != font_width*term->cols + offset_width*2 ||
win_height != font_height*term->rows + offset_height*2) {
/* If this is too large windows will resize it to the maximum
* allowed window size, we will then be back in here and resize
* the font or terminal to fit.
*/
SetWindowPos(hwnd, NULL, 0, 0,
font_width*term->cols + extra_width,
font_height*term->rows + extra_height,
SWP_NOMOVE | SWP_NOZORDER);
}
InvalidateRect(hwnd, NULL, TRUE);
return;
}
/* Okay the user doesn't want us to change the font so we try the
* window. But that may be too big for the screen which forces us
* to change the terminal.
*/
if ((resize_action == RESIZE_TERM && reinit<=0) ||
(resize_action == RESIZE_EITHER && reinit<0) ||
reinit>0) {
offset_width = offset_height = window_border;
extra_width = wr.right - wr.left - cr.right + cr.left + offset_width*2;
extra_height = wr.bottom - wr.top - cr.bottom + cr.top +offset_height*2;
if (win_width != font_width*term->cols + offset_width*2 ||
win_height != font_height*term->rows + offset_height*2) {
static RECT ss;
int width, height;
get_fullscreen_rect(&ss);
width = (ss.right - ss.left - extra_width) / font_width;
height = (ss.bottom - ss.top - extra_height) / font_height;
/* Grrr too big */
if ( term->rows > height || term->cols > width ) {
if (resize_action == RESIZE_EITHER) {
/* Make the font the biggest we can */
if (term->cols > width)
font_width = (ss.right - ss.left - extra_width)
/ term->cols;
if (term->rows > height)
font_height = (ss.bottom - ss.top - extra_height)
/ term->rows;
deinit_fonts();
init_fonts(font_width, font_height);
width = (ss.right - ss.left - extra_width) / font_width;
height = (ss.bottom - ss.top - extra_height) / font_height;
} else {
if ( height > term->rows ) height = term->rows;
if ( width > term->cols ) width = term->cols;
term_size(term, height, width,
conf_get_int(conf, CONF_savelines));
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window() -> term resize to (%d,%d)",
height, width));
#endif
}
}
SetWindowPos(hwnd, NULL, 0, 0,
font_width*term->cols + extra_width,
font_height*term->rows + extra_height,
SWP_NOMOVE | SWP_NOZORDER);
InvalidateRect(hwnd, NULL, TRUE);
#ifdef RDB_DEBUG_PATCH
debug((27, "reset_window() -> window resize to (%d,%d)",
font_width*term->cols + extra_width,
font_height*term->rows + extra_height));
#endif
}
return;
}
/* We're allowed to or must change the font but do we want to ? */
if (font_width != (win_width-window_border*2)/term->cols ||
font_height != (win_height-window_border*2)/term->rows) {
deinit_fonts();
init_fonts((win_width-window_border*2)/term->cols,
(win_height-window_border*2)/term->rows);
offset_width = (win_width-font_width*term->cols)/2;
offset_height = (win_height-font_height*term->rows)/2;
extra_width = wr.right - wr.left - cr.right + cr.left +offset_width*2;
extra_height = wr.bottom - wr.top - cr.bottom + cr.top+offset_height*2;
InvalidateRect(hwnd, NULL, TRUE);
#ifdef RDB_DEBUG_PATCH
debug((25, "reset_window() -> font resize to (%d,%d)",
font_width, font_height));
#endif
}
}
static void set_input_locale(HKL kl)
{
char lbuf[20];
GetLocaleInfo(LOWORD(kl), LOCALE_IDEFAULTANSICODEPAGE,
lbuf, sizeof(lbuf));
kbd_codepage = atoi(lbuf);
}
static void click(Mouse_Button b, int x, int y, int shift, int ctrl, int alt)
{
int thistime = GetMessageTime();
if (send_raw_mouse &&
!(shift && conf_get_int(conf, CONF_mouse_override))) {
lastbtn = MBT_NOTHING;
term_mouse(term, b, translate_button(b), MA_CLICK,
x, y, shift, ctrl, alt);
return;
}
if (lastbtn == b && thistime - lasttime < dbltime) {
lastact = (lastact == MA_CLICK ? MA_2CLK :
lastact == MA_2CLK ? MA_3CLK :
lastact == MA_3CLK ? MA_CLICK : MA_NOTHING);
} else {
lastbtn = b;
lastact = MA_CLICK;
}
if (lastact != MA_NOTHING)
term_mouse(term, b, translate_button(b), lastact,
x, y, shift, ctrl, alt);
lasttime = thistime;
}
/*
* Translate a raw mouse button designation (LEFT, MIDDLE, RIGHT)
* into a cooked one (SELECT, EXTEND, PASTE).
*/
static Mouse_Button translate_button(Mouse_Button button)
{
if (button == MBT_LEFT)
return MBT_SELECT;
if (button == MBT_MIDDLE)
return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ?
MBT_PASTE : MBT_EXTEND;
if (button == MBT_RIGHT)
return conf_get_int(conf, CONF_mouse_is_xterm) == 1 ?
MBT_EXTEND : MBT_PASTE;
return 0; /* shouldn't happen */
}
static void show_mouseptr(int show)
{
/* NB that the counter in ShowCursor() is also frobbed by
* update_mouse_pointer() */
static int cursor_visible = 1;
if (!conf_get_int(conf, CONF_hide_mouseptr))
show = 1; /* override if this feature disabled */
if (cursor_visible && !show)
ShowCursor(FALSE);
else if (!cursor_visible && show)
ShowCursor(TRUE);
cursor_visible = show;
}
static int is_alt_pressed(void)
{
BYTE keystate[256];
int r = GetKeyboardState(keystate);
if (!r)
return FALSE;
if (keystate[VK_MENU] & 0x80)
return TRUE;
if (keystate[VK_RMENU] & 0x80)
return TRUE;
return FALSE;
}
static int resizing;
void notify_remote_exit(void *fe)
{
int exitcode, close_on_exit;
if (!session_closed &&
(exitcode = back->exitcode(backhandle)) >= 0) {
close_on_exit = conf_get_int(conf, CONF_close_on_exit);
/* Abnormal exits will already have set session_closed and taken
* appropriate action. */
if (close_on_exit == FORCE_ON ||
(close_on_exit == AUTO && exitcode != INT_MAX)) {
PostQuitMessage(0);
} else {
queue_toplevel_callback(close_session, NULL);
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(unsigned long next)
{
unsigned long now = GETTICKCOUNT();
long ticks;
if (now - next < INT_MAX)
ticks = 0;
else
ticks = next - now;
KillTimer(hwnd, TIMING_TIMER_ID);
SetTimer(hwnd, TIMING_TIMER_ID, ticks, NULL);
timing_next_time = next;
}
static void conf_cache_data(void)
{
/* Cache some items from conf to speed lookups in very hot code */
cursor_type = conf_get_int(conf, CONF_cursor_type);
vtmode = conf_get_int(conf, CONF_vtmode);
}
static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
HDC hdc;
static int ignore_clip = FALSE;
static int need_backend_resize = FALSE;
static int fullscr_on_max = FALSE;
static int processed_resize = FALSE;
static UINT last_mousemove = 0;
int resize_action;
switch (message) {
case WM_TIMER:
if ((UINT_PTR)wParam == TIMING_TIMER_ID) {
unsigned long next;
KillTimer(hwnd, TIMING_TIMER_ID);
if (run_timers(timing_next_time, &next)) {
timer_change_notify(next);
} else {
}
}
return 0;
case WM_CREATE:
break;
case WM_CLOSE:
{
char *str;
show_mouseptr(1);
str = dupprintf("%s Exit Confirmation", appname);
if (session_closed || !conf_get_int(conf, CONF_warn_on_close) ||
MessageBox(hwnd,
"Are you sure you want to close this session?",
str, MB_ICONWARNING | MB_OKCANCEL | MB_DEFBUTTON1)
== IDOK)
DestroyWindow(hwnd);
sfree(str);
}
return 0;
case WM_DESTROY:
show_mouseptr(1);
PostQuitMessage(0);
return 0;
case WM_INITMENUPOPUP:
if ((HMENU)wParam == savedsess_menu) {
/* About to pop up Saved Sessions sub-menu.
* Refresh the session list. */
get_sesslist(&sesslist, FALSE); /* free */
get_sesslist(&sesslist, TRUE);
update_savedsess_menu();
return 0;
}
break;
case WM_COMMAND:
case WM_SYSCOMMAND:
switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
case IDM_SHOWLOG:
showeventlog(hwnd);
break;
/* brian Jiang */
case IDM_FIND:
show_find(hwnd);
break;
case IDM_NEWSESS:
case IDM_DUPSESS:
case IDM_SAVEDSESS:
{
char b[2048];
char c[30], *cl;
int freecl = FALSE;
BOOL inherit_handles;
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE filemap = NULL;
if (wParam == IDM_DUPSESS) {
/*
* Allocate a file-mapping memory chunk for the
* config structure.
*/
SECURITY_ATTRIBUTES sa;
void *p;
int size;
size = conf_serialised_size(conf);
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
filemap = CreateFileMapping(INVALID_HANDLE_VALUE,
&sa,
PAGE_READWRITE,
0, size, NULL);
if (filemap && filemap != INVALID_HANDLE_VALUE) {
p = MapViewOfFile(filemap, FILE_MAP_WRITE, 0, 0, size);
if (p) {
conf_serialise(conf, p);
UnmapViewOfFile(p);
}
}
inherit_handles = TRUE;
sprintf(c, "putty &%p:%u", filemap, (unsigned)size);
cl = c;
} else if (wParam == IDM_SAVEDSESS) {
unsigned int sessno = ((lParam - IDM_SAVED_MIN)
/ MENU_SAVED_STEP) + 1;
if (sessno < (unsigned)sesslist.nsessions) {
char *session = sesslist.sessions[sessno];
cl = dupprintf("putty @%s", session);
inherit_handles = FALSE;
freecl = TRUE;
} else
break;
} else /* IDM_NEWSESS */ {
cl = NULL;
inherit_handles = FALSE;
}
GetModuleFileName(NULL, b, sizeof(b) - 1);
si.cb = sizeof(si);
si.lpReserved = NULL;
si.lpDesktop = NULL;
si.lpTitle = NULL;
si.dwFlags = 0;
si.cbReserved2 = 0;
si.lpReserved2 = NULL;
CreateProcess(b, cl, NULL, NULL, inherit_handles,
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (filemap)
CloseHandle(filemap);
if (freecl)
sfree(cl);
}
break;
case IDM_RESTART:
if (!back) {
logevent(NULL, "----- Session restarted -----");
term_pwron(term, FALSE);
start_backend();
}
break;
case IDM_RECONF:
{
Conf *prev_conf;
int init_lvl = 1;
int reconfig_result;
if (reconfiguring)
break;
else
reconfiguring = TRUE;
/*
* Copy the current window title into the stored
* previous configuration, so that doing nothing to
* the window title field in the config box doesn't
* reset the title to its startup state.
*/
conf_set_str(conf, CONF_wintitle, window_name);
prev_conf = conf_copy(conf);
reconfig_result =
do_reconfig(hwnd, back ? back->cfg_info(backhandle) : 0);
reconfiguring = FALSE;
if (!reconfig_result) {
conf_free(prev_conf);
break;
}
conf_cache_data();
resize_action = conf_get_int(conf, CONF_resize_action);
{
/* Disable full-screen if resizing forbidden */
int i;
for (i = 0; i < lenof(popup_menus); i++)
EnableMenuItem(popup_menus[i].menu, IDM_FULLSCREEN,
MF_BYCOMMAND |
(resize_action == RESIZE_DISABLED)
? MF_GRAYED : MF_ENABLED);
/* Gracefully unzoom if necessary */
if (IsZoomed(hwnd) && (resize_action == RESIZE_DISABLED))
ShowWindow(hwnd, SW_RESTORE);
}
/* Pass new config data to the logging module */
log_reconfig(logctx, conf);
sfree(logpal);
/*
* Flush the line discipline's edit buffer in the
* case where local editing has just been disabled.
*/
if (ldisc) {
ldisc_configure(ldisc, conf);
ldisc_send(ldisc, NULL, 0, 0);
}
if (pal)
DeleteObject(pal);
logpal = NULL;
pal = NULL;
conftopalette();
init_palette();
/* Pass new config data to the terminal */
term_reconfig(term, conf);
/* Pass new config data to the back end */
if (back)
back->reconfig(backhandle, conf);
/* Screen size changed ? */
if (conf_get_int(conf, CONF_height) !=
conf_get_int(prev_conf, CONF_height) ||
conf_get_int(conf, CONF_width) !=
conf_get_int(prev_conf, CONF_width) ||
conf_get_int(conf, CONF_savelines) !=
conf_get_int(prev_conf, CONF_savelines) ||
resize_action == RESIZE_FONT ||
(resize_action == RESIZE_EITHER && IsZoomed(hwnd)) ||
resize_action == RESIZE_DISABLED)
term_size(term, conf_get_int(conf, CONF_height),
conf_get_int(conf, CONF_width),
conf_get_int(conf, CONF_savelines));
/* Enable or disable the scroll bar, etc */
{
LONG nflg, flag = GetWindowLongPtr(hwnd, GWL_STYLE);
LONG nexflag, exflag =
GetWindowLongPtr(hwnd, GWL_EXSTYLE);
nexflag = exflag;
if (conf_get_int(conf, CONF_alwaysontop) !=
conf_get_int(prev_conf, CONF_alwaysontop)) {
if (conf_get_int(conf, CONF_alwaysontop)) {
nexflag |= WS_EX_TOPMOST;
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE);
} else {
nexflag &= ~(WS_EX_TOPMOST);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE);
}
}
if (conf_get_int(conf, CONF_sunken_edge))
nexflag |= WS_EX_CLIENTEDGE;
else
nexflag &= ~(WS_EX_CLIENTEDGE);
nflg = flag;
if (conf_get_int(conf, is_full_screen() ?
CONF_scrollbar_in_fullscreen :
CONF_scrollbar))
nflg |= WS_VSCROLL;
else
nflg &= ~WS_VSCROLL;
if (resize_action == RESIZE_DISABLED ||
is_full_screen())
nflg &= ~WS_THICKFRAME;
else
nflg |= WS_THICKFRAME;
if (resize_action == RESIZE_DISABLED)
nflg &= ~WS_MAXIMIZEBOX;
else
nflg |= WS_MAXIMIZEBOX;
if (nflg != flag || nexflag != exflag) {
if (nflg != flag)
SetWindowLongPtr(hwnd, GWL_STYLE, nflg);
if (nexflag != exflag)
SetWindowLongPtr(hwnd, GWL_EXSTYLE, nexflag);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_NOACTIVATE | SWP_NOCOPYBITS |
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_FRAMECHANGED);
init_lvl = 2;
}
}
/* Oops */
if (resize_action == RESIZE_DISABLED && IsZoomed(hwnd)) {
force_normal(hwnd);
init_lvl = 2;
}
set_title(NULL, conf_get_str(conf, CONF_wintitle));
if (IsIconic(hwnd)) {
SetWindowText(hwnd,
conf_get_int(conf, CONF_win_name_always) ?
window_name : icon_name);
}
{
FontSpec *font = conf_get_fontspec(conf, CONF_font);
FontSpec *prev_font = conf_get_fontspec(prev_conf,
CONF_font);
if (!strcmp(font->name, prev_font->name) ||
!strcmp(conf_get_str(conf, CONF_line_codepage),
conf_get_str(prev_conf, CONF_line_codepage)) ||
font->isbold != prev_font->isbold ||
font->height != prev_font->height ||
font->charset != prev_font->charset ||
conf_get_int(conf, CONF_font_quality) !=
conf_get_int(prev_conf, CONF_font_quality) ||
conf_get_int(conf, CONF_vtmode) !=
conf_get_int(prev_conf, CONF_vtmode) ||
conf_get_int(conf, CONF_bold_style) !=
conf_get_int(prev_conf, CONF_bold_style) ||
resize_action == RESIZE_DISABLED ||
resize_action == RESIZE_EITHER ||
resize_action != conf_get_int(prev_conf,
CONF_resize_action))
init_lvl = 2;
}
InvalidateRect(hwnd, NULL, TRUE);
reset_window(init_lvl);
conf_free(prev_conf);
}
break;
case IDM_COPYALL:
term_copyall(term);
break;
case IDM_PASTE:
request_paste(NULL);
break;
case IDM_CLRSB:
term_clrsb(term);
break;
case IDM_RESET:
term_pwron(term, TRUE);
if (ldisc)
ldisc_send(ldisc, NULL, 0, 0);
break;
/* One Key Cleaning Screen by Kasper */
case IDM_CLEAN:
term_pwron(term, TRUE);
if (ldisc)
ldisc_send(ldisc, NULL, 0, 0);
term_clrsb(term);
break;
/* end */
/* Title change by Kasper */
case IDM_CHNTITLE:
show_titlechange(hwnd);
break;
/* end */
case IDM_ABOUT:
showabout(hwnd);
break;
case IDM_HELP:
launch_help(hwnd, NULL);
break;
case SC_MOUSEMENU:
/*
* We get this if the System menu has been activated
* using the mouse.
*/
show_mouseptr(1);
break;
case SC_KEYMENU:
/*
* We get this if the System menu has been activated
* using the keyboard. This might happen from within
* TranslateKey, in which case it really wants to be
* followed by a `space' character to actually _bring
* the menu up_ rather than just sitting there in
* `ready to appear' state.
*/
show_mouseptr(1); /* make sure pointer is visible */
if( lParam == 0 )
PostMessage(hwnd, WM_CHAR, ' ', 0);
break;
case IDM_FULLSCREEN:
flip_full_screen();
break;
default:
if (wParam >= IDM_SAVED_MIN && wParam < IDM_SAVED_MAX) {
SendMessage(hwnd, WM_SYSCOMMAND, IDM_SAVEDSESS, wParam);
}
if (wParam >= IDM_SPECIAL_MIN && wParam <= IDM_SPECIAL_MAX) {
int i = (wParam - IDM_SPECIAL_MIN) / 0x10;
/*
* Ensure we haven't been sent a bogus SYSCOMMAND
* which would cause us to reference invalid memory
* and crash. Perhaps I'm just too paranoid here.
*/
if (i >= n_specials)
break;
if (back)
back->special(backhandle, specials[i].code);
}
}
break;
#define X_POS(l) ((int)(short)LOWORD(l))
#define Y_POS(l) ((int)(short)HIWORD(l))
#define TO_CHR_X(x) ((((x)<0 ? (x)-font_width+1 : (x))-offset_width) / font_width)
#define TO_CHR_Y(y) ((((y)<0 ? (y)-font_height+1: (y))-offset_height) / font_height)
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
if (message == WM_RBUTTONDOWN &&
((wParam & MK_CONTROL) ||
(conf_get_int(conf, CONF_mouse_is_xterm) == 2))) {
POINT cursorpos;
show_mouseptr(1); /* make sure pointer is visible */
GetCursorPos(&cursorpos);
TrackPopupMenu(popup_menus[CTXMENU].menu,
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON,
cursorpos.x, cursorpos.y,
0, hwnd, NULL);
break;
}
{
int button, press;
switch (message) {
case WM_LBUTTONDOWN:
button = MBT_LEFT;
wParam |= MK_LBUTTON;
press = 1;
break;
case WM_MBUTTONDOWN:
button = MBT_MIDDLE;
wParam |= MK_MBUTTON;
press = 1;
break;
case WM_RBUTTONDOWN:
button = MBT_RIGHT;
wParam |= MK_RBUTTON;
press = 1;
break;
case WM_LBUTTONUP:
button = MBT_LEFT;
wParam &= ~MK_LBUTTON;
press = 0;
break;
case WM_MBUTTONUP:
button = MBT_MIDDLE;
wParam &= ~MK_MBUTTON;
press = 0;
break;
case WM_RBUTTONUP:
button = MBT_RIGHT;
wParam &= ~MK_RBUTTON;
press = 0;
break;
default:
button = press = 0; /* shouldn't happen */
}
show_mouseptr(1);
/*
* Special case: in full-screen mode, if the left
* button is clicked in the very top left corner of the
* window, we put up the System menu instead of doing
* selection.
*/
{
char mouse_on_hotspot = 0;
POINT pt;
GetCursorPos(&pt);
#ifndef NO_MULTIMON
{
HMONITOR mon;
MONITORINFO mi;
mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONULL);
if (mon != NULL) {
mi.cbSize = sizeof(MONITORINFO);
GetMonitorInfo(mon, &mi);
if (mi.rcMonitor.left == pt.x &&
mi.rcMonitor.top == pt.y) {
mouse_on_hotspot = 1;
}
}
}
#else
if (pt.x == 0 && pt.y == 0) {
mouse_on_hotspot = 1;
}
#endif
if (is_full_screen() && press &&
button == MBT_LEFT && mouse_on_hotspot) {
SendMessage(hwnd, WM_SYSCOMMAND, SC_MOUSEMENU,
MAKELPARAM(pt.x, pt.y));
return 0;
}
}
if (press) {
click(button,
TO_CHR_X(X_POS(lParam)), TO_CHR_Y(Y_POS(lParam)),
wParam & MK_SHIFT, wParam & MK_CONTROL,
is_alt_pressed());
SetCapture(hwnd);
} else {
term_mouse(term, button, translate_button(button), MA_RELEASE,
TO_CHR_X(X_POS(lParam)),
TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
wParam & MK_CONTROL, is_alt_pressed());
if (!(wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)))
ReleaseCapture();
}
}
return 0;
case WM_MOUSEMOVE:
{
/*
* Windows seems to like to occasionally send MOUSEMOVE
* events even if the mouse hasn't moved. Don't unhide
* the mouse pointer in this case.
*/
static WPARAM wp = 0;
static LPARAM lp = 0;
if (wParam != wp || lParam != lp ||
last_mousemove != WM_MOUSEMOVE) {
show_mouseptr(1);
wp = wParam; lp = lParam;
last_mousemove = WM_MOUSEMOVE;
}
}
/*
* Add the mouse position and message time to the random
* number noise.
*/
noise_ultralight(lParam);
if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
GetCapture() == hwnd) {
Mouse_Button b;
if (wParam & MK_LBUTTON)
b = MBT_LEFT;
else if (wParam & MK_MBUTTON)
b = MBT_MIDDLE;
else
b = MBT_RIGHT;
term_mouse(term, b, translate_button(b), MA_DRAG,
TO_CHR_X(X_POS(lParam)),
TO_CHR_Y(Y_POS(lParam)), wParam & MK_SHIFT,
wParam & MK_CONTROL, is_alt_pressed());
}
return 0;
case WM_NCMOUSEMOVE:
{
static WPARAM wp = 0;
static LPARAM lp = 0;
if (wParam != wp || lParam != lp ||
last_mousemove != WM_NCMOUSEMOVE) {
show_mouseptr(1);
wp = wParam; lp = lParam;
last_mousemove = WM_NCMOUSEMOVE;
}
}
noise_ultralight(lParam);
break;
case WM_IGNORE_CLIP:
ignore_clip = wParam; /* don't panic on DESTROYCLIPBOARD */
break;
case WM_DESTROYCLIPBOARD:
if (!ignore_clip)
term_deselect(term);
ignore_clip = FALSE;
return 0;
case WM_PAINT:
{
PAINTSTRUCT p;
HideCaret(hwnd);
hdc = BeginPaint(hwnd, &p);
if (pal) {
SelectPalette(hdc, pal, TRUE);
RealizePalette(hdc);
}
/*
* We have to be careful about term_paint(). It will
* set a bunch of character cells to INVALID and then
* call do_paint(), which will redraw those cells and
* _then mark them as done_. This may not be accurate:
* when painting in WM_PAINT context we are restricted
* to the rectangle which has just been exposed - so if
* that only covers _part_ of a character cell and the
* rest of it was already visible, that remainder will
* not be redrawn at all. Accordingly, we must not
* paint any character cell in a WM_PAINT context which
* already has a pending update due to terminal output.
* The simplest solution to this - and many, many
* thanks to Hung-Te Lin for working all this out - is
* not to do any actual painting at _all_ if there's a
* pending terminal update: just mark the relevant
* character cells as INVALID and wait for the
* scheduled full update to sort it out.
*
* I have a suspicion this isn't the _right_ solution.
* An alternative approach would be to have terminal.c
* separately track what _should_ be on the terminal
* screen and what _is_ on the terminal screen, and
* have two completely different types of redraw (one
* for full updates, which syncs the former with the
* terminal itself, and one for WM_PAINT which syncs
* the latter with the former); yet another possibility
* would be to have the Windows front end do what the
* GTK one already does, and maintain a bitmap of the
* current terminal appearance so that WM_PAINT becomes
* completely trivial. However, this should do for now.
*/
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,
!term->window_update_pending);
if (p.fErase ||
p.rcPaint.left < offset_width ||
p.rcPaint.top < offset_height ||
p.rcPaint.right >= offset_width + font_width*term->cols ||
p.rcPaint.bottom>= offset_height + font_height*term->rows)
{
HBRUSH fillcolour, oldbrush;
HPEN edge, oldpen;
fillcolour = CreateSolidBrush (
colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
oldbrush = SelectObject(hdc, fillcolour);
edge = CreatePen(PS_SOLID, 0,
colours[ATTR_DEFBG>>ATTR_BGSHIFT]);
oldpen = SelectObject(hdc, edge);
/*
* Jordan Russell reports that this apparently
* ineffectual IntersectClipRect() call masks a
* Windows NT/2K bug causing strange display
* problems when the PuTTY window is taller than
* the primary monitor. It seems harmless enough...
*/
IntersectClipRect(hdc,
p.rcPaint.left, p.rcPaint.top,
p.rcPaint.right, p.rcPaint.bottom);
ExcludeClipRect(hdc,
offset_width, offset_height,
offset_width+font_width*term->cols,
offset_height+font_height*term->rows);
Rectangle(hdc, p.rcPaint.left, p.rcPaint.top,
p.rcPaint.right, p.rcPaint.bottom);
/* SelectClipRgn(hdc, NULL); */
SelectObject(hdc, oldbrush);
DeleteObject(fillcolour);
SelectObject(hdc, oldpen);
DeleteObject(edge);
}
SelectObject(hdc, GetStockObject(SYSTEM_FONT));
SelectObject(hdc, GetStockObject(WHITE_PEN));
EndPaint(hwnd, &p);
ShowCaret(hwnd);
}
return 0;
case WM_NETEVENT:
{
/*
* To protect against re-entrancy when Windows's recv()
* immediately triggers a new WSAAsyncSelect window
* message, we don't call select_result directly from this
* handler but instead wait until we're back out at the
* top level of the message loop.
*/
struct wm_netevent_params *params =
snew(struct wm_netevent_params);
params->wParam = wParam;
params->lParam = lParam;
queue_toplevel_callback(wm_netevent_callback, params);
}
return 0;
case WM_SETFOCUS:
term_set_focus(term, TRUE);
CreateCaret(hwnd, caretbm, font_width, font_height);
ShowCaret(hwnd);
flash_window(0); /* stop */
compose_state = 0;
term_update(term);
break;
case WM_KILLFOCUS:
show_mouseptr(1);
term_set_focus(term, FALSE);
DestroyCaret();
caret_x = caret_y = -1; /* ensure caret is replaced next time */
term_update(term);
break;
case WM_ENTERSIZEMOVE:
#ifdef RDB_DEBUG_PATCH
debug((27, "WM_ENTERSIZEMOVE"));
#endif
EnableSizeTip(1);
resizing = TRUE;
need_backend_resize = FALSE;
break;
case WM_EXITSIZEMOVE:
EnableSizeTip(0);
resizing = FALSE;
#ifdef RDB_DEBUG_PATCH
debug((27, "WM_EXITSIZEMOVE"));
#endif
if (need_backend_resize) {
term_size(term, conf_get_int(conf, CONF_height),
conf_get_int(conf, CONF_width),
conf_get_int(conf, CONF_savelines));
InvalidateRect(hwnd, NULL, TRUE);
}
break;
case WM_SIZING:
/*
* This does two jobs:
* 1) Keep the sizetip uptodate
* 2) Make sure the window size is _stepped_ in units of the font size.
*/
resize_action = conf_get_int(conf, CONF_resize_action);
if (resize_action == RESIZE_TERM ||
(resize_action == RESIZE_EITHER && !is_alt_pressed())) {
int width, height, w, h, ew, eh;
LPRECT r = (LPRECT) lParam;
if (!need_backend_resize && resize_action == RESIZE_EITHER &&
(conf_get_int(conf, CONF_height) != term->rows ||
conf_get_int(conf, CONF_width) != term->cols)) {
/*
* Great! It seems that both the terminal size and the
* font size have been changed and the user is now dragging.
*
* It will now be difficult to get back to the configured
* font size!
*
* This would be easier but it seems to be too confusing.
*/
conf_set_int(conf, CONF_height, term->rows);
conf_set_int(conf, CONF_width, term->cols);
InvalidateRect(hwnd, NULL, TRUE);
need_backend_resize = TRUE;
}
width = r->right - r->left - extra_width;
height = r->bottom - r->top - extra_height;
w = (width + font_width / 2) / font_width;
if (w < 1)
w = 1;
h = (height + font_height / 2) / font_height;
if (h < 1)
h = 1;
UpdateSizeTip(hwnd, w, h);
ew = width - w * font_width;
eh = height - h * font_height;
if (ew != 0) {
if (wParam == WMSZ_LEFT ||
wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
r->left += ew;
else
r->right -= ew;
}
if (eh != 0) {
if (wParam == WMSZ_TOP ||
wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
r->top += eh;
else
r->bottom -= eh;
}
if (ew || eh)
return 1;
else
return 0;
} else {
int width, height, w, h, rv = 0;
int window_border = conf_get_int(conf, CONF_window_border);
int ex_width = extra_width + (window_border - offset_width) * 2;
int ex_height = extra_height + (window_border - offset_height) * 2;
LPRECT r = (LPRECT) lParam;
width = r->right - r->left - ex_width;
height = r->bottom - r->top - ex_height;
w = (width + term->cols/2)/term->cols;
h = (height + term->rows/2)/term->rows;
if ( r->right != r->left + w*term->cols + ex_width)
rv = 1;
if (wParam == WMSZ_LEFT ||
wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT)
r->left = r->right - w*term->cols - ex_width;
else
r->right = r->left + w*term->cols + ex_width;
if (r->bottom != r->top + h*term->rows + ex_height)
rv = 1;
if (wParam == WMSZ_TOP ||
wParam == WMSZ_TOPRIGHT || wParam == WMSZ_TOPLEFT)
r->top = r->bottom - h*term->rows - ex_height;
else
r->bottom = r->top + h*term->rows + ex_height;
return rv;
}
/* break; (never reached) */
case WM_FULLSCR_ON_MAX:
fullscr_on_max = TRUE;
break;
case WM_MOVE:
sys_cursor_update();
break;
case WM_SIZE:
resize_action = conf_get_int(conf, CONF_resize_action);
#ifdef RDB_DEBUG_PATCH
debug((27, "WM_SIZE %s (%d,%d)",
(wParam == SIZE_MINIMIZED) ? "SIZE_MINIMIZED":
(wParam == SIZE_MAXIMIZED) ? "SIZE_MAXIMIZED":
(wParam == SIZE_RESTORED && resizing) ? "to":
(wParam == SIZE_RESTORED) ? "SIZE_RESTORED":
"...",
LOWORD(lParam), HIWORD(lParam)));
#endif
if (wParam == SIZE_MINIMIZED)
SetWindowText(hwnd,
conf_get_int(conf, CONF_win_name_always) ?
window_name : icon_name);
if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
SetWindowText(hwnd, window_name);
if (wParam == SIZE_RESTORED) {
processed_resize = FALSE;
clear_full_screen();
if (processed_resize) {
/*
* Inhibit normal processing of this WM_SIZE; a
* secondary one was triggered just now by
* clear_full_screen which contained the correct
* client area size.
*/
return 0;
}
}
if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
fullscr_on_max = FALSE;
processed_resize = FALSE;
make_full_screen();
if (processed_resize) {
/*
* Inhibit normal processing of this WM_SIZE; a
* secondary one was triggered just now by
* make_full_screen which contained the correct client
* area size.
*/
return 0;
}
}
processed_resize = TRUE;
if (resize_action == RESIZE_DISABLED) {
/* A resize, well it better be a minimize. */
reset_window(-1);
} else {
int width, height, w, h;
int window_border = conf_get_int(conf, CONF_window_border);
width = LOWORD(lParam);
height = HIWORD(lParam);
if (wParam == SIZE_MAXIMIZED && !was_zoomed) {
was_zoomed = 1;
prev_rows = term->rows;
prev_cols = term->cols;
if (resize_action == RESIZE_TERM) {
w = width / font_width;
if (w < 1) w = 1;
h = height / font_height;
if (h < 1) h = 1;
if (resizing) {
/*
* As below, if we're in the middle of an
* interactive resize we don't call
* back->size. In Windows 7, this case can
* arise in maximisation as well via the Aero
* snap UI.
*/
need_backend_resize = TRUE;
conf_set_int(conf, CONF_height, h);
conf_set_int(conf, CONF_width, w);
} else {
term_size(term, h, w,
conf_get_int(conf, CONF_savelines));
}
}
reset_window(0);
} else if (wParam == SIZE_RESTORED && was_zoomed) {
was_zoomed = 0;
if (resize_action == RESIZE_TERM) {
w = (width-window_border*2) / font_width;
if (w < 1) w = 1;
h = (height-window_border*2) / font_height;
if (h < 1) h = 1;
term_size(term, h, w, conf_get_int(conf, CONF_savelines));
reset_window(2);
} else if (resize_action != RESIZE_FONT)
reset_window(2);
else
reset_window(0);
} else if (wParam == SIZE_MINIMIZED) {
/* do nothing */
} else if (resize_action == RESIZE_TERM ||
(resize_action == RESIZE_EITHER &&
!is_alt_pressed())) {
w = (width-window_border*2) / font_width;
if (w < 1) w = 1;
h = (height-window_border*2) / font_height;
if (h < 1) h = 1;
if (resizing) {
/*
* Don't call back->size in mid-resize. (To
* prevent massive numbers of resize events
* getting sent down the connection during an NT
* opaque drag.)
*/
need_backend_resize = TRUE;
conf_set_int(conf, CONF_height, h);
conf_set_int(conf, CONF_width, w);
} else {
term_size(term, h, w, conf_get_int(conf, CONF_savelines));
}
} else {
reset_window(0);
}
}
sys_cursor_update();
return 0;
case WM_VSCROLL:
switch (LOWORD(wParam)) {
case SB_BOTTOM:
term_scroll(term, -1, 0);
break;
case SB_TOP:
term_scroll(term, +1, 0);
break;
case SB_LINEDOWN:
term_scroll(term, 0, +1);
break;
case SB_LINEUP:
term_scroll(term, 0, -1);
break;
case SB_PAGEDOWN:
term_scroll(term, 0, +term->rows / 2);
break;
case SB_PAGEUP:
term_scroll(term, 0, -term->rows / 2);
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
/*
* Use GetScrollInfo instead of HIWORD(wParam) to get
* 32-bit scroll position.
*/
{
SCROLLINFO si;
si.cbSize = sizeof(si);
si.fMask = SIF_TRACKPOS;
if (GetScrollInfo(hwnd, SB_VERT, &si) == 0)
si.nTrackPos = HIWORD(wParam);
term_scroll(term, 1, si.nTrackPos);
}
break;
}
break;
case WM_PALETTECHANGED:
if ((HWND) wParam != hwnd && pal != NULL) {
HDC hdc = get_ctx(NULL);
if (hdc) {
if (RealizePalette(hdc) > 0)
UpdateColors(hdc);
free_ctx(hdc);
}
}
break;
case WM_QUERYNEWPALETTE:
if (pal != NULL) {
HDC hdc = get_ctx(NULL);
if (hdc) {
if (RealizePalette(hdc) > 0)
UpdateColors(hdc);
free_ctx(hdc);