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 ? "<text>" : "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;
}