/* See LICENSE for license details. */ #include "term.h" #include #include #include #include #include #include #include #include /* harfbuzz additions */ #if 0 void hbunloadfonts(); void hbtransform(XftGlyphFontSpec *, const Letter*, size_t, int, int); #endif /* types used in config.h */ typedef struct Shortcut Shortcut; typedef struct MouseShortcut MouseShortcut; typedef struct Key Key; struct Shortcut{ uint mod; KeySym keysym; void (*func)(Arg *); Arg arg; }; struct MouseShortcut { uint mod; uint button; void (*func)(Arg *); Arg arg; uint release; }; struct Key { KeySym k; uint mask; char *s; /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ schar appkey; /* application keypad */ schar appcursor; /* application cursor */ }; /* X modifiers */ #define XK_ANY_MOD UINT_MAX #define XK_NO_MOD 0 #define XK_SWITCH_MOD (1<<13) /* function definitions used in config.h */ static void clipcopy(Arg *); static void clippaste(Arg *); static void numlock(Arg *); static void selpaste(Arg *); static void zoom(Arg *); static void zoomabs(Arg *); static void zoomreset(Arg *); static void ttysend(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 { Display *dpy; Colormap cmap; Window win; Drawable buf; GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ Atom xembed, wmdeletewin, netwmname, 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 depth; /* color depth */ 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 int xmakeglyphfontspecs(XftGlyphFontSpec *, Letter *, int, int, int); static void xdrawglyphfontspecs(XftGlyphFontSpec *, Letter, int, int, int); static void xdrawglyph(Letter, 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, char *, Color *); static int xloadfont(Font *, FcPattern *); static void xloadfonts(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, [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 = nil; static int frclen = 0; static int frccap = 0; static char *usedfont = nil; static double usedfontsize = 0; static double defaultfontsize = 0; static char *opt_alpha = nil; static char *opt_class = nil; static char **opt_cmd = nil; static char *opt_embed = nil; static char *opt_font = nil; static char *opt_io = nil; static char *opt_line = nil; static char *opt_name = nil; static char *opt_title = nil; static int oldbutton = 3; /* button event on startup: 3 = release */ void clipcopy(Arg *_) { Atom clipboard; free(xsel.clipboard); xsel.clipboard = nil; if (xsel.primary != nil) { xsel.clipboard = xstrdup(xsel.primary); clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); } } void clippaste(Arg *_) { Atom clipboard; clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, xw.win, CurrentTime); } void selpaste(Arg *_) { XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, xw.win, CurrentTime); } void numlock(Arg *_) { win.mode ^= Wnumlock; } void zoom(Arg *arg) { Arg larg; larg.f = usedfontsize + arg->f; zoomabs(&larg); } void zoomabs(Arg *arg) { xunloadfonts(); xloadfonts(usedfont, arg->f); cresize(0, 0); redraw(); xhints(); } void zoomreset(Arg *arg) { Arg larg; if (defaultfontsize > 0) { larg.f = defaultfontsize; zoomabs(&larg); } } void ttysend(Arg *arg) { ttywrite(arg->s, strlen(arg->s), 1); } int evcol(XEvent *e) { int x = e->xbutton.x - win.hb; LIMIT(x, 0, win.tw - 1); return x / win.cw; } int evrow(XEvent *e) { int y = e->xbutton.y - win.vb; LIMIT(y, 0, win.th - 1); return y / win.ch; } void mousesel(XEvent *e, int done) { int type, seltype = SelRegular; uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); for (type = 1; type < arrlen(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, x = evcol(e), y = evrow(e), button = e->xbutton.button, state = e->xbutton.state; char buf[40]; static int ox, oy; /* from urxvt */ if (e->xbutton.type == MotionNotify) { if (x == ox && y == oy) return; if (!IS_SET(Wmousemotion) && !IS_SET(Wmousemany)) return; /* MOUSE_MOTION: no reporting if no button is pressed */ if (IS_SET(Wmousemotion) && oldbutton == 3) return; button = oldbutton + 32; ox = x; oy = y; } else { if (!IS_SET(Wmousesgr) && 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; /* Wmousex10: no button release reporting */ if (IS_SET(Wmousex10)) return; if (button == 64 || button == 65) return; } } if (!IS_SET(Wmousex10)) { button += ((state & ShiftMask ) ? 4 : 0) + ((state & Mod4Mask ) ? 8 : 0) + ((state & ControlMask) ? 16 : 0); } if (IS_SET(Wmousesgr)) { len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", button, x+1, y+1, e->xbutton.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); } 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 + arrlen(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) { struct timespec now; int snap; if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } if (mouseaction(e, 0)) return; if (e->xbutton.button == 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 = SnapLine; } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { snap = SnapWord; } 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(Wbrcktpaste) && ofs == 0) ttywrite("\033[200~", 6, 0); ttywrite((char *)data, nitems * format / 8, 1); if (IS_SET(Wbrcktpaste) && 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(nil); } 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 != nil) { 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) { if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) { mousereport(e); return; } if (mouseaction(e, 1)) return; if (e->xbutton.button == Button1) mousesel(e, 1); } void bmotion(XEvent *e) { if (IS_SET(Wmouse) && !(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); win.hb = (win.w - col*win.cw)/2; win.vb = (win.h - row*win.ch)/2; 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, xw.depth); XftDrawChange(xw.draw, xw.buf); xclear(0, 0, win.w, win.h); /* resize to new width */ xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); } ushort sixd_to_16bit(int x) { return x == 0 ? 0 : 0x3737 + 0x2828 * x; } int xloadcolor(int i, 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(arrlen(colorname), 256); dc.col = xmalloc(dc.collen * sizeof(Color)); } for (i = 0; i < dc.collen; i++) if (!xloadcolor(i, nil, &dc.col[i])) { if (colorname[i]) fatal("could not allocate color '%s'\n", colorname[i]); else fatal("could not allocate color %d\n", i); } if (opt_alpha) alpha = strtof(opt_alpha, nil); dc.col[defaultbg].color.alpha = (ushort)(0xffff * alpha); dc.col[defaultbg].pixel &= 0x00ffffff; dc.col[defaultbg].pixel |= (uchar)(0xff*alpha) << 24; loaded = 1; } int xsetcolorname(int x, char *name) { Color ncolor; if (!BETWEEN(x, 0, dc.collen)) 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(Wreverse)? 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 = 1; sizeh->width_inc = 1; 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, nil, nil, nil, 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(nil, configured, FcMatchPattern); XftDefaultSubstitute(xw.dpy, xw.scr, configured); match = FcFontMatch(nil, 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 = nil; 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(char *fontstr, double fontsize) { FcPattern *pattern; double fontval; if (fontstr[0] == '-') pattern = XftXlfdParse(fontstr, False, False); else pattern = FcNameParse((FcChar8 *)fontstr); if (!pattern) fatal("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)) fatal("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)) fatal("can't open font %s\n", fontstr); FcPatternDel(pattern, FC_WEIGHT); FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); if (xloadfont(&dc.ibfont, pattern)) fatal("can't open font %s\n", fontstr); FcPatternDel(pattern, FC_SLANT); FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); if (xloadfont(&dc.bfont, pattern)) fatal("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) { #if 0 hbunloadfonts(); #endif /* 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 = nil, .callback = ximdestroy }; XICCallback icdestroy = { .client_data = nil, .callback = xicdestroy }; xw.ime.xim = XOpenIM(xw.dpy, nil, nil, nil); if (xw.ime.xim == nil) return 0; if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, nil)) fprintf(stderr, "XSetIMValues: " "Could not set XNDestroyCallback.\n"); xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, nil); if (xw.ime.xic == nil) { xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNDestroyCallback, &icdestroy, nil); } if (xw.ime.xic == nil) 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, nil, nil, nil, ximinstantiate, nil); } void ximdestroy(XIM xim, XPointer client, XPointer call) { xw.ime.xim = nil; XRegisterIMInstantiateCallback(xw.dpy, nil, nil, nil, ximinstantiate, nil); XFree(xw.ime.spotlist); } int xicdestroy(XIC xim, XPointer client, XPointer call) { xw.ime.xic = nil; return 1; } void xinit(int cols, int rows) { XGCValues gcvalues; Cursor cursor; Window parent; XColor xmousefg, xmousebg; XWindowAttributes attr; XVisualInfo vis; pid_t thispid = getpid(); if (!(xw.dpy = XOpenDisplay(nil))) fatal("can't open display\n"); if (!(opt_embed && (parent == strtol(opt_embed, nil, 0)))) { parent = XRootWindow(xw.dpy, xw.scr); xw.depth = 32; } else { XGetWindowAttributes(xw.dpy, parent, &attr); xw.depth = attr.depth; } XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); xw.vis = vis.visual; xw.scr = XDefaultScreen(xw.dpy); /* font */ if (!FcInit()) fatal("could not init fontconfig.\n"); usedfont = (opt_font == nil)? font : opt_font; xloadfonts(usedfont, 0); /* colors */ xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); xloadcols(); /* adjust fixed window geometry */ win.w = 2 * win.hb + cols*win.cw; win.h = 2 * win.vb + 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; xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, win.w, win.h, 0, xw.depth, InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); 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); 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)); /* Xft rendering context */ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); /* input methods */ if (!ximopen(xw.dpy)) XRegisterIMInstantiateCallback(xw.dpy, nil, nil, nil, ximinstantiate, nil); /* 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); 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 = Wnumlock; 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 = nil; xsel.clipboard = nil; xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); if (xsel.xtarget == None) xsel.xtarget = XA_STRING; } int xmakeglyphfontspecs(XftGlyphFontSpec *specs, Letter *glyphs, int len, int x, int y) { float winx = win.hb + x * win.cw, winy = win.vb + y * win.ch, xp, yp; ushort mode, prevmode = USHRT_MAX; Font *font = &dc.font; int frcflags = FRC_NORMAL; float runewidth = win.cw; rune r; FT_UInt glyphidx; FcResult fcres; FcPattern *fcpattern, *fontpattern; FcFontSet *fcsets[] = { nil }; FcCharSet *fccharset; int i, f, numspecs = 0; for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { /* Fetch rune and mode for current glyph. */ r = glyphs[i].u; mode = glyphs[i].mode; /* Skip dummy wide-character spacing. */ #if 0 if(mode & Gwdummy) #endif if(mode == Gwdummy) continue; /* Determine font for glyph if different from previous glyph. */ if(prevmode != mode){ prevmode = mode; font = &dc.font; frcflags = FRC_NORMAL; runewidth = win.cw * ((mode & Gwide) ? 2.0f : 1.0f); if ((mode & Gitalic) && (mode & Gbold)) { font = &dc.ibfont; frcflags = FRC_ITALICBOLD; } else if (mode & Gitalic) { font = &dc.ifont; frcflags = FRC_ITALIC; } else if (mode & Gbold) { font = &dc.bfont; frcflags = FRC_BOLD; } yp = winy + font->ascent; } /* lookup character index with default font. */ glyphidx = XftCharIndex(xw.dpy, font->match, r); if(glyphidx){ specs[numspecs].font = font->match; specs[numspecs].glyph = glyphidx; specs[numspecs].x = (short)xp; specs[numspecs].y = (short)yp; xp += runewidth; numspecs++; continue; } /* Fallback on font cache, search the font cache for match. */ for(f = 0; f < frclen; f++) { glyphidx = XftCharIndex(xw.dpy, frc[f].font, r); /* 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 == r) { 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, r); 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) fatal("XftFontOpenPattern failed seeking fallback font: %s\n", strerror(errno)); frc[frclen].flags = frcflags; frc[frclen].unicodep = r; glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, r); 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; xp += runewidth; numspecs++; } #if 0 hbtransform(specs, glyphs, len, x, y); #endif return numspecs; } void xdrawglyphfontspecs(XftGlyphFontSpec *specs, Letter base, int len, int x, int y) { int charlen = len * ((base.mode & Gwide) ? 2 : 1); int winx = win.hb + x * win.cw, winy = win.vb + 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 & Gitalic && base.mode & Gbold) { if (dc.ibfont.badslant || dc.ibfont.badweight) base.fg = defaultattr; } else if ((base.mode & Gitalic && dc.ifont.badslant) || (base.mode & Gbold && 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 & Gfaint) == Gbold && BETWEEN(base.fg, 0, 7)) fg = &dc.col[base.fg + 8]; if (IS_SET(Wreverse)) { 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 & Gboldfaint) == Gfaint) { 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 & Greverse) temp = fg, fg = bg, bg = temp; if (base.mode & Gblink && win.mode & Wblink) fg = bg; if (base.mode & Ginvisible) fg = bg; /* Intelligent cleaning up of the borders. */ if (x == 0) { xclear(0, (y == 0)? 0 : winy, win.vb, winy + win.ch + ((winy + win.ch >= win.vb + win.th)? win.h : 0)); } if (winx + width >= win.hb + win.tw) { xclear(winx + width, (y == 0)? 0 : winy, win.w, ((winy + win.ch >= win.vb + win.th)? win.h : (winy + win.ch))); } if (y == 0) xclear(winx, 0, winx + width, win.hb); if (winy + win.ch >= win.hb + 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 & Gunline) XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1); if (base.mode & Gstruck) XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, width, 1); /* Reset clip to none. */ XftDrawSetClip(xw.draw, 0); } void xdrawglyph(Letter g, int x, int y) { int numspecs; XftGlyphFontSpec spec; numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); xdrawglyphfontspecs(&spec, g, numspecs, x, y); } void xdrawcursor(int cx, int cy, Letter g, int ox, int oy, Letter og, Letter *line, int len) { Color drawcol; /* remove the old cursor */ if(selected(ox, oy)) og.mode ^= Greverse; xdrawglyph(og, ox, oy); #if 0 xdrawline(line, 0, oy, len); #endif if(IS_SET(Whide)) return; /* * Select the right color for the right mode. */ g.mode &= Gbold|Gitalic|Gunline|Gstruck|Gwide; if(IS_SET(Wreverse)) { g.mode |= Greverse; 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 = og.bg; //defaultbg; g.bg = og.fg; //defaultcs; if (IS_TRUECOL(og.fg)) { drawcol.color.alpha = 0xffff; drawcol.color.red = TRUERED(og.fg); drawcol.color.green = TRUEGREEN(og.fg); drawcol.color.blue = TRUEBLUE(og.fg); goto drawnew; } } drawcol = dc.col[g.bg]; } drawnew: if(IS_SET(Wfocused)) { switch (win.cursor) { case 7: /* st extension */ g.u = 0x2603; /* snowman (U+2603) */ /* fallthrough */ case 0: /* Blinking Block */ case 1: /* Blinking Block (Default) */ case 2: /* Steady Block */ xdrawglyph(g, cx, cy); break; case 3: /* Blinking Underline */ case 4: /* Steady Underline */ XftDrawRect(xw.draw, &drawcol, win.hb + cx * win.cw, win.vb + (cy + 1) * win.ch - cursorthickness, win.cw, cursorthickness); break; case 5: /* Blinking bar */ case 6: /* Steady bar */ XftDrawRect(xw.draw, &drawcol, win.hb + cx * win.cw, win.vb + cy * win.ch, cursorthickness, win.ch); break; } } else { XftDrawRect(xw.draw, &drawcol, win.hb + cx * win.cw, win.vb + cy * win.ch, win.cw - 1, 1); XftDrawRect(xw.draw, &drawcol, win.hb + cx * win.cw, win.vb + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, win.hb + (cx + 1) * win.cw - 1, win.vb + cy * win.ch, 1, win.ch - 1); XftDrawRect(xw.draw, &drawcol, win.hb + cx * win.cw, win.vb + (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 xsettitle(char *p) { XTextProperty prop; DEFAULT(p, opt_title); Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, &prop); XSetWMName(xw.dpy, xw.win, &prop); XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); XFree(prop.value); } int xstartdraw(void) { return IS_SET(Wvisible); } void xdrawline(Letter *line, int x1, int y1, int x2) { int i, x, ox, numspecs; Letter base, new; XftGlyphFontSpec *specs = xw.specbuf; numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); i = ox = 0; for (x = x1; x < x2 && i < numspecs; x++) { new = line[x]; if (new.mode == Gwdummy) continue; if (selected(x, y1)) new.mode ^= Greverse; if (i > 0 && GLYPHCMP(base, new)) { xdrawglyphfontspecs(specs, base, i, ox, y1); specs += i; numspecs -= i; i = 0; } if (i == 0) { ox = x; base = new; } i++; } if (i > 0) xdrawglyphfontspecs(specs, base, i, ox, y1); } 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(Wreverse)? defaultfg : defaultbg].pixel); } void xximspot(int x, int y) { if (xw.ime.xic == nil) 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, nil); } void expose(XEvent *ev) { redraw(); } void visibility(XEvent *ev) { XVisibilityEvent *e = &ev->xvisibility; MODBIT(win.mode, e->state != VisibilityFullyObscured, Wvisible); } void unmap(XEvent *ev) { win.mode &= ~Wvisible; } void xsetpointermotion(int set) { MODBIT(xw.attrs.event_mask, set, PointerMotionMask); XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); } void xsetmode(int set, uint flags) { int mode = win.mode; MODBIT(win.mode, set, flags); if ((win.mode & Wreverse) != (mode & Wreverse)) redraw(); } int xsetcursor(int cursor) { if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ return 1; win.cursor = cursor; 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(Wfocused))) xseturgency(1); if (bellvolume) XkbBell(xw.dpy, xw.win, bellvolume, (Atom)nil); } 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 |= Wfocused; xseturgency(0); if (IS_SET(Wfocus)) ttywrite("\033[I", 3, 0); } else { if (xw.ime.xic) XUnsetICFocus(xw.ime.xic); win.mode &= ~Wfocused; if (IS_SET(Wfocus)) 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 < arrlen(mappedkeys); i++) { if (mappedkeys[i] == k) break; } if (i == arrlen(mappedkeys)) { if ((k & 0xFFFF) < 0xFD00) return nil; } for (kp = key; kp < key + arrlen(key); kp++) { if (kp->k != k) continue; if (!match(kp->mask, state)) continue; if (IS_SET(Wappkeypad) ? kp->appkey < 0 : kp->appkey > 0) continue; if (IS_SET(Wnumlock) && kp->appkey == 2) continue; if (IS_SET(Wappcursor) ? kp->appcursor < 0 : kp->appcursor > 0) continue; return kp->s; } return nil; } void kpress(XEvent *ev) { XKeyEvent *e = &ev->xkey; KeySym ksym; char buf[64], *customkey; int len; rune c; Status status; Shortcut *bp; if (IS_SET(Wkbdblock)) return; if (xw.ime.xic) len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); else len = XLookupString(e, buf, sizeof buf, &ksym, nil); /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + arrlen(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(W8bit)) { if (*buf < 0177) { c = *buf | 0x80; len = utf8·encode(&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 |= Wfocused; xseturgency(0); } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { win.mode &= ~Wfocused; } } 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 : nil; if (pselect(MAX(xfd, ttyfd)+1, &rfd, nil, nil, tv, nil) < 0) { if (errno == EINTR) continue; fatal("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; 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 && tattrset(Gblink)) { timeout = blinktimeout - TIMEDIFF(now, lastblink); if (timeout <= 0) { if (-timeout > blinktimeout) /* start visible */ win.mode |= Wblink; win.mode ^= Wblink; tsetdirtattr(Gblink); lastblink = now; timeout = blinktimeout; } } draw(); XFlush(xw.dpy); drawing = 0; } } void usage(void) { fatal("usage: %s [-aiv] [-c class] [-f font] [-A alpha] [-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 'A': opt_alpha = EARGF(usage()); 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': fatal("%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) ? "term" : 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; }