Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/config.c
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/config.c	(revision 6566)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/config.c	(working copy)
@@ -1500,7 +1500,53 @@
 				 colour_handler, P(cd));
     cd->button->generic.column = 1;
     ctrl_columns(s, 1, 100);
+	
+	/*
+	 * HACK: Hyperlink stuff: The Window/Hyperlinks panel.
+	 */
 
+    ctrl_settitle(b, "Window/Hyperlinks", "Options controlling behaviour of hyperlinks");
+
+    s = ctrl_getset(b, "Window/Hyperlinks", "general", "General options for hyperlinks");
+
+	ctrl_radiobuttons(s, "Underline hyperlinks:", NO_SHORTCUT, 1,
+			  HELPCTX(no_help),
+			  dlg_stdradiobutton_handler,
+			  I(offsetof(Config, url_underline)),
+			  "Always", NO_SHORTCUT, I(URLHACK_UNDERLINE_ALWAYS),
+			  "When hovered upon", NO_SHORTCUT, I(URLHACK_UNDERLINE_HOVER),
+			  "Never", NO_SHORTCUT, I(URLHACK_UNDERLINE_NEVER),
+			  NULL);
+
+    ctrl_checkbox(s, "Use ctrl+click to launch hyperlinks (instead of a click)", 'l',
+		  HELPCTX(no_help),
+		  dlg_stdcheckbox_handler, I(offsetof(Config,url_ctrl_click)));
+
+	s = ctrl_getset(b, "Window/Hyperlinks", "browser", "Browser application");
+
+    ctrl_checkbox(s, "Use the default browser", 'b',
+		  HELPCTX(no_help),
+		  dlg_stdcheckbox_handler, I(offsetof(Config,url_defbrowser)));
+
+    ctrl_filesel(s, "or specify an application to open hyperlinks with:", 's',
+		"Application (*.exe)\0*.exe\0All files (*.*)\0*.*\0\0", TRUE,
+		"Select executable to open hyperlinks with", HELPCTX(no_help),
+		 dlg_stdfilesel_handler, I(offsetof(Config, url_browser)));
+
+	s = ctrl_getset(b, "Window/Hyperlinks", "regexp", "Regular expression");
+    
+    ctrl_checkbox(s, "Use the default regular expression", 'r',
+		  HELPCTX(no_help),
+		  dlg_stdcheckbox_handler, I(offsetof(Config,url_defregex)));
+
+	ctrl_editbox(s, "or specify your own:", NO_SHORTCUT, 100,
+		 HELPCTX(no_help),
+		 dlg_stdeditbox_handler, I(offsetof(Config,url_regex)),
+		 I(sizeof(((Config *)0)->url_regex)));
+
+    ctrl_text(s, "The single white space will be cropped in front of the link, if exists.",
+	      HELPCTX(no_help));
+
     /*
      * The Connection panel. This doesn't show up if we're in a
      * non-network utility such as pterm. We tell this by being
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/putty.h
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/putty.h	(revision 6566)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/putty.h	(working copy)
@@ -287,6 +287,14 @@
 };
 
 enum {
+	/* HACK: Hyperlink stuff */
+
+	URLHACK_UNDERLINE_ALWAYS,
+	URLHACK_UNDERLINE_HOVER,
+	URLHACK_UNDERLINE_NEVER
+};
+
+enum {
     /*
      * Line discipline options which the backend might try to control.
      */
@@ -572,6 +580,15 @@
     FontSpec widefont;
     FontSpec wideboldfont;
     int shadowboldoffset;
+
+	/* HACK: Hyperlink stuff */
+
+	int url_ctrl_click;
+	int url_underline;
+	int url_defbrowser;
+	int url_defregex;
+	char url_browser[MAX_PATH];
+	char url_regex[1024];
 };
 
 /*
@@ -1176,4 +1193,6 @@
 int run_timers(long now, long *next);
 void timer_change_notify(long next);
 
+#define CHAR_MASK    0x000000FFUL
+
 #endif
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regexp.h
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regexp.h	(revision 0)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regexp.h	(revision 0)
@@ -0,0 +1,22 @@
+/*
+ * Definitions etc. for regexp(3) routines.
+ *
+ * Caveat:  this is V8 regexp(3) [actually, a reimplementation thereof],
+ * not the System V one.
+ */
+#define NSUBEXP  16
+typedef struct regexp {
+	char *startp[NSUBEXP];
+	char *endp[NSUBEXP];
+	char regstart;		/* Internal use only. */
+	char reganch;		/* Internal use only. */
+	char *regmust;		/* Internal use only. */
+	int regmlen;		/* Internal use only. */
+	char program[1];	/* Unwarranted chumminess with compiler. */
+} regexp;
+
+regexp *regcomp( char* re);
+int regexec( regexp* r, char* str);
+void regsub( regexp* r, char* str, char* substr);
+void regerror( char* s);	/* for internal use only */
+void set_regerror_func( void (*func)( char*));
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regmagic.h
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regmagic.h	(revision 0)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regmagic.h	(revision 0)
@@ -0,0 +1,5 @@
+/*
+ * The first byte of the regexp internal "program" is actually this magic
+ * number; the start node begins in the second byte.
+ */
+#define	MAGIC	0234
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regexp.cpp
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regexp.cpp	(revision 0)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/re_lib/regexp.cpp	(revision 0)
@@ -0,0 +1,1212 @@
+/*
+ * regcomp and regexec -- regsub and regerror are elsewhere
+ * @(#)regexp.c	1.3 of 18 April 87
+ *
+ *	Copyright (c) 1986 by University of Toronto.
+ *	Written by Henry Spencer.  Not derived from licensed software.
+ *
+ *	Permission is granted to anyone to use this software for any
+ *	purpose on any computer system, and to redistribute it freely,
+ *	subject to the following restrictions:
+ *
+ *	1. The author is not responsible for the consequences of use of
+ *		this software, no matter how awful, even if they arise
+ *		from defects in it.
+ *
+ *	2. The origin of this software must not be misrepresented, either
+ *		by explicit claim or by omission.
+ *
+ *	3. Altered versions must be plainly marked as such, and must not
+ *		be misrepresented as being the original software.
+ *
+ * Beware that some of this code is subtly aware of the way operator
+ * precedence is structured in regular expressions.  Serious changes in
+ * regular-expression syntax might require a total rethink.
+ */
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+
+#include "regexp.h"
+#include "regmagic.h"
+
+/*
+ * The "internal use only" fields in regexp.h are present to pass info from
+ * compile to execute that permits the execute phase to run lots faster on
+ * simple cases.  They are:
+ *
+ * regstart	char that must begin a match; '\0' if none obvious
+ * reganch	is the match anchored (at beginning-of-line only)?
+ * regmust	string (pointer into program) that match must include, or NULL
+ * regmlen	length of regmust string
+ *
+ * Regstart and reganch permit very fast decisions on suitable starting points
+ * for a match, cutting down the work a lot.  Regmust permits fast rejection
+ * of lines that cannot possibly match.  The regmust tests are costly enough
+ * that regcomp() supplies a regmust only if the r.e. contains something
+ * potentially expensive (at present, the only such thing detected is * or +
+ * at the start of the r.e., which can involve a lot of backup).  Regmlen is
+ * supplied because the test in regexec() needs it and regcomp() is computing
+ * it anyway.
+ */
+
+/*
+ * Structure for regexp "program".  This is essentially a linear encoding
+ * of a nondeterministic finite-state machine (aka syntax charts or
+ * "railroad normal form" in parsing technology).  Each node is an opcode
+ * plus a "next" pointer, possibly plus an operand.  "Next" pointers of
+ * all nodes except BRANCH implement concatenation; a "next" pointer with
+ * a BRANCH on both ends of it is connecting two alternatives.  (Here we
+ * have one of the subtle syntax dependencies:  an individual BRANCH (as
+ * opposed to a collection of them) is never concatenated with anything
+ * because of operator precedence.)  The operand of some types of node is
+ * a literal string; for others, it is a node leading into a sub-FSM.  In
+ * particular, the operand of a BRANCH node is the first node of the branch.
+ * (NB this is *not* a tree structure:  the tail of the branch connects
+ * to the thing following the set of BRANCHes.)  The opcodes are:
+ */
+
+/* definition	number	opnd?	meaning */
+#define	END	0	/* no	End of program. */
+#define	BOL	1	/* no	Match "" at beginning of line. */
+#define	EOL	2	/* no	Match "" at end of line. */
+#define	ANY	3	/* no	Match any one character. */
+#define	ANYOF	4	/* str	Match any character in this string. */
+#define	ANYBUT	5	/* str	Match any character not in this string. */
+#define	BRANCH	6	/* node	Match this alternative, or the next... */
+#define	BACK	7	/* no	Match "", "next" ptr points backward. */
+#define	EXACTLY	8	/* str	Match this string. */
+#define	NOTHING	9	/* no	Match empty string. */
+#define	STAR	10	/* node	Match this (simple) thing 0 or more times. */
+#define	PLUS	11	/* node	Match this (simple) thing 1 or more times. */
+#define	OPEN	20	/* no	Mark this point in input as start of #n. */
+			/*	OPEN+1 is number 1, etc. */
+#define	CLOSE	OPEN + NSUBEXP	/* no	Analogous to OPEN. */
+
+/*
+ * Opcode notes:
+ *
+ * BRANCH	The set of branches constituting a single choice are hooked
+ *		together with their "next" pointers, since precedence prevents
+ *		anything being concatenated to any individual branch.  The
+ *		"next" pointer of the last BRANCH in a choice points to the
+ *		thing following the whole choice.  This is also where the
+ *		final "next" pointer of each individual branch points; each
+ *		branch starts with the operand node of a BRANCH node.
+ *
+ * BACK		Normal "next" pointers all implicitly point forward; BACK
+ *		exists to make loop structures possible.
+ *
+ * STAR,PLUS	'?', and complex '*' and '+', are implemented as circular
+ *		BRANCH structures using BACK.  Simple cases (one character
+ *		per match) are implemented with STAR and PLUS for speed
+ *		and to minimize recursive plunges.
+ *
+ * OPEN,CLOSE	...are numbered at compile time.
+ */
+
+/*
+ * A node is one char of opcode followed by two chars of "next" pointer.
+ * "Next" pointers are stored as two 8-bit pieces, high order first.  The
+ * value is a positive offset from the opcode of the node containing it.
+ * An operand, if any, simply follows the node.  (Note that much of the
+ * code generation knows about this implicit relationship.)
+ *
+ * Using two bytes for the "next" pointer is vast overkill for most things,
+ * but allows patterns to get big without disasters.
+ */
+#define	OP(p)	(*(p))
+#define	NEXT(p)	(((*((p)+1)&0377)<<8) + (*((p)+2)&0377))
+#define	OPERAND(p)	((p) + 3)
+
+/*
+ * See regmagic.h for one further detail of program structure.
+ */
+
+
+/*
+ * Utility definitions.
+ */
+#ifndef CHARBITS
+#define	UCHARAT(p)	((int)*(unsigned char *)(p))
+#else
+#define	UCHARAT(p)	((int)*(p)&CHARBITS)
+#endif
+
+#define	FAIL(m)	{ regerror(m); return(NULL); }
+#define	ISMULT(c)	((c) == '*' || (c) == '+' || (c) == '?')
+#define	META	"^$.[()|?+*\\"
+
+/*
+ * Flags to be passed up and down.
+ */
+#define	HASWIDTH	01	/* Known never to match null string. */
+#define	SIMPLE		02	/* Simple enough to be STAR/PLUS operand. */
+#define	SPSTART		04	/* Starts with * or +. */
+#define	WORST		0	/* Worst case. */
+
+/*
+ * Global work variables for regcomp().
+ */
+static char *regparse;		/* Input-scan pointer. */
+static int regnpar;		/* () count. */
+static char regdummy;
+static char *regcode;		/* Code-emit pointer; &regdummy = don't. */
+static long regsize;		/* Code size. */
+
+/*
+ * Forward declarations for regcomp()'s friends.
+ */
+#ifndef STATIC
+#define	STATIC	static
+#endif
+STATIC char* reg( int paren, int* flagp);
+STATIC char* regbranch( int* flagp);
+STATIC char* regpiece( int* flagp);
+STATIC char* regatom( int* flagp);
+STATIC char* regnode( char op);
+STATIC char* regnext( char* p);
+STATIC void regc( char c);
+STATIC void reginsert( char op, char* opnd);
+STATIC void regtail( char* p, char* val);
+STATIC void regoptail( char* p,  char* val);
+#ifdef STRCSPN
+STATIC int strcspn();
+#endif
+
+static void (*regerror_func)( char* s) = 0;
+
+void regerror( char* s)
+{
+	if( regerror_func)
+		(*regerror_func)( s);
+}
+
+void set_regerror_func( void (*func)( char*))
+{
+	regerror_func = func;
+}
+
+/*
+ - regcomp - compile a regular expression into internal code
+ *
+ * We can't allocate space until we know how big the compiled form will be,
+ * but we can't compile it (and thus know how big it is) until we've got a
+ * place to put the code.  So we cheat:  we compile it twice, once with code
+ * generation turned off and size counting turned on, and once "for real".
+ * This also means that we don't allocate space until we are sure that the
+ * thing really will compile successfully, and we never have to move the
+ * code and thus invalidate pointers into it.  (Note that it has to be in
+ * one piece because free() must be able to free it all.)
+ *
+ * Beware that the optimization-preparation code in here knows about some
+ * of the structure of the compiled regexp.
+ */
+regexp *
+regcomp( char* exp)
+{
+	register regexp *r;
+	register char *scan;
+	register char *longest;
+	register int len;
+	int flags;
+
+	if (exp == NULL)
+		FAIL("NULL argument");
+
+	/* First pass: determine size, legality. */
+	regparse = exp;
+	regnpar = 1;
+	regsize = 0L;
+	regcode = &regdummy;
+	regc(MAGIC);
+	if (reg(0, &flags) == NULL)
+		return(NULL);
+
+	/* Small enough for pointer-storage convention? */
+	if (regsize >= 32767L)		/* Probably could be 65535L. */
+		FAIL("regexp too big");
+
+	/* Allocate space. */
+	r = (regexp *)malloc(sizeof(regexp) + (unsigned)regsize);
+	if (r == NULL)
+		FAIL("out of space");
+
+	/* Second pass: emit code. */
+	regparse = exp;
+	regnpar = 1;
+	regcode = r->program;
+	regc(MAGIC);
+	if (reg(0, &flags) == NULL)
+		return(NULL);
+
+	/* Dig out information for optimizations. */
+	r->regstart = '\0';	/* Worst-case defaults. */
+	r->reganch = 0;
+	r->regmust = NULL;
+	r->regmlen = 0;
+	scan = r->program+1;			/* First BRANCH. */
+	if (OP(regnext(scan)) == END) {		/* Only one top-level choice. */
+		scan = OPERAND(scan);
+
+		/* Starting-point info. */
+		if (OP(scan) == EXACTLY)
+			r->regstart = *OPERAND(scan);
+		else if (OP(scan) == BOL)
+			r->reganch++;
+
+		/*
+		 * If there's something expensive in the r.e., find the
+		 * longest literal string that must appear and make it the
+		 * regmust.  Resolve ties in favor of later strings, since
+		 * the regstart check works with the beginning of the r.e.
+		 * and avoiding duplication strengthens checking.  Not a
+		 * strong reason, but sufficient in the absence of others.
+		 */
+		if (flags&SPSTART) {
+			longest = NULL;
+			len = 0;
+			for (; scan != NULL; scan = regnext(scan))
+				if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) {
+					longest = OPERAND(scan);
+					len = strlen(OPERAND(scan));
+				}
+			r->regmust = longest;
+			r->regmlen = len;
+		}
+	}
+
+	return(r);
+}
+
+/*
+ - reg - regular expression, i.e. main body or parenthesized thing
+ *
+ * Caller must absorb opening parenthesis.
+ *
+ * Combining parenthesis handling with the base level of regular expression
+ * is a trifle forced, but the need to tie the tails of the branches to what
+ * follows makes it hard to avoid.
+ */
+static char *
+reg( int paren, int* flagp)	/* paren - Parenthesized? */
+{
+	register char *ret;
+	register char *br;
+	register char *ender;
+	register int parno;
+	int flags;
+
+	*flagp = HASWIDTH;	/* Tentatively. */
+
+	/* Make an OPEN node, if parenthesized. */
+	if (paren) {
+		if (regnpar >= NSUBEXP)
+			FAIL("too many ()");
+		parno = regnpar;
+		regnpar++;
+		ret = regnode(OPEN+parno);
+	} else
+		ret = NULL;
+
+	/* Pick up the branches, linking them together. */
+	br = regbranch(&flags);
+	if (br == NULL)
+		return(NULL);
+	if (ret != NULL)
+		regtail(ret, br);	/* OPEN -> first. */
+	else
+		ret = br;
+	if (!(flags&HASWIDTH))
+		*flagp &= ~HASWIDTH;
+	*flagp |= flags&SPSTART;
+	while (*regparse == '|') {
+		regparse++;
+		br = regbranch(&flags);
+		if (br == NULL)
+			return(NULL);
+		regtail(ret, br);	/* BRANCH -> BRANCH. */
+		if (!(flags&HASWIDTH))
+			*flagp &= ~HASWIDTH;
+		*flagp |= flags&SPSTART;
+	}
+
+	/* Make a closing node, and hook it on the end. */
+	ender = regnode((paren) ? CLOSE+parno : END);	
+	regtail(ret, ender);
+
+	/* Hook the tails of the branches to the closing node. */
+	for (br = ret; br != NULL; br = regnext(br))
+		regoptail(br, ender);
+
+	/* Check for proper termination. */
+	if (paren && *regparse++ != ')') {
+		FAIL("unmatched ()");
+	} else if (!paren && *regparse != '\0') {
+		if (*regparse == ')') {
+			FAIL("unmatched ()");
+		} else
+			FAIL("junk on end");	/* "Can't happen". */
+		/* NOTREACHED */
+	}
+
+	return(ret);
+}
+
+/*
+ - regbranch - one alternative of an | operator
+ *
+ * Implements the concatenation operator.
+ */
+static char *
+regbranch( int* flagp)
+{
+	register char *ret;
+	register char *chain;
+	register char *latest;
+	int flags;
+
+	*flagp = WORST;		/* Tentatively. */
+
+	ret = regnode(BRANCH);
+	chain = NULL;
+	while (*regparse != '\0' && *regparse != '|' && *regparse != ')') {
+		latest = regpiece(&flags);
+		if (latest == NULL)
+			return(NULL);
+		*flagp |= flags&HASWIDTH;
+		if (chain == NULL)	/* First piece. */
+			*flagp |= flags&SPSTART;
+		else
+			regtail(chain, latest);
+		chain = latest;
+	}
+	if (chain == NULL)	/* Loop ran zero times. */
+		(void) regnode(NOTHING);
+
+	return(ret);
+}
+
+/*
+ - regpiece - something followed by possible [*+?]
+ *
+ * Note that the branching code sequences used for ? and the general cases
+ * of * and + are somewhat optimized:  they use the same NOTHING node as
+ * both the endmarker for their branch list and the body of the last branch.
+ * It might seem that this node could be dispensed with entirely, but the
+ * endmarker role is not redundant.
+ */
+static char *
+regpiece( int* flagp)
+{
+	register char *ret;
+	register char op;
+	register char *next;
+	int flags;
+
+	ret = regatom(&flags);
+	if (ret == NULL)
+		return(NULL);
+
+	op = *regparse;
+	if (!ISMULT(op)) {
+		*flagp = flags;
+		return(ret);
+	}
+
+	if (!(flags&HASWIDTH) && op != '?')
+		FAIL("*+ operand could be empty");
+	*flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH);
+
+	if (op == '*' && (flags&SIMPLE))
+		reginsert(STAR, ret);
+	else if (op == '*') {
+		/* Emit x* as (x&|), where & means "self". */
+		reginsert(BRANCH, ret);			/* Either x */
+		regoptail(ret, regnode(BACK));		/* and loop */
+		regoptail(ret, ret);			/* back */
+		regtail(ret, regnode(BRANCH));		/* or */
+		regtail(ret, regnode(NOTHING));		/* null. */
+	} else if (op == '+' && (flags&SIMPLE))
+		reginsert(PLUS, ret);
+	else if (op == '+') {
+		/* Emit x+ as x(&|), where & means "self". */
+		next = regnode(BRANCH);			/* Either */
+		regtail(ret, next);
+		regtail(regnode(BACK), ret);		/* loop back */
+		regtail(next, regnode(BRANCH));		/* or */
+		regtail(ret, regnode(NOTHING));		/* null. */
+	} else if (op == '?') {
+		/* Emit x? as (x|) */
+		reginsert(BRANCH, ret);			/* Either x */
+		regtail(ret, regnode(BRANCH));		/* or */
+		next = regnode(NOTHING);		/* null. */
+		regtail(ret, next);
+		regoptail(ret, next);
+	}
+	regparse++;
+	if (ISMULT(*regparse))
+		FAIL("nested *?+");
+
+	return(ret);
+}
+
+/*
+ - regatom - the lowest level
+ *
+ * Optimization:  gobbles an entire sequence of ordinary characters so that
+ * it can turn them into a single node, which is smaller to store and
+ * faster to run.  Backslashed characters are exceptions, each becoming a
+ * separate node; the code is simpler that way and it's not worth fixing.
+ */
+static char *
+regatom( int* flagp)
+{
+	register char *ret;
+	int flags;
+
+	*flagp = WORST;		/* Tentatively. */
+
+	switch (*regparse++) {
+	case '^':
+		ret = regnode(BOL);
+		break;
+	case '$':
+		ret = regnode(EOL);
+		break;
+	case '.':
+		ret = regnode(ANY);
+		*flagp |= HASWIDTH|SIMPLE;
+		break;
+	case '[': {
+			int rclass;
+			int classend;
+
+			if (*regparse == '^') {	/* Complement of range. */
+				ret = regnode(ANYBUT);
+				regparse++;
+			} else
+				ret = regnode(ANYOF);
+			if (*regparse == ']' || *regparse == '-')
+				regc(*regparse++);
+			while (*regparse != '\0' && *regparse != ']') {
+				if (*regparse == '-') {
+					regparse++;
+					if (*regparse == ']' || *regparse == '\0')
+						regc('-');
+					else {
+						rclass = UCHARAT(regparse-2)+1;
+						classend = UCHARAT(regparse);
+						if (rclass > classend+1)
+							FAIL("invalid [] range");
+						for (; rclass <= classend; rclass++)
+							regc(rclass);
+						regparse++;
+					}
+				} else
+					regc(*regparse++);
+			}
+			regc('\0');
+			if (*regparse != ']')
+				FAIL("unmatched []");
+			regparse++;
+			*flagp |= HASWIDTH|SIMPLE;
+		}
+		break;
+	case '(':
+		ret = reg(1, &flags);
+		if (ret == NULL)
+			return(NULL);
+		*flagp |= flags&(HASWIDTH|SPSTART);
+		break;
+	case '\0':
+	case '|':
+	case ')':
+		FAIL("internal urp");	/* Supposed to be caught earlier. */
+		break;
+	case '?':
+	case '+':
+	case '*':
+		FAIL("?+* follows nothing");
+		break;
+	case '\\':
+		if (*regparse == '\0')
+			FAIL("trailing \\");
+		ret = regnode(EXACTLY);
+		regc(*regparse++);
+		regc('\0');
+		*flagp |= HASWIDTH|SIMPLE;
+		break;
+	default: {
+			register int len;
+			register char ender;
+
+			regparse--;
+			len = strcspn(regparse, META);
+			if (len <= 0)
+				FAIL("internal disaster");
+			ender = *(regparse+len);
+			if (len > 1 && ISMULT(ender))
+				len--;		/* Back off clear of ?+* operand. */
+			*flagp |= HASWIDTH;
+			if (len == 1)
+				*flagp |= SIMPLE;
+			ret = regnode(EXACTLY);
+			while (len > 0) {
+				regc(*regparse++);
+				len--;
+			}
+			regc('\0');
+		}
+		break;
+	}
+
+	return(ret);
+}
+
+/*
+ - regnode - emit a node
+ */
+static char *			/* Location. */
+regnode( char op)
+{
+	register char *ret;
+	register char *ptr;
+
+	ret = regcode;
+	if (ret == &regdummy) {
+		regsize += 3;
+		return(ret);
+	}
+
+	ptr = ret;
+	*ptr++ = op;
+	*ptr++ = '\0';		/* Null "next" pointer. */
+	*ptr++ = '\0';
+	regcode = ptr;
+
+	return(ret);
+}
+
+/*
+ - regc - emit (if appropriate) a byte of code
+ */
+static void
+regc( char b)
+{
+	if (regcode != &regdummy)
+		*regcode++ = b;
+	else
+		regsize++;
+}
+
+/*
+ - reginsert - insert an operator in front of already-emitted operand
+ *
+ * Means relocating the operand.
+ */
+static void
+reginsert( char op, char* opnd)
+{
+	register char *src;
+	register char *dst;
+	register char *place;
+
+	if (regcode == &regdummy) {
+		regsize += 3;
+		return;
+	}
+
+	src = regcode;
+	regcode += 3;
+	dst = regcode;
+	while (src > opnd)
+		*--dst = *--src;
+
+	place = opnd;		/* Op node, where operand used to be. */
+	*place++ = op;
+	*place++ = '\0';
+	*place++ = '\0';
+}
+
+/*
+ - regtail - set the next-pointer at the end of a node chain
+ */
+static void
+regtail( char* p, char* val)
+{
+	register char *scan;
+	register char *temp;
+	register int offset;
+
+	if (p == &regdummy)
+		return;
+
+	/* Find last node. */
+	scan = p;
+	for (;;) {
+		temp = regnext(scan);
+		if (temp == NULL)
+			break;
+		scan = temp;
+	}
+
+	if (OP(scan) == BACK)
+		offset = scan - val;
+	else
+		offset = val - scan;
+	*(scan+1) = (offset>>8)&0377;
+	*(scan+2) = offset&0377;
+}
+
+/*
+ - regoptail - regtail on operand of first argument; nop if operandless
+ */
+static void
+regoptail( char* p,  char* val)
+{
+	/* "Operandless" and "op != BRANCH" are synonymous in practice. */
+	if (p == NULL || p == &regdummy || OP(p) != BRANCH)
+		return;
+	regtail(OPERAND(p), val);
+}
+
+/*
+ * regexec and friends
+ */
+
+/*
+ * Global work variables for regexec().
+ */
+static char *reginput;		/* String-input pointer. */
+static char *regbol;		/* Beginning of input, for ^ check. */
+static char **regstartp;	/* Pointer to startp array. */
+static char **regendp;		/* Ditto for endp. */
+
+/*
+ * Forwards.
+ */
+STATIC int regtry( regexp* prog, char* string);
+STATIC int regmatch( char* prog);
+STATIC int regrepeat( char* p);
+
+#ifdef DEBUG
+int regnarrate = 0;
+void regdump( regexp* r);
+STATIC char* regprop( char* op);
+#endif
+
+/*
+ - regexec - match a regexp against a string
+ */
+int
+regexec( regexp* prog, char* string)
+{
+	register char *s;
+
+	/* Be paranoid... */
+	if (prog == NULL || string == NULL) {
+		regerror("NULL parameter");
+		return(0);
+	}
+
+	/* Check validity of program. */
+	if (UCHARAT(prog->program) != MAGIC) {
+		regerror("corrupted program");
+		return(0);
+	}
+
+	/* If there is a "must appear" string, look for it. */
+	if (prog->regmust != NULL) {
+		s = string;
+		while ((s = strchr(s, prog->regmust[0])) != NULL) {
+			if (strncmp(s, prog->regmust, prog->regmlen) == 0)
+				break;	/* Found it. */
+			s++;
+		}
+		if (s == NULL)	/* Not present. */
+			return(0);
+	}
+
+	/* Mark beginning of line for ^ . */
+	regbol = string;
+
+	/* Simplest case:  anchored match need be tried only once. */
+	if (prog->reganch)
+		return(regtry(prog, string));
+
+	/* Messy cases:  unanchored match. */
+	s = string;
+	if (prog->regstart != '\0')
+		/* We know what char it must start with. */
+		while ((s = strchr(s, prog->regstart)) != NULL) {
+			if (regtry(prog, s))
+				return(1);
+			s++;
+		}
+	else
+		/* We don't -- general case. */
+		do {
+			if (regtry(prog, s))
+				return(1);
+		} while (*s++ != '\0');
+
+	/* Failure. */
+	return(0);
+}
+
+/*
+ - regtry - try match at specific point
+ */
+static int			/* 0 failure, 1 success */
+regtry( regexp* prog, char* string)
+{
+	register int i;
+	register char **sp;
+	register char **ep;
+
+	reginput = string;
+	regstartp = prog->startp;
+	regendp = prog->endp;
+
+	sp = prog->startp;
+	ep = prog->endp;
+	for (i = NSUBEXP; i > 0; i--) {
+		*sp++ = NULL;
+		*ep++ = NULL;
+	}
+	if (regmatch(prog->program + 1)) {
+		prog->startp[0] = string;
+		prog->endp[0] = reginput;
+		return(1);
+	} else
+		return(0);
+}
+
+/*
+ - regmatch - main matching routine
+ *
+ * Conceptually the strategy is simple:  check to see whether the current
+ * node matches, call self recursively to see whether the rest matches,
+ * and then act accordingly.  In practice we make some effort to avoid
+ * recursion, in particular by going through "ordinary" nodes (that don't
+ * need to know whether the rest of the match failed) by a loop instead of
+ * by recursion.
+ */
+static int			/* 0 failure, 1 success */
+regmatch( char* prog)
+{
+	register char *scan;	/* Current node. */
+	char *next;		/* Next node. */
+
+	scan = prog;
+#ifdef DEBUG
+	if (scan != NULL && regnarrate)
+		fprintf(stderr, "%s(\n", regprop(scan));
+#endif
+	while (scan != NULL) {
+#ifdef DEBUG
+		if (regnarrate)
+			fprintf(stderr, "%s...\n", regprop(scan));
+#endif
+		next = regnext(scan);
+
+		switch (OP(scan)) {
+		case BOL:
+			if (reginput != regbol)
+				return(0);
+			break;
+		case EOL:
+			if (*reginput != '\0')
+				return(0);
+			break;
+		case ANY:
+			if (*reginput == '\0')
+				return(0);
+			reginput++;
+			break;
+		case EXACTLY: {
+				register int len;
+				register char *opnd;
+
+				opnd = OPERAND(scan);
+				/* Inline the first character, for speed. */
+				if (*opnd != *reginput)
+					return(0);
+				len = strlen(opnd);
+				if (len > 1 && strncmp(opnd, reginput, len) != 0)
+					return(0);
+				reginput += len;
+			}
+			break;
+		case ANYOF:
+			if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) == NULL)
+				return(0);
+			reginput++;
+			break;
+		case ANYBUT:
+			if (*reginput == '\0' || strchr(OPERAND(scan), *reginput) != NULL)
+				return(0);
+			reginput++;
+			break;
+		case NOTHING:
+			break;
+		case BACK:
+			break;
+		case OPEN+1:
+		case OPEN+2:
+		case OPEN+3:
+		case OPEN+4:
+		case OPEN+5:
+		case OPEN+6:
+		case OPEN+7:
+		case OPEN+8:
+		case OPEN+9:
+		case OPEN+10:
+		case OPEN+11:
+		case OPEN+12:
+		case OPEN+13:
+		case OPEN+14:
+		case OPEN+15: {
+				register int no;
+				register char *save;
+
+				no = OP(scan) - OPEN;
+				save = reginput;
+
+				if (regmatch(next)) {
+					/*
+					 * Don't set startp if some later
+					 * invocation of the same parentheses
+					 * already has.
+					 */
+					if (regstartp[no] == NULL)
+						regstartp[no] = save;
+					return(1);
+				} else
+					return(0);
+			}
+			break;
+		case CLOSE+1:
+		case CLOSE+2:
+		case CLOSE+3:
+		case CLOSE+4:
+		case CLOSE+5:
+		case CLOSE+6:
+		case CLOSE+7:
+		case CLOSE+8:
+		case CLOSE+9:
+		case CLOSE+10:
+		case CLOSE+11:
+		case CLOSE+12:
+		case CLOSE+13:
+		case CLOSE+14:
+		case CLOSE+15: {
+				register int no;
+				register char *save;
+
+				no = OP(scan) - CLOSE;
+				save = reginput;
+
+				if (regmatch(next)) {
+					/*
+					 * Don't set endp if some later
+					 * invocation of the same parentheses
+					 * already has.
+					 */
+					if (regendp[no] == NULL)
+						regendp[no] = save;
+					return(1);
+				} else
+					return(0);
+			}
+			break;
+		case BRANCH: {
+				register char *save;
+
+				if (OP(next) != BRANCH)		/* No choice. */
+					next = OPERAND(scan);	/* Avoid recursion. */
+				else {
+					do {
+						save = reginput;
+						if (regmatch(OPERAND(scan)))
+							return(1);
+						reginput = save;
+						scan = regnext(scan);
+					} while (scan != NULL && OP(scan) == BRANCH);
+					return(0);
+					/* NOTREACHED */
+				}
+			}
+			break;
+		case STAR:
+		case PLUS: {
+				register char nextch;
+				register int no;
+				register char *save;
+				register int min;
+
+				/*
+				 * Lookahead to avoid useless match attempts
+				 * when we know what character comes next.
+				 */
+				nextch = '\0';
+				if (OP(next) == EXACTLY)
+					nextch = *OPERAND(next);
+				min = (OP(scan) == STAR) ? 0 : 1;
+				save = reginput;
+				no = regrepeat(OPERAND(scan));
+				while (no >= min) {
+					/* If it could work, try it. */
+					if (nextch == '\0' || *reginput == nextch)
+						if (regmatch(next))
+							return(1);
+					/* Couldn't or didn't -- back up. */
+					no--;
+					reginput = save + no;
+				}
+				return(0);
+			}
+			break;
+		case END:
+			return(1);	/* Success! */
+			break;
+		default:
+			regerror("memory corruption");
+			return(0);
+			break;
+		}
+
+		scan = next;
+	}
+
+	/*
+	 * We get here only if there's trouble -- normally "case END" is
+	 * the terminating point.
+	 */
+	regerror("corrupted pointers");
+	return(0);
+}
+
+/*
+ - regrepeat - repeatedly match something simple, report how many
+ */
+static int
+regrepeat( char* p)
+{
+	register int count = 0;
+	register char *scan;
+	register char *opnd;
+
+	scan = reginput;
+	opnd = OPERAND(p);
+	switch (OP(p)) {
+	case ANY:
+		count = strlen(scan);
+		scan += count;
+		break;
+	case EXACTLY:
+		while (*opnd == *scan) {
+			count++;
+			scan++;
+		}
+		break;
+	case ANYOF:
+		while (*scan != '\0' && strchr(opnd, *scan) != NULL) {
+			count++;
+			scan++;
+		}
+		break;
+	case ANYBUT:
+		while (*scan != '\0' && strchr(opnd, *scan) == NULL) {
+			count++;
+			scan++;
+		}
+		break;
+	default:		/* Oh dear.  Called inappropriately. */
+		regerror("internal foulup");
+		count = 0;	/* Best compromise. */
+		break;
+	}
+	reginput = scan;
+
+	return(count);
+}
+
+/*
+ - regnext - dig the "next" pointer out of a node
+ */
+static char *
+regnext( char* p)
+{
+	register int offset;
+
+	if (p == &regdummy)
+		return(NULL);
+
+	offset = NEXT(p);
+	if (offset == 0)
+		return(NULL);
+
+	if (OP(p) == BACK)
+		return(p-offset);
+	else
+		return(p+offset);
+}
+
+#ifdef DEBUG
+
+STATIC char *regprop();
+
+/*
+ - regdump - dump a regexp onto stdout in vaguely comprehensible form
+ */
+void
+regdump( regexp* r)
+{
+	register char *s;
+	register char op = EXACTLY;	/* Arbitrary non-END op. */
+	register char *next;
+
+	s = r->program + 1;
+	while (op != END) {	/* While that wasn't END last time... */
+		op = OP(s);
+		printf("%2d%s", s-r->program, regprop(s));	/* Where, what. */
+		next = regnext(s);
+		if (next == NULL)		/* Next ptr. */
+			printf("(0)");
+		else 
+			printf("(%d)", (s-r->program)+(next-s));
+		s += 3;
+		if (op == ANYOF || op == ANYBUT || op == EXACTLY) {
+			/* Literal string, where present. */
+			while (*s != '\0') {
+				putchar(*s);
+				s++;
+			}
+			s++;
+		}
+		putchar('\n');
+	}
+
+	/* Header fields of interest. */
+	if (r->regstart != '\0')
+		printf("start `%c' ", r->regstart);
+	if (r->reganch)
+		printf("anchored ");
+	if (r->regmust != NULL)
+		printf("must have \"%s\"", r->regmust);
+	printf("\n");
+}
+
+/*
+ - regprop - printable representation of opcode
+ */
+static char *
+regprop( char* op)
+{
+	register char *p;
+	static char buf[50];
+
+	(void) strcpy(buf, ":");
+
+	switch (OP(op)) {
+	case BOL:
+		p = "BOL";
+		break;
+	case EOL:
+		p = "EOL";
+		break;
+	case ANY:
+		p = "ANY";
+		break;
+	case ANYOF:
+		p = "ANYOF";
+		break;
+	case ANYBUT:
+		p = "ANYBUT";
+		break;
+	case BRANCH:
+		p = "BRANCH";
+		break;
+	case EXACTLY:
+		p = "EXACTLY";
+		break;
+	case NOTHING:
+		p = "NOTHING";
+		break;
+	case BACK:
+		p = "BACK";
+		break;
+	case END:
+		p = "END";
+		break;
+	case OPEN+1:
+	case OPEN+2:
+	case OPEN+3:
+	case OPEN+4:
+	case OPEN+5:
+	case OPEN+6:
+	case OPEN+7:
+	case OPEN+8:
+	case OPEN+9:
+		sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN);
+		p = NULL;
+		break;
+	case CLOSE+1:
+	case CLOSE+2:
+	case CLOSE+3:
+	case CLOSE+4:
+	case CLOSE+5:
+	case CLOSE+6:
+	case CLOSE+7:
+	case CLOSE+8:
+	case CLOSE+9:
+		sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE);
+		p = NULL;
+		break;
+	case STAR:
+		p = "STAR";
+		break;
+	case PLUS:
+		p = "PLUS";
+		break;
+	default:
+		regerror("corrupted opcode");
+		break;
+	}
+	if (p != NULL)
+		(void) strcat(buf, p);
+	return(buf);
+}
+#endif
+
+/*
+ * The following is provided for those people who do not have strcspn() in
+ * their C libraries.  They should get off their butts and do something
+ * about it; at least one public-domain implementation of those (highly
+ * useful) string routines has been published on Usenet.
+ */
+#ifdef STRCSPN
+/*
+ * strcspn - find length of initial segment of s1 consisting entirely
+ * of characters not from s2
+ */
+
+static int
+strcspn( char* s1, char* s2)
+{
+	register char *scan1;
+	register char *scan2;
+	register int count;
+
+	count = 0;
+	for (scan1 = s1; *scan1 != '\0'; scan1++) {
+		for (scan2 = s2; *scan2 != '\0';)	/* ++ moved down. */
+			if (*scan1 == *scan2++)
+				return(count);
+		count++;
+	}
+	return(count);
+}
+#endif
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/settings.c
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/settings.c	(revision 6566)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/settings.c	(working copy)
@@ -8,6 +8,8 @@
 #include "putty.h"
 #include "storage.h"
 
