From 3eb170a9a5ec7a02ecd8b5719a7890c0a45cd4c2 Mon Sep 17 00:00:00 2001 From: veltza <106755522+veltza@users.noreply.github.com> Date: Sun, 7 Aug 2022 17:21:55 +0300 Subject: [PATCH] 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 --- config.def.h | 4 +- patch/openurlonclick.c | 191 +++++++++++++++++++++++++++++------------ patch/openurlonclick.h | 7 +- patch/scrollback.c | 10 +++ patch/st_include.h | 3 + patch/x_include.h | 5 +- st.c | 8 ++ st.h | 5 +- x.c | 87 ++++++++++++++++++- 9 files changed, 256 insertions(+), 64 deletions(-) diff --git a/config.def.h b/config.def.h index 5b0f055..82f282f 100644 --- a/config.def.h +++ b/config.def.h @@ -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 @@ -752,4 +754,4 @@ static char *plumb_cmd = "plumb"; #define UNDERCURL_CAPPED 2 // Active style #define UNDERCURL_STYLE UNDERCURL_SPIKY -#endif // UNDERCURL_PATCH \ No newline at end of file +#endif // UNDERCURL_PATCH diff --git a/patch/openurlonclick.c b/patch/openurlonclick.c index 7b4b82f..1e9a3db 100644 --- a/patch/openurlonclick.c +++ b/patch/openurlonclick.c @@ -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); - system(command); + 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); + } } diff --git a/patch/openurlonclick.h b/patch/openurlonclick.h index 093fd0d..10f21ce 100644 --- a/patch/openurlonclick.h +++ b/patch/openurlonclick.h @@ -1 +1,6 @@ -static void openUrlOnClick(int col, int row, char* url_opener); \ No newline at end of file +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); diff --git a/patch/scrollback.c b/patch/scrollback.c index 91a8a7a..2644cfe 100644 --- a/patch/scrollback.c +++ b/patch/scrollback.c @@ -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 } diff --git a/patch/st_include.h b/patch/st_include.h index f8823c9..727bb88 100644 --- a/patch/st_include.h +++ b/patch/st_include.h @@ -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 diff --git a/patch/x_include.h b/patch/x_include.h index ea1ebc1..a737408 100644 --- a/patch/x_include.h +++ b/patch/x_include.h @@ -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 @@ -34,4 +31,4 @@ #endif #if VIM_BROWSE_PATCH #include "normalMode.h" -#endif \ No newline at end of file +#endif diff --git a/st.c b/st.c index 5ea8cad..04380ff 100644 --- a/st.c +++ b/st.c @@ -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; diff --git a/st.h b/st.h index 4dff2a3..e4db7ea 100644 --- a/st.h +++ b/st.h @@ -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 diff --git a/x.c b/x.c index 9c3f09c..47a54f3 100644 --- a/x.c +++ b/x.c @@ -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,14 +791,16 @@ brelease(XEvent *e) if (btn == Button1 && !IS_SET(MODE_NORMAL)) { mousesel(e, 1); #if OPENURLONCLICK_PATCH - openUrlOnClick(evcol(e), evrow(e), url_opener); + if (url_click && e->xkey.state & url_opener_modkey) + openUrlOnClick(evcol(e), evrow(e), url_opener); #endif // OPENURLONCLICK_PATCH } #else if (btn == Button1) { mousesel(e, 1); #if OPENURLONCLICK_PATCH - openUrlOnClick(evcol(e), evrow(e), url_opener); + if (url_click && e->xkey.state & url_opener_modkey) + openUrlOnClick(evcol(e), evrow(e), url_opener); #endif // OPENURLONCLICK_PATCH } #endif // VIM_BROWSE_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,9 +3108,26 @@ 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