Add scrollback support to the openurlonclick patch

The openurlonclick and scrollback patches are now working together,
so links can be clicked in the scrollback buffer too. This update also
adds url underlining and other improvements to the openurlonclick patch.

The full list of changes in the openurlonclick patch:
- Adds scrollback support
- Adds modkey option
- Better url detection
- Underlines url when the mouse pointer is over a link
- Opens a browser as a background process, so it won't lock the terminal anymore
- Fixes a segmentation fault bug
This commit is contained in:
veltza 2022-08-07 17:21:55 +03:00
parent 9e0e419781
commit 3eb170a9a5
9 changed files with 256 additions and 64 deletions

View File

@ -33,6 +33,8 @@ static int borderpx = 2;
#endif // RELATIVEBORDER_PATCH
#if OPENURLONCLICK_PATCH
/* modkey options: ControlMask, ShiftMask or XK_ANY_MOD */
static uint url_opener_modkey = XK_ANY_MOD;
static char *url_opener = "xdg-open";
#endif // OPENURLONCLICK_PATCH

View File

@ -1,59 +1,140 @@
#if SCROLLBACK_PATCH && !VIM_BROWSE_PATCH
#define TLINEURL(y) TLINE(y)
#else
#define TLINEURL(y) term.line[y]
#endif // SCROLLBACK_PATCH
#if VIM_BROWSE_PATCH
extern int buffCols;
#endif // VIM_BROWSE_PATCH
int url_x1, url_y1, url_x2, url_y2 = -1;
int url_draw, url_click, url_maxcol;
static int
isvalidurlchar(Rune u)
{
/* () and [] can appear in urls, but excluding them here will reduce false
* positives when figuring out where a given url ends. See copyurl patch.
*/
static char urlchars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789-._~:/?#@!$&'*+,;=%";
return u < 128 && strchr(urlchars, (int)u) != NULL;
}
/* find the end of the wrapped line */
static int
findeowl(int row)
{
#if VIM_BROWSE_PATCH
int col = buffCols - 1;
#elif COLUMNS_PATCH
int col = term.maxcol - 1;
#else
int col = term.col - 1;
#endif // VIM_BROWSE_PATCH
do {
if (TLINEURL(row)[col].mode & ATTR_WRAP)
return col;
} while (TLINEURL(row)[col].u == ' ' && --col >= 0);
return -1;
}
void
clearurl(void)
{
while (url_y1 <= url_y2)
term.dirty[url_y1++] = 1;
url_y2 = -1;
}
char *
detecturl(int col, int row, int draw)
{
static char url[2048];
int x1, y1, x2, y2, wrapped;
int row_start = row;
int col_start = col;
int i = sizeof(url)/2+1, j = sizeof(url)/2;
#if SCROLLBACK_PATCH && !VIM_BROWSE_PATCH
int minrow = term.scr - term.histn, maxrow = term.scr + term.row - 1;
/* Fixme: MODE_ALTSCREEN is not defined here, I had to use the magic number 1<<2 */
if ((term.mode & (1 << 2)) != 0)
minrow = 0, maxrow = term.row - 1;
#else
int minrow = 0, maxrow = term.row - 1;
#endif // scrollback_patch
url_maxcol = 0;
/* clear previously underlined url */
if (draw)
clearurl();
if (!isvalidurlchar(TLINEURL(row)[col].u))
return NULL;
/* find the first character of url */
do {
x1 = col_start, y1 = row_start;
url_maxcol = MAX(url_maxcol, x1);
url[--i] = TLINEURL(row_start)[col_start].u;
if (--col_start < 0) {
if (--row_start < minrow || (col_start = findeowl(row_start)) < 0)
break;
}
} while (i > 0 && isvalidurlchar(TLINEURL(row_start)[col_start].u));
/* early detection */
if (url[i] != 'h')
return NULL;
/* find the last character of url */
do {
x2 = col, y2 = row;
url_maxcol = MAX(url_maxcol, x2);
url[j++] = TLINEURL(row)[col].u;
wrapped = TLINEURL(row)[col].mode & ATTR_WRAP;
#if VIM_BROWSE_PATCH
if (++col >= buffCols || wrapped) {
#elif COLUMNS_PATCH
if (++col >= term.maxcol || wrapped) {
#else
if (++col >= term.col || wrapped) {
#endif // VIM_BROWSE_PATCH
col = 0;
if (++row > maxrow || !wrapped)
break;
}
} while (j < sizeof(url)-1 && isvalidurlchar(TLINEURL(row)[col].u));
url[j] = 0;
if (strncmp("https://", &url[i], 8) && strncmp("http://", &url[i], 7))
return NULL;
/* underline url (see xdrawglyphfontspecs() in x.c) */
if (draw) {
url_x1 = (y1 >= 0) ? x1 : 0;
url_x2 = (y2 < term.row) ? x2 : url_maxcol;
url_y1 = MAX(y1, 0);
url_y2 = MIN(y2, term.row-1);
url_draw = 1;
for (y1 = url_y1; y1 <= url_y2; y1++)
term.dirty[y1] = 1;
}
return &url[i];
}
void
openUrlOnClick(int col, int row, char* url_opener)
{
int row_start = row;
int col_start = col;
int row_end = row;
int col_end = col;
if (term.line[row][col].u == ' ')
return;
/* while previous character is not space */
while (term.line[row_start][col_start-1].u != ' ') {
if (col_start == 0)
{
// Before moving start pointer to the previous line we check if it ends with space
if (term.line[row_start - 1][term.col - 1].u == ' ')
break;
col_start=term.col - 1;
row_start--;
} else {
col_start--;
}
}
/* while next character is not space nor end of line */
while (term.line[row_end][col_end].u != ' ') {
col_end++;
if (col_end == term.col - 1)
{
if (term.line[row_end + 1][0].u == ' ')
break;
col_end=0;
row_end++;
}
}
char url[200] = "";
int url_index=0;
do {
url[url_index] = term.line[row_start][col_start].u;
url_index++;
col_start++;
if (col_start == term.col)
{
col_start = 0;
row_start++;
}
} while (url_index < (sizeof(url)-1) &&
(row_start != row_end || col_start != col_end));
if (strncmp("http", url, 4) != 0) {
return;
}
char command[strlen(url_opener)+strlen(url)+2];
sprintf(command, "%s %s", url_opener, url);
char *url = detecturl(col, row, 1);
if (url) {
char command[strlen(url_opener) + strlen(url) + 5];
sprintf(command, "%s \"%s\"&", url_opener, url);
system(command);
}
}

View File

@ -1 +1,6 @@
static inline void restoremousecursor(void) {
if (!(win.mode & MODE_MOUSE) && xw.pointerisvisible)
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
}
static void clearurl(void);
static void openUrlOnClick(int col, int row, char* url_opener);

View File

@ -18,6 +18,11 @@ kscrolldown(const Arg* a)
#if SIXEL_PATCH
scroll_images(-1*n);
#endif // SIXEL_PATCH
#if OPENURLONCLICK_PATCH
if (n > 0)
restoremousecursor();
#endif // OPENURLONCLICK_PATCH
}
void
@ -42,4 +47,9 @@ kscrollup(const Arg* a)
#if SIXEL_PATCH
scroll_images(n);
#endif // SIXEL_PATCH
#if OPENURLONCLICK_PATCH
if (n > 0)
restoremousecursor();
#endif // OPENURLONCLICK_PATCH
}