+#include "urlhack.h"
+
 /*
  * Tables of string <-> enum value mappings
  */
@@ -438,6 +440,15 @@
     write_setting_fontspec(sesskey, "WideBoldFont", cfg->wideboldfont);
     write_setting_i(sesskey, "ShadowBold", cfg->shadowbold);
     write_setting_i(sesskey, "ShadowBoldOffset", cfg->shadowboldoffset);
+
+	/* HACK: Hyperlink stuff */
+
+	write_setting_i(sesskey, "HyperlinkUnderline", cfg->url_underline);
+	write_setting_i(sesskey, "HyperlinkUseCtrlClick", cfg->url_ctrl_click);
+	write_setting_i(sesskey, "HyperlinkBrowserUseDefault", cfg->url_defbrowser);
+	write_setting_s(sesskey, "HyperlinkBrowser", cfg->url_browser);
+	write_setting_i(sesskey, "HyperlinkRegularExpressionUseDefault", cfg->url_defregex);
+	write_setting_s(sesskey, "HyperlinkRegularExpression", cfg->url_regex);
 }
 
 void load_settings(char *section, int do_host, Config * cfg)
@@ -756,6 +767,15 @@
     gppfont(sesskey, "WideFont", &cfg->widefont);
     gppfont(sesskey, "WideBoldFont", &cfg->wideboldfont);
     gppi(sesskey, "ShadowBoldOffset", 1, &cfg->shadowboldoffset);
