From c827a90d71ca48a99f5e94a4698574f8146c394f Mon Sep 17 00:00:00 2001 From: ProsperousPotato Date: Sat, 14 Jun 2025 16:39:03 +0100 Subject: rewrite from original --- x.c | 879 +++++++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 505 insertions(+), 374 deletions(-) (limited to 'x.c') diff --git a/x.c b/x.c index b1ef2ba..0f775ce 100644 --- a/x.c +++ b/x.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -14,13 +16,12 @@ #include #include #include -#include char *argv0; #include "arg.h" #include "st.h" #include "win.h" -#include "hb.h" +#include "graphics.h" /* types used in config.h */ typedef struct { @@ -47,19 +48,6 @@ typedef struct { signed char appcursor; /* application cursor */ } Key; -/* Xresources preferences */ -enum resource_type { - STRING = 0, - INTEGER = 1, - FLOAT = 2 -}; - -typedef struct { - char *name; - enum resource_type type; - void *dst; -} ResourcePref; - /* X modifiers */ #define XK_ANY_MOD UINT_MAX #define XK_NO_MOD 0 @@ -70,12 +58,20 @@ static void clipcopy(const Arg *); static void clippaste(const Arg *); static void numlock(const Arg *); static void selpaste(const Arg *); -static void changealpha(const Arg *); static void zoom(const Arg *); static void zoomabs(const Arg *); static void zoomreset(const Arg *); static void ttysend(const Arg *); +static void chgalpha(const Arg *); + +static void previewimage(const Arg *); +static void showimageinfo(const Arg *); +static void togglegrdebug(const Arg *); +static void dumpgrstate(const Arg *); +static void unloadimages(const Arg *); +static void toggleimages(const Arg *); + /* config.h for applying patches and the configuration. */ #include "config.h" @@ -97,6 +93,7 @@ typedef XftGlyphFontSpec GlyphFontSpec; typedef struct { int tw, th; /* tty width and height */ int w, h; /* window width and height */ + int hborderpx, vborderpx; int ch; /* char height */ int cw; /* char width */ int mode; /* window state/mode flags */ @@ -161,6 +158,8 @@ static inline ushort sixd_to_16bit(int); static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); static void xdrawglyph(Glyph, int, int); +static void xdrawimages(Glyph, Line, int x1, int y1, int x2); +static void xdrawoneimagecell(Glyph, int x, int y); static void xclear(int, int, int, int); static int xgeommasktogravity(int); static int ximopen(Display *); @@ -173,8 +172,6 @@ static void xresize(int, int); static void xhints(void); static int xloadcolor(int, const char *, Color *); static int xloadfont(Font *, FcPattern *); -static int xloadsparefont(FcPattern *, int); -static void xloadsparefonts(void); static void xloadfonts(const char *, double); static void xunloadfont(Font *); static void xunloadfonts(void); @@ -182,7 +179,6 @@ static void xsetenv(void); static void xseturgency(int); static int evcol(XEvent *); static int evrow(XEvent *); -static float clamp(float, float, float); static void expose(XEvent *); static void visibility(XEvent *); @@ -240,6 +236,7 @@ static DC dc; static XWindow xw; static XSelection xsel; static TermWindow win; +static unsigned int mouse_col = 0, mouse_row = 0; /* Font Ring Cache */ enum { @@ -263,7 +260,11 @@ static char *usedfont = NULL; static double usedfontsize = 0; static double defaultfontsize = 0; -static char *opt_alpha = NULL; +/* declared in config.h */ +extern int disablebold; +extern int disableitalic; +extern int disableroman; + static char *opt_class = NULL; static char **opt_cmd = NULL; static char *opt_embed = NULL; @@ -273,16 +274,8 @@ static char *opt_line = NULL; static char *opt_name = NULL; static char *opt_title = NULL; -static int focused = 0; - -static int oldbutton = 3; /* button event on startup: 3 = release */ static uint buttons; /* bit field of pressed buttons */ -/* declared in config.h */ -extern int disablebold; -extern int disableitalic; -extern int disableroman; - void clipcopy(const Arg *dummy) { @@ -321,18 +314,6 @@ numlock(const Arg *dummy) win.mode ^= MODE_NUMLOCK; } -void -changealpha(const Arg *arg) -{ - if((alpha > 0 && arg->f < 0) || (alpha < 1 && arg->f > 0)) - alpha += arg->f; - alpha = clamp(alpha, 0.0, 1.0); - alphaUnfocus = clamp(alpha-alphaOffset, 0.0, 1.0); - - xloadcols(); - redraw(); -} - void zoom(const Arg *arg) { @@ -347,7 +328,6 @@ zoomabs(const Arg *arg) { xunloadfonts(); xloadfonts(usedfont, arg->f); - xloadsparefonts(); cresize(0, 0); redraw(); xhints(); @@ -370,10 +350,72 @@ ttysend(const Arg *arg) ttywrite(arg->s, strlen(arg->s), 1); } +void +previewimage(const Arg *arg) +{ + Glyph g = getglyphat(mouse_col, mouse_row); + if (g.mode & ATTR_IMAGE) { + uint32_t image_id = tgetimgid(&g); + fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", + image_id, tgetimgplacementid(&g), tgetimgcol(&g), + tgetimgrow(&g)); + gr_preview_image(image_id, arg->s); + } +} + +void +showimageinfo(const Arg *arg) +{ + Glyph g = getglyphat(mouse_col, mouse_row); + if (g.mode & ATTR_IMAGE) { + uint32_t image_id = tgetimgid(&g); + fprintf(stderr, "Clicked on placeholder %u/%u, x=%d, y=%d\n", + image_id, tgetimgplacementid(&g), tgetimgcol(&g), + tgetimgrow(&g)); + char stcommand[256] = {0}; + size_t len = snprintf(stcommand, sizeof(stcommand), "%s -e less", argv0); + if (len > sizeof(stcommand) - 1) { + fprintf(stderr, "Executable name too long: %s\n", + argv0); + return; + } + gr_show_image_info(image_id, tgetimgplacementid(&g), + tgetimgcol(&g), tgetimgrow(&g), + tgetisclassicplaceholder(&g), + tgetimgdiacriticcount(&g), argv0); + } +} + +void +togglegrdebug(const Arg *arg) +{ + graphics_debug_mode = (graphics_debug_mode + 1) % 3; + redraw(); +} + +void +dumpgrstate(const Arg *arg) +{ + gr_dump_state(); +} + +void +unloadimages(const Arg *arg) +{ + gr_unload_images_to_reduce_ram(); +} + +void +toggleimages(const Arg *arg) +{ + graphics_display_images = !graphics_display_images; + redraw(); +} + int evcol(XEvent *e) { - int x = e->xbutton.x - borderpx; + int x = e->xbutton.x - win.hborderpx; LIMIT(x, 0, win.tw - 1); return x / win.cw; } @@ -381,20 +423,11 @@ evcol(XEvent *e) int evrow(XEvent *e) { - int y = e->xbutton.y - borderpx; + int y = e->xbutton.y - win.vborderpx; LIMIT(y, 0, win.th - 1); return y / win.ch; } -float -clamp(float value, float lower, float upper) { - if(value < lower) - return lower; - if(value > upper) - return upper; - return value; -} - void mousesel(XEvent *e, int done) { @@ -415,59 +448,68 @@ mousesel(XEvent *e, int done) void mousereport(XEvent *e) { - int len, x = evcol(e), y = evrow(e), - button = e->xbutton.button, state = e->xbutton.state; + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; char buf[40]; static int ox, oy; - /* from urxvt */ - if (e->xbutton.type == MotionNotify) { + if (e->type == MotionNotify) { if (x == ox && y == oy) return; if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) return; - /* MOUSE_MOTION: no reporting if no button is pressed */ - if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) return; - - button = oldbutton + 32; - ox = x; - oy = y; + /* Set btn to lowest-numbered pressed button, or 12 if no + * buttons are pressed. */ + for (btn = 1; btn <= 11 && !(buttons & (1<<(btn-1))); btn++) + ; + code = 32; } else { - if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { - button = 3; - } else { - button -= Button1; - if (button >= 3) - button += 64 - 3; - } - if (e->xbutton.type == ButtonPress) { - oldbutton = button; - ox = x; - oy = y; - } else if (e->xbutton.type == ButtonRelease) { - oldbutton = 3; + btn = e->xbutton.button; + /* Only buttons 1 through 11 can be encoded */ + if (btn < 1 || btn > 11) + return; + if (e->type == ButtonRelease) { /* MODE_MOUSEX10: no button release reporting */ if (IS_SET(MODE_MOUSEX10)) return; - if (button == 64 || button == 65) + /* Don't send release events for the scroll wheel */ + if (btn == 4 || btn == 5) return; } + code = 0; } + ox = x; + oy = y; + + /* Encode btn into code. If no button is pressed for a motion event in + * MODE_MOUSEMANY, then encode it as a release. */ + if ((!IS_SET(MODE_MOUSESGR) && e->type == ButtonRelease) || btn == 12) + code += 3; + else if (btn >= 8) + code += 128 + btn - 8; + else if (btn >= 4) + code += 64 + btn - 4; + else + code += btn - 1; + if (!IS_SET(MODE_MOUSEX10)) { - button += ((state & ShiftMask ) ? 4 : 0) - + ((state & Mod4Mask ) ? 8 : 0) - + ((state & ControlMask) ? 16 : 0); + code += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod1Mask ) ? 8 : 0) /* meta key: alt */ + + ((state & ControlMask) ? 16 : 0); } if (IS_SET(MODE_MOUSESGR)) { len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", - button, x+1, y+1, + code, x+1, y+1, e->type == ButtonRelease ? 'm' : 'M'); } else if (x < 223 && y < 223) { len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", - 32+button, 32+x+1, 32+y+1); + 32+code, 32+x+1, 32+y+1); } else { return; } @@ -494,6 +536,9 @@ mouseaction(XEvent *e, uint release) /* ignore Buttonmask for Button - it's set on release */ uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + mouse_col = evcol(e); + mouse_row = evrow(e); + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { if (ms->release == release && ms->button == e->xbutton.button && @@ -781,6 +826,9 @@ cresize(int width, int height) col = MAX(1, col); row = MAX(1, row); + win.hborderpx = (win.w - col * win.cw) * anysize_halign / 100; + win.vborderpx = (win.h - row * win.ch) * anysize_valign / 100; + tresize(col, row); xresize(col, row); ttyresize(win.tw, win.th); @@ -832,16 +880,6 @@ xloadcolor(int i, const char *name, Color *ncolor) return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); } -void -xloadalpha(void) -{ - float const usedAlpha = focused ? alpha : alphaUnfocus; - if (opt_alpha) alpha = strtof(opt_alpha, NULL); - dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * usedAlpha); - dc.col[defaultbg].pixel &= 0x00FFFFFF; - dc.col[defaultbg].pixel |= (unsigned char)(0xff * usedAlpha) << 24; -} - void xloadcols(void) { @@ -849,12 +887,15 @@ xloadcols(void) static int loaded; Color *cp; - if (!loaded) { - dc.collen = 1 + (defaultbg = MAX(LEN(colorname), 256)); + if (loaded) { + for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) + XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); + } else { + dc.collen = MAX(LEN(colorname), 256); dc.col = xmalloc(dc.collen * sizeof(Color)); } - for (i = 0; i+1 < dc.collen; i++) + for (i = 0; i < dc.collen; i++) if (!xloadcolor(i, NULL, &dc.col[i])) { if (colorname[i]) die("could not allocate color '%s'\n", colorname[i]); @@ -862,17 +903,16 @@ xloadcols(void) die("could not allocate color %d\n", i); } - if (dc.collen) // cannot die, as the color is already loaded. - xloadcolor(background, NULL, &dc.col[defaultbg]); - - xloadalpha(); + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; loaded = 1; } int xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) { - if (!BETWEEN(x, 0, dc.collen)) + if (!BETWEEN(x, 0, dc.collen - 1)) return 1; *r = dc.col[x].color.red >> 8; @@ -887,7 +927,7 @@ xsetcolorname(int x, const char *name) { Color ncolor; - if (!BETWEEN(x, 0, dc.collen)) + if (!BETWEEN(x, 0, dc.collen - 1)) return 1; if (!xloadcolor(x, name, &ncolor)) @@ -896,6 +936,12 @@ xsetcolorname(int x, const char *name) XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); dc.col[x] = ncolor; + if (x == defaultbg) { + dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00FFFFFF; + dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24; + } + return 0; } @@ -913,8 +959,8 @@ xclear(int x1, int y1, int x2, int y2) void xhints(void) { - XClassHint class = {opt_name ? opt_name : "st", - opt_class ? opt_class : "St"}; + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; XWMHints wm = {.flags = InputHint, .input = 1}; XSizeHints *sizeh; @@ -923,8 +969,8 @@ xhints(void) sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; sizeh->height = win.h; sizeh->width = win.w; - sizeh->height_inc = win.ch; - sizeh->width_inc = win.cw; + sizeh->height_inc = 1; + sizeh->width_inc = 1; sizeh->base_height = 2 * borderpx; sizeh->base_width = 2 * borderpx; sizeh->min_height = win.ch + 2 * borderpx; @@ -1068,7 +1114,8 @@ xloadfonts(const char *fontstr, double fontsize) FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); usedfontsize = 12; } - defaultfontsize = usedfontsize; + if (defaultfontsize <= 0) + defaultfontsize = usedfontsize; } if (xloadfont(&dc.font, pattern)) @@ -1078,7 +1125,7 @@ xloadfonts(const char *fontstr, double fontsize) FcPatternGetDouble(dc.font.match->pattern, FC_PIXEL_SIZE, 0, &fontval); usedfontsize = fontval; - if (fontsize == 0) + if (defaultfontsize <= 0 && fontsize == 0) defaultfontsize = fontval; } @@ -1087,121 +1134,26 @@ xloadfonts(const char *fontstr, double fontsize) win.ch = ceilf(dc.font.height * chscale); FcPatternDel(pattern, FC_SLANT); - if (!disableitalic) - FcPatternAddInteger(pattern, FC_SLANT, FRC_ITALIC); + if (!disableitalic) + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); if (xloadfont(&dc.ifont, pattern)) die("can't open font %s\n", fontstr); FcPatternDel(pattern, FC_WEIGHT); - if (!disablebold) - FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (!disablebold) + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); if (xloadfont(&dc.ibfont, pattern)) die("can't open font %s\n", fontstr); FcPatternDel(pattern, FC_SLANT); - if (!disableroman) - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (!disableroman) + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); if (xloadfont(&dc.bfont, pattern)) die("can't open font %s\n", fontstr); FcPatternDestroy(pattern); } -int -xloadsparefont(FcPattern *pattern, int flags) -{ - FcPattern *match; - FcResult result; - - match = FcFontMatch(NULL, pattern, &result); - if (!match) { - return 1; - } - - if (!(frc[frclen].font = XftFontOpenPattern(xw.dpy, match))) { - FcPatternDestroy(match); - return 1; - } - - frc[frclen].flags = flags; - /* Believe U+0000 glyph will present in each default font */ - frc[frclen].unicodep = 0; - frclen++; - - return 0; -} - -void -xloadsparefonts(void) -{ - FcPattern *pattern; - double sizeshift, fontval; - int fc; - char **fp; - - if (frclen != 0) - die("can't embed spare fonts. cache isn't empty"); - - /* Calculate count of spare fonts */ - fc = sizeof(font2) / sizeof(*font2); - if (fc == 0) - return; - - /* Allocate memory for cache entries. */ - if (frccap < 4 * fc) { - frccap += 4 * fc - frccap; - frc = xrealloc(frc, frccap * sizeof(Fontcache)); - } - - for (fp = font2; fp - font2 < fc; ++fp) { - - if (**fp == '-') - pattern = XftXlfdParse(*fp, False, False); - else - pattern = FcNameParse((FcChar8 *)*fp); - - if (!pattern) - die("can't open spare font %s\n", *fp); - - if (defaultfontsize > 0) { - sizeshift = usedfontsize - defaultfontsize; - if (sizeshift != 0 && - FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == - FcResultMatch) { - fontval += sizeshift; - FcPatternDel(pattern, FC_PIXEL_SIZE); - FcPatternDel(pattern, FC_SIZE); - FcPatternAddDouble(pattern, FC_PIXEL_SIZE, fontval); - } - } - - FcPatternAddBool(pattern, FC_SCALABLE, 1); - - FcConfigSubstitute(NULL, pattern, FcMatchPattern); - XftDefaultSubstitute(xw.dpy, xw.scr, pattern); - - if (xloadsparefont(pattern, FRC_NORMAL)) - die("can't open spare font %s\n", *fp); - - FcPatternDel(pattern, FC_SLANT); - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); - if (xloadsparefont(pattern, FRC_ITALIC)) - die("can't open spare font %s\n", *fp); - - FcPatternDel(pattern, FC_WEIGHT); - FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); - if (xloadsparefont(pattern, FRC_ITALICBOLD)) - die("can't open spare font %s\n", *fp); - - FcPatternDel(pattern, FC_SLANT); - FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); - if (xloadsparefont(pattern, FRC_BOLD)) - die("can't open spare font %s\n", *fp); - - FcPatternDestroy(pattern); - } -} - void xunloadfont(Font *f) { @@ -1214,9 +1166,6 @@ xunloadfont(Font *f) void xunloadfonts(void) { - /* Clear Harfbuzz font cache. */ - hbunloadfonts(); - /* Free the loaded fonts in the font cache. */ while (frclen > 0) XftFontClose(xw.dpy, frc[--frclen].font); @@ -1286,25 +1235,29 @@ xinit(int cols, int rows) { XGCValues gcvalues; Cursor cursor; - Window parent; + Window parent, root; pid_t thispid = getpid(); XColor xmousefg, xmousebg; XWindowAttributes attr; XVisualInfo vis; + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); xw.scr = XDefaultScreen(xw.dpy); - if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) { - parent = XRootWindow(xw.dpy, xw.scr); - xw.depth = 32; + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + + if (XMatchVisualInfo(xw.dpy, xw.scr, 32, TrueColor, &vis) != 0) { + xw.vis = vis.visual; + xw.depth = vis.depth; } else { XGetWindowAttributes(xw.dpy, parent, &attr); + xw.vis = attr.visual; xw.depth = attr.depth; } - XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); - xw.vis = vis.visual; - /* font */ if (!FcInit()) die("could not init fontconfig.\n"); @@ -1312,16 +1265,16 @@ xinit(int cols, int rows) usedfont = (opt_font == NULL)? font : opt_font; xloadfonts(usedfont, 0); - /* spare fonts */ - xloadsparefonts(); + /* Backup default alpha value */ + alpha_def = alpha; /* colors */ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); xloadcols(); /* adjust fixed window geometry */ - win.w = 2 * borderpx + cols * win.cw; - win.h = 2 * borderpx + rows * win.ch; + win.w = 2 * win.hborderpx + 2 * borderpx + cols * win.cw; + win.h = 2 * win.vborderpx + 2 * borderpx + rows * win.ch; if (xw.gm & XNegative) xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; if (xw.gm & YNegative) @@ -1340,11 +1293,15 @@ xinit(int cols, int rows) win.w, win.h, 0, xw.depth, InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); + if (parent != root) + XReparentWindow(xw.dpy, xw.win, parent, xw.l, xw.t); memset(&gcvalues, 0, sizeof(gcvalues)); gcvalues.graphics_exposures = False; - xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); - dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + xw.depth); XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); @@ -1402,13 +1359,14 @@ xinit(int cols, int rows) if (xsel.xtarget == None) xsel.xtarget = XA_STRING; - boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); + // Initialize the graphics (image display) module. + gr_init(xw.dpy, xw.vis, xw.cmap); } int xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) { - float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; + float winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, xp, yp; ushort mode, prevmode = USHRT_MAX; Font *font = &dc.font; int frcflags = FRC_NORMAL; @@ -1427,9 +1385,14 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x mode = glyphs[i].mode; /* Skip dummy wide-character spacing. */ - if (mode & ATTR_WDUMMY) + if (mode == ATTR_WDUMMY) continue; + /* Draw spaces for image placeholders (images will be drawn + * separately). */ + if (mode & ATTR_IMAGE) + rune = ' '; + /* Determine font for glyph if different from previous glyph. */ if (prevmode != mode) { prevmode = mode; @@ -1449,13 +1412,8 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x yp = winy + font->ascent; } - if (mode & ATTR_BOXDRAW) { - /* minor shoehorning: boxdraw uses only this ushort */ - glyphidx = boxdrawindex(&glyphs[i]); - } else { - /* Lookup character index with default font. */ - glyphidx = XftCharIndex(xw.dpy, font->match, rune); - } + /* Lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, rune); if (glyphidx) { specs[numspecs].font = font->match; specs[numspecs].glyph = glyphidx; @@ -1539,17 +1497,82 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x numspecs++; } - /* Harfbuzz transformation for ligatures. */ - hbtransform(specs, glyphs, len, x, y); - return numspecs; } +/* Draws a horizontal dashed line of length `w` starting at `(x, y)`. `wavelen` + * is the length of the dash plus the length of the gap. `fraction` is the + * fraction of the dash length compared to `wavelen`. */ +static void +xdrawunderdashed(Draw draw, Color *color, int x, int y, int w, + int wavelen, float fraction, int thick) +{ + int dashw = MAX(1, fraction * wavelen); + for (int i = x - x % wavelen; i < x + w; i += wavelen) { + int startx = MAX(i, x); + int endx = MIN(i + dashw, x + w); + if (startx < endx) + XftDrawRect(xw.draw, color, startx, y, endx - startx, + thick); + } +} + +/* Draws an undercurl. `h` is the total height, including line thickness. */ +static void +xdrawundercurl(Draw draw, Color *color, int x, int y, int w, int h, int thick) +{ + XGCValues gcvals = {.foreground = color->pixel, + .line_width = thick, + .line_style = LineSolid, + .cap_style = CapRound}; + GC gc = XCreateGC(xw.dpy, XftDrawDrawable(xw.draw), + GCForeground | GCLineWidth | GCLineStyle | GCCapStyle, + &gcvals); + + XRectangle clip = {.x = x, .y = y, .width = w, .height = h}; + XSetClipRectangles(xw.dpy, gc, 0, 0, &clip, 1, Unsorted); + + int yoffset = thick / 2; + int segh = MAX(1, h - thick); + /* Make sure every segment is at a 45 degree angle, otherwise it doesn't + * look good without antialiasing. */ + int segw = segh; + int wavelen = MAX(1, segw * 2); + + for (int i = x - (x % wavelen); i < x + w; i += wavelen) { + XPoint points[3] = {{.x = i, .y = y + yoffset}, + {.x = i + segw, .y = y + yoffset + segh}, + {.x = i + wavelen, .y = y + yoffset}}; + XDrawLines(xw.dpy, XftDrawDrawable(xw.draw), gc, points, 3, + CoordModeOrigin); + } + + XFreeGC(xw.dpy, gc); +} + +void +chgalpha(const Arg *arg) +{ + if (arg->f == -1.0f && alpha >= 0.1f) + alpha -= 0.1f; + else if (arg->f == 1.0f && alpha < 1.0f) + alpha += 0.1f; + else if (arg->f == 0.0f) + alpha = alpha_def; + else + return; + + dc.col[defaultbg].color.alpha = (unsigned short)(0xFFFF * alpha); + /* Required to remove artifacting from borderpx */ + cresize(0, 0); + redraw(); +} + void xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) { int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); - int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + int winx = win.hborderpx + x * win.cw, winy = win.vborderpx + y * win.ch, width = charlen * win.cw; Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; XRenderColor colfg, colbg; @@ -1639,17 +1662,17 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i /* Intelligent cleaning up of the borders. */ if (x == 0) { - xclear(0, (y == 0)? 0 : winy, borderpx, + xclear(0, (y == 0)? 0 : winy, win.hborderpx, winy + win.ch + - ((winy + win.ch >= borderpx + win.th)? win.h : 0)); + ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0)); } - if (winx + width >= borderpx + win.tw) { + if (winx + width >= win.hborderpx + win.tw) { xclear(winx + width, (y == 0)? 0 : winy, win.w, - ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch))); + ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch))); } if (y == 0) - xclear(winx, 0, winx + width, borderpx); - if (winy + win.ch >= borderpx + win.th) + xclear(winx, 0, winx + width, win.vborderpx); + if (winy + win.ch >= win.vborderpx + win.th) xclear(winx, winy + win.ch, winx + width, win.h); /* Clean up the region we want to draw to. */ @@ -1662,22 +1685,68 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i r.width = width; XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); - if (base.mode & ATTR_BOXDRAW) { - drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len); + /* Decoration color. */ + Color decor; + uint32_t decorcolor = tgetdecorcolor(&base); + if (decorcolor == DECOR_DEFAULT_COLOR) { + decor = *fg; + } else if (IS_TRUECOL(decorcolor)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(decorcolor); + colfg.green = TRUEGREEN(decorcolor); + colfg.blue = TRUEBLUE(decorcolor); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &decor); } else { - /* Render the glyphs. */ - XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + decor = dc.col[decorcolor]; } - - /* Render underline and strikethrough. */ + decor.color.alpha = 0xffff; + decor.pixel |= 0xff << 24; + + /* Float thickness, used as a base to compute other values. */ + float fthick = dc.font.height / 18.0; + /* Integer thickness in pixels. Must not be 0. */ + int thick = MAX(1, roundf(fthick)); + /* The default gap between the baseline and a single underline. */ + int gap = roundf(fthick * 2); + /* The total thickness of a double underline. */ + int doubleh = thick * 2 + ceilf(fthick * 0.5); + /* The total thickness of an undercurl. */ + int curlh = thick * 2 + roundf(fthick * 0.75); + + /* Render the underline before the glyphs. */ if (base.mode & ATTR_UNDERLINE) { - XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, - width, 1); + uint32_t style = tgetdecorstyle(&base); + int liney = winy + dc.font.ascent + gap; + /* Adjust liney to guarantee that a single underline fits. */ + liney -= MAX(0, liney + thick - (winy + win.ch)); + if (style == UNDERLINE_DOUBLE) { + liney -= MAX(0, liney + doubleh - (winy + win.ch)); + XftDrawRect(xw.draw, &decor, winx, liney, width, thick); + XftDrawRect(xw.draw, &decor, winx, + liney + doubleh - thick, width, thick); + } else if (style == UNDERLINE_DOTTED) { + xdrawunderdashed(xw.draw, &decor, winx, liney, width, + thick * 2, 0.5, thick); + } else if (style == UNDERLINE_DASHED) { + int wavelen = MAX(2, win.cw * 0.9); + xdrawunderdashed(xw.draw, &decor, winx, liney, width, + wavelen, 0.65, thick); + } else if (style == UNDERLINE_CURLY) { + liney -= MAX(0, liney + curlh - (winy + win.ch)); + xdrawundercurl(xw.draw, &decor, winx, liney, width, + curlh, thick); + } else { + XftDrawRect(xw.draw, &decor, winx, liney, width, thick); + } } + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render strikethrough. Alway use the fg color. */ if (base.mode & ATTR_STRUCK) { - XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, - width, 1); + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, + width, thick); } /* Reset clip to none. */ @@ -1692,28 +1761,34 @@ xdrawglyph(Glyph g, int x, int y) numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); xdrawglyphfontspecs(&spec, g, numspecs, x, y); + if (g.mode & ATTR_IMAGE) { + gr_start_drawing(xw.buf, win.cw, win.ch); + xdrawoneimagecell(g, x, y); + gr_finish_drawing(xw.buf); + } } void -xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) { Color drawcol; /* remove the old cursor */ if (selected(ox, oy)) og.mode ^= ATTR_REVERSE; - - /* Redraw the line where cursor was previously. - * It will restore the ligatures broken by the cursor. */ - xdrawline(line, 0, oy, len); + xdrawglyph(og, ox, oy); if (IS_SET(MODE_HIDE)) return; + // If it's an image, just draw a ballot box for simplicity. + if (g.mode & ATTR_IMAGE) + g.u = 0x2610; + /* * Select the right color for the right mode. */ - g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW; + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; if (IS_SET(MODE_REVERSE)) { g.mode |= ATTR_REVERSE; @@ -1750,39 +1825,167 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int le case 3: /* Blinking Underline */ case 4: /* Steady Underline */ XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + (cy + 1) * win.ch - \ + win.hborderpx + cx * win.cw, + win.vborderpx + (cy + 1) * win.ch - \ cursorthickness, win.cw, cursorthickness); break; case 5: /* Blinking bar */ case 6: /* Steady bar */ XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + cy * win.ch, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, cursorthickness, win.ch); break; } } else { XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + cy * win.ch, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, win.cw - 1, 1); XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + cy * win.ch, + win.hborderpx + cx * win.cw, + win.vborderpx + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, - borderpx + (cx + 1) * win.cw - 1, - borderpx + cy * win.ch, + win.hborderpx + (cx + 1) * win.cw - 1, + win.vborderpx + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, - borderpx + cx * win.cw, - borderpx + (cy + 1) * win.ch - 1, + win.hborderpx + cx * win.cw, + win.vborderpx + (cy + 1) * win.ch - 1, win.cw, 1); } } +/* Draw (or queue for drawing) image cells between columns x1 and x2 assuming + * that they have the same attributes (and thus the same lower 24 bits of the + * image ID and the same placement ID). */ +void +xdrawimages(Glyph base, Line line, int x1, int y1, int x2) { + int y_pix = win.vborderpx + y1 * win.ch; + uint32_t image_id_24bits = base.fg & 0xFFFFFF; + uint32_t placement_id = tgetimgplacementid(&base); + // Columns and rows are 1-based, 0 means unspecified. + int last_col = 0; + int last_row = 0; + int last_start_col = 0; + int last_start_x = x1; + // The most significant byte is also 1-base, subtract 1 before use. + uint32_t last_id_4thbyteplus1 = 0; + // We may need to inherit row/column/4th byte from the previous cell. + Glyph *prev = &line[x1 - 1]; + if (x1 > 0 && (prev->mode & ATTR_IMAGE) && + (prev->fg & 0xFFFFFF) == image_id_24bits && + prev->decor == base.decor) { + last_row = tgetimgrow(prev); + last_col = tgetimgcol(prev); + last_id_4thbyteplus1 = tgetimgid4thbyteplus1(prev); + last_start_col = last_col + 1; + } + for (int x = x1; x < x2; ++x) { + Glyph *g = &line[x]; + uint32_t cur_row = tgetimgrow(g); + uint32_t cur_col = tgetimgcol(g); + uint32_t cur_id_4thbyteplus1 = tgetimgid4thbyteplus1(g); + uint32_t num_diacritics = tgetimgdiacriticcount(g); + // If the row is not specified, assume it's the same as the row + // of the previous cell. Note that `cur_row` may contain a + // value imputed earlier, which will be preserved if `last_row` + // is zero (i.e. we don't know the row of the previous cell). + if (last_row && (num_diacritics == 0 || !cur_row)) + cur_row = last_row; + // If the column is not specified and the row is the same as the + // row of the previous cell, then assume that the column is the + // next one. + if (last_col && (num_diacritics <= 1 || !cur_col) && + cur_row == last_row) + cur_col = last_col + 1; + // If the additional id byte is not specified and the + // coordinates are consecutive, assume the byte is also the + // same. + if (last_id_4thbyteplus1 && + (num_diacritics <= 2 || !cur_id_4thbyteplus1) && + cur_row == last_row && cur_col == last_col + 1) + cur_id_4thbyteplus1 = last_id_4thbyteplus1; + // If we couldn't infer row and column, start from the top left + // corner. + if (cur_row == 0) + cur_row = 1; + if (cur_col == 0) + cur_col = 1; + // If this cell breaks a contiguous stripe of image cells, draw + // that line and start a new one. + if (cur_col != last_col + 1 || cur_row != last_row || + cur_id_4thbyteplus1 != last_id_4thbyteplus1) { + uint32_t image_id = image_id_24bits; + if (last_id_4thbyteplus1) + image_id |= (last_id_4thbyteplus1 - 1) << 24; + if (last_row != 0) { + int x_pix = + win.hborderpx + last_start_x * win.cw; + gr_append_imagerect( + xw.buf, image_id, placement_id, + last_start_col - 1, last_col, + last_row - 1, last_row, last_start_x, + y1, x_pix, y_pix, win.cw, win.ch, + base.mode & ATTR_REVERSE); + } + last_start_col = cur_col; + last_start_x = x; + } + last_row = cur_row; + last_col = cur_col; + last_id_4thbyteplus1 = cur_id_4thbyteplus1; + // Populate the missing glyph data to enable inheritance between + // runs and support the naive implementation of tgetimgid. + if (!tgetimgrow(g)) + tsetimgrow(g, cur_row); + // We cannot save this information if there are > 511 cols. + if (!tgetimgcol(g) && (cur_col & ~0x1ff) == 0) + tsetimgcol(g, cur_col); + if (!tgetimgid4thbyteplus1(g)) + tsetimg4thbyteplus1(g, cur_id_4thbyteplus1); + } + uint32_t image_id = image_id_24bits; + if (last_id_4thbyteplus1) + image_id |= (last_id_4thbyteplus1 - 1) << 24; + // Draw the last contiguous stripe. + if (last_row != 0) { + int x_pix = win.hborderpx + last_start_x * win.cw; + gr_append_imagerect(xw.buf, image_id, placement_id, + last_start_col - 1, last_col, last_row - 1, + last_row, last_start_x, y1, x_pix, y_pix, + win.cw, win.ch, base.mode & ATTR_REVERSE); + } +} + +/* Draw just one image cell without inheriting attributes from the left. */ +void xdrawoneimagecell(Glyph g, int x, int y) { + if (!(g.mode & ATTR_IMAGE)) + return; + int x_pix = win.hborderpx + x * win.cw; + int y_pix = win.vborderpx + y * win.ch; + uint32_t row = tgetimgrow(&g) - 1; + uint32_t col = tgetimgcol(&g) - 1; + uint32_t placement_id = tgetimgplacementid(&g); + uint32_t image_id = tgetimgid(&g); + gr_append_imagerect(xw.buf, image_id, placement_id, col, col + 1, row, + row + 1, x, y, x_pix, y_pix, win.cw, win.ch, + g.mode & ATTR_REVERSE); +} + +/* Prepare for image drawing. */ +void xstartimagedraw(int *dirty, int rows) { + gr_start_drawing(xw.buf, win.cw, win.ch); + gr_mark_dirty_animations(dirty, rows); +} + +/* Draw all queued image cells. */ +void xfinishimagedraw() { + gr_finish_drawing(xw.buf); +} + void xsetenv(void) { @@ -1798,6 +2001,9 @@ xseticontitle(char *p) XTextProperty prop; DEFAULT(p, opt_title); + if (p[0] == '\0') + p = opt_title; + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop) != Success) return; @@ -1812,6 +2018,9 @@ xsettitle(char *p) XTextProperty prop; DEFAULT(p, opt_title); + if (p[0] == '\0') + p = opt_title; + if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop) != Success) return; @@ -1843,6 +2052,8 @@ xdrawline(Line line, int x1, int y1, int x2) new.mode ^= ATTR_REVERSE; if (i > 0 && ATTRCMP(base, new)) { xdrawglyphfontspecs(specs, base, i, ox, y1); + if (base.mode & ATTR_IMAGE) + xdrawimages(base, line, ox, y1, x); specs += i; numspecs -= i; i = 0; @@ -1855,6 +2066,8 @@ xdrawline(Line line, int x1, int y1, int x2) } if (i > 0) xdrawglyphfontspecs(specs, base, i, ox, y1); + if (i > 0 && base.mode & ATTR_IMAGE) + xdrawimages(base, line, ox, y1, x); } void @@ -1958,22 +2171,12 @@ focus(XEvent *ev) xseturgency(0); if (IS_SET(MODE_FOCUS)) ttywrite("\033[I", 3, 0); - if (!focused) { - focused = 1; - xloadcols(); - tfulldirt(); - } } else { if (xw.ime.xic) XUnsetICFocus(xw.ime.xic); win.mode &= ~MODE_FOCUSED; if (IS_SET(MODE_FOCUS)) ttywrite("\033[O", 3, 0); - if (focused) { - focused = 0; - xloadcols(); - tfulldirt(); - } } } @@ -2024,11 +2227,9 @@ void kpress(XEvent *ev) { XKeyEvent *e = &ev->xkey; - KeySym ksym; - char *buf = NULL, *customkey; - int len = 0; - int buf_size = 64; - int critical = - 1; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; Rune c; Status status; Shortcut *bp; @@ -2036,44 +2237,30 @@ kpress(XEvent *ev) if (IS_SET(MODE_KBDLOCK)) return; -reallocbuf: - if (critical > 0) - goto cleanup; - if (buf) - free(buf); - - buf = xmalloc((buf_size) * sizeof(char)); - critical += 1; - if (xw.ime.xic) { - len = XmbLookupString(xw.ime.xic, e, buf, buf_size, &ksym, &status); - if (status == XBufferOverflow) { - buf_size = len; - goto reallocbuf; - } + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; } else { - // Not sure how to fix this and if it is fixable - // but at least it does write something into the buffer - // so it is not as critical - len = XLookupString(e, buf, buf_size, &ksym, NULL); + len = XLookupString(e, buf, sizeof buf, &ksym, NULL); } /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { if (ksym == bp->keysym && match(bp->mod, e->state)) { bp->func(&(bp->arg)); - goto cleanup; + return; } } /* 2. custom keys from config.h */ if ((customkey = kmap(ksym, e->state))) { ttywrite(customkey, strlen(customkey), 1); - goto cleanup; + return; } /* 3. composed string from input method */ if (len == 0) - goto cleanup; + return; if (len == 1 && e->state & Mod1Mask) { if (IS_SET(MODE_8BIT)) { if (*buf < 0177) { @@ -2086,11 +2273,7 @@ reallocbuf: len = 2; } } - if (len <= buf_size) - ttywrite(buf, len, 1); -cleanup: - if (buf) - free(buf); + ttywrite(buf, len, 1); } void @@ -2109,6 +2292,7 @@ cmessage(XEvent *e) } } else if (e->xclient.data.l[0] == xw.wmdeletewin) { ttyhangup(); + gr_deinit(); exit(0); } } @@ -2159,6 +2343,13 @@ run(void) if (XPending(xw.dpy)) timeout = 0; /* existing events might not set xfd */ + /* Decrease the timeout if there are active animations. */ + if (graphics_next_redraw_delay != INT_MAX && + IS_SET(MODE_VISIBLE)) + timeout = timeout < 0 ? graphics_next_redraw_delay + : MIN(timeout, + graphics_next_redraw_delay); + seltv.tv_sec = timeout / 1E3; seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); tv = timeout >= 0 ? &seltv : NULL; @@ -2225,59 +2416,6 @@ run(void) } } -int -resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) -{ - char **sdst = dst; - int *idst = dst; - float *fdst = dst; - - char fullname[256]; - char fullclass[256]; - char *type; - XrmValue ret; - - snprintf(fullname, sizeof(fullname), "%s.%s", - opt_name ? opt_name : "st", name); - snprintf(fullclass, sizeof(fullclass), "%s.%s", - opt_class ? opt_class : "St", name); - fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; - - XrmGetResource(db, fullname, fullclass, &type, &ret); - if (ret.addr == NULL || strncmp("String", type, 64)) - return 1; - - switch (rtype) { - case STRING: - *sdst = ret.addr; - break; - case INTEGER: - *idst = strtoul(ret.addr, NULL, 10); - break; - case FLOAT: - *fdst = strtof(ret.addr, NULL); - break; - } - return 0; -} - -void -config_init(void) -{ - char *resm; - XrmDatabase db; - ResourcePref *p; - - XrmInitialize(); - resm = XResourceManagerString(xw.dpy); - if (!resm) - return; - - db = XrmGetStringDatabase(resm); - for (p = resources; p < resources + LEN(resources); p++) - resource_load(db, p->name, p->type, p->dst); -} - void usage(void) { @@ -2303,7 +2441,8 @@ main(int argc, char *argv[]) allowaltscreen = 0; break; case 'A': - opt_alpha = EARGF(usage()); + alpha = strtof(EARGF(usage()), NULL); + LIMIT(alpha, 0.0, 1.0); break; case 'c': opt_class = EARGF(usage()); @@ -2354,15 +2493,8 @@ run: setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); - - if(!(xw.dpy = XOpenDisplay(NULL))) - die("Can't open display\n"); - - config_init(); cols = MAX(cols, 1); rows = MAX(rows, 1); - defaultbg = MAX(LEN(colorname), 256); - alphaUnfocus = alpha-alphaOffset; tnew(cols, rows); xinit(cols, rows); xsetenv(); @@ -2371,4 +2503,3 @@ run: return 0; } - -- cgit v1.2.3