From f4100cdc221e52cef8e835b6bbaae32b0caaea6b Mon Sep 17 00:00:00 2001 From: doryan Date: Sat, 14 Sep 2024 23:39:50 +0400 Subject: [PATCH] feat(emojies): add support --- .gitignore | 2 + config.def.h | 5 +- config.h | 5 +- st | Bin 115480 -> 115624 bytes x.c | 101 +++ x.c.orig | 2183 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 2294 insertions(+), 2 deletions(-) create mode 100644 x.c.orig diff --git a/.gitignore b/.gitignore index 5761abc..7d44417 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.o + +*.diff diff --git a/config.def.h b/config.def.h index dfdc59e..36c2dff 100644 --- a/config.def.h +++ b/config.def.h @@ -5,7 +5,10 @@ * * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html */ -static char *font = "FiraCode Nerd Font Propo:pixelsize=13"; +static char *font = "FiraCode Nerd Font Propo:pixelsize=13:antialias=true:autohint=true"; +static char *font2[] = { + "Noto Color Emoji:pixelsize=13:antialias=true:autohint=true", +}; static int borderpx = 5; /* diff --git a/config.h b/config.h index dfdc59e..36c2dff 100644 --- a/config.h +++ b/config.h @@ -5,7 +5,10 @@ * * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html */ -static char *font = "FiraCode Nerd Font Propo:pixelsize=13"; +static char *font = "FiraCode Nerd Font Propo:pixelsize=13:antialias=true:autohint=true"; +static char *font2[] = { + "Noto Color Emoji:pixelsize=13:antialias=true:autohint=true", +}; static int borderpx = 5; /* diff --git a/st b/st index 09477cfe06bb98a5e2a993ca7d079b0232cddf5f..366560c5400e06a88d9ab0d6ce730e1b4ff93907 100755 GIT binary patch delta 43988 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 z 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 delta 43161 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 diff --git a/x.c b/x.c index 0b2bcf1..126eed9 100644 --- a/x.c +++ b/x.c @@ -159,6 +159,8 @@ static void xhints(void); static int xloadcolor(int, const char *, Color *); static int xloadfont(Font *, FcPattern *); static void xloadfonts(const char *, double); +static int xloadsparefont(FcPattern *, int); +static void xloadsparefonts(void); static void xunloadfont(Font *); static void xunloadfonts(void); static void xsetenv(void); @@ -309,6 +311,7 @@ zoomabs(const Arg *arg) { xunloadfonts(); xloadfonts(usedfont, arg->f); + xloadsparefonts(); cresize(0, 0); redraw(); xhints(); @@ -1053,6 +1056,101 @@ xloadfonts(const char *fontstr, double fontsize) 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) { @@ -1153,6 +1251,9 @@ xinit(int cols, int rows) usedfont = (opt_font == NULL)? font : opt_font; xloadfonts(usedfont, 0); + /* spare fonts */ + xloadsparefonts(); + /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xloadcols(); diff --git a/x.c.orig b/x.c.orig new file mode 100644 index 0000000..0b2bcf1 --- /dev/null +++ b/x.c.orig @@ -0,0 +1,2183 @@ +/* See LICENSE for license details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char *argv0; +#include "arg.h" +#include "st.h" +#include "win.h" +#include "hb.h" + +/* types used in config.h */ +typedef struct { + uint mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +typedef struct { + uint mod; + uint button; + void (*func)(const Arg *); + const Arg arg; + uint release; +} MouseShortcut; + +typedef struct { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ +} Key; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13|1<<14) + +/* function definitions used in config.h */ +static void clipcopy(const Arg *); +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void zoom(const Arg *); +static void zoomabs(const Arg *); +static void zoomreset(const Arg *); +static void ttysend(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +} TermWindow; + +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +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 xdrawglyph(Glyph, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, const char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(const char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, +/* + * Uncomment if you want the selection to disappear when you select something + * different in another window. + */ +/* [SelectionClear] = selclear_, */ + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + Rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = NULL; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = NULL; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_class = NULL; +static char **opt_cmd = NULL; +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; + +static uint buttons; /* bit field of pressed buttons */ +static int cursorblinks = 0; + +void +clipcopy(const Arg *dummy) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = NULL; + + if (xsel.primary != NULL) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(const Arg *dummy) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(const Arg *dummy) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(const Arg *dummy) +{ + win.mode ^= MODE_NUMLOCK; +} + +void +zoom(const Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(const Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(const Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(const Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - borderpx; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - borderpx; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SEL_REGULAR; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < LEN(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, btn, code; + int x = evcol(e), y = evrow(e); + int state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + if (e->type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) + return; + /* MODE_MOUSEMOTION: no reporting if no button is pressed */ + if (IS_SET(MODE_MOUSEMOTION) && buttons == 0) + return; + /* 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 { + 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; + /* 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)) { + 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", + 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+code, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + int btn = e->xbutton.button; + struct timespec now; + int snap; + + if (1 <= btn && btn <= 11) + buttons |= 1 << (btn-1); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (btn == Button1) { + /* + * If the user clicks below predefined timeouts specific + * snapping behaviour is exposed. + */ + clock_gettime(CLOCK_MONOTONIC, &now); + if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) { + snap = SNAP_LINE; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SNAP_WORD; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + ofs = 0; + if (e->type == SelectionNotify) + property = e->xselection.property; + else if (e->type == PropertyNotify) + property = e->xproperty.atom; + + if (property == None) + return; + + do { + if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, + BUFSIZ/4, False, AnyPropertyType, + &type, &format, &nitems, &rem, + &data)) { + fprintf(stderr, "Clipboard allocation failed\n"); + return; + } + + if (e->type == PropertyNotify && nitems == 0 && rem == 0) { + /* + * If there is some PropertyNotify with no data, then + * this is the signal of the selection owner that all + * data has been transferred. We won't need to receive + * PropertyNotify events anymore. + */ + MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + } + + if (type == incratom) { + /* + * Activate the PropertyNotify events so we receive + * when the selection owner does send us the next + * chunk of data. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); + continue; + } + + /* + * As seen in getsel: + * Line endings are inconsistent in the terminal and GUI world + * copy and pasting. When receiving some selection data, + * replace all '\n' with '\r'. + * FIXME: Fix the computer world. + */ + repl = data; + last = data + nitems * format / 8; + while ((repl = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(MODE_BRCKTPASTE) && rem == 0) + ttywrite("\033[201~", 6, 0); + XFree(data); + /* number of 32-bit chunks returned */ + ofs += nitems * format / 32; + } while (rem > 0); + + /* + * Deleting the property again tells the selection owner to send the + * next data chunk in the property. + */ + XDeleteProperty(xw.dpy, xw.win, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(NULL); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (XSelectionRequestEvent *) e; + xev.type = SelectionNotify; + xev.requestor = xsre->requestor; + xev.selection = xsre->selection; + xev.target = xsre->target; + xev.time = xsre->time; + if (xsre->property == None) + xsre->property = xsre->target; + + /* reject */ + xev.property = None; + + xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + if (xsre->selection == XA_PRIMARY) { + seltext = xsel.primary; + } else if (xsre->selection == clipboard) { + seltext = xsel.clipboard; + } else { + fprintf(stderr, + "Unhandled clipboard selection 0x%lx\n", + xsre->selection); + return; + } + if (seltext != NULL) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + int btn = e->xbutton.button; + + if (1 <= btn && btn <= 11) + buttons &= ~(1 << (btn-1)); + + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (btn == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec) * 4); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, const char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + color.red = sixd_to_16bit( ((i-16)/36)%6 ); + color.green = sixd_to_16bit( ((i-16)/6) %6 ); + color.blue = sixd_to_16bit( ((i-16)/1) %6 ); + } else { /* greyscale */ + color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, + xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + int i; + static int loaded; + Color *cp; + + 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 < dc.collen; i++) + if (!xloadcolor(i, NULL, &dc.col[i])) { + if (colorname[i]) + die("could not allocate color '%s'\n", colorname[i]); + else + die("could not allocate color %d\n", i); + } + loaded = 1; +} + +int +xgetcolor(int x, unsigned char *r, unsigned char *g, unsigned char *b) +{ + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + *r = dc.col[x].color.red >> 8; + *g = dc.col[x].color.green >> 8; + *b = dc.col[x].color.blue >> 8; + + return 0; +} + +int +xsetcolorname(int x, const char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen - 1)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], + x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + 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->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = win.h; + } + if (xw.gm & (XValue|YValue)) { + sizeh->flags |= USPosition | PWinGravity; + sizeh->x = xw.l; + sizeh->y = xw.t; + sizeh->win_gravity = xgeommasktogravity(xw.gm); + } + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(Font *f, FcPattern *pattern) +{ + FcPattern *configured; + FcPattern *match; + FcResult result; + XGlyphInfo extents; + int wantattr, haveattr; + + /* + * Manually configure instead of calling XftMatchFont + * so that we can use the configured pattern for + * "missing glyph" lookups. + */ + configured = FcPatternDuplicate(pattern); + if (!configured) + return 1; + + FcConfigSubstitute(NULL, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(NULL, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = NULL; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(const char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((const FcChar8 *)fontstr); + + if (!pattern) + die("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = -1; + } else { + /* + * Default font size is 12, if none given. This is to + * have a known usedfontsize value. + */ + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); + usedfontsize = 12; + } + defaultfontsize = usedfontsize; + } + + if (xloadfont(&dc.font, pattern)) + die("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + die("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +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); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if (xw.ime.xim == NULL) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + NULL); + + if (xw.ime.xic == NULL) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + NULL); + } + if (xw.ime.xic == NULL) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = NULL; + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = NULL; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent, root; + pid_t thispid = getpid(); + XColor xmousefg, xmousebg; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("can't open display\n"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + + /* font */ + if (!FcInit()) + die("could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XDefaultColormap(xw.dpy, xw.scr); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * borderpx + cols * win.cw; + win.h = 2 * borderpx + rows * win.ch; + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + root = XRootWindow(xw.dpy, xw.scr); + if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) + parent = root; + xw.win = XCreateWindow(xw.dpy, root, xw.l, xw.t, + win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), 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; + dc.gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec) * 4); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) { + XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL, + ximinstantiate, NULL); + } + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { + xmousebg.red = 0x0000; + xmousebg.green = 0x0000; + xmousebg.blue = 0x0000; + } + + XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); + + 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); + xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); + XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, + PropModeReplace, (uchar *)&thispid, 1); + + win.mode = MODE_NUMLOCK; + resettitle(); + xhints(); + XMapWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2); + xsel.primary = NULL; + xsel.clipboard = NULL; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +void +xresetfontsettings(ushort mode, Font **font, int *frcflags) +{ + *font = &dc.font; + if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { + *font = &dc.ibfont; + *frcflags = FRC_ITALICBOLD; + } else if (mode & ATTR_ITALIC) { + *font = &dc.ifont; + *frcflags = FRC_ITALIC; + } else if (mode & ATTR_BOLD) { + *font = &dc.bfont; + *frcflags = FRC_BOLD; + } +} + +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, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + Rune rune; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + 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); + + for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + mode = glyphs[i].mode & ~ATTR_WRAP; + + /* Skip dummy wide-character spacing. */ + if (mode & ATTR_WDUMMY && i < (len - 1)) + continue; + + 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); + } + + /* 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; + + /* 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); + } + + 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++; + } + } + + /* Cleanup and get ready for next segment. */ + hbcleanup(&shaped); + start = i; + + /* Determine font for glyph if different from previous glyph. */ + if (prevmode != mode) { + prevmode = mode; + xresetfontsettings(mode, &font, &frcflags); + yp = winy + font->ascent; + } + } + } + + return numspecs; +} + +void +xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y, int charlen) +{ + int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, + width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || + (base.mode & ATTR_BOLD && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(MODE_REVERSE)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, + &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, + &revbg); + bg = &revbg; + } + } + + if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } + + if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK) + fg = bg; + + if (base.mode & ATTR_INVISIBLE) + 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 (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); + + /* Set the clip region because Xft is sometimes dirty. */ + 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); + } + + if (base.mode & ATTR_STRUCK) { + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3, + width, 1); + } + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Glyph g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &g, 1, x, y); + xdrawglyphfontspecs(specs, g, numspecs, x, y, (g.mode & ATTR_WIDE) ? 2 : 1); +} + +void +xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og, Line line, int len) +{ + 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); + + if (IS_SET(MODE_HIDE)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE; + + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if (selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = defaultbg; + g.bg = defaultcs; + } + drawcol = dc.col[g.bg]; + } + + /* draw the new one */ + if (IS_SET(MODE_FOCUSED)) { + switch (win.cursor) { + default: + case 0: /* blinking block */ + case 1: /* blinking block (default) */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 2: /* steady block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* blinking underline */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 4: /* steady underline */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - \ + cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* blinking bar */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 6: /* steady bar */ + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + cursorthickness, win.ch); + break; + case 7: /* blinking st cursor */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 8: /* steady st cursor */ + g.u = xsetcursor; + xdrawglyph(g, cx, cy); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + (cx + 1) * win.cw - 1, + borderpx + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + borderpx + cx * win.cw, + borderpx + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +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; + XSetWMIconName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname); + XFree(prop.value); +} + +void +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; + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(MODE_VISIBLE); +} + +void +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; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) { + numspecs = xmakeglyphfontspecs(specs, &line[ox], x2 - ox, ox, y1); + xdrawglyphfontspecs(specs, base, numspecs, ox, y1, x2 - ox); + } +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == NULL) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~MODE_VISIBLE; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, unsigned int flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */ + return 1; + win.cursor = cursor; + cursorblinks = win.cursor == 0 || win.cursor == 1 || + win.cursor == 3 || win.cursor == 5 || + win.cursor == 7; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(MODE_FOCUSED))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= MODE_FOCUSED; + xseturgency(0); + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~MODE_FOCUSED; + if (IS_SET(MODE_FOCUS)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < LEN(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == LEN(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for (kp = key; kp < key + LEN(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2) + continue; + + if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return NULL; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym = NoSymbol; + char buf[64], *customkey; + int len; + Rune c; + Status status; + Shortcut *bp; + + if (IS_SET(MODE_KBDLOCK)) + return; + + if (xw.ime.xic) { + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + if (status == XBufferOverflow) + return; + } else { + 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)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(MODE_8BIT)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8encode(c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +void +cmessage(XEvent *e) +{ + /* + * See xembed specs + * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html + */ + if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { + if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { + win.mode |= MODE_FOCUSED; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~MODE_FOCUSED; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + XNextEvent(xw.dpy, &ev); + /* + * This XFilterEvent call is required because of XOpenIM. It + * does filter out the key event and some client message for + * the input method too. + */ + if (XFilterEvent(&ev, None)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { + if (errno == EINTR) + continue; + die("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + if (IS_SET(MODE_BLINK)) { + win.mode ^= MODE_BLINK; + } + lastblink = now; + drawing = 1; + } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } + + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + die("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + cols = MAX(cols, 1); + rows = MAX(rows, 1); + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + run(); + + return 0; +}