module x; import core.stdc.stdio; import core.stdc.stdlib; import core.stdc.string; import core.stdc.time; import core.stdc.errno; import core.sys.posix.time : clock_gettime, CLOCK_MONOTONIC; import core.sys.posix.unistd : getpid; import core.sys.posix.sys.select; import st; import win; import win : MOUSE; import config; import patches; static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { import config : alphaUnfocused, alpha, defaultbg; import st : tfulldirt, dc; import main : opt_alpha; } static if (isPatchEnabled!"SCROLLBACK_MOUSE_PATCH" || isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") { import st : tisaltscr; } // Import TLINE function from st module static if (isPatchEnabled!"SCROLLBACK_PATCH") { import st : TLINE; } else { import st : TLINE; } import xft_types; // Import additional types struct MouseShortcut { uint mod; uint button; extern(C) void function(const(st.Arg)*) func; st.Arg arg; uint release; int screen; } // Screen constants for shortcuts enum { S_PRI = -1, // primary screen S_ALL = 0, // both primary and alt screen S_ALT = 1 // alternate screen } import std.conv : octal; import std.math : ceil; // Import X11 types import deimos.X11.X; import deimos.X11.Xlib; import deimos.X11.Xutil; import deimos.X11.keysym; import deimos.X11.Xatom; // Cursor font constants enum { XC_xterm = 152, XC_hand2 = 60 } // Selection snap modes enum SelectionSnap { SNAP_WORD = 1, SNAP_LINE = 2 } // Mouse button constants enum { Button1 = 1, Button2 = 2, Button3 = 3, Button4 = 4, Button5 = 5, Button6 = 6, Button7 = 7, Button8 = 8, Button9 = 9, Button10 = 10, Button11 = 11, } // Import Xft extern(C) { XftDraw* XftDrawCreate(Display* dpy, Drawable drawable, Visual* visual, Colormap colormap); void XftDrawDestroy(XftDraw* draw); XftFont* XftFontOpenName(Display* dpy, int screen, const(char)* name); void XftFontClose(Display* dpy, XftFont* font); Bool XftColorAllocName(Display* dpy, Visual* visual, Colormap cmap, const(char)* name, XftColor* result); Bool XftColorAllocValue(Display* dpy, Visual* visual, Colormap cmap, const(XRenderColor)* color, XftColor* result); void XftColorFree(Display* dpy, Visual* visual, Colormap cmap, XftColor* color); void XftDrawString8(XftDraw* d, const(XftColor)* color, XftFont* font, int x, int y, const(FcChar8)* string, int len); void XftDrawGlyphFontSpec(XftDraw* draw, const(XftColor)* color, const(XftGlyphFontSpec)* glyphs, int nglyphs); void XftDrawRect(XftDraw* d, const(XftColor)* color, int x, int y, uint width, uint height); int XftCharExists(Display* dpy, XftFont* xfont, FcChar32 ch); void XftTextExtents8(Display* dpy, XftFont* font, const(FcChar8)* string, int len, XGlyphInfo* extents); } // Type aliases alias FcBool = int; alias FcConfig = void; // Import Fontconfig extern(C) { int FcInit(); FcPattern* FcNameParse(const(FcChar8)* name); FcPattern* FcPatternDuplicate(const(FcPattern)* p); void FcPatternDestroy(FcPattern* p); FcBool FcPatternAddDouble(FcPattern* p, const(char)* object, double d); FcBool FcPatternAddInteger(FcPattern* p, const(char)* object, int i); FcResult FcPatternGetInteger(const(FcPattern)* p, const(char)* object, int n, int* i); FcBool FcPatternGetDouble(const(FcPattern)* p, const(char)* object, int n, double* d); FcResult FcPatternGetString(const(FcPattern)* p, const(char)* object, int n, FcChar8** s); FcBool FcConfigSubstitute(FcConfig* config, FcPattern* p, FcMatchKind kind); void FcDefaultSubstitute(FcPattern* pattern); FcPattern* FcFontMatch(FcConfig* config, FcPattern* p, FcResult* result); XftFont* XftFontOpenPattern(Display* dpy, FcPattern* pattern); void FcPatternDel(FcPattern* p, const(char)* object); void FcPatternAddString(FcPattern* p, const(char)* object, const(FcChar8)* s); FcFontSet* FcFontSort(FcConfig* config, FcPattern* p, FcBool trim, FcCharSet** csp, FcResult* result); void XftFontClose(Display* dpy, XftFont* font); FcCharSet* FcCharSetCreate(); FcBool FcCharSetAddChar(FcCharSet* fcs, FcChar32 ucs4); FcBool FcPatternAddCharSet(FcPattern* p, const(char)* object, const(FcCharSet)* c); FcBool FcPatternAddBool(FcPattern* p, const(char)* object, FcBool b); FcPattern* FcFontSetMatch(FcConfig* config, FcFontSet** sets, int nsets, FcPattern* p, FcResult* result); void FcCharSetDestroy(FcCharSet* fcs); } // FcCharSet is opaque struct FcCharSet; // Fontconfig constants enum FcMatchKind { FcMatchPattern, FcMatchFont, FcMatchScan } enum FcResult { FcResultMatch, FcResultNoMatch, FcResultTypeMismatch, FcResultNoId, FcResultOutOfMemory } enum XftResult { Match = 0, NoMatch, TypeMismatch, NoId } // Constants enum FC_FAMILY = "family"; enum FC_WEIGHT = "weight"; enum FC_SLANT = "slant"; enum FC_PIXEL_SIZE = "pixelsize"; enum FC_SIZE = "size"; enum FC_CHARSET = "charset"; enum FC_SCALABLE = "scalable"; enum FC_WEIGHT_BOLD = 200; enum FC_SLANT_ITALIC = 100; // Global window state - imported from st module // Macro for checking window mode flags bool IS_SET(WinMode flag) { return (st.win.mode & flag) != 0; } // X11 event handling extern(C) void xsetmode(int set, uint flags) { int mode = st.win.mode; if (set) st.win.mode |= flags; else st.win.mode &= ~flags; if ((st.win.mode & WinMode.REVERSE) != (mode & WinMode.REVERSE)) redraw(); } extern(C) void xsetpointermotion(int set) { if (!set) { XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); return; } xw.attrs.event_mask |= PointerMotionMask; XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); } extern(C) int xsetcursor(int cursor) { if (!between(cursor, 0, 8)) /* 7-8: st extensions */ return 1; st.win.cursor = cursor; cursorblinks = cursor == 0 || cursor == 1 || cursor == 3 || cursor == 5 || cursor == 7; return 0; } extern(C) void xbell() { if (bellvolume) XkbBell(xw.dpy, xw.win, bellvolume, cast(Atom)null); } extern(C) void xinit(int cols, int rows) { XGCValues gcvalues; Cursor cursor; Window parent; pid_t thispid = getpid(); XColor xmousefg, xmousebg; xw.dpy = XOpenDisplay(null); if (!xw.dpy) 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 is null) ? cast(char*)(font ~ "\0").ptr : opt_font; xloadfonts(usedfont, 0); /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); xloadcols(); /* adjust fixed window geometry */ st.win.w = 2 * config.borderpx + cols * st.win.cw; st.win.h = 2 * config.borderpx + rows * st.win.ch; if (xw.gm & XNegative) xw.l += XDisplayWidth(xw.dpy, xw.scr) - st.win.w - 2; if (xw.gm & YNegative) xw.t += XDisplayHeight(xw.dpy, xw.scr) - st.win.h - 2; /* Events */ xw.attrs.background_pixel = dc.col[config.defaultbg].pixel; xw.attrs.border_pixel = dc.col[config.defaultbg].pixel; xw.attrs.bit_gravity = NorthWestGravity; xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask; xw.attrs.colormap = xw.cmap; if (opt_embed) { parent = strtol(opt_embed, null, 0); } else { parent = 0; } if (!parent) parent = XRootWindow(xw.dpy, xw.scr); xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, st.win.w, st.win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | CWEventMask | CWColormap, &xw.attrs); memset(&gcvalues, 0, gcvalues.sizeof); gcvalues.graphics_exposures = False; dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, &gcvalues); xw.buf = XCreatePixmap(xw.dpy, xw.win, st.win.w, st.win.h, XDefaultDepth(xw.dpy, xw.scr)); XSetForeground(xw.dpy, dc.gc, dc.col[config.defaultbg].pixel); XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, st.win.w, st.win.h); /* font spec buffer */ xw.specbuf = cast(GlyphFontSpec*)xmalloc(cols * GlyphFontSpec.sizeof); /* Xft rendering context */ xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); /* input methods */ ximopen(xw.dpy); /* white cursor, black outline */ cursor = XCreateFontCursor(xw.dpy, XC_xterm); XDefineCursor(xw.dpy, xw.win, cursor); static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { xw.upointer = XCreateFontCursor(xw.dpy, XC_hand2); static if (!isPatchEnabled!"HIDECURSOR_PATCH") { xw.vpointer = cursor; xw.pointerisvisible = 1; } } if (XParseColor(xw.dpy, xw.cmap, colorname[config.mousefg], &xmousefg) == 0) { xmousefg.red = 0xffff; xmousefg.green = 0xffff; xmousefg.blue = 0xffff; } if (XParseColor(xw.dpy, xw.cmap, colorname[config.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, cast(ubyte*)&thispid, 1); st.win.mode = WinMode.NUMLOCK | WinMode.VISIBLE; 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", False); if (xsel.xtarget == None) xsel.xtarget = XA_STRING; static if (isPatchEnabled!"BOXDRAW_PATCH") { import patch.boxdraw : boxdraw_xinit; boxdraw_xinit(xw.dpy, xw.cmap, xw.draw, xw.vis); } // Initialize event handlers init_handlers(); } extern(C) void xloadcols() { 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(colorname.length, 256); dc.col = cast(Color*)xmalloc(dc.collen * Color.sizeof); } for (i = 0; i < dc.collen; i++) { if (!xloadcolor(i, null, &dc.col[i])) { if (i < colorname.length && colorname[i]) die("could not allocate color '%s'\n", colorname[i]); else die("could not allocate color %d\n", i); } // Color loading confirmed working } loaded = 1; static if (isPatchEnabled!"ALPHA_PATCH" && isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { xloadalpha(); } } static if (isPatchEnabled!"ALPHA_PATCH" && isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { void xloadalpha() { import std.conv : to; float usedAlpha = focused ? alpha : alphaUnfocused; if (opt_alpha.length > 0) alpha = to!float(opt_alpha); dc.col[defaultbg].color.alpha = cast(ushort)(0xffff * usedAlpha); dc.col[defaultbg].pixel &= 0x00FFFFFF; dc.col[defaultbg].pixel |= cast(ubyte)(0xff * usedAlpha) << 24; } } int xloadcolor(int i, const(char)* name, Color* ncolor) { XRenderColor color = { 0xffff, 0xffff, 0xffff, 0xffff }; if (!name) { if (between(i, 16, 255)) { /* 256 color */ if (i < 6*6*6+16) { /* same colors as xterm */ /* 6x6x6 color cube */ 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 = color.green = color.blue = cast(ushort)(0x0808 + 0x0a0a * (i - (6*6*6+16))); } return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, ncolor); } else { name = colorname[i]; } } return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); } ushort sixd_to_16bit(int x) { return cast(ushort)(x == 0 ? 0 : 0x3737 + 0x2828 * x); } extern(C) void xsetenv() { char[256] buf; snprintf(buf.ptr, buf.sizeof, "%lu", xw.win); import std.process : environment; import std.string : fromStringz; environment["WINDOWID"] = fromStringz(buf.ptr).idup; } void xhints() { XClassHint cls = { opt_name ? opt_name : config.termname, opt_class ? opt_class : config.termname }; XWMHints wm = { flags: InputHint, input: 1 }; XSizeHints* sizeh; sizeh = XAllocSizeHints(); sizeh.flags = PSize | PResizeInc | PBaseSize | PMinSize; sizeh.height = st.win.h; sizeh.width = st.win.w; sizeh.height_inc = st.win.ch; sizeh.width_inc = st.win.cw; sizeh.base_height = 2 * borderpx; sizeh.base_width = 2 * borderpx; sizeh.min_height = st.win.ch + 2 * borderpx; sizeh.min_width = st.win.cw + 2 * borderpx; if (xw.isfixed) { sizeh.flags |= PMaxSize; sizeh.min_width = sizeh.max_width = st.win.w; sizeh.min_height = sizeh.max_height = st.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, &cls); XFree(sizeh); } int xgeommasktogravity(int mask) { switch (mask & (XNegative|YNegative)) { case 0: return NorthWestGravity; case XNegative: return NorthEastGravity; case YNegative: return SouthWestGravity; default: return SouthEastGravity; } } extern(C) void resettitle() { xsettitle(null); } void xsettitle(char* p) { XTextProperty prop; string DEFAULT_NAME = "st"; char* def_name = cast(char*)DEFAULT_NAME.ptr; Xutf8TextListToTextProperty(xw.dpy, p ? &p : &def_name, 1, XICCEncodingStyle.XUTF8StringStyle, &prop); XSetWMName(xw.dpy, xw.win, &prop); XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); XFree(prop.value); } int ximopen(Display* dpy) { XIMStyles* imstyles; XIMStyle prefedit; XIMStyle status; XIM xim; Window win = xw.win; const(char)* imvalues; xim = XOpenIM(dpy, null, null, null); if (xim is null) { XSetLocaleModifiers("@im=none"); xim = XOpenIM(dpy, null, null, null); } if (xim is null) { xw.ime.xim = null; return 0; } if (XGetIMValues(xim, XNQueryInputStyle, &imstyles, null) || !imstyles) { fprintf(stderr, "XIM: Could not obtain supported styles.\n"); XCloseIM(xim); return 0; } prefedit = XIMPreeditNothing; status = XIMStatusNothing; // Create spot location list for IME xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, null); xw.ime.xic = XCreateIC(xim, XNInputStyle, prefedit | status, XNClientWindow, win, XNFocusWindow, win, null); if (xw.ime.xic is null) { fprintf(stderr, "XIM: Could not create input context.\n"); XCloseIM(xim); return 0; } xw.ime.xim = xim; return 1; } extern(C) void redraw() { tfulldirt(); draw(); } extern(C) void draw() { int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; if (!xstartdraw()) return; /* adjust cursor position */ term.ocx = clamp(term.ocx, 0, term.col-1); term.ocy = clamp(term.ocy, 0, term.row-1); if (TLINE(term.ocy)[term.ocx].mode & GlyphAttribute.WDUMMY) term.ocx--; if (TLINE(term.c.y)[cx].mode & GlyphAttribute.WDUMMY) cx--; drawregion(0, 0, term.col, term.row); if (term.scr == 0) xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx], term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]); term.ocx = cx; term.ocy = term.c.y; xfinishdraw(); if (ocx != term.ocx || ocy != term.ocy) xximspot(term.ocx, term.ocy); } extern(C) void drawregion(int x1, int y1, int x2, int y2) { int y; // Clamp values to valid ranges x1 = clamp(x1, 0, term.col); x2 = clamp(x2, 0, term.col); y1 = clamp(y1, 0, term.row); y2 = clamp(y2, 0, term.row); if (x2 <= x1 || y2 <= y1) { return; } int lines_drawn = 0; int dirty_lines = 0; for (y = y1; y < y2; y++) { if (y >= term.row) continue; if (term.dirty[y]) dirty_lines++; if (!term.dirty[y]) continue; term.dirty[y] = 0; xdrawline(TLINE(y), x1, y, x2); lines_drawn++; } } extern(C) void xdrawline(Line line, int x1, int y, int x2) { static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") { int i, x, ox, numspecs, numspecs_cached; Glyph base, new_; XftGlyphFontSpec* specs; // Validate bounds if (x1 >= x2 || x1 < 0 || x2 > term.col) { return; } // Pre-calculate all glyphspecs numspecs_cached = xmakeglyphfontspecs(xw.specbuf, &line[x1], x2 - x1, x1, y); // Draw line in 2 passes: background and foreground // This way wide glyphs won't get truncated import st : DrawingMode; for (int dmode = DrawingMode.BG; dmode <= DrawingMode.FG; dmode <<= 1) { specs = xw.specbuf; numspecs = numspecs_cached; i = ox = 0; for (x = x1; x < x2 && i < numspecs; x++) { new_ = line[x]; if (new_.mode & GlyphAttribute.WDUMMY) continue; // Check if this character is selected and apply highlighting if (selected(x, y)) { new_.mode ^= GlyphAttribute.REVERSE; } // Check if attributes changed from base glyph if (i > 0 && (base.mode != new_.mode || base.fg != new_.fg || base.bg != new_.bg)) { // Draw the previous run xdrawglyphfontspecs(specs, base, i, ox, y, dmode); specs += i; numspecs -= i; i = 0; } if (i == 0) { ox = x; base = new_; } i++; } // Draw the last run if (i > 0) { xdrawglyphfontspecs(specs, base, i, ox, y, dmode); } } } else { // Original single-pass implementation for when WIDE_GLYPHS_PATCH is disabled int i, x, ox, numspecs; Glyph base, new_; XftGlyphFontSpec* specs = xw.specbuf; // Validate bounds if (x1 >= x2 || x1 < 0 || x2 > term.col) { return; } i = ox = 0; for (x = x1; x < x2; x++) { new_ = line[x]; if (new_.mode & GlyphAttribute.WDUMMY) continue; // Check if this character is selected and apply highlighting if (selected(x, y)) { new_.mode ^= GlyphAttribute.REVERSE; } // Check if attributes changed from base glyph if (i > 0 && (base.mode != new_.mode || base.fg != new_.fg || base.bg != new_.bg)) { // Draw the previous run numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y); xdrawglyphfontspecs(specs, base, numspecs, ox, y); specs += numspecs; i = 0; } if (i == 0) { ox = x; base = new_; } i++; } // Draw the last run if (i > 0) { numspecs = xmakeglyphfontspecs(specs, &line[ox], x - ox, ox, y); xdrawglyphfontspecs(specs, base, numspecs, ox, y); } } } int xstartdraw() { bool visible = IS_SET(WinMode.VISIBLE); return visible; } void xfinishdraw() { if (st.win.w <= 0 || st.win.h <= 0) return; if (xw.buf == 0 || xw.win == 0) return; XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, st.win.w, st.win.h, 0, 0); XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(WinMode.REVERSE) ? config.defaultfg : config.defaultbg].pixel); } void xximspot(int x, int y) { if (xw.ime.xic is null) return; xw.ime.spot.x = cast(short)(borderpx + x * st.win.cw); xw.ime.spot.y = cast(short)(borderpx + (y + 1) * st.win.ch); XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, null); } void expose(XEvent* e) { redraw(); } void visibility(XEvent* e) { XVisibilityEvent* ev = &e.xvisibility; st.win.mode = (ev.state != VisibilityFullyObscured) ? (st.win.mode | WinMode.VISIBLE) : (st.win.mode & ~WinMode.VISIBLE); } void unmap(XEvent* e) { st.win.mode &= ~WinMode.VISIBLE; } void xseturgency(int add) { XWMHints* h = XGetWMHints(xw.dpy, xw.win); if (add) h.flags |= XUrgencyHint; else h.flags &= ~XUrgencyHint; XSetWMHints(xw.dpy, xw.win, h); XFree(h); } void focus(XEvent* e) { // DEBUG removed XFocusChangeEvent* ev = &e.xfocus; if (ev.mode == NotifyGrab) { // DEBUG removed return; } if (ev.type == FocusIn) { // DEBUG removed if (xw.ime.xic) XSetICFocus(xw.ime.xic); st.win.mode |= WinMode.FOCUSED; xseturgency(0); // DEBUG removed if (IS_SET(WinMode.FOCUS)) ttywrite("\x1b[I", 3, 0); static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { focused = 1; xloadcols(); tfulldirt(); } } else { // DEBUG removed if (xw.ime.xic) XUnsetICFocus(xw.ime.xic); st.win.mode &= ~WinMode.FOCUSED; if (IS_SET(WinMode.FOCUS)) ttywrite("\x1b[O", 3, 0); static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { focused = 0; xloadcols(); tfulldirt(); } } // DEBUG removed } int match(uint mask, uint state) { enum XK_ANY_MOD = uint.max; // For mouse events, ignore button masks and other irrelevant modifiers enum uint buttonMasks = Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask; enum uint ignoreMouse = Mod2Mask | 0x2000 | buttonMasks; // Mod2Mask + XK_SWITCH_MOD + button masks return mask == XK_ANY_MOD || mask == (state & ~ignoreMouse); } void kpress(XEvent* e) { XKeyEvent* ev = &e.xkey; KeySym ksym = 0; char[64] buf; int len; const(Shortcut)* bp; Status status; if (IS_SET(WinMode.KBDLOCK)) return; if (xw.ime.xic) len = Xutf8LookupString(xw.ime.xic, ev, buf.ptr, cast(int)buf.sizeof, &ksym, &status); else len = XLookupString(ev, buf.ptr, cast(int)buf.sizeof, &ksym, null); /* 1. shortcuts */ if (shortcuts.ptr !is null) { for (bp = shortcuts.ptr; bp < shortcuts.ptr + shortcuts.length; bp++) { if (ksym == bp.keysym && match(bp.mod, ev.state)) { if (bp.func) bp.func(&(bp.arg)); return; } } } /* 2. custom keys from config.h */ customkey = kmap(ksym, ev.state); if (customkey) { ttywrite(customkey, strlen(customkey), 0); // Don't echo custom keys return; } /* 3. composed string from input method */ if (len == 0) return; if (len == 1 && ev.state & Mod1Mask) { if (IS_SET(WinMode.MODE_8BIT)) { if (buf[0] < octal!"177") { char[2] c = [ '\x1b', buf[0] ]; len = 2; buf[0..2] = c; } } else { buf[1] = buf[0]; buf[0] = '\x1b'; len = 2; } } ttywrite(buf.ptr, len, 0); // Don't echo keyboard input } 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) { st.win.mode |= WinMode.FOCUSED; xseturgency(0); } else if (e.xclient.data.l[1] == XEMBED_FOCUS_OUT) { st.win.mode &= ~WinMode.FOCUSED; } } else if (e.xclient.data.l[0] == xw.wmdeletewin) { ttyhangup(); exit(0); } } void resize(XEvent* e) { static timespec last_resize; timespec now; if (e.xconfigure.width == st.win.w && e.xconfigure.height == st.win.h) { return; } // Rate limit resize events to prevent spinlock clock_gettime(CLOCK_MONOTONIC, &now); if (TIMEDIFF(now, last_resize) < 10) { // Less than 10ms since last resize return; } last_resize = now; // Consume any pending ConfigureNotify events to avoid processing outdated sizes XEvent ev; while (XCheckTypedWindowEvent(xw.dpy, xw.win, ConfigureNotify, &ev)) { e.xconfigure = ev.xconfigure; } cresize(e.xconfigure.width, e.xconfigure.height); } void cresize(int width, int height) { int col, row; if (width != 0) st.win.w = width; if (height != 0) st.win.h = height; // Ensure font dimensions are valid if (st.win.cw <= 0 || st.win.ch <= 0) return; col = (st.win.w - 2 * borderpx) / st.win.cw; row = (st.win.h - 2 * borderpx) / st.win.ch; col = max(2, col); // Match C implementation: minimum 2 columns row = max(1, row); tresize(col, row); xresize(col, row); ttyresize(st.win.tw, st.win.th); } void xresize(int col, int row) { st.win.tw = col * st.win.cw; st.win.th = row * st.win.ch; // Free old pixmap if it exists if (xw.buf != 0) { XFreePixmap(xw.dpy, xw.buf); xw.buf = 0; // Clear the reference immediately } // Create new pixmap if (st.win.w <= 0 || st.win.h <= 0) { return; } xw.buf = XCreatePixmap(xw.dpy, xw.win, st.win.w, st.win.h, XDefaultDepth(xw.dpy, xw.scr)); if (xw.buf == 0) { die("Failed to create pixmap\n"); } // Ensure XftDraw is updated with the new pixmap if (xw.draw !is null) { XftDrawChange(xw.draw, xw.buf); } else { // Try to recreate it xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); if (xw.draw is null) { die("Failed to create XftDraw\n"); } } // Clear the new pixmap xclear(0, 0, st.win.w, st.win.h); // Force a sync to ensure X server processes the changes XSync(xw.dpy, False); /* resize to new width */ void* old_specbuf = xw.specbuf; xw.specbuf = cast(GlyphFontSpec*)xrealloc(xw.specbuf, col * GlyphFontSpec.sizeof); if (xw.specbuf is null && col > 0) { die("Failed to realloc specbuf\n"); } } void xclear(int x1, int y1, int x2, int y2) { if (xw.draw is null) { return; } if (x2 <= x1 || y2 <= y1) { return; } auto col_idx = IS_SET(WinMode.REVERSE) ? config.defaultfg : config.defaultbg; if (col_idx >= dc.collen) { col_idx = 0; // Use black as fallback } XftDrawRect(xw.draw, &dc.col[col_idx], x1, y1, x2 - x1, y2 - y1); } extern(C) void run() { // DEBUG removed XEvent ev; int w = st.win.w, h = st.win.h; fd_set rfd; int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; timespec seltv, now, lastblink, trigger; timespec* tv; double timeout; /* Waiting for window mapping */ // DEBUG removed do { // DEBUG removed 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)) { // DEBUG removed continue; } if (ev.type == ConfigureNotify) { // DEBUG removed w = ev.xconfigure.width; h = ev.xconfigure.height; } } while (ev.type != MapNotify); // DEBUG removed // DEBUG removed ttyfd = ttynew(null, null, null, null); // DEBUG removed cresize(w, h); // DEBUG removed // DEBUG removed for (timeout = -1, drawing = 0, lastblink = timespec.init;;) { // DEBUG removed FD_ZERO(&rfd); FD_SET(ttyfd, &rfd); FD_SET(xfd, &rfd); // DEBUG removed if (XPending(xw.dpy)) timeout = 0; /* existing events might not set xfd */ // DEBUG removed seltv.tv_sec = cast(long)(timeout / 1E3); seltv.tv_nsec = cast(long)(1E6 * (timeout - 1E3 * seltv.tv_sec)); tv = timeout >= 0 ? &seltv : null; // DEBUG removed int select_result = pselect(max(xfd, ttyfd)+1, &rfd, null, null, tv, null); if (select_result < 0) { if (errno == EINTR) continue; die("select failed: %s\n", strerror(errno)); } clock_gettime(CLOCK_MONOTONIC, &now); if (FD_ISSET(ttyfd, &rfd)) { // DEBUG removed ttyread(); } xev = 0; // DEBUG removed while (XPending(xw.dpy)) { xev = 1; // DEBUG removed XNextEvent(xw.dpy, &ev); if (ev.type == KeyPress) { //printf("run: got KeyPress event\n"); //fflush(stdout); } if (XFilterEvent(&ev, None)) { //printf("run: event type %d filtered by XFilterEvent\n", ev.type); //fflush(stdout); continue; } if (handler[ev.type]) { //printf("run: calling handler for event type %d\n", ev.type); //fflush(stdout); if (ev.type == KeyPress) { //printf("run: KeyPress handler is %p, kpress is %p\n", handler[KeyPress], &kpress); //fflush(stdout); } (handler[ev.type])(&ev); //printf("run: handler returned\n"); //fflush(stdout); } else { //printf("run: no handler for event type %d\n", ev.type); //fflush(stdout); } } /* * 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 bfps milliseconds. 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(GlyphAttribute.BLINK)) { timeout = blinktimeout - TIMEDIFF(now, lastblink); if (timeout <= 0) { if (-timeout > blinktimeout) /* start visible */ st.win.mode |= WinMode.BLINK; st.win.mode ^= WinMode.BLINK; tsetdirtattr(GlyphAttribute.BLINK); lastblink = now; timeout = blinktimeout; } } if (blinktimeout && cursorblinks) { timeout = blinktimeout - TIMEDIFF(now, lastblink); if (timeout <= 0) { if (-timeout > blinktimeout) /* start visible */ st.win.cursor = 0; st.win.cursor ^= 1; timeout = blinktimeout; } } draw(); XFlush(xw.dpy); drawing = 0; } } void xloadfonts(char* fontstr, double fontsize) { FcPattern* pattern; double fontval; import std.string : fromStringz; if (fontstr[0] == '-') pattern = XftXlfdParse(fontstr, False, False); else pattern = FcNameParse(cast(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, cast(double)fontsize); usedfontsize = fontsize; } else { if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == FcResult.FcResultMatch) { usedfontsize = fontval; } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == FcResult.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(cast(FcPattern*)dc.font.match.pattern, FC_PIXEL_SIZE, 0, &fontval); usedfontsize = fontval; if (fontsize == 0) defaultfontsize = fontval; } /* Setting character width and height. */ st.win.cw = cast(int)ceil(cast(double)dc.font.width); st.win.ch = cast(int)ceil(cast(double)dc.font.height); FcPatternDel(pattern, FC_WEIGHT); FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); if (xloadfont(&dc.bfont, pattern)) die("can't open font %s\n", fontstr); FcPatternDel(pattern, FC_SLANT); static if (!isPatchEnabled!"DISABLE_ITALIC_FONTS_PATCH") { 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); FcPatternDestroy(pattern); } int xloadfont(st.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, FcMatchKind.FcMatchPattern); XftDefaultSubstitute(xw.dpy, xw.scr, configured); match = FcFontMatch(null, configured, &result); if (!match) { FcPatternDestroy(configured); return 1; } f.match = XftFontOpenPattern(xw.dpy, match); if (!f.match) { FcPatternDestroy(configured); FcPatternDestroy(match); return 1; } // Log the font that was actually loaded - get from the opened font's pattern FcChar8* fontname; if (f.match && f.match.pattern && FcPatternGetString(cast(FcPattern*)f.match.pattern, FC_FAMILY, 0, &fontname) == FcResult.FcResultMatch) { import std.string : fromStringz; } if ((FcPatternGetInteger(pattern, "slant", 0, &wantattr) == FcResult.FcResultMatch)) { /* * Check if xft was unable to find a font with the appropriate * slant but gave us one anyway. Try to mitigate. */ if ((FcPatternGetInteger(cast(FcPattern*)f.match.pattern, "slant", 0, &haveattr) != FcResult.FcResultMatch) || haveattr < wantattr) { f.badslant = 1; } } if ((FcPatternGetInteger(pattern, "weight", 0, &wantattr) == FcResult.FcResultMatch)) { if ((FcPatternGetInteger(cast(FcPattern*)f.match.pattern, "weight", 0, &haveattr) != FcResult.FcResultMatch) || haveattr != wantattr) { f.badweight = 1; } } XftTextExtentsUtf8(xw.dpy, f.match, cast(const(FcChar8)*)config.ascii_printable.ptr, cast(int)config.ascii_printable.length, &extents); f.width = st.divceil(extents.xOff, cast(int)config.ascii_printable.length); f.ascent = f.match.ascent; f.descent = f.match.descent; f.height = f.ascent + f.descent; f.set = null; f.pattern = configured; return 0; } // Event handlers array __gshared void function(XEvent*)[LASTEvent] handler; void init_handlers() { // DEBUG removed handler[KeyPress] = &kpress; handler[ClientMessage] = &cmessage; handler[ConfigureNotify] = &resize; handler[VisibilityNotify] = &visibility; handler[UnmapNotify] = &unmap; handler[Expose] = &expose; handler[FocusIn] = &focus; handler[FocusOut] = &focus; handler[MotionNotify] = &bmotion; handler[ButtonPress] = &bpress; handler[ButtonRelease] = &brelease; handler[SelectionNotify] = &selnotify; handler[SelectionRequest] = &selrequest; handler[SelectionClear] = &selclear; } // Helper functions for mouse events int evcol(XEvent* e) { int x = e.xbutton.x - borderpx; int col = x / st.win.cw; return clamp(col, 0, term.col - 1); } int evrow(XEvent* e) { int y = e.xbutton.y - borderpx; int row = y / st.win.ch; return clamp(row, 0, term.row - 1); } void mousereport(XEvent* e) { int len; int btn = e.xbutton.button; int button = btn; int state = e.xbutton.state; char[40] buf; const(int) x = evcol(e); const(int) y = evrow(e); if (e.xbutton.type == MotionNotify) { if (IS_SET(WinMode.MOUSEMOTION) && buttons == 0) return; /* MODE_MOUSEMOTION: no reporting if no button is pressed */ button = 32 + (oldbutton + 2); } else { if (!IS_SET(WinMode.MOUSESGR) && e.xbutton.type == ButtonRelease) { button = 3; } else { switch (btn) { case Button1: button = 0; break; case Button2: button = 1; break; case Button3: button = 2; break; case Button4: button = 64; break; case Button5: button = 65; break; case Button6: button = 128; break; case Button7: button = 129; break; default: return; } if (e.xbutton.type == ButtonRelease || button >= 64) state = 0; oldbutton = button; } } if (!IS_SET(WinMode.MOUSEX10)) { button += ((state & ShiftMask ) ? 4 : 0) + ((state & Mod1Mask ) ? 8 : 0) + ((state & ControlMask) ? 16 : 0); } if (IS_SET(WinMode.MOUSESGR)) { len = snprintf(buf.ptr, buf.sizeof, "\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.ptr, buf.sizeof, "\033[M%c%c%c", 32+button, 32+x+1, 32+y+1); } else { return; } ttywrite(buf.ptr, len, 0); } int mouseaction(XEvent* e, uint release) { static if (isPatchEnabled!"RIGHTCLICKTOPLUMB_PATCH") { import patch.rightclicktoplumb : plumb; if (e.xbutton.button == Button3 && release) { char* sel_text = getsel(); if (sel_text) { plumb(sel_text); import core.stdc.stdlib : free; free(sel_text); return 1; } } } // Check mouse shortcuts const(MouseShortcut)[] shortcuts = getMouseShortcuts(); static if (isPatchEnabled!"SCROLLBACK_MOUSE_PATCH" || isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") { int screen = tisaltscr() ? S_ALT : S_PRI; } const(MouseShortcut)* ms; for (ms = shortcuts.ptr; ms < shortcuts.ptr + shortcuts.length; ms++) { if (ms.release == release && ms.button == e.xbutton.button && (match(ms.mod, e.xbutton.state) || match(ms.mod, e.xbutton.state & ~forcemousemod))) { // Check if the shortcut applies to the current screen static if (isPatchEnabled!"SCROLLBACK_MOUSE_PATCH" || isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") { if (ms.screen != S_ALL && ms.screen != screen) continue; } // Skip scrollback shortcuts when in alternate screen mode (vim, less, etc) static if (isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") { import st : IS_SET_TERMMODE = IS_SET; if (IS_SET_TERMMODE(TermMode.ALTSCREEN) && (ms.func == &kscrollup || ms.func == &kscrolldown)) { continue; } } import std.logger : trace; trace(" matched! calling function"); if (ms.func !is null) ms.func(&ms.arg); return 1; } } return 0; } void setsel(char* str, Time t) { import std.logger : trace; trace("setsel called with str: ", str ? "" : "null", ", time: ", t); if (!str) return; import core.stdc.stdlib : free; import st : xstrdup; free(xsel.primary); xsel.primary = str; XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) { import st : selclear; selclear(); } } void mousesel(XEvent* e, int done) { static int oldey, oldex; int type, seltype = SEL_REGULAR; uint state = e.xbutton.state & ~(Button1Mask | forcemousemod); for (type = 1; type < selmaskslen; type++) { if (match(selmasks[type], state)) { seltype = type; break; } } selextend(evcol(e), evrow(e), seltype, done); if (done) { setsel(getsel(), e.xbutton.time); } oldey = e.xbutton.y; oldex = e.xbutton.x; } // Mouse event handlers void bmotion(XEvent* e) { static if (isPatchEnabled!"HIDECURSOR_PATCH") { // TODO: handle cursor hiding } static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { import patch.openurlonclick : detecturl, url_click; if (!IS_SET(MOUSE)) { if (!(e.xbutton.state & Button1Mask) && detecturl(evcol(e), evrow(e), 1)) XDefineCursor(xw.dpy, xw.win, xw.upointer); else XDefineCursor(xw.dpy, xw.win, xw.vpointer); } url_click = 0; } if (IS_SET(MOUSE) && !(e.xbutton.state & forcemousemod)) { mousereport(e); return; } if (e.xbutton.state & Button1Mask) { mousesel(e, 0); } } void bpress(XEvent* e) { import std.logger : trace; int btn = e.xbutton.button; timespec now; int snap; trace("bpress: button ", btn, " at (", e.xbutton.x, ",", e.xbutton.y, ") -> col=", evcol(e), " row=", evrow(e)); if (btn >= 1 && btn <= 11) buttons |= 1 << (btn - 1); if (IS_SET(MOUSE) && !(e.xbutton.state & forcemousemod)) { trace("bpress: MOUSE mode active, sending report"); 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 = SelectionSnap.SNAP_LINE; } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { snap = SelectionSnap.SNAP_WORD; } else { snap = 0; } xsel.tclick2 = xsel.tclick1; xsel.tclick1 = now; static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") { // TODO: handle keyboard select mode } selstart(evcol(e), evrow(e), snap); static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { import patch.openurlonclick : clearurl, url_click; clearurl(); url_click = 1; } } } void brelease(XEvent* e) { import std.logger : trace; int btn = e.xbutton.button; trace("brelease: button ", btn, " at (", e.xbutton.x, ",", e.xbutton.y, ") -> col=", evcol(e), " row=", evrow(e)); if (btn >= 1 && btn <= 11) buttons &= ~(1 << (btn - 1)); trace("brelease: IS_SET(MOUSE)=", IS_SET(MOUSE), " forcemousemod=", forcemousemod, " state=", e.xbutton.state); if (IS_SET(MOUSE) && !(e.xbutton.state & forcemousemod)) { trace("brelease: MOUSE mode active, sending report"); mousereport(e); return; } if (mouseaction(e, 1)) { return; } if (btn == Button1) { mousesel(e, 1); static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { import patch.openurlonclick : openUrlOnClick, url_click; import config : url_opener_modkey, url_opener; if (url_click && (e.xbutton.state & url_opener_modkey)) { import std.string : toStringz; auto opener_cstr = toStringz(url_opener); openUrlOnClick(evcol(e), evrow(e), cast(char*)opener_cstr); } } } static if (isPatchEnabled!"RIGHTCLICKTOPLUMB_PATCH") { // Right click plumb is already handled in mouseaction } } // Selection handling void selnotify(XEvent* e) { ulong nitems, ofs, rem; int format; ubyte* data; ubyte* last; ubyte* repl; Atom type, incratom, property = None; incratom = XInternAtom(xw.dpy, "INCR", False); 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, st.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. */ xw.attrs.event_mask &= ~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. */ xw.attrs.event_mask |= PropertyChangeMask; XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); /* * Deleting the property is the transfer start signal. */ XDeleteProperty(xw.dpy, xw.win, cast(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 = cast(ubyte*)memchr(repl, '\n', last - repl)) !is null) { *repl++ = '\r'; } if (IS_SET(WinMode.BRCKTPASTE) && ofs == 0) ttywrite("\033[200~", 6, 0); ttywrite(cast(char*)data, nitems * format / 8, 1); if (IS_SET(WinMode.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, cast(int)property); } void selrequest(XEvent* e) { XSelectionRequestEvent* xsre; XSelectionEvent xev; Atom xa_targets, string, clipboard; char* seltext; xsre = cast(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", False); if (xsre.target == xa_targets) { /* respond with the supported type */ string = xsel.xtarget; XChangeProperty(xsre.display, xsre.requestor, xsre.property, XA_ATOM, 32, PropModeReplace, cast(ubyte*)&string, 1); xev.property = xsre.property; } else if (xsre.target == xsel.xtarget || xsre.target == XA_STRING) { /* * with XA_STRING non ascii characters may be incorrect in the * requestor. It is not our problem, use utf8. */ clipboard = XInternAtom(xw.dpy, "CLIPBOARD", False); 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 !is null) { XChangeProperty(xsre.display, xsre.requestor, xsre.property, xsre.target, 8, PropModeReplace, cast(ubyte*)seltext, cast(int)strlen(seltext)); xev.property = xsre.property; } } /* all done, send a notification to the listener */ if (!XSendEvent(xsre.display, xsre.requestor, True, NoEventMask, cast(XEvent*)&xev)) fprintf(stderr, "Error sending SelectionNotify event\n"); } void selclear(XEvent* e) { xsel.primary = null; xsel.clipboard = null; } // Clipboard functions extern(C) void clipcopy(const(st.Arg)* dummy) { import core.stdc.stdlib : free; import st : xstrdup, xsel; Atom clipboard; free(xsel.clipboard); xsel.clipboard = null; if (xsel.primary !is null) { xsel.clipboard = xstrdup(xsel.primary); clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); } } extern(C) void xclipcopy() { clipcopy(null); } extern(C) void clippaste(const(st.Arg)* dummy) { static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") { // TODO: check keyboard select mode } Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, xw.win, CurrentTime); } extern(C) void selpaste(const(st.Arg)* dummy) { static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") { // TODO: check keyboard select mode } XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, xw.win, CurrentTime); } // Constants for xembed enum { XEMBED_FOCUS_IN = 4, XEMBED_FOCUS_OUT = 5, } // Additional required externs extern(C) { void XkbBell(Display* display, Window window, int percent, Atom name); int Xutf8TextListToTextProperty(Display* display, char** list, int count, XICCEncodingStyle style, XTextProperty* text_prop_return); int Xutf8LookupString(XIC ic, XKeyPressedEvent* event, char* buffer_return, int bytes_buffer, KeySym* keysym_return, Status* status_return); void XftDefaultSubstitute(Display* dpy, int screen, FcPattern* pattern); void XftTextExtentsUtf8(Display* dpy, XftFont* pub, const(FcChar8)* string, int len, XGlyphInfo* extents); FcPattern* XftXlfdParse(const(char)* xlfd_orig, Bool ignore_scalable, Bool complete); void XftDrawChange(XftDraw* draw, Drawable drawable); int XGetWindowProperty(Display* display, Window w, Atom property, long offset, long length, Bool delete_, Atom req_type, Atom* actual_type_return, int* actual_format_return, ulong* nitems_return, ulong* bytes_after_return, ubyte** prop_return); int XDeleteProperty(Display* display, Window w, Atom property); void XFree(void* data); Bool XSendEvent(Display* display, Window w, Bool propagate, long event_mask, XEvent* event_send); Bool XCheckTypedWindowEvent(Display* display, Window w, int event_type, XEvent* event_return); } enum XICCEncodingStyle { XStringStyle, XCompoundTextStyle, XTextStyle, XStdICCTextStyle, XUTF8StringStyle } enum LASTEvent = 36; // Global option variables (these should be defined somewhere) __gshared { char* opt_cmd; char* opt_io; char* opt_title; char* opt_embed; char* opt_class; char* opt_font; char* opt_line; char* opt_name; int oldbutton; char* usedfont; double usedfontsize; double defaultfontsize; uint ignoremod = 0; int cursorblinks; char* customkey; uint buttons; // bit field of pressed buttons uint forcemousemod = ShiftMask; static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") { int focused = 0; } // Font cache variables Fontcache* frc = null; int frclen = 0; int frccap = 0; } // Font cache structure (if not already defined elsewhere) struct Fontcache { XftFont* font; int flags; uint unicodep; } // Required kmap function char* kmap(KeySym k, uint state) { const(Key)* kp; int i; /* Check for mapped keys out of X11 function keys. */ for (i = 0; i < mappedkeys.length; i++) { if (mappedkeys[i] == k) break; } if (i == mappedkeys.length) { if ((k & 0xFFFF) < 0xFD00) return null; } for (kp = key.ptr; kp < key.ptr + key.length; kp++) { if (kp.k != k) continue; if (!match(kp.mask, state)) continue; if (IS_SET(WinMode.APPKEYPAD) ? kp.appkey < 0 : kp.appkey > 0) continue; if (IS_SET(WinMode.NUMLOCK) && kp.appkey == 2) continue; if (IS_SET(WinMode.APPCURSOR) ? kp.appcursor < 0 : kp.appcursor > 0) continue; return cast(char*)kp.s; } return null; } // Additional required imports and externs extern(C) { extern __gshared const(KeySym)[] mappedkeys; extern __gshared const(Key)[] key; extern __gshared const(Shortcut)[] shortcuts; } // Additional helper functions void xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) { Color drawcol; /* remove the old cursor */ if (selected(ox, oy)) og.mode ^= GlyphAttribute.REVERSE; xdrawglyph(og, ox, oy); if (IS_SET(WinMode.HIDE)) return; /* * Select the right color for the right mode. */ g.mode &= GlyphAttribute.BOLD | GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.STRUCK | GlyphAttribute.WIDE; if (IS_SET(WinMode.REVERSE)) { g.mode |= GlyphAttribute.REVERSE; g.bg = config.defaultfg; if (selected(cx, cy)) { drawcol = dc.col[config.defaultcs]; g.fg = config.defaultrcs; } else { drawcol = dc.col[config.defaultrcs]; g.fg = config.defaultcs; } } else { if (selected(cx, cy)) { g.fg = config.defaultfg; g.bg = config.defaultrcs; } else { g.fg = config.defaultbg; g.bg = config.defaultcs; } drawcol = dc.col[g.bg]; } /* draw the new one */ if (IS_SET(WinMode.FOCUSED)) { switch (st.win.cursor) { case 0: /* blinking block */ case 1: /* blinking block (default) */ if (IS_SET(WinMode.BLINK)) break; goto case; case 2: /* steady block */ xdrawglyph(g, cx, cy); break; case 3: /* blinking underline */ if (IS_SET(WinMode.BLINK)) break; goto case; case 4: /* steady underline */ XftDrawRect(xw.draw, &drawcol, borderpx + cx * st.win.cw, borderpx + (cy + 1) * st.win.ch - cursorthickness, st.win.cw, cursorthickness); break; case 5: /* blinking bar */ if (IS_SET(WinMode.BLINK)) break; goto case; case 6: /* steady bar */ XftDrawRect(xw.draw, &drawcol, borderpx + cx * st.win.cw, borderpx + cy * st.win.ch, cursorthickness, st.win.ch); break; case 7: /* blinking st cursor */ if (IS_SET(WinMode.BLINK)) break; goto case; case 8: /* steady st cursor */ g.u = stcursor; xdrawglyph(g, cx, cy); break; default: break; } } else { XftDrawRect(xw.draw, &drawcol, borderpx + cx * st.win.cw, borderpx + cy * st.win.ch, st.win.cw - 1, 1); XftDrawRect(xw.draw, &drawcol, borderpx + cx * st.win.cw, borderpx + cy * st.win.ch, 1, st.win.ch - 1); XftDrawRect(xw.draw, &drawcol, borderpx + (cx + 1) * st.win.cw - 1, borderpx + cy * st.win.ch, 1, st.win.ch - 1); XftDrawRect(xw.draw, &drawcol, borderpx + cx * st.win.cw, borderpx + (cy + 1) * st.win.ch - 1, st.win.cw, 1); } } void xdrawglyph(Glyph g, int x, int y) { int numspecs; XftGlyphFontSpec[1] spec; numspecs = xmakeglyphfontspecs(&spec[0], &g, 1, x, y); xdrawglyphfontspecs(&spec[0], g, numspecs, x, y); } int xmakeglyphfontspecs(XftGlyphFontSpec* specs, const(Glyph)* glyphs, int len, int x, int y) { if (len <= 0 || specs is null || glyphs is null) { return 0; } float winx = borderpx + x * st.win.cw, winy = borderpx + y * st.win.ch, xp, yp; ushort mode, prevmode = ushort.max; st.Font* font = &dc.font; int frcflags = 0; float runewidth = st.win.cw * ((glyphs[0].mode & GlyphAttribute.WIDE) ? 2.0f : 1.0f); Rune rune; FT_UInt glyphidx; int i, numspecs = 0; // No need to limit len - caller manages buffer size xp = winx; yp = winy + font.ascent; // Initialize yp here in case prevmode check is skipped for (i = 0; i < len; i++) { /* Fetch rune and mode for current glyph. */ rune = glyphs[i].u; mode = cast(ushort)glyphs[i].mode; /* Skip dummy wide-character spacing. */ if (mode & GlyphAttribute.WDUMMY) continue; /* Determine font for glyph if different from previous glyph. */ if (prevmode != mode) { prevmode = mode; font = &dc.font; frcflags = 0; runewidth = st.win.cw * ((mode & GlyphAttribute.WIDE) ? 2.0f : 1.0f); if ((mode & GlyphAttribute.ITALIC) && (mode & GlyphAttribute.BOLD)) { font = &dc.ibfont; frcflags = FRC_ITALICBOLD; } else if (mode & GlyphAttribute.ITALIC) { font = &dc.ifont; frcflags = FRC_ITALIC; } else if (mode & GlyphAttribute.BOLD) { font = &dc.bfont; frcflags = FRC_BOLD; } yp = winy + font.ascent; } /* Lookup character index with fallback font. */ static if (isPatchEnabled!"BOXDRAW_PATCH") { import patch.boxdraw : boxdrawindex; import st : ATTR_BOXDRAW; if (mode & ATTR_BOXDRAW) { /* minor shoehorning: boxdraw uses only this ushort */ specs[numspecs].font = font.match; specs[numspecs].glyph = boxdrawindex(&glyphs[i]); specs[numspecs].x = cast(short)xp; specs[numspecs].y = cast(short)yp; xp += runewidth; numspecs++; continue; } } glyphidx = XftCharIndex(xw.dpy, font.match, rune); if (glyphidx) { specs[numspecs].font = font.match; specs[numspecs].glyph = glyphidx; specs[numspecs].x = cast(short)xp; specs[numspecs].y = cast(short)yp; xp += runewidth; numspecs++; continue; } /* Fallback on font cache, search the font cache for match. */ int f; 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) { FcResult result; font.set = FcFontSort(null, font.pattern, 1, null, &result); } if (!font.set) continue; /* * 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* fcpattern = FcPatternDuplicate(font.pattern); FcCharSet* fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, rune); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddBool(fcpattern, FC_SCALABLE, 1); FcConfigSubstitute(null, fcpattern, FcMatchKind.FcMatchPattern); FcDefaultSubstitute(fcpattern); FcFontSet*[1] fcsets = [font.set]; FcResult fcres; FcPattern* fontpattern = FcFontSetMatch(null, fcsets.ptr, 1, fcpattern, &fcres); /* Allocate memory for the new cache entry. */ if (frclen >= frccap) { frccap += 16; frc = cast(Fontcache*)xrealloc(frc, frccap * Fontcache.sizeof); } frc[frclen].font = XftFontOpenPattern(xw.dpy, fontpattern); if (!frc[frclen].font) { import core.stdc.errno : errno; import core.stdc.string : strerror; 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); } if (f < frclen) { specs[numspecs].font = frc[f].font; specs[numspecs].glyph = glyphidx; specs[numspecs].x = cast(short)xp; specs[numspecs].y = cast(short)yp; xp += runewidth; numspecs++; } } return numspecs; } void xdrawglyphfontspecs(const(XftGlyphFontSpec)* specs, Glyph base, int len, int x, int y, int dmode = 0) { if (specs is null || len <= 0) return; if (xw.draw is null) return; // Bold color issue fixed int charlen = len * ((base.mode & GlyphAttribute.WIDE) ? 2 : 1); int winx = borderpx + x * st.win.cw, winy = borderpx + y * st.win.ch; int width = charlen * st.win.cw; static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") { import st : DrawingMode; // If no drawing mode specified, draw both background and foreground if (dmode == 0) { dmode = DrawingMode.BG | DrawingMode.FG; } } Color* fg, bg, temp; int i; // Bounds checking if (winx < 0 || winy < 0 || winx + width > st.win.w || winy + st.win.ch > st.win.h) { // Don't return - let it clip naturally } /* Fallback on color display for attributes not supported by the font */ // NOTE: Disabled color fallback - it was changing all bold colors to orange // The terminal should use the bold font variant even if weight doesn't match exactly /* if (base.mode & GlyphAttribute.ITALIC && base.mode & GlyphAttribute.BOLD) { if (dc.ibfont.badslant || dc.ibfont.badweight) base.fg = config.defaultattr; } else if ((base.mode & GlyphAttribute.ITALIC && dc.ifont.badslant) || (base.mode & GlyphAttribute.BOLD && dc.bfont.badweight)) { base.fg = config.defaultattr; } */ if (IS_TRUECOL(base.fg)) { XftColorFree(xw.dpy, xw.vis, xw.cmap, &truecols.fg); if (!xtruecolor(base.fg, &truecols.fg)) fg = &dc.col[config.defaultfg]; else fg = &truecols.fg; } else { if (base.fg >= dc.collen) { fg = &dc.col[config.defaultfg]; } else { fg = &dc.col[base.fg]; // Bright colors confirmed working } } if (IS_TRUECOL(base.bg)) { XftColorFree(xw.dpy, xw.vis, xw.cmap, &truecols.bg); if (!xtruecolor(base.bg, &truecols.bg)) bg = &dc.col[config.defaultbg]; else bg = &truecols.bg; } else { if (base.bg >= dc.collen) { bg = &dc.col[config.defaultbg]; } else { bg = &dc.col[base.bg]; } } /* Change basic system colors [0-7] to bright system colors [8-15] */ if ((base.mode & GlyphAttribute.BOLD) && BETWEEN(base.fg, 0, 7)) { fg = &dc.col[base.fg + 8]; } if (IS_SET(WinMode.REVERSE)) { if (fg == &dc.col[config.defaultfg]) { fg = &dc.col[config.defaultbg]; } else { truecols.colfg.color.red = cast(ushort)(~fg.color.red); truecols.colfg.color.green = cast(ushort)(~fg.color.green); truecols.colfg.color.blue = cast(ushort)(~fg.color.blue); truecols.colfg.color.alpha = fg.color.alpha; XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &truecols.colfg.color, &truecols.colfg); fg = &truecols.colfg; } if (bg == &dc.col[config.defaultbg]) { bg = &dc.col[config.defaultfg]; } else { truecols.colbg.color.red = cast(ushort)(~bg.color.red); truecols.colbg.color.green = cast(ushort)(~bg.color.green); truecols.colbg.color.blue = cast(ushort)(~bg.color.blue); truecols.colbg.color.alpha = bg.color.alpha; XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &truecols.colbg.color, &truecols.colbg); bg = &truecols.colbg; } } if (base.mode & GlyphAttribute.REVERSE) { temp = fg; fg = bg; bg = temp; } if (base.mode & GlyphAttribute.FAINT && !IS_TRUECOL(base.fg)) { truecols.colfg.color.red = fg.color.red / 2; truecols.colfg.color.green = fg.color.green / 2; truecols.colfg.color.blue = fg.color.blue / 2; truecols.colfg.color.alpha = fg.color.alpha; XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &truecols.colfg.color, &truecols.colfg); fg = &truecols.colfg; } if (base.mode & GlyphAttribute.BLINK && IS_SET(WinMode.BLINK)) fg = bg; if (base.mode & GlyphAttribute.INVISIBLE) fg = bg; /* Intelligent cleaning up of the borders. */ if (x == 0) { int clear_y2 = winy + st.win.ch + ((winy + st.win.ch >= borderpx + st.win.th) ? st.win.h : 0); xclear(0, (y == 0) ? 0 : winy, borderpx, clear_y2); } if (winx + width >= borderpx + st.win.tw) { int clear_y2 = ((winy + st.win.ch >= borderpx + st.win.th) ? st.win.h : (winy + st.win.ch)); xclear(winx + width, (y == 0) ? 0 : winy, st.win.w, clear_y2); } if (y == 0) { xclear(winx, 0, winx + width, borderpx); } if (winy + st.win.ch >= borderpx + st.win.th) { xclear(winx, winy + st.win.ch, winx + width, st.win.h); } /* Clean up the region we want to draw to. */ if (bg is null) { return; } static if (isPatchEnabled!"ALPHA_PATCH" && isPatchEnabled!"ALPHA_GRADIENT_PATCH") { import config : grad_alpha, stat_alpha; // Apply gradient to background alpha bg.color.alpha = cast(ushort)(grad_alpha * 0xffff * (st.win.h - y * st.win.ch) / st.win.h + stat_alpha * 0xffff); // Uncomment to invert the gradient: // bg.color.alpha = cast(ushort)(grad_alpha * 0xffff * (y * st.win.ch) / st.win.h + stat_alpha * 0xffff); } static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") { import st : DrawingMode; // Draw background only if requested if (dmode & DrawingMode.BG) { XftDrawRect(xw.draw, bg, winx, winy, width, st.win.ch); } // Draw foreground only if requested if ((dmode & DrawingMode.FG) && fg !is null) { XftDrawGlyphFontSpec(xw.draw, fg, specs, len); } } else { // Original single-pass drawing static if (isPatchEnabled!"ALPHA_PATCH" && isPatchEnabled!"ALPHA_GRADIENT_PATCH") { import config : grad_alpha, stat_alpha; // Apply gradient to background alpha bg.color.alpha = cast(ushort)(grad_alpha * 0xffff * (st.win.h - y * st.win.ch) / st.win.h + stat_alpha * 0xffff); // Uncomment to invert the gradient: // bg.color.alpha = cast(ushort)(grad_alpha * 0xffff * (y * st.win.ch) / st.win.h + stat_alpha * 0xffff); } XftDrawRect(xw.draw, bg, winx, winy, width, st.win.ch); /* Render the glyphs. */ if (fg is null) { return; } XftDrawGlyphFontSpec(xw.draw, fg, specs, len); } static if (isPatchEnabled!"BOXDRAW_PATCH") { import patch.boxdraw : drawboxes; import st : ATTR_BOXDRAW; if (base.mode & ATTR_BOXDRAW) { drawboxes(winx, winy, st.win.cw, st.win.ch, fg, bg, specs, len); } } /* Render underline and strikethrough. */ static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") { import st : DrawingMode; if (dmode & DrawingMode.FG) { if (base.mode & GlyphAttribute.UNDERLINE) { XftDrawRect(xw.draw, fg, winx, cast(int)(winy + dc.font.ascent * chscale + 1), width, 1); } if (base.mode & GlyphAttribute.STRUCK) { XftDrawRect(xw.draw, fg, winx, cast(int)(winy + 2 * dc.font.ascent * chscale / 3), width, 1); } } } else { if (base.mode & GlyphAttribute.UNDERLINE) { XftDrawRect(xw.draw, fg, winx, cast(int)(winy + dc.font.ascent * chscale + 1), width, 1); } if (base.mode & GlyphAttribute.STRUCK) { XftDrawRect(xw.draw, fg, winx, cast(int)(winy + 2 * dc.font.ascent * chscale / 3), width, 1); } } static if (isPatchEnabled!"OPENURLONCLICK_PATCH") { static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") { import st : DrawingMode; if (dmode & DrawingMode.FG) { import patch.openurlonclick : url_draw, url_y1, url_y2, url_x1, url_x2, url_maxcol; /* underline url (openurlonclick patch) */ if (url_draw && y >= url_y1 && y <= url_y2) { int x1 = (y == url_y1) ? url_x1 : 0; int x2 = (y == url_y2) ? min(url_x2, term.col-1) : url_maxcol; if (x + charlen > x1 && x <= x2) { int xu = max(x, x1); int wu = (x2 - xu + 1) * st.win.cw; xu = borderpx + xu * st.win.cw; XftDrawRect(xw.draw, fg, xu, cast(int)(winy + dc.font.ascent * chscale + 2), wu, 1); url_draw = (y != url_y2 || x + charlen <= x2); } } } } else { import patch.openurlonclick : url_draw, url_y1, url_y2, url_x1, url_x2, url_maxcol; /* underline url (openurlonclick patch) */ if (url_draw && y >= url_y1 && y <= url_y2) { int x1 = (y == url_y1) ? url_x1 : 0; int x2 = (y == url_y2) ? min(url_x2, term.col-1) : url_maxcol; if (x + charlen > x1 && x <= x2) { int xu = max(x, x1); int wu = (x2 - xu + 1) * st.win.cw; xu = borderpx + xu * st.win.cw; XftDrawRect(xw.draw, fg, xu, cast(int)(winy + dc.font.ascent * chscale + 2), wu, 1); url_draw = (y != url_y2 || x + charlen <= x2); } } } } } int xtruecolor(uint color, XftColor* ncolor) { XRenderColor col; col.alpha = 0xffff; col.red = cast(ushort)TRUERED(color); col.green = cast(ushort)TRUEGREEN(color); col.blue = cast(ushort)TRUEBLUE(color); return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &col, ncolor); } // Color manipulation macros uint TRUERED(uint x) { return ((x) & 0xff0000) >> 8; } uint TRUEGREEN(uint x) { return ((x) & 0xff00); } uint TRUEBLUE(uint x) { return ((x) & 0xff) << 8; } bool IS_TRUECOL(uint x) { return ((1 << 24) & (x)) != 0; } bool BETWEEN(T)(T x, T a, T b) { return (a) <= (x) && (x) <= (b); } // Time difference in milliseconds long TIMEDIFF(timespec t1, timespec t2) { return (t1.tv_sec - t2.tv_sec) * 1000 + (t1.tv_nsec - t2.tv_nsec) / 1_000_000; } // Additional globals __gshared { struct TrueColors { XftColor fg; XftColor bg; XftColor colfg; XftColor colbg; } TrueColors truecols; } // Font cache constants enum { FRC_NORMAL = 0, FRC_ITALIC = 1, FRC_BOLD = 2, FRC_ITALICBOLD = 3 } // Additional imports // wcwidth is declared in st.d alias ushort FT_UInt; alias ushort FcChar8; alias uint FcChar32; extern(C) FT_UInt XftCharIndex(Display* dpy, XftFont* pub, FcChar32 ucs4); // Mouse timing constants from config extern(C) __gshared { extern uint doubleclicktimeout; extern uint tripleclicktimeout; extern const(uint)[] selmasks; extern int selmaskslen; } // Mouse shortcuts are imported from config.d through extern(C) linkage extern(C) const(MouseShortcut)[] getMouseShortcuts(); // Selection types enum { SEL_REGULAR = 1, SEL_RECTANGULAR = 2 } struct XGlyphInfo { ushort width; ushort height; short x; short y; short xOff; short yOff; }