summaryrefslogtreecommitdiff
path: root/x.c
diff options
context:
space:
mode:
authorZachIR <zachir@librem.one>2025-07-08 21:48:39 -0500
committerZachIR <zachir@librem.one>2025-07-08 21:48:39 -0500
commitcbffff166748e7dada4b66ed1c4f10f495eefa97 (patch)
treee5b15a872bb79b4beb56c8177b229c58b356bdb5 /x.c
parentd79cc4cea16572f7f80c3842031406172630f697 (diff)
Add in st-graphics repo
Diffstat (limited to 'x.c')
-rw-r--r--x.c768
1 files changed, 588 insertions, 180 deletions
diff --git a/x.c b/x.c
index b0819ac..919495a 100644
--- a/x.c
+++ b/x.c
@@ -4,6 +4,8 @@
#include <limits.h>
#include <locale.h>
#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
@@ -19,6 +21,7 @@ char *argv0;
#include "arg.h"
#include "st.h"
#include "win.h"
+#include "graphics.h"
#include "hb.h"
/* types used in config.h */
@@ -60,6 +63,14 @@ static void zoom(const Arg *);
static void zoomabs(const Arg *);
static void zoomreset(const Arg *);
static void ttysend(const Arg *);
+void kscrollup(const Arg *);
+void kscrolldown(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"
@@ -82,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 */
@@ -145,8 +157,10 @@ typedef struct {
static inline ushort sixd_to_16bit(int);
static void xresetfontsettings(ushort mode, Font **font, int *frcflags);
static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);
-static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int, int);
+static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, 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 *);
@@ -223,6 +237,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 {
@@ -246,7 +261,6 @@ static char *usedfont = NULL;
static double usedfontsize = 0;
static double defaultfontsize = 0;
-static char *opt_alpha = NULL;
static char *opt_class = NULL;
static char **opt_cmd = NULL;
static char *opt_embed = NULL;
@@ -332,10 +346,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;
}
@@ -343,7 +419,7 @@ 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;
}
@@ -456,6 +532,9 @@ 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);
+ 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 &&
@@ -743,6 +822,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);
@@ -817,9 +899,6 @@ xloadcols(void)
die("could not allocate color %d\n", i);
}
- /* set alpha value of bg color */
- if (opt_alpha)
- alpha = strtof(opt_alpha, NULL);
dc.col[defaultbg].color.alpha = (unsigned short)(0xffff * alpha);
dc.col[defaultbg].pixel &= 0x00FFFFFF;
dc.col[defaultbg].pixel |= (unsigned char)(0xff * alpha) << 24;
@@ -853,6 +932,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;
}
@@ -880,8 +965,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;
@@ -1025,7 +1110,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))
@@ -1035,7 +1121,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;
}
@@ -1145,7 +1231,7 @@ xinit(int cols, int rows)
{
XGCValues gcvalues;
Cursor cursor;
- Window parent;
+ Window parent, root;
pid_t thispid = getpid();
XColor xmousefg, xmousebg;
XWindowAttributes attr;
@@ -1155,17 +1241,19 @@ xinit(int cols, int rows)
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");
@@ -1178,8 +1266,8 @@ xinit(int cols, int rows)
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)
@@ -1198,11 +1286,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);
@@ -1259,6 +1351,11 @@ xinit(int cols, int rows)
xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);
if (xsel.xtarget == None)
xsel.xtarget = XA_STRING;
+
+ // Initialize the graphics (image display) module.
+ gr_init(xw.dpy, xw.vis, xw.cmap);
+
+ boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis);
}
void
@@ -1280,135 +1377,230 @@ xresetfontsettings(ushort mode, Font **font, int *frcflags)
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;
- ushort mode = glyphs[0].mode & ~ATTR_WRAP;
+ 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;
- float runewidth = win.cw * ((glyphs[0].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ float runewidth = win.cw;
Rune rune;
FT_UInt glyphidx;
FcResult fcres;
FcPattern *fcpattern, *fontpattern;
FcFontSet *fcsets[] = { NULL };
FcCharSet *fccharset;
- int f, code_idx, numspecs = 0;
+ int i, f, length = 0, start = 0, numspecs = 0;
float cluster_xp = xp, cluster_yp = yp;
HbTransformData shaped = { 0 };
/* Initial values. */
+ mode = prevmode = glyphs[0].mode & ~ATTR_WRAP;
xresetfontsettings(mode, &font, &frcflags);
- /* Shape the segment. */
- hbtransform(&shaped, font->match, glyphs, 0, len);
- xp = winx; yp = winy + font->ascent;
- cluster_xp = xp; cluster_yp = yp;
+ for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {
+ mode = glyphs[i].mode & ~ATTR_WRAP;
- for (code_idx = 0; code_idx < shaped.count; code_idx++) {
- int idx = shaped.glyphs[code_idx].cluster;
-
- if (glyphs[idx].mode & ATTR_WDUMMY)
+ /* Skip dummy wide-character spacing. */
+ if (mode & ATTR_WDUMMY && i < (len - 1))
continue;
- /* Advance the drawing cursor if we've moved to a new cluster */
- if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
- xp += runewidth;
- cluster_xp = xp;
- cluster_yp = yp;
- }
-
- if (shaped.glyphs[code_idx].codepoint != 0) {
- /* If symbol is found, put it into the specs. */
- specs[numspecs].font = font->match;
- specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
- specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
- specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
- cluster_xp += shaped.positions[code_idx].x_advance / 64.;
- cluster_yp += shaped.positions[code_idx].y_advance / 64.;
- numspecs++;
- } else {
- /* If it's not found, try to fetch it through the font cache. */
- rune = glyphs[idx].u;
- for (f = 0; f < frclen; f++) {
- glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
- /* Everything correct. */
- if (glyphidx && frc[f].flags == frcflags)
- break;
- /* We got a default font for a not found glyph. */
- if (!glyphidx && frc[f].flags == frcflags
- && frc[f].unicodep == rune) {
- break;
- }
+ if (
+ prevmode != mode
+ || ATTRCMP(glyphs[start], glyphs[i])
+ || selected(x + i, y) != selected(x + start, y)
+ || i == (len - 1)
+ ) {
+ /* Handle 1-character wide segments and end of line */
+ length = i - start;
+ if (i == start) {
+ length = 1;
+ } else if (i == (len - 1)) {
+ length = (i - start + 1);
}
- /* Nothing was found. Use fontconfig to find matching font. */
- if (f >= frclen) {
- if (!font->set)
- font->set = FcFontSort(0, font->pattern,
- 1, 0, &fcres);
- fcsets[0] = font->set;
-
- /*
- * Nothing was found in the cache. Now use
- * some dozen of Fontconfig calls to get the
- * font for one single character.
- *
- * Xft and fontconfig are design failures.
- */
- fcpattern = FcPatternDuplicate(font->pattern);
- fccharset = FcCharSetCreate();
-
- FcCharSetAddChar(fccharset, rune);
- FcPatternAddCharSet(fcpattern, FC_CHARSET,
- fccharset);
- FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
-
- FcConfigSubstitute(0, fcpattern,
- FcMatchPattern);
- FcDefaultSubstitute(fcpattern);
-
- fontpattern = FcFontSetMatch(0, fcsets, 1,
- fcpattern, &fcres);
-
- /* Allocate memory for the new cache entry. */
- if (frclen >= frccap) {
- frccap += 16;
- frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ /* Shape the segment. */
+ hbtransform(&shaped, font->match, glyphs, start, length);
+ runewidth = win.cw * ((glyphs[start].mode & ATTR_WIDE) ? 2.0f : 1.0f);
+ cluster_xp = xp; cluster_yp = yp;
+ for (int code_idx = 0; code_idx < shaped.count; code_idx++) {
+ int idx = shaped.glyphs[code_idx].cluster;
+
+ if (glyphs[start + idx].mode & ATTR_WDUMMY)
+ continue;
+
+ /* Draw spaces for image placeholders (images
+ * will be drawn separately). */
+ if (glyphs[start + idx].mode & ATTR_IMAGE)
+ continue;
+
+ /* Advance the drawing cursor if we've moved to a new cluster */
+ if (code_idx > 0 && idx != shaped.glyphs[code_idx - 1].cluster) {
+ xp += runewidth;
+ cluster_xp = xp;
+ cluster_yp = yp;
+ runewidth = win.cw * ((glyphs[start + idx].mode & ATTR_WIDE) ? 2.0f : 1.0f);
}
- frc[frclen].font = XftFontOpenPattern(xw.dpy,
- fontpattern);
- if (!frc[frclen].font)
- die("XftFontOpenPattern failed seeking fallback font: %s\n",
- strerror(errno));
- frc[frclen].flags = frcflags;
- frc[frclen].unicodep = rune;
-
- glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
+ if (glyphs[start + idx].mode & ATTR_BOXDRAW) {
+ /* minor shoehorning: boxdraw uses only this ushort */
+ specs[numspecs].font = font->match;
+ specs[numspecs].glyph = boxdrawindex(&glyphs[start + idx]);
+ specs[numspecs].x = xp;
+ specs[numspecs].y = yp;
+ numspecs++;
+ } else if (shaped.glyphs[code_idx].codepoint != 0) {
+ /* If symbol is found, put it into the specs. */
+ specs[numspecs].font = font->match;
+ specs[numspecs].glyph = shaped.glyphs[code_idx].codepoint;
+ specs[numspecs].x = cluster_xp + (short)(shaped.positions[code_idx].x_offset / 64.);
+ specs[numspecs].y = cluster_yp - (short)(shaped.positions[code_idx].y_offset / 64.);
+ cluster_xp += shaped.positions[code_idx].x_advance / 64.;
+ cluster_yp += shaped.positions[code_idx].y_advance / 64.;
+ numspecs++;
+ } else {
+ /* If it's not found, try to fetch it through the font cache. */
+ rune = glyphs[start + idx].u;
+ for (f = 0; f < frclen; f++) {
+ glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);
+ /* Everything correct. */
+ if (glyphidx && frc[f].flags == frcflags)
+ break;
+ /* We got a default font for a not found glyph. */
+ if (!glyphidx && frc[f].flags == frcflags
+ && frc[f].unicodep == rune) {
+ break;
+ }
+ }
+
+ /* Nothing was found. Use fontconfig to find matching font. */
+ if (f >= frclen) {
+ if (!font->set)
+ font->set = FcFontSort(0, font->pattern, 1, 0, &fcres);
+ fcsets[0] = font->set;
+
+ /*
+ * Nothing was found in the cache. Now use
+ * some dozen of Fontconfig calls to get the
+ * font for one single character.
+ *
+ * Xft and fontconfig are design failures.
+ */
+ fcpattern = FcPatternDuplicate(font->pattern);
+ fccharset = FcCharSetCreate();
+
+ FcCharSetAddChar(fccharset, rune);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET,
+ fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, 1);
+
+ FcConfigSubstitute(0, fcpattern,
+ FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+
+ fontpattern = FcFontSetMatch(0, fcsets, 1,
+ fcpattern, &fcres);
+
+ /* Allocate memory for the new cache entry. */
+ if (frclen >= frccap) {
+ frccap += 16;
+ frc = xrealloc(frc, frccap * sizeof(Fontcache));
+ }
+
+ frc[frclen].font = XftFontOpenPattern(xw.dpy,
+ fontpattern);
+ if (!frc[frclen].font)
+ die("XftFontOpenPattern failed seeking fallback font: %s\n",
+ strerror(errno));
+ frc[frclen].flags = frcflags;
+ frc[frclen].unicodep = rune;
+
+ glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);
+
+ f = frclen;
+ frclen++;
+
+ FcPatternDestroy(fcpattern);
+ FcCharSetDestroy(fccharset);
+ }
+
+ specs[numspecs].font = frc[f].font;
+ specs[numspecs].glyph = glyphidx;
+ specs[numspecs].x = (short)xp;
+ specs[numspecs].y = (short)yp;
+ numspecs++;
+ }
+ }
- f = frclen;
- frclen++;
+ /* Cleanup and get ready for next segment. */
+ hbcleanup(&shaped);
+ start = i;
- FcPatternDestroy(fcpattern);
- FcCharSetDestroy(fccharset);
+ /* Determine font for glyph if different from previous glyph. */
+ if (prevmode != mode) {
+ prevmode = mode;
+ xresetfontsettings(mode, &font, &frcflags);
+ yp = winy + font->ascent;
}
-
- specs[numspecs].font = frc[f].font;
- specs[numspecs].glyph = glyphidx;
- specs[numspecs].x = (short)xp;
- specs[numspecs].y = (short)yp;
- numspecs++;
}
}
- /* Cleanup and get ready for next segment. */
hbcleanup(&shaped);
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
-xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen)
+xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen, int dmode)
{
- 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;
@@ -1497,46 +1689,105 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
fg = bg;
/* Intelligent cleaning up of the borders. */
- if (x == 0) {
- xclear(0, (y == 0)? 0 : winy, borderpx,
- winy + win.ch +
- ((winy + win.ch >= borderpx + win.th)? win.h : 0));
- }
- if (winx + width >= borderpx + win.tw) {
- xclear(winx + width, (y == 0)? 0 : winy, win.w,
- ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));
+ if (dmode & DRAW_BG) {
+ if (x == 0) {
+ xclear(0, (y == 0)? 0 : winy, win.hborderpx,
+ winy + win.ch +
+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : 0));
+ }
+ if (winx + width >= win.hborderpx + win.tw) {
+ xclear(winx + width, (y == 0)? 0 : winy, win.w,
+ ((winy + win.ch >= win.vborderpx + win.th)? win.h : (winy + win.ch)));
+ }
+ if (y == 0)
+ 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. */
+ XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
}
- if (y == 0)
- xclear(winx, 0, winx + width, borderpx);
- if (winy + win.ch >= borderpx + win.th)
- xclear(winx, winy + win.ch, winx + width, win.h);
- /* Clean up the region we want to draw to. */
- XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);
+ if (!(dmode & DRAW_FG))
+ return;
- /* Set the clip region because Xft is sometimes dirty. */
+ /* Set the clip region only for the underline. */
r.x = 0;
r.y = 0;
r.height = win.ch;
r.width = width;
XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);
- /* Render the glyphs. */
- XftDrawGlyphFontSpec(xw.draw, fg, specs, len);
-
- /* Render underline and strikethrough. */
- if (base.mode & ATTR_UNDERLINE) {
- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
- width, 1);
+ /* 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 {
+ decor = dc.col[decorcolor];
}
-
- if (base.mode & ATTR_STRUCK) {
- XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
- width, 1);
+ 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) {
+ 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);
+ }
}
/* Reset clip to none. */
XftDrawSetClip(xw.draw, 0);
+
+ if (base.mode & ATTR_BOXDRAW) {
+ drawboxes(winx, winy, width / len, win.ch, fg, bg, specs, len);
+ } else {
+ /* 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 / 3,
+ width, thick);
+ }
}
void
@@ -1546,7 +1797,12 @@ xdrawglyph(Glyph g, int x, int y)
XftGlyphFontSpec *specs = xw.specbuf;
numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y);
- xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1);
+ xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1, DRAW_BG | DRAW_FG);
+ if (g.mode & ATTR_IMAGE) {
+ gr_start_drawing(xw.buf, win.cw, win.ch);
+ xdrawoneimagecell(g, x, y);
+ gr_finish_drawing(xw.buf);
+ }
}
void
@@ -1560,15 +1816,21 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int le
/* Redraw the line where cursor was previously.
* It will restore the ligatures broken by the cursor. */
+ gr_start_drawing(xw.buf, win.cw, win.ch);
xdrawline(line, 0, oy, len);
+ gr_finish_drawing(xw.buf);
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;
+ g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE|ATTR_BOXDRAW;
if (IS_SET(MODE_REVERSE)) {
g.mode |= ATTR_REVERSE;
@@ -1605,39 +1867,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)
{
@@ -1692,29 +2082,38 @@ xdrawline(Line line, int x1, int y1, int x2)
{
int i, x, ox, numspecs;
Glyph base, new;
- XftGlyphFontSpec *specs = xw.specbuf;
-
- i = ox = 0;
- for (x = x1; x < x2; x++) {
- new = line[x];
- if (new.mode == ATTR_WDUMMY)
- continue;
- if (selected(x, y1))
- new.mode ^= ATTR_REVERSE;
- if ((i > 0) && ATTRCMP(base, new)) {
- numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
- xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox);
- i = 0;
+ XftGlyphFontSpec *specs;
+
+ /* Draw line in 2 passes: background and foreground. This way wide
+ * glyphs won't get truncated (#223) */
+ for (int dmode = DRAW_BG; dmode <= DRAW_FG; dmode <<= 1) {
+ specs = xw.specbuf;
+ i = ox = 0;
+ for (x = x1; x < x2; x++) {
+ new = line[x];
+ if (new.mode == ATTR_WDUMMY)
+ continue;
+ if (selected(x, y1))
+ new.mode ^= ATTR_REVERSE;
+ if (i > 0 && ATTRCMP(base, new)) {
+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y1);
+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x - ox, dmode);
+ if (base.mode & ATTR_IMAGE && dmode == DRAW_FG)
+ xdrawimages(base, line, ox, y1, x);
+ i = 0;
+ }
+ if (i == 0) {
+ ox = x;
+ base = new;
+ }
+ i++;
}
- if (i == 0) {
- ox = x;
- base = new;
+ if (i > 0) {
+ numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
+ xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox, dmode);
}
- i++;
- }
- if (i > 0) {
- numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1);
- xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox);
+ if (i > 0 && base.mode & ATTR_IMAGE && dmode == DRAW_FG)
+ xdrawimages(base, line, ox, y1, x);
}
}
@@ -1940,6 +2339,7 @@ cmessage(XEvent *e)
}
} else if (e->xclient.data.l[0] == xw.wmdeletewin) {
ttyhangup();
+ gr_deinit();
exit(0);
}
}
@@ -1990,6 +2390,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;
@@ -2081,7 +2488,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());