+
+	/* HACK: Hyperlink stuff */
+
+	gppi(sesskey, "HyperlinkUnderline", 2, &cfg->url_underline);
+	gppi(sesskey, "HyperlinkUseCtrlClick", 1, &cfg->url_ctrl_click);
+	gppi(sesskey, "HyperlinkBrowserUseDefault", 1, &cfg->url_defbrowser);
+	gpps(sesskey, "HyperlinkBrowser", "", cfg->url_browser, sizeof(cfg->url_browser));
+	gppi(sesskey, "HyperlinkRegularExpressionUseDefault", 1, &cfg->url_defregex);
+	gpps(sesskey, "HyperlinkRegularExpression", urlhack_default_regex, cfg->url_regex, sizeof(cfg->url_regex));
 }
 
 void do_defaults(char *session, Config * cfg)
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/terminal.c
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/terminal.c	(revision 6566)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/terminal.c	(working copy)
@@ -1,12 +1,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>
-
 #include <time.h>
 #include <assert.h>
 #include "putty.h"
 #include "terminal.h"
+#include "urlhack.h"
 
+
 #define poslt(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x < (p2).x ) )
 #define posle(p1,p2) ( (p1).y < (p2).y || ( (p1).y == (p2).y && (p1).x <= (p2).x ) )
 #define poseq(p1,p2) ( (p1).y == (p2).y && (p1).x == (p2).x )
