dst/source/x.d
2025-06-26 13:47:07 -05:00

2460 lines
76 KiB
D

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;
}