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