@@ -4581,6 +4582,49 @@
 #endif /* OPTIMISE_SCROLL */
     termchar *newline;
 
+  /*
+	 * HACK: Find visible hyperlinks
+	 *
+	 * TODO: We should find out somehow that the stuff on screen has changed since last
+	 *       paint. How to do it?
+	 */
+
+	int urlhack_underline_always = (term->cfg.url_underline == URLHACK_UNDERLINE_ALWAYS);
+
+	int urlhack_underline =
+		term->cfg.url_underline == URLHACK_UNDERLINE_ALWAYS ||
+		(term->cfg.url_underline == URLHACK_UNDERLINE_HOVER && (!term->cfg.url_ctrl_click || urlhack_is_ctrl_pressed())) ? 1 : 0;
+
+	int urlhack_is_link = 0, urlhack_hover_current = 0;
+	int urlhack_toggle_x = term->cols, urlhack_toggle_y = term->rows;
+	int urlhack_region_index = 0;
+	text_region urlhack_region;
+
+	if (term->url_update) {
+		urlhack_reset();
+
+		for (i = 0; i < term->rows; i++) {
+			termline *lp = scrlineptr(term->disptop + i);
+			//unsigned long* lp = scrlineptr(term->disptop + i);
+			for (j = 0; j < term->cols; j++) {
+				urlhack_putchar((char)(lp->chars[j].chr & CHAR_MASK));
+			}
+			unlineptr(lp);
+		}
+
+		urlhack_go_find_me_some_hyperlinks(term->cols);
+	}
+	urlhack_region = urlhack_get_link_region(urlhack_region_index);
+	urlhack_toggle_x = urlhack_region.x0;
+	urlhack_toggle_y = urlhack_region.y0;
+
+	if (urlhack_underline_always)
+		urlhack_hover_current = 1;
+	else
+		urlhack_hover_current = urlhack_is_in_this_link_region(urlhack_region, urlhack_mouse_old_x, urlhack_mouse_old_y);
+
+	/* HACK: ends.. */
+
     chlen = 1024;
     ch = snewn(chlen, wchar_t);
 
@@ -4735,6 +4779,44 @@
 	    if (j < term->cols-1 && d[1].chr == UCSWIDE)
 		tattr |= ATTR_WIDE;
 
+		/* HACK: Underline link regions if user has configured us so */
+
+		if (urlhack_underline) {
+			if (j == urlhack_toggle_x && i == urlhack_toggle_y) {
+				urlhack_is_link = urlhack_is_link == 1 ? 0 : 1;
+
+				// Find next bound for the toggle
+				
+				if (urlhack_is_link == 1) {
+					urlhack_toggle_x = urlhack_region.x1;
+					urlhack_toggle_y = urlhack_region.y1;
+
+					if (urlhack_toggle_x == term->cols - 1) {
+						// Handle special case where link ends at the last char of the row
+						urlhack_toggle_y++;
+						urlhack_toggle_x = 0;
+					}
+				}
+				else {
+					urlhack_region = urlhack_get_link_region(++urlhack_region_index);
+
+					if (urlhack_underline_always)
+						urlhack_hover_current = 1;
+					else
+						urlhack_hover_current = urlhack_is_in_this_link_region(urlhack_region, urlhack_mouse_old_x, urlhack_mouse_old_y);
+
+					urlhack_toggle_x = urlhack_region.x0;
+					urlhack_toggle_y = urlhack_region.y0;
+				}
+			}
+
+			if (urlhack_is_link == 1 && urlhack_hover_current == 1) tattr |= ATTR_UNDER;
+
+			term->url_update = 0;
+		}
+
+		/* HACK: Hack ends */
+
 	    /* Video reversing things */
 	    if (term->selstate == DRAGGING || term->selstate == SELECTED) {
 		if (term->seltype == LEXICOGRAPHIC)
@@ -5549,7 +5631,6 @@
     }
 
     selpoint.x = x;
