diff --git a/config.def.h b/config.def.h
index 2cd740a..0eadbce 100644
--- a/config.def.h
+++ b/config.def.h
@@ -472,3 +472,14 @@ static char ascii_printable[] =
 	" !\"#$%&'()*+,-./0123456789:;<=>?"
 	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
 	"`abcdefghijklmnopqrstuvwxyz{|}~";
+
+/*
+ * Open urls starting with urlprefixes, contatining urlchars
+ * by passing as ARG1 to urlhandler.
+ */
+char* urlhandler = "xdg-open";
+char urlchars[] =
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	"abcdefghijklmnopqrstuvwxyz"
+	"0123456789-._~:/?#@!$&'*+,;=%";
+char* urlprefixes[] = {"http://", "https://", NULL};
diff --git a/patches/st-clickurl-0.8.5.diff b/patches/st-clickurl-0.8.5.diff
new file mode 100644
index 0000000..1ba479f
--- /dev/null
+++ b/patches/st-clickurl-0.8.5.diff
@@ -0,0 +1,192 @@
+From d5b492049f48dc411b0dd7dc01a403304c20438d Mon Sep 17 00:00:00 2001
+From: Jishnu Sen <jishnu1@gmail.com>
+Date: Sun, 7 Apr 2024 22:54:46 -0700
+Subject: [PATCH] Highlight URLs with control and follow with click
+
+---
+ config.def.h | 11 +++++++
+ st.c         | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ st.h         |  9 ++++++
+ x.c          | 24 +++++++++++++-
+ 4 files changed, 132 insertions(+), 1 deletion(-)
+
+diff --git a/config.def.h b/config.def.h
+index 91ab8ca..4961830 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -472,3 +472,14 @@ static char ascii_printable[] =
+ 	" !\"#$%&'()*+,-./0123456789:;<=>?"
+ 	"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
+ 	"`abcdefghijklmnopqrstuvwxyz{|}~";
++
++/*
++ * Open urls starting with urlprefixes, contatining urlchars
++ * by passing as ARG1 to urlhandler.
++ */
++char* urlhandler = "xdg-open";
++char urlchars[] =
++	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
++	"abcdefghijklmnopqrstuvwxyz"
++	"0123456789-._~:/?#@!$&'*+,;=%";
++char* urlprefixes[] = {"http://", "https://", NULL};
+diff --git a/st.c b/st.c
+index 51049ba..8f2156c 100644
+--- a/st.c
++++ b/st.c
+@@ -643,6 +643,95 @@ getsel(void)
+ 	return str;
+ }
+ 
++char *
++strstrany(char* s, char** strs) {
++	char *match;
++	for (int i = 0; strs[i]; i++) {
++		if ((match = strstr(s, strs[i]))) {
++			return match;
++		}
++	}
++	return NULL;
++}
++
++void
++highlighturls(void)
++{
++	char *match;
++	char *linestr = calloc(sizeof(char), term.col+1); /* assume ascii */
++	for (int i = term.top; i < term.bot; i++) {
++		int url_start = -1;
++		for (int j = 0; j < term.col; j++) {
++			if (term.line[i][j].u < 127) {
++				linestr[j] = term.line[i][j].u;
++			}
++			linestr[term.col] = '\0';
++		}
++		while ((match = strstrany(linestr + url_start + 1, urlprefixes))) {
++			url_start = match - linestr;
++			for (int c = url_start; c < term.col && strchr(urlchars, linestr[c]); c++) {
++				term.line[i][c].mode |= ATTR_URL;
++				tsetdirt(i, c);
++			}
++		}
++	}
++	free(linestr);
++}
++
++void
++unhighlighturls(void)
++{
++	for (int i = term.top; i < term.bot; i++) {
++		for (int j = 0; j < term.col; j++) {
++			Glyph* g = &term.line[i][j];
++			if (g->mode & ATTR_URL) {
++				g->mode &= ~ATTR_URL;
++				tsetdirt(i, j);
++			}
++		}
++	}
++	return;
++}
++
++void
++followurl(int x, int y) {
++	char *linestr = calloc(sizeof(char), term.col+1); /* assume ascii */
++	char *match;
++	for (int i = 0; i < term.col; i++) {
++		if (term.line[x][i].u < 127) {
++			linestr[i] = term.line[x][i].u;
++		}
++		linestr[term.col] = '\0';
++	}
++	int url_start = -1;
++	while ((match = strstrany(linestr + url_start + 1, urlprefixes))) {
++		url_start = match - linestr;
++		int url_end = url_start;
++		for (int c = url_start; c < term.col && strchr(urlchars, linestr[c]); c++) {
++			url_end++;
++		}
++		if (url_start <= y && y < url_end) {
++			linestr[url_end] = '\0';
++			break;
++		}
++	}
++	if (url_start == -1) {
++		free(linestr);
++		return;
++	}
++
++	pid_t chpid;
++	if ((chpid = fork()) == 0) {
++		if (fork() == 0)
++			execlp(urlhandler, urlhandler, linestr + url_start, NULL);
++		exit(1);
++	}
++	if (chpid > 0)
++		waitpid(chpid, NULL, 0);
++	free(linestr);
++	unhighlighturls();
++}
++
+ void
+ selclear(void)
+ {
+diff --git a/st.h b/st.h
+index 519b9bd..354e7f9 100644
+--- a/st.h
++++ b/st.h
+@@ -34,6 +34,7 @@ enum glyph_attribute {
+ 	ATTR_WIDE       = 1 << 9,
+ 	ATTR_WDUMMY     = 1 << 10,
+ 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
++	ATTR_URL	= 1 << 14,
+ };
+ 
+ enum selection_mode {
+@@ -105,6 +106,10 @@ void selextend(int, int, int, int);
+ int selected(int, int);
+ char *getsel(void);
+ 
++void highlighturls(void);
++void unhighlighturls(void);
++void followurl(int, int);
++
+ size_t utf8encode(Rune, char *);
+ 
+ void *xmalloc(size_t);
+@@ -126,3 +131,7 @@ extern unsigned int tabspaces;
+ extern unsigned int defaultfg;
+ extern unsigned int defaultbg;
+ extern unsigned int defaultcs;
++extern char *urlhandler;
++extern char urlchars[];
++extern char *urlprefixes[];
++extern int nurlprefixes;
+diff --git a/x.c b/x.c
+index 8a16faa..13f68e4 100644
+--- a/x.c
++++ b/x.c
+@@ -191,6 +191,7 @@ static void usage(void);
+ 
+ static void (*handler[LASTEvent])(XEvent *) = {
+ 	[KeyPress] = kpress,
++	[KeyRelease] = kpress,
+ 	[ClientMessage] = cmessage,
+ 	[ConfigureNotify] = resize,
+ 	[VisibilityNotify] = visibility,
+@@ -445,6 +446,15 @@ mouseaction(XEvent *e, uint release)
+ 	/* ignore Button<N>mask for Button<N> - it's set on release */
+ 	uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
+ 
++	if (release == 0 &&
++	    e->xbutton.button == Button1 &&
++	    (match(ControlMask, state) ||
++	     match(ControlMask, state & ~forcemousemod))) {
++		followurl(evrow(e), evcol(e));
++		return 1;
++	}
++
++
+ 	for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
+ 		if (ms->release == release &&
+ 		    ms->button == e->xbutton.button &&
+2.44.0
+
diff --git a/st.c b/st.c
index 03b9bc8..90b6bb1 100644
--- a/st.c
+++ b/st.c
@@ -636,6 +636,95 @@ getsel(void)
 	return str;
 }
 
+char *
+strstrany(char* s, char** strs) {
+	char *match;
+	for (int i = 0; strs[i]; i++) {
+		if ((match = strstr(s, strs[i]))) {
+			return match;
+		}
+	}
+	return NULL;
+}
+
+void
+highlighturls(void)
+{
+	char *match;
+	char *linestr = calloc(sizeof(char), term.col+1); /* assume ascii */
+	for (int i = term.top; i < term.bot; i++) {
+		int url_start = -1;
+		for (int j = 0; j < term.col; j++) {
+			if (term.line[i][j].u < 127) {
+				linestr[j] = term.line[i][j].u;
+			}
+			linestr[term.col] = '\0';
+		}
+		while ((match = strstrany(linestr + url_start + 1, urlprefixes))) {
+			url_start = match - linestr;
+			for (int c = url_start; c < term.col && strchr(urlchars, linestr[c]); c++) {
+				term.line[i][c].mode |= ATTR_URL;
+				tsetdirt(i, c);
+			}
+		}
+	}
+	free(linestr);
+}
+
+void
+unhighlighturls(void)
+{
+	for (int i = term.top; i < term.bot; i++) {
+		for (int j = 0; j < term.col; j++) {
+			Glyph* g = &term.line[i][j];
+			if (g->mode & ATTR_URL) {
+				g->mode &= ~ATTR_URL;
+				tsetdirt(i, j);
+			}
+		}
+	}
+	return;
+}
+
+void
+followurl(int x, int y) {
+	char *linestr = calloc(sizeof(char), term.col+1); /* assume ascii */
+	char *match;
+	for (int i = 0; i < term.col; i++) {
+		if (term.line[x][i].u < 127) {
+			linestr[i] = term.line[x][i].u;
+		}
+		linestr[term.col] = '\0';
+	}
+	int url_start = -1;
+	while ((match = strstrany(linestr + url_start + 1, urlprefixes))) {
+		url_start = match - linestr;
+		int url_end = url_start;
+		for (int c = url_start; c < term.col && strchr(urlchars, linestr[c]); c++) {
+			url_end++;
+		}
+		if (url_start <= y && y < url_end) {
+			linestr[url_end] = '\0';
+			break;
+		}
+	}
+	if (url_start == -1) {
+		free(linestr);
+		return;
+	}
+
+	pid_t chpid;
+	if ((chpid = fork()) == 0) {
+		if (fork() == 0)
+			execlp(urlhandler, urlhandler, linestr + url_start, NULL);
+		exit(1);
+	}
+	if (chpid > 0)
+		waitpid(chpid, NULL, 0);
+	free(linestr);
+	unhighlighturls();
+}
+
 void
 selclear(void)
 {
diff --git a/st.h b/st.h
index fd3b0d8..a8c58aa 100644
--- a/st.h
+++ b/st.h
@@ -34,6 +34,7 @@ enum glyph_attribute {
 	ATTR_WIDE       = 1 << 9,
 	ATTR_WDUMMY     = 1 << 10,
 	ATTR_BOLD_FAINT = ATTR_BOLD | ATTR_FAINT,
+	ATTR_URL	= 1 << 14,
 };
 
 enum selection_mode {
@@ -105,6 +106,10 @@ void selextend(int, int, int, int);
 int selected(int, int);
 char *getsel(void);
 
+void highlighturls(void);
+void unhighlighturls(void);
+void followurl(int, int);
+
 size_t utf8encode(Rune, char *);
 
 void *xmalloc(size_t);
@@ -124,3 +129,7 @@ extern unsigned int tabspaces;
 extern unsigned int defaultfg;
 extern unsigned int defaultbg;
 extern unsigned int defaultcs;
+extern char *urlhandler;
+extern char urlchars[];
+extern char *urlprefixes[];
+extern int nurlprefixes;
diff --git a/x.c b/x.c
index d73152b..81e9661 100644
--- a/x.c
+++ b/x.c
@@ -191,6 +191,7 @@ static void usage(void);
 
 static void (*handler[LASTEvent])(XEvent *) = {
 	[KeyPress] = kpress,
+	[KeyRelease] = kpress,
 	[ClientMessage] = cmessage,
 	[ConfigureNotify] = resize,
 	[VisibilityNotify] = visibility,
@@ -452,6 +453,15 @@ mouseaction(XEvent *e, uint release)
 	/* ignore Button<N>mask for Button<N> - it's set on release */
 	uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
 
+	if (release == 0 &&
+	    e->xbutton.button == Button1 &&
+	    (match(ControlMask, state) ||
+	     match(ControlMask, state & ~forcemousemod))) {
+		followurl(evrow(e), evcol(e));
+		return 1;
+	}
+
+
 	for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
 		if (ms->release == release &&
 		    ms->button == e->xbutton.button &&
@@ -1495,7 +1505,7 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
 	XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
 
 	/* Render underline and strikethrough. */
-	if (base.mode & ATTR_UNDERLINE) {
+	if (base.mode & ATTR_UNDERLINE || base.mode & ATTR_URL) {
 		XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
 				width, 1);
 	}
@@ -1859,6 +1869,18 @@ kpress(XEvent *ev)
 	} else {
 		len = XLookupString(e, buf, sizeof buf, &ksym, NULL);
 	}
+
+	/* 0. highlight URLs when control held */
+ 	if (ksym == XK_Control_L) {
+ 		highlighturls();
+ 	} else if (ev->type == KeyRelease && e->keycode == XKeysymToKeycode(e->display, XK_Control_L)) {
+ 		unhighlighturls();
+ 	}
+ 
+ 	/* KeyRelease not relevant to shortcuts */
+ 	if (ev->type == KeyRelease)
+ 		return;
+ 
 	/* 1. shortcuts */
 	for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
 		if (ksym == bp->keysym && match(bp->mod, e->state)) {