2460 lines
76 KiB
D
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;
|
||
|
|
}
|