-    unlineptr(ldata);
 
     if (raw_mouse) {
 	int encstate = 0, r, c;
@@ -5577,8 +5658,10 @@
 	    }
 	    switch (a) {
 	      case MA_DRAG:
-		if (term->xterm_mouse == 1)
+			  if (term->xterm_mouse == 1) {
+    unlineptr(ldata);
 		    return;
+			  }
 		encstate += 0x20;
 		break;
 	      case MA_RELEASE:
@@ -5586,8 +5669,10 @@
 		term->mouse_is_down = 0;
 		break;
 	      case MA_CLICK:
-		if (term->mouse_is_down == braw)
+			  if (term->mouse_is_down == braw) {
+    unlineptr(ldata);
 		    return;
+			  }
 		term->mouse_is_down = braw;
 		break;
 	      default: break;	       /* placate gcc warning about enum use */
@@ -5602,6 +5687,7 @@
 	    sprintf(abuf, "\033[M%c%c%c", encstate, c, r);
 	    ldisc_send(term->ldisc, abuf, 6, 0);
 	}
+    unlineptr(ldata);
 	return;
     }
 
@@ -5624,8 +5710,60 @@
 	term->seltype = default_seltype;
 	term->selanchor = selpoint;
 	term->selmode = SM_CHAR;
-    } else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
+	}
+	else if (bcooked == MBT_SELECT && a == MA_RELEASE && term->selstate == ABOUT_TO) {
 	deselect(term);
+	term->selstate = NO_SELECTION;
+
+	/* slug unlineptr
+	 * HACK: Hyperlink stuff: Check whether the click coordinates are inside link
+	 * region, if so -> copy stuff url to temporary buffer and launch it. Delete
+	 * the temporary buffer.
+	 */
+	
+	if ((!term->cfg.url_ctrl_click || (term->cfg.url_ctrl_click && urlhack_is_ctrl_pressed())) && urlhack_is_in_link_region(x, y)) {
+		int i;
+		char *linkbuf = NULL;
+		text_region region = urlhack_get_link_bounds(x, y);
+
+		if (region.y0 == region.y1) {
+			linkbuf = snewn(region.x1 - region.x0 + 2, char);
+			
+			for (i = region.x0; i < region.x1; i++)
+				linkbuf[i - region.x0] = (char)(ldata->chars[i].chr);
+			
+			linkbuf[i - region.x0] = '\0';
+		}
+		else {
+			termline *urldata = scrlineptr(region.y0);
+			int linklen, pos = region.x0, row = region.y0;
+
+			linklen = (term->cols - region.x0) +
+				((region.y1 - region.y0 - 1) * term->cols) + region.x1 + 1;
+
+			linkbuf = snewn(linklen, char);
+
+			for (i = region.x0; i < linklen + region.x0; i++) {
+				linkbuf[i - region.x0] = (char)(urldata->chars[i % term->cols].chr);
+				
+				if (((i + 1) % term->cols) == 0) {
+					row++;
+					urldata = lineptr(row);
+				}
+			}
+
+			linkbuf[linklen - 1] = '\0';
+			unlineptr(urldata);
+		}
+		
+		urlhack_launch_url(!term->cfg.url_defbrowser ? term->cfg.url_browser : NULL, linkbuf);
+		
+		sfree(linkbuf);
+	}
+
+	/* HACK: Hack ends */
+	} else if (bcooked == MBT_SELECT && (a == MA_2CLK || a == MA_3CLK)) {
+	deselect(term);
 	term->selmode = (a == MA_2CLK ? SM_WORD : SM_LINE);
 	term->selstate = DRAGGING;
 	term->selstart = term->selanchor = selpoint;
@@ -5634,8 +5772,11 @@
 	sel_spread(term);
     } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
 	       (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
-	if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
+			   if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint)) {
+		    unlineptr(ldata);
+
 	    return;
+			   }
 	if (bcooked == MBT_EXTEND && a != MA_DRAG &&
 	    term->selstate == SELECTED) {
 	    if (term->seltype == LEXICOGRAPHIC) {
@@ -5720,6 +5861,8 @@
     }
 
     term_update(term);
+	    unlineptr(ldata);
+
 }
 
 void term_key(Terminal *term, Key_Sym keysym, wchar_t *text, size_t tlen,
@@ -6248,6 +6391,7 @@
 	if (term->selstate != DRAGGING)
 	    term_out(term);
 	term->in_term_out = FALSE;
+	term->url_update = TRUE;
     }
 
     /*
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/terminal.h
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/terminal.h	(revision 6566)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/terminal.h	(working copy)
@@ -243,6 +243,8 @@
      */
     int in_term_out;
 
+    int url_update;
+    
     /*
      * We schedule a window update shortly after receiving terminal
      * data. This tracks whether one is currently pending.
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/urlhack.cpp
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/urlhack.cpp	(revision 0)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/urlhack.cpp	(revision 0)
@@ -0,0 +1,256 @@
+#include <windows.h>
+#include <vector>
+#include "urlhack.h"
+
+extern int urlhack_mouse_old_x = -1, urlhack_mouse_old_y = -1;
+
+static std::vector<text_region> link_regions;
+static std::string browser_app;
+
+extern const char* urlhack_default_regex = "(^|[ ]|((https?|ftp):\\/\\/))(([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|net|org|info|biz|gov|name|edu|[a-zA-Z][a-zA-Z]))(:[0-9]+)?((\\/|\\?)[^ \"]*[^ ,;\\.:\">)])?";
+
+
+
+void urlhack_clear_link_regions()
+{
+	link_regions.clear();
+}
+
+
+
+int urlhack_is_in_link_region(int x, int y)
+{
+	std::vector<text_region>::iterator i = link_regions.begin();
+
+	while (i != link_regions.end()) {
+		text_region r = *i;
+
+		if ((r.y0 == r.y1 && y == r.y0 && y == r.y1 && x >= r.x0 && x < r.x1) ||
+			(r.y0 != r.y1 && ((y == r.y0 && x >= r.x0) || (y == r.y1 && x < r.x1) || (y > r.y0 && y < r.y1))))
+			return true;
+
+		i++;
+	}
+	
+	return false;
+}
+
+
+
+int urlhack_is_in_this_link_region(text_region r, int x, int y)
+{
+	if ((r.y0 == r.y1 && y == r.y0 && y == r.y1 && x >= r.x0 && x < r.x1) ||
+		(r.y0 != r.y1 && ((y == r.y0 && x >= r.x0) || (y == r.y1 && x < r.x1) || (y > r.y0 && y < r.y1))))
+		return true;
+	
+	return false;
+}
+
+
+
+text_region urlhack_get_link_bounds(int x, int y)
+{
+	std::vector<text_region>::iterator i = link_regions.begin();
+
+	while (i != link_regions.end()) {
+		text_region r = *i;
+
+		if ((r.y0 == r.y1 && y == r.y0 && y == r.y1 && x >= r.x0 && x < r.x1) ||
+			(r.y0 != r.y1 && ((y == r.y0 && x >= r.x0) || (y == r.y1 && x < r.x1) || (y > r.y0 && y < r.y1))))
+			return *i;
+
+		i++;
+	}
+
+	text_region region;
+	region.x0 = region.y0 = region.x1 = region.y1 = -1;
+	return region;
+}
+
+
+
+text_region urlhack_get_link_region(int index)
+{
+	text_region region;
+
+	if (index < 0 || index >= (int)link_regions.size()) {
+		region.x0 = region.y0 = region.x1 = region.y1 = -1;
+		return region;
+	}
+	else {
+		return link_regions.at(index);
+	}
+}
+
+
+
+void urlhack_add_link_region(int x0, int y0, int x1, int y1)
+{
+	text_region region;
+
+	region.x0 = x0;
+	region.y0 = y0;
+	region.x1 = x1;
+	region.y1 = y1;
+
+	link_regions.insert(link_regions.end(), region);
+}
+
+
+
+void urlhack_launch_url(const char* app, const char *url)
+{
+	if (app) {
+		ShellExecute(NULL, NULL, app, url, NULL, SW_SHOW);
+		return;
+	}
+
+	if (browser_app.size() == 0) {
+		// Find out the default app
+		HKEY key;
+		DWORD dwValue;
+		char *str;
+
+		if (RegOpenKeyEx(HKEY_CLASSES_ROOT, "HTTP\\shell\\open\\command", 0, KEY_READ, &key) == ERROR_SUCCESS) {
+			if (!RegQueryValueEx(key, NULL, NULL, NULL, NULL, &dwValue) == ERROR_SUCCESS) return;
+			
+			str = new char[dwValue + 1];
+
+			RegQueryValueEx(key, NULL, NULL, NULL, (BYTE*)str, &dwValue);
+			RegCloseKey(key);
+
+			browser_app = str;
+			delete[] str;
+
+			// Drop all stuff from the path and leave only the executable and ze path
+
+			if (browser_app.at(0) == '"') {
+				browser_app.erase(0, 1);
+				
+				if (browser_app.find('"') > 0)
+					browser_app.resize(browser_app.find('"'));
+			}
+			else {
+				if (browser_app.find(' ') > 0)
+					browser_app.resize(browser_app.find(' '));
+			}
+		}
+		else {
+			MessageBox(NULL, "Couldn't find your default browser, sorry 'bout that. " \
+				"The default browser is stored in the default value of registry key HCR\\HTTP\\shell\\open\\command. " \
+				"Check its value.", "Nutty Oops!", MB_OK | MB_ICONINFORMATION);
+		}
+	}
+
+	std::string u = url;
+
+	if (u.find("http://") == std::string::npos && u.find("https://") == std::string::npos &&
+		u.find("ftp://") == std::string::npos && u.find("ftps://") == std::string::npos) {
+		if (u.find("ftp.") != std::string::npos)
+			u.insert(0, "ftp://");
+		else
+			u.insert(0, "http://");
+	}
+
+	ShellExecute(NULL, NULL, browser_app.c_str(), u.c_str(), NULL, SW_SHOW);
+}
+
+
+
+
+
+
+int urlhack_is_ctrl_pressed()
+{
+	return HIWORD(GetAsyncKeyState(VK_CONTROL));
+}
+
+
+
+// Regular expression stuff
+
+static int urlhack_disabled = 0;
+static int is_regexp_compiled = 0;
+static regexp* urlhack_rx;
+static std::string text_mass;
+
+
+
+void urlhack_reset()
+{
+	text_mass.clear();
+}
+
+
+
+void urlhack_putchar(char ch)
+{
+	char r00fles[2] = { ch, 0 };
+	text_mass.append(r00fles);
+}
+
+
+
+static void rtfm(char *error)
+{
+	std::string error_msg = "Following error occured when compiling the regular expression\n" \
+		                    "for the hyperlink support. Hyperlink detection is disabled during\n" \
+							"this session (restart Nutty to give another go on the regular\n" \
+							"expressions).\n\n";
+
+	std::string actual_error_msg = error_msg;
+
+	actual_error_msg.append(error);
+
+	MessageBox(0, actual_error_msg.c_str(), "Nutty Oops!", MB_OK);
+}
+
+
+
+void urlhack_set_regular_expression(const char* expression)
+{
+	is_regexp_compiled = 0;
+	urlhack_disabled = 0;
+
+	set_regerror_func(rtfm);
+	urlhack_rx = regcomp(const_cast<char*>(expression));
+
+	if (urlhack_rx == 0) {
+		urlhack_disabled = 1;
+	}
+
+	is_regexp_compiled = 1;
+}
+
+
+
+void urlhack_go_find_me_some_hyperlinks(int screen_width)
+{
+	if (urlhack_disabled != 0) return;
+
+	if (is_regexp_compiled == 0) {
+		urlhack_set_regular_expression(urlhack_default_regex);
+	}
+
+	urlhack_clear_link_regions();
+
+
+	char* text = const_cast<char*>(text_mass.c_str());
+	char* text_pos = text;
+
+	while (regexec(urlhack_rx, text_pos) == 1) {
+		char* start_pos = *urlhack_rx->startp[0] == ' ' ? urlhack_rx->startp[0] + 1: urlhack_rx->startp[0];
+
+		int x0 = (start_pos - text) % screen_width;
+		int y0 = (start_pos - text) / screen_width;
+		int x1 = (urlhack_rx->endp[0] - text) % screen_width;
+		int y1 = (urlhack_rx->endp[0] - text) / screen_width;
+
+		if (x0 >= screen_width) x0 = screen_width - 1;
+		if (x1 >= screen_width) x1 = screen_width - 1;
+
+		urlhack_add_link_region(x0, y0, x1, y1);
+
+		text_pos = urlhack_rx->endp[0] + 1;
+	}
+}
\ No newline at end of file
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/urlhack.h
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/urlhack.h	(revision 0)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/urlhack.h	(revision 0)
@@ -0,0 +1,36 @@
+#pragma once
+#ifndef _URLHACK_H
+#define _URLHACK_H
+
+#include "re_lib/regexp.h"
+
+typedef struct { int x0, y0, x1, y1; } text_region;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char* urlhack_default_regex;
+int urlhack_mouse_old_x, urlhack_mouse_old_y;
+
+void urlhack_reset();
+void urlhack_go_find_me_some_hyperlinks(int screen_width);
+void urlhack_putchar(char ch);
+text_region urlhack_get_link_region(int index);
+
+void urlhack_clear_link_regions();
+int urlhack_is_in_link_region(int x, int y);
+int urlhack_is_in_this_link_region(text_region r, int x, int y);
+text_region urlhack_get_link_bounds(int x, int y);
+void urlhack_add_link_region(int x0, int y0, int x1, int y1);
+void urlhack_launch_url(const char* app, const char *url);
+int urlhack_is_ctrl_pressed();
+void urlhack_set_regular_expression(const char* expression);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+#endif // _URLHACK_H
\ No newline at end of file
Index: C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/windows/window.c
===================================================================
--- C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/windows/window.c	(revision 6566)
+++ C:/Documents and Settings/Chris/My Documents/Dev/putty/wc/windows/window.c	(working copy)
@@ -9,6 +9,7 @@
 #include "terminal.h"
 #include "storage.h"
 #include "win_res.h"
+#include "urlhack.h"
 
 #ifndef NO_MULTIMON
 #if WINVER < 0x0500
@@ -710,6 +711,14 @@
     SetWindowPos(hwnd, NULL, 0, 0, guess_width, guess_height,
 		 SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
 
+	/*
+	 * HACK: Set the regular expression
+	 */
+
+	if (term->cfg.url_defregex == 0) {
+		urlhack_set_regular_expression(term->cfg.url_regex);
+	}
+
     /*
      * Set up a caret bitmap, with no content.
      */
@@ -1855,6 +1864,9 @@
     timing_next_time = next;
 }
 
+static int urlhack_cursor_is_hand = 0;
+
+
 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
 				WPARAM wParam, LPARAM lParam)
 {
@@ -1863,6 +1875,7 @@
     static int need_backend_resize = FALSE;
     static int fullscr_on_max = FALSE;
     static UINT last_mousemove = 0;
+	POINT cursor_pt;
 
     switch (message) {
       case WM_TIMER:
@@ -2340,7 +2353,26 @@
 	 * number noise.
 	 */
 	noise_ultralight(lParam);
+	
+	/* HACK: Change cursor type if hovering over link */
 
+	if (urlhack_mouse_old_x != TO_CHR_X(X_POS(lParam)) || urlhack_mouse_old_y != TO_CHR_Y(Y_POS(lParam))) {
+		urlhack_mouse_old_x = TO_CHR_X(X_POS(lParam));
+		urlhack_mouse_old_y = TO_CHR_Y(Y_POS(lParam));
+
+		if ((!term->cfg.url_ctrl_click || urlhack_is_ctrl_pressed()) &&
+			urlhack_is_in_link_region(urlhack_mouse_old_x, urlhack_mouse_old_y)) {
+				if (urlhack_cursor_is_hand == 0) {
+					SetClassLong(hwnd, GCL_HCURSOR, LoadCursor(NULL, IDC_HAND));
+					urlhack_cursor_is_hand = 1;
+				}
+		}
+		else if (urlhack_cursor_is_hand == 1) {
+			SetClassLong(hwnd, GCL_HCURSOR, LoadCursor(NULL, IDC_IBEAM));
+			urlhack_cursor_is_hand = 0;
+		}
+	}
+
 	if (wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON) &&
 	    GetCapture() == hwnd) {
 	    Mouse_Button b;
@@ -2742,9 +2774,26 @@
 	}
 	return FALSE;
       case WM_KEYDOWN:
+	/* HACK: Change cursor if we are in ctrl+click link mode */
+	if (wParam == VK_CONTROL && term->cfg.url_ctrl_click) {
+		GetCursorPos(&cursor_pt);
+		ScreenToClient(hwnd, &cursor_pt);
+
+		if (urlhack_is_in_link_region(TO_CHR_X(cursor_pt.x), TO_CHR_Y(cursor_pt.y)))
+			SetCursor(LoadCursor(NULL, IDC_HAND));
+	}
+
+	goto HACK_SYSKEY_UP;
       case WM_SYSKEYDOWN:
+	/* HACK: Change cursor if we are in ctrl+click link mode */
+	if (wParam == VK_CONTROL && term->cfg.url_ctrl_click) {
+		SetCursor(LoadCursor(NULL, IDC_IBEAM));
+	}
+
+	goto HACK_SYSKEY_UP;
       case WM_KEYUP:
       case WM_SYSKEYUP:
+HACK_SYSKEY_UP:
 	/*
 	 * Add the scan code and keypress timing to the random
 	 * number noise.