View File

@ -11,6 +11,9 @@
#if KEYBOARDSELECT_PATCH
#include "keyboardselect_st.h"
#endif
#if OPENURLONCLICK_PATCH
#include "openurlonclick.h"
#endif
#if RIGHTCLICKTOPLUMB_PATCH
#include "rightclicktoplumb_st.h"
#endif

View File

@ -20,9 +20,6 @@
#if NETWMICON_PATCH
#include "netwmicon.h"
#endif
#if OPENURLONCLICK_PATCH
#include "openurlonclick.h"
#endif
#if RIGHTCLICKTOPLUMB_PATCH
#include "rightclicktoplumb_x.h"
#endif

8
st.c
View File

@ -1387,6 +1387,10 @@ tswapscreen(void)
void
tscrolldown(int orig, int n)
{
#if OPENURLONCLICK_PATCH
restoremousecursor();
#endif //OPENURLONCLICK_PATCH
#if VIM_BROWSE_PATCH
if (!orig && historyBufferScroll(-n))
return;
@ -1435,6 +1439,10 @@ tscrollup(int orig, int n, int copyhist)
tscrollup(int orig, int n)
#endif // SCROLLBACK_PATCH
{
#if OPENURLONCLICK_PATCH
restoremousecursor();
#endif //OPENURLONCLICK_PATCH
#if VIM_BROWSE_PATCH
if (!orig && historyBufferScroll(n))
return;

5
st.h
View File

@ -219,13 +219,16 @@ typedef struct {
#endif // BACKGROUND_IMAGE_PATCH
Visual *vis;
XSetWindowAttributes attrs;
#if HIDECURSOR_PATCH
#if HIDECURSOR_PATCH || OPENURLONCLICK_PATCH
/* Here, we use the term *pointer* to differentiate the cursor
* one sees when hovering the mouse over the terminal from, e.g.,
* a green rectangle where text would be entered. */
Cursor vpointer, bpointer; /* visible and hidden pointers */
int pointerisvisible;
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
Cursor upointer;
#endif // OPENURLONCLICK_PATCH
int scr;
int isfixed; /* is fixed geometry? */
#if ALPHA_PATCH

83
x.c
View File

@ -536,6 +536,11 @@ bpress(XEvent *e)
#if !VIM_BROWSE_PATCH
selstart(evcol(e), evrow(e), snap);
#endif // VIM_BROWSE_PATCH
#if OPENURLONCLICK_PATCH
clearurl();
url_click = 1;
#endif // OPENURLONCLICK_PATCH
}
}
@ -786,6 +791,7 @@ brelease(XEvent *e)
if (btn == Button1 && !IS_SET(MODE_NORMAL)) {
mousesel(e, 1);
#if OPENURLONCLICK_PATCH
if (url_click && e->xkey.state & url_opener_modkey)
openUrlOnClick(evcol(e), evrow(e), url_opener);
#endif // OPENURLONCLICK_PATCH
}
@ -793,6 +799,7 @@ brelease(XEvent *e)
if (btn == Button1) {
mousesel(e, 1);
#if OPENURLONCLICK_PATCH
if (url_click && e->xkey.state & url_opener_modkey)
openUrlOnClick(evcol(e), evrow(e), url_opener);
#endif // OPENURLONCLICK_PATCH
}
@ -821,6 +828,18 @@ bmotion(XEvent *e)
xsetpointermotion(0);
}
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
#if VIM_BROWSE_PATCH
if (!IS_SET(MODE_NORMAL))
#endif // VIM_BROWSE_PATCH
if (!IS_SET(MODE_MOUSE)) {
if (!(e->xbutton.state & Button1Mask) && detecturl(evcol(e), evrow(e), 1))
XDefineCursor(xw.dpy, xw.win, xw.upointer);
else
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
}
url_click = 0;
#endif // OPENURLONCLICK_PATCH
if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {
mousereport(e);
@ -911,6 +930,10 @@ xloadcolor(int i, const char *name, Color *ncolor)
#if VIM_BROWSE_PATCH
void normalMode()
{
#if OPENURLONCLICK_PATCH
clearurl();
restoremousecursor();
#endif // OPENURLONCLICK_PATCH
historyModeToggle((win.mode ^=MODE_NORMAL) & MODE_NORMAL);
}
#endif // VIM_BROWSE_PATCH
@ -1443,6 +1466,9 @@ xinit(int cols, int rows)
#endif // ST_EMBEDDER_PATCH
;
xw.attrs.colormap = xw.cmap;
#if OPENURLONCLICK_PATCH
xw.attrs.event_mask |= PointerMotionMask;
#endif // OPENURLONCLICK_PATCH
#if !ALPHA_PATCH
if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))
@ -1534,6 +1560,14 @@ xinit(int cols, int rows)
XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
xw.upointer = XCreateFontCursor(xw.dpy, XC_hand2);
#if !HIDECURSOR_PATCH
xw.vpointer = cursor;
xw.pointerisvisible = 1;
#endif // HIDECURSOR_PATCH
#endif // OPENURLONCLICK_PATCH
xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
@ -2365,6 +2399,28 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
}
#endif // WIDE_GLYPHS_PATCH
#if OPENURLONCLICK_PATCH
if (url_draw && y >= url_y1 && y <= url_y2) {
int x1 = (y == url_y1) ? url_x1 : 0;
int x2 = (y == url_y2) ? MIN(url_x2, term.col-1) : url_maxcol;
if (x + charlen > x1 && x <= x2) {
int xu = MAX(x, x1);
int wu = (x2 - xu + 1) * win.cw;
#if ANYSIZE_PATCH
xu = win.hborderpx + xu * win.cw;
#else
xu = borderpx + xu * win.cw;
#endif // ANYSIZE_PATCH
#if VERTCENTER_PATCH
XftDrawRect(xw.draw, fg, xu, winy + win.cyo + dc.font.ascent * chscale + 2, wu, 1);
#else
XftDrawRect(xw.draw, fg, xu, winy + dc.font.ascent * chscale + 2, wu, 1);
#endif // VERTCENTER_PATCH
url_draw = (y != url_y2 || x + charlen <= x2);
}
}
#endif // OPENURLONCLICK_PATCH
#if !WIDE_GLYPHS_PATCH
/* Reset clip to none. */
XftDrawSetClip(xw.draw, 0);
@ -2865,6 +2921,9 @@ xsetpointermotion(int set)
if (!set && !xw.pointerisvisible)
return;
#endif // HIDECURSOR_PATCH
#if OPENURLONCLICK_PATCH
set = 1; /* keep MotionNotify event enabled */
#endif // OPENURLONCLICK_PATCH
MODBIT(xw.attrs.event_mask, set, PointerMotionMask);
XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);
}
@ -2883,8 +2942,15 @@ xsetmode(int set, unsigned int flags)
if (win.mode & MODE_MOUSE)
XUndefineCursor(xw.dpy, xw.win);
else
#if HIDECURSOR_PATCH
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
#else
XDefineCursor(xw.dpy, xw.win, cursor);
#endif // HIDECURSOR_PATCH
}
#elif OPENURLONCLICK_PATCH
if (win.mode & MODE_MOUSE && xw.pointerisvisible)
XDefineCursor(xw.dpy, xw.win, xw.vpointer);
#endif // SWAPMOUSE_PATCH
if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))
redraw();
@ -3042,10 +3108,27 @@ kpress(XEvent *ev)
#if HIDECURSOR_PATCH
if (xw.pointerisvisible) {
#if OPENURLONCLICK_PATCH
#if ANYSIZE_PATCH
int x = e->x - win.hborderpx;
int y = e->y - win.vborderpx;
#else
int x = e->x - borderpx;
int y = e->y - borderpx;
#endif // ANYSIZE_PATCH
LIMIT(x, 0, win.tw - 1);
LIMIT(y, 0, win.th - 1);
if (!detecturl(x / win.cw, y / win.ch, 0)) {
XDefineCursor(xw.dpy, xw.win, xw.bpointer);
xsetpointermotion(1);
xw.pointerisvisible = 0;
}
#else
XDefineCursor(xw.dpy, xw.win, xw.bpointer);
xsetpointermotion(1);
xw.pointerisvisible = 0;
#endif // OPENURLONCLICK_PATCH
}
#endif // HIDECURSOR_PATCH
if (IS_SET(MODE_KBDLOCK))