X-Git-Url: https://jasonwoof.com/gitweb/?p=st.git;a=blobdiff_plain;f=st.c;h=afa6813989c5b545a94c7b0c346b6aa6510c8e86;hp=8c8efafe9f954aefaeb405cc01eedf36997d8d99;hb=be7c6d7fb09ff50127332060d771b94a3bc8e44c;hpb=25f47cb83a71f7d2872a197209cc2d8cd1c955ec diff --git a/st.c b/st.c index 8c8efaf..afa6813 100644 --- a/st.c +++ b/st.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -24,11 +25,14 @@ #include #include #include -#include #include #include + #define Glyph Glyph_ #define Font Font_ +#define Draw XftDraw * +#define Colour XftColor +#define Colourmap Colormap #if defined(__linux) #include @@ -39,7 +43,7 @@ #endif #define USAGE \ - "st " VERSION " (c) 2010-2012 st engineers\n" \ + "st " VERSION " (c) 2010-2013 st engineers\n" \ "usage: st [-v] [-c class] [-f font] [-g geometry] [-o file]" \ " [-t title] [-w windowid] [-e command ...]\n" @@ -48,17 +52,19 @@ #define XEMBED_FOCUS_OUT 5 /* Arbitrary sizes */ -#define ESC_BUF_SIZ 256 +#define UTF_SIZ 4 +#define ESC_BUF_SIZ (128*UTF_SIZ) #define ESC_ARG_SIZ 16 -#define STR_BUF_SIZ 256 -#define STR_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ #define DRAW_BUF_SIZ 20*1024 -#define UTF_SIZ 4 -#define XK_NO_MOD UINT_MAX -#define XK_ANY_MOD 0 +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13) #define REDRAW_TIMEOUT (80*1000) /* 80 ms */ +/* macros */ #define SERRNO strerror(errno) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) < (b) ? (b) : (a)) @@ -67,10 +73,8 @@ #define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg) -#define IS_SET(flag) (term.mode & (flag)) +#define IS_SET(flag) ((term.mode & (flag)) != 0) #define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_usec-t2.tv_usec)/1000) -#define X2COL(x) (((x) - BORDER)/xw.cw) -#define Y2ROW(y) (((y) - BORDER)/xw.ch) #define VT102ID "\033[?6c" @@ -85,18 +89,14 @@ enum glyph_attribute { }; enum cursor_movement { - CURSOR_UP, - CURSOR_DOWN, - CURSOR_LEFT, - CURSOR_RIGHT, CURSOR_SAVE, CURSOR_LOAD }; enum cursor_state { CURSOR_DEFAULT = 0, - CURSOR_HIDE = 1, - CURSOR_WRAPNEXT = 2 + CURSOR_WRAPNEXT = 1, + CURSOR_ORIGIN = 2 }; enum glyph_state { @@ -105,16 +105,20 @@ enum glyph_state { }; enum term_mode { - MODE_WRAP = 1, + MODE_WRAP = 1, MODE_INSERT = 2, MODE_APPKEYPAD = 4, MODE_ALTSCREEN = 8, - MODE_CRLF = 16, + MODE_CRLF = 16, MODE_MOUSEBTN = 32, MODE_MOUSEMOTION = 64, MODE_MOUSE = 32|64, MODE_REVERSE = 128, - MODE_KBDLOCK = 256 + MODE_KBDLOCK = 256, + MODE_HIDE = 512, + MODE_ECHO = 1024, + MODE_APPCURSOR = 2048, + MODE_MOUSESGR = 4096, }; enum escape_state { @@ -123,6 +127,7 @@ enum escape_state { ESC_STR = 4, /* DSC, OSC, PM, APC */ ESC_ALTCHARSET = 8, ESC_STR_END = 16, /* a final string was encountered */ + ESC_TEST = 32, /* Enter in test mode */ }; enum window_state { @@ -131,6 +136,11 @@ enum window_state { WIN_FOCUSED = 4 }; +enum selection_type { + SEL_REGULAR = 1, + SEL_RECTANGULAR = 2 +}; + /* bit macro */ #undef B0 enum { B0=1, B1=2, B2=4, B3=8, B4=16, B5=32, B6=64, B7=128 }; @@ -148,7 +158,7 @@ typedef struct { uchar state; /* state flags */ } Glyph; -typedef Glyph* Line; +typedef Glyph *Line; typedef struct { Glyph attr; /* current char attributes */ @@ -164,7 +174,7 @@ typedef struct { int len; /* raw string length */ char priv; int arg[ESC_ARG_SIZ]; - int narg; /* nb of args */ + int narg; /* nb of args */ char mode; } CSIEscape; @@ -190,26 +200,26 @@ typedef struct { int bot; /* bottom scroll limit */ int mode; /* terminal mode flags */ int esc; /* escape state flags */ + bool numlock; /* lock numbers in keyboard */ bool *tabs; } Term; /* Purely graphic info */ typedef struct { - Display* dpy; - Colormap cmap; + Display *dpy; + Colourmap cmap; Window win; - XdbeBackBuffer buf; + Drawable buf; Atom xembed, wmdeletewin; XIM xim; XIC xic; - XftDraw *xft_draw; + Draw draw; Visual *vis; int scr; bool isfixed; /* is fixed geometry? */ int fx, fy, fw, fh; /* fixed geometry */ int tw, th; /* tty width and height */ - int w; /* window width */ - int h; /* window height */ + int w, h; /* window width and height */ int ch; /* char height */ int cw; /* char width */ char state; /* focus, redraw, visible */ @@ -219,11 +229,16 @@ typedef struct { KeySym k; uint mask; char s[ESC_BUF_SIZ]; + /* three valued logic variables: 0 indifferent, 1 on, -1 off */ + signed char appkey; /* application keypad */ + signed char appcursor; /* application cursor */ + signed char crlf; /* crlf mode */ } Key; /* TODO: use better name for vars... */ typedef struct { int mode; + int type; int bx, by; int ex, ey; struct { @@ -236,6 +251,27 @@ typedef struct { struct timeval tclick2; } Selection; +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +/* function definitions used in config.h */ +static void clippaste(const Arg *); +static void numlock(const Arg *); +static void selpaste(const Arg *); +static void xzoom(const Arg *); + +/* Config.h for applying patches and the configuration. */ #include "config.h" /* Font structure */ @@ -246,19 +282,21 @@ typedef struct { int descent; short lbearing; short rbearing; - XftFont *xft_set; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; } Font; /* Drawing Context */ typedef struct { - XftColor xft_col[LEN(colorname) < 256 ? 256 : LEN(colorname)]; - GC gc; + Colour col[LEN(colorname) < 256 ? 256 : LEN(colorname)]; Font font, bfont, ifont, ibfont; + GC gc; } DC; static void die(const char *, ...); static void draw(void); -static void redraw(void); +static void redraw(int); static void drawregion(int, int, int, int); static void execsh(void); static void sigchld(int); @@ -273,13 +311,14 @@ static void strhandle(void); static void strparse(void); static void strreset(void); -static void tclearregion(int, int, int, int); +static void tclearregion(int, int, int, int, int); static void tcursor(int); static void tdeletechar(int); static void tdeleteline(int); static void tinsertblank(int); static void tinsertblankline(int); static void tmoveto(int, int); +static void tmoveato(int x, int y); static void tnew(int, int); static void tnewline(int); static void tputtab(bool); @@ -289,13 +328,15 @@ static int tresize(int, int); static void tscrollup(int, int); static void tscrolldown(int, int); static void tsetattr(int*, int); -static void tsetchar(char*); +static void tsetchar(char *, Glyph *, int, int); static void tsetscroll(int, int); static void tswapscreen(void); static void tsetdirt(int, int); static void tsetmode(bool, bool, int *, int); static void tfulldirt(void); +static void techo(char *, int); +static inline bool match(uint, uint); static void ttynew(void); static void ttyread(void); static void ttyresize(void); @@ -307,10 +348,15 @@ static void xclear(int, int, int, int); static void xdrawcursor(void); static void xinit(void); static void xloadcols(void); +static int xsetcolorname(int, const char *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(char *, int); +static void xsettitle(char *); static void xresettitle(void); static void xseturgency(int); static void xsetsel(char*); static void xtermclear(int, int, int, int); +static void xunloadfonts(void); static void xresize(int, int); static void expose(XEvent *); @@ -319,6 +365,7 @@ static void unmap(XEvent *); static char *kmap(KeySym, uint); static void kpress(XEvent *); static void cmessage(XEvent *); +static void cresize(int, int); static void resize(XEvent *); static void focus(XEvent *); static void brelease(XEvent *); @@ -331,7 +378,6 @@ static void selrequest(XEvent *); static void selinit(void); static inline bool selected(int, int); static void selcopy(void); -static void selpaste(void); static void selscroll(int, int); static int utf8decode(char *, long *); @@ -339,10 +385,10 @@ static int utf8encode(long *, char *); static int utf8size(char *); static int isfullutf8(char *, int); +static ssize_t xwrite(int, char *, size_t); static void *xmalloc(size_t); static void *xrealloc(void *, size_t); -static void *xcalloc(size_t nmemb, size_t size); -static char *smstrcat(char *, ...); +static void *xcalloc(size_t, size_t); static void (*handler[LASTEvent])(XEvent *) = { [KeyPress] = kpress, @@ -378,6 +424,45 @@ static char *opt_embed = NULL; static char *opt_class = NULL; static char *opt_font = NULL; +static char *usedfont = NULL; +static int usedfontsize = 0; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + long c; + int flags; +} Fontcache; + +/* + * Fontcache is a ring buffer, with frccur as current position and frclen as + * the current length of used elements. + */ + +static Fontcache frc[1024]; +static int frccur = -1, frclen = 0; + +ssize_t +xwrite(int fd, char *s, size_t len) { + size_t aux = len; + + while(len > 0) { + ssize_t r = write(fd, s, len); + if(r < 0) + return r; + len -= r; + s += r; + } + return aux; +} + void * xmalloc(size_t len) { void *p = malloc(len); @@ -406,44 +491,6 @@ xcalloc(size_t nmemb, size_t size) { return p; } -char * -smstrcat(char *src, ...) -{ - va_list fmtargs; - char *ret, *p, *v; - int len, slen, flen; - - len = slen = strlen(src); - - va_start(fmtargs, src); - for(;;) { - v = va_arg(fmtargs, char *); - if(v == NULL) - break; - len += strlen(v); - } - va_end(fmtargs); - - p = ret = xmalloc(len+1); - memmove(p, src, slen); - p += slen; - - va_start(fmtargs, src); - for(;;) { - v = va_arg(fmtargs, char *); - if(v == NULL) - break; - flen = strlen(v); - memmove(p, v, flen); - p += flen; - } - va_end(fmtargs); - - ret[len] = '\0'; - - return ret; -} - int utf8decode(char *s, long *u) { uchar c; @@ -580,6 +627,22 @@ selinit(void) { sel.xtarget = XA_STRING; } +static int +x2col(int x) { + x -= borderpx; + x /= xw.cw; + + return LIMIT(x, 0, term.col-1); +} + +static int +y2row(int y) { + y -= borderpx; + y /= xw.ch; + + return LIMIT(y, 0, term.row-1); +} + static inline bool selected(int x, int y) { int bx, ex; @@ -594,28 +657,48 @@ selected(int x, int y) { || (y == sel.e.y && x <= sel.e.x)) || (y == sel.b.y && x >= sel.b.x && (x <= sel.e.x || sel.b.y != sel.e.y)); + switch(sel.type) { + case SEL_REGULAR: + return ((sel.b.y < y && y < sel.e.y) + || (y == sel.e.y && x <= sel.e.x)) + || (y == sel.b.y && x >= sel.b.x + && (x <= sel.e.x || sel.b.y != sel.e.y)); + case SEL_RECTANGULAR: + return ((sel.b.y <= y && y <= sel.e.y) + && (sel.b.x <= x && x <= sel.e.x)); + }; } void -getbuttoninfo(XEvent *e, int *b, int *x, int *y) { - if(b) - *b = e->xbutton.button; +getbuttoninfo(XEvent *e) { + int type; + uint state = e->xbutton.state &~Button1Mask; + + sel.alt = IS_SET(MODE_ALTSCREEN); + + sel.ex = x2col(e->xbutton.x); + sel.ey = y2row(e->xbutton.y); - *x = X2COL(e->xbutton.x); - *y = Y2ROW(e->xbutton.y); sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex; sel.b.y = MIN(sel.by, sel.ey); sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx; sel.e.y = MAX(sel.by, sel.ey); + + sel.type = SEL_REGULAR; + for(type = 1; type < LEN(selmasks); ++type) { + if(match(selmasks[type], state)) { + sel.type = type; + break; + } + } } void mousereport(XEvent *e) { - int x = X2COL(e->xbutton.x); - int y = Y2ROW(e->xbutton.y); - int button = e->xbutton.button; - int state = e->xbutton.state; - char buf[] = { '\033', '[', 'M', 0, 32+x+1, 32+y+1 }; + int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y), + button = e->xbutton.button, state = e->xbutton.state, + len; + char buf[40]; static int ob, ox, oy; /* from urxvt */ @@ -624,7 +707,9 @@ mousereport(XEvent *e) { return; button = ob + 32; ox = x, oy = y; - } else if(e->xbutton.type == ButtonRelease || button == AnyButton) { + } else if(!IS_SET(MODE_MOUSESGR) + && (e->xbutton.type == ButtonRelease + || button == AnyButton)) { button = 3; } else { button -= Button1; @@ -636,11 +721,23 @@ mousereport(XEvent *e) { } } - buf[3] = 32 + button + (state & ShiftMask ? 4 : 0) + button += (state & ShiftMask ? 4 : 0) + (state & Mod4Mask ? 8 : 0) + (state & ControlMask ? 16 : 0); - ttywrite(buf, sizeof(buf)); + len = 0; + if(IS_SET(MODE_MOUSESGR)) { + len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", + button, x+1, y+1, + e->xbutton.type == ButtonRelease ? 'm' : 'M'); + } else if(x < 223 && y < 223) { + len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", + 32+button, 32+x+1, 32+y+1); + } else { + return; + } + + ttywrite(buf, len); } void @@ -654,8 +751,13 @@ bpress(XEvent *e) { draw(); } sel.mode = 1; - sel.ex = sel.bx = X2COL(e->xbutton.x); - sel.ey = sel.by = Y2ROW(e->xbutton.y); + sel.type = SEL_REGULAR; + sel.ex = sel.bx = x2col(e->xbutton.x); + sel.ey = sel.by = y2row(e->xbutton.y); + } else if(e->xbutton.button == Button4) { + ttywrite("\031", 1); + } else if(e->xbutton.button == Button5) { + ttywrite("\005", 1); } } @@ -663,7 +765,7 @@ void selcopy(void) { char *str, *ptr, *p; int x, y, bufsize, is_selected = 0, size; - Glyph *gp; + Glyph *gp, *last; if(sel.bx == -1) { str = NULL; @@ -672,12 +774,21 @@ selcopy(void) { ptr = str = xmalloc(bufsize); /* append every set & selected glyph to the selection */ - for(y = 0; y < term.row; y++) { - for(x = 0; x < term.col; x++) { - gp = &term.line[y][x]; + for(y = sel.b.y; y < sel.e.y + 1; y++) { + is_selected = 0; + gp = &term.line[y][0]; + last = gp + term.col; + + while(--last >= gp && !(last->state & GLYPH_SET)) + /* nothing */; - if(!(is_selected = selected(x, y))) + for(x = 0; gp <= last; x++, ++gp) { + if(!selected(x, y)) { continue; + } else { + is_selected = 1; + } + p = (gp->state & GLYPH_SET) ? gp->c : " "; size = utf8size(p); memcpy(ptr, p, size); @@ -689,7 +800,6 @@ selcopy(void) { } *ptr = 0; } - sel.alt = IS_SET(MODE_ALTSCREEN); xsetsel(str); } @@ -716,12 +826,22 @@ selnotify(XEvent *e) { } void -selpaste(void) { +selpaste(const Arg *dummy) { XConvertSelection(xw.dpy, XA_PRIMARY, sel.xtarget, XA_PRIMARY, xw.win, CurrentTime); } -void selclear(XEvent *e) { +void +clippaste(const Arg *dummy) { + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, sel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +selclear(XEvent *e) { if(sel.bx == -1) return; sel.bx = -1; @@ -787,22 +907,22 @@ brelease(XEvent *e) { } if(e->xbutton.button == Button2) { - selpaste(); + selpaste(NULL); } else if(e->xbutton.button == Button1) { sel.mode = 0; - getbuttoninfo(e, NULL, &sel.ex, &sel.ey); + getbuttoninfo(e); term.dirty[sel.ey] = 1; if(sel.bx == sel.ex && sel.by == sel.ey) { sel.bx = -1; gettimeofday(&now, NULL); - if(TIMEDIFF(now, sel.tclick2) <= TRIPLECLICK_TIMEOUT) { + if(TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) { /* triple click on the line */ sel.b.x = sel.bx = 0; sel.e.x = sel.ex = term.col; sel.b.y = sel.e.y = sel.ey; selcopy(); - } else if(TIMEDIFF(now, sel.tclick1) <= DOUBLECLICK_TIMEOUT) { + } else if(TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) { /* double click to select word */ sel.bx = sel.ex; while(sel.bx > 0 && term.line[sel.ey][sel.bx-1].state & GLYPH_SET && @@ -829,23 +949,24 @@ brelease(XEvent *e) { void bmotion(XEvent *e) { - int starty, endy, oldey, oldex; + int oldey, oldex, oldsby, oldsey; if(IS_SET(MODE_MOUSE)) { mousereport(e); return; } - if(sel.mode) { - oldey = sel.ey; - oldex = sel.ex; - getbuttoninfo(e, NULL, &sel.ex, &sel.ey); + if(!sel.mode) + return; - if(oldey != sel.ey || oldex != sel.ex) { - starty = MIN(oldey, sel.ey); - endy = MAX(oldey, sel.ey); - tsetdirt(starty, endy); - } + oldey = sel.ey; + oldex = sel.ex; + oldsby = sel.b.y; + oldsey = sel.e.y; + getbuttoninfo(e); + + if(oldey != sel.ey || oldex != sel.ex) { + tsetdirt(MIN(sel.b.y, oldsby), MAX(sel.e.y, oldsey)); } } @@ -863,11 +984,23 @@ void execsh(void) { char **args; char *envshell = getenv("SHELL"); + const struct passwd *pass = getpwuid(getuid()); + char buf[sizeof(long) * 8 + 1]; unsetenv("COLUMNS"); unsetenv("LINES"); unsetenv("TERMCAP"); + if(pass) { + setenv("LOGNAME", pass->pw_name, 1); + setenv("USER", pass->pw_name, 1); + setenv("SHELL", pass->pw_shell, 0); + setenv("HOME", pass->pw_dir, 0); + } + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); + signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); @@ -875,9 +1008,9 @@ execsh(void) { signal(SIGTERM, SIG_DFL); signal(SIGALRM, SIG_DFL); - DEFAULT(envshell, SHELL); - putenv("TERM="TNAME); - args = opt_cmd ? opt_cmd : (char*[]){envshell, "-i", NULL}; + DEFAULT(envshell, shell); + setenv("TERM", termname, 1); + args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL}; execvp(args[0], args); exit(EXIT_FAILURE); } @@ -925,13 +1058,12 @@ ttynew(void) { cmdfd = m; signal(SIGCHLD, sigchld); if(opt_io) { - if(!strcmp(opt_io, "-")) { - iofd = STDOUT_FILENO; - } else { - if((iofd = open(opt_io, O_WRONLY | O_CREAT, 0666)) < 0) { - fprintf(stderr, "Error opening %s:%s\n", - opt_io, strerror(errno)); - } + iofd = (!strcmp(opt_io, "-")) ? + STDOUT_FILENO : + open(opt_io, O_WRONLY | O_CREAT, 0666); + if(iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + opt_io, strerror(errno)); } } } @@ -1027,18 +1159,20 @@ treset(void) { term.c = (TCursor){{ .mode = ATTR_NULL, - .fg = DefaultFG, - .bg = DefaultBG + .fg = defaultfg, + .bg = defaultbg }, .x = 0, .y = 0, .state = CURSOR_DEFAULT}; memset(term.tabs, 0, term.col * sizeof(*term.tabs)); - for(i = TAB; i < term.col; i += TAB) + for(i = tabspaces; i < term.col; i += tabspaces) term.tabs[i] = 1; term.top = 0; term.bot = term.row - 1; term.mode = MODE_WRAP; - tclearregion(0, 0, term.col-1, term.row-1); + tclearregion(0, 0, term.col-1, term.row-1, 0); + tmoveto(0, 0); + tcursor(CURSOR_SAVE); } void @@ -1056,6 +1190,8 @@ tnew(int col, int row) { term.alt [row] = xmalloc(term.col * sizeof(Glyph)); term.dirty[row] = 0; } + + term.numlock = 1; memset(term.tabs, 0, term.col * sizeof(*term.tabs)); /* setup screen */ treset(); @@ -1078,7 +1214,7 @@ tscrolldown(int orig, int n) { LIMIT(n, 0, term.bot-orig+1); - tclearregion(0, term.bot-n+1, term.col-1, term.bot); + tclearregion(0, term.bot-n+1, term.col-1, term.bot, 0); for(i = term.bot; i >= orig+n; i--) { temp = term.line[i]; @@ -1098,7 +1234,7 @@ tscrollup(int orig, int n) { Line temp; LIMIT(n, 0, term.bot-orig+1); - tclearregion(0, orig, term.col-1, orig+n-1); + tclearregion(0, orig, term.col-1, orig+n-1, 0); for(i = orig; i <= term.bot-n; i++) { temp = term.line[i]; @@ -1122,14 +1258,24 @@ selscroll(int orig, int n) { sel.bx = -1; return; } - if(sel.by < term.top) { - sel.by = term.top; - sel.bx = 0; - } - if(sel.ey > term.bot) { - sel.ey = term.bot; - sel.ex = term.col; - } + switch(sel.type) { + case SEL_REGULAR: + if(sel.by < term.top) { + sel.by = term.top; + sel.bx = 0; + } + if(sel.ey > term.bot) { + sel.ey = term.bot; + sel.ex = term.col; + } + break; + case SEL_RECTANGULAR: + if(sel.by < term.top) + sel.by = term.top; + if(sel.ey > term.bot) + sel.ey = term.bot; + break; + }; sel.b.y = sel.by, sel.b.x = sel.bx; sel.e.y = sel.ey, sel.e.x = sel.ex; } @@ -1172,18 +1318,33 @@ csiparse(void) { } } +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) { + tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0)); +} + void tmoveto(int x, int y) { + int miny, maxy; + + if(term.c.state & CURSOR_ORIGIN) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } LIMIT(x, 0, term.col-1); - LIMIT(y, 0, term.row-1); + LIMIT(y, miny, maxy); term.c.state &= ~CURSOR_WRAPNEXT; term.c.x = x; term.c.y = y; } void -tsetchar(char *c) { - char *vt100_0[62] = { /* 0x41 - 0x7e */ +tsetchar(char *c, Glyph *attr, int x, int y) { + static char *vt100_0[62] = { /* 0x41 - 0x7e */ "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */ 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */ 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */ @@ -1197,21 +1358,21 @@ tsetchar(char *c) { /* * The table is proudly stolen from rxvt. */ - if(term.c.attr.mode & ATTR_GFX) { + if(attr->mode & ATTR_GFX) { if(c[0] >= 0x41 && c[0] <= 0x7e && vt100_0[c[0] - 0x41]) { c = vt100_0[c[0] - 0x41]; } } - term.dirty[term.c.y] = 1; - term.line[term.c.y][term.c.x] = term.c.attr; - memcpy(term.line[term.c.y][term.c.x].c, c, UTF_SIZ); - term.line[term.c.y][term.c.x].state |= GLYPH_SET; + term.dirty[y] = 1; + term.line[y][x] = *attr; + memcpy(term.line[y][x].c, c, UTF_SIZ); + term.line[y][x].state |= GLYPH_SET; } void -tclearregion(int x1, int y1, int x2, int y2) { +tclearregion(int x1, int y1, int x2, int y2, int bce) { int x, y, temp; if(x1 > x2) @@ -1226,8 +1387,15 @@ tclearregion(int x1, int y1, int x2, int y2) { for(y = y1; y <= y2; y++) { term.dirty[y] = 1; - for(x = x1; x <= x2; x++) - term.line[y][x].state = 0; + for(x = x1; x <= x2; x++) { + if(bce) { + term.line[y][x] = term.c.attr; + memcpy(term.line[y][x].c, " ", 2); + term.line[y][x].state |= GLYPH_SET; + } else { + term.line[y][x].state = 0; + } + } } } @@ -1240,13 +1408,13 @@ tdeletechar(int n) { term.dirty[term.c.y] = 1; if(src >= term.col) { - tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 0); return; } memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph)); - tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y, 0); } void @@ -1258,13 +1426,13 @@ tinsertblank(int n) { term.dirty[term.c.y] = 1; if(dst >= term.col) { - tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 0); return; } memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph)); - tclearregion(src, term.c.y, dst - 1, term.c.y); + tclearregion(src, term.c.y, dst - 1, term.c.y, 0); } void @@ -1292,19 +1460,20 @@ tsetattr(int *attr, int l) { case 0: term.c.attr.mode &= ~(ATTR_REVERSE | ATTR_UNDERLINE | ATTR_BOLD \ | ATTR_ITALIC | ATTR_BLINK); - term.c.attr.fg = DefaultFG; - term.c.attr.bg = DefaultBG; + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; break; case 1: term.c.attr.mode |= ATTR_BOLD; break; - case 3: /* enter standout (highlight) */ + case 3: term.c.attr.mode |= ATTR_ITALIC; break; case 4: term.c.attr.mode |= ATTR_UNDERLINE; break; - case 5: + case 5: /* slow blink */ + case 6: /* rapid blink */ term.c.attr.mode |= ATTR_BLINK; break; case 7: @@ -1314,13 +1483,14 @@ tsetattr(int *attr, int l) { case 22: term.c.attr.mode &= ~ATTR_BOLD; break; - case 23: /* leave standout (highlight) mode */ + case 23: term.c.attr.mode &= ~ATTR_ITALIC; break; case 24: term.c.attr.mode &= ~ATTR_UNDERLINE; break; case 25: + case 26: term.c.attr.mode &= ~ATTR_BLINK; break; case 27: @@ -1343,7 +1513,7 @@ tsetattr(int *attr, int l) { } break; case 39: - term.c.attr.fg = DefaultFG; + term.c.attr.fg = defaultfg; break; case 48: if(i + 2 < l && attr[i + 1] == 5) { @@ -1362,7 +1532,7 @@ tsetattr(int *attr, int l) { } break; case 49: - term.c.attr.bg = DefaultBG; + term.c.attr.bg = defaultbg; break; default: if(BETWEEN(attr[i], 30, 37)) { @@ -1403,61 +1573,70 @@ tsetscroll(int t, int b) { void tsetmode(bool priv, bool set, int *args, int narg) { int *lim, mode; + bool alt; for(lim = args + narg; args < lim; ++args) { if(priv) { switch(*args) { break; case 1: /* DECCKM -- Cursor key */ - MODBIT(term.mode, set, MODE_APPKEYPAD); + MODBIT(term.mode, set, MODE_APPCURSOR); break; case 5: /* DECSCNM -- Reverse video */ mode = term.mode; MODBIT(term.mode, set, MODE_REVERSE); if(mode != term.mode) - redraw(); + redraw(REDRAW_TIMEOUT); break; - case 6: /* XXX: DECOM -- Origin */ + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CURSOR_ORIGIN); + tmoveato(0, 0); break; case 7: /* DECAWM -- Auto wrap */ MODBIT(term.mode, set, MODE_WRAP); break; - case 8: /* XXX: DECARM -- Auto repeat */ - break; case 0: /* Error (IGNORED) */ + case 2: /* DECANM -- ANSI/VT52 (IGNORED) */ + case 3: /* DECCOLM -- Column (IGNORED) */ + case 4: /* DECSCLM -- Scroll (IGNORED) */ + case 8: /* DECARM -- Auto repeat (IGNORED) */ + case 18: /* DECPFF -- Printer feed (IGNORED) */ + case 19: /* DECPEX -- Printer extent (IGNORED) */ + case 42: /* DECNRCM -- National characters (IGNORED) */ case 12: /* att610 -- Start blinking cursor (IGNORED) */ break; - case 25: - MODBIT(term.c.state, !set, CURSOR_HIDE); + case 25: /* DECTCEM -- Text Cursor Enable Mode */ + MODBIT(term.mode, !set, MODE_HIDE); break; case 1000: /* 1000,1002: enable xterm mouse report */ MODBIT(term.mode, set, MODE_MOUSEBTN); + MODBIT(term.mode, 0, MODE_MOUSEMOTION); break; case 1002: MODBIT(term.mode, set, MODE_MOUSEMOTION); + MODBIT(term.mode, 0, MODE_MOUSEBTN); + break; + case 1006: + MODBIT(term.mode, set, MODE_MOUSESGR); break; case 1049: /* = 1047 and 1048 */ case 47: - case 1047: - if(IS_SET(MODE_ALTSCREEN)) - tclearregion(0, 0, term.col-1, term.row-1); - if((set && !IS_SET(MODE_ALTSCREEN)) || - (!set && IS_SET(MODE_ALTSCREEN))) { - tswapscreen(); + case 1047: { + alt = IS_SET(MODE_ALTSCREEN); + if(alt) { + tclearregion(0, 0, term.col-1, + term.row-1, 0); } + if(set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); if(*args != 1049) break; + } /* pass through */ case 1048: tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD); break; default: - /* case 2: DECANM -- ANSI/VT52 (NOT SUPPOURTED) */ - /* case 3: DECCOLM -- Column (NOT SUPPORTED) */ - /* case 4: DECSCLM -- Scroll (NOT SUPPORTED) */ - /* case 18: DECPFF -- Printer feed (NOT SUPPORTED) */ - /* case 19: DECPEX -- Printer extent (NOT SUPPORTED) */ - /* case 42: DECNRCM -- National characters (NOT SUPPORTED) */ fprintf(stderr, "erresc: unknown private set/reset mode %d\n", *args); @@ -1473,7 +1652,8 @@ tsetmode(bool priv, bool set, int *args, int narg) { case 4: /* IRM -- Insertion-replacement */ MODBIT(term.mode, set, MODE_INSERT); break; - case 12: /* XXX: SRM -- Send/Receive */ + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, MODE_ECHO); break; case 20: /* LNM -- Linefeed/new line */ MODBIT(term.mode, set, MODE_CRLF); @@ -1504,11 +1684,11 @@ csihandle(void) { tinsertblank(csiescseq.arg[0]); break; case 'A': /* CUU -- Cursor Up */ - case 'e': DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x, term.c.y-csiescseq.arg[0]); break; case 'B': /* CUD -- Cursor Down */ + case 'e': /* VPR --Cursor Down */ DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x, term.c.y+csiescseq.arg[0]); break; @@ -1517,7 +1697,7 @@ csihandle(void) { ttywrite(VT102ID, sizeof(VT102ID) - 1); break; case 'C': /* CUF -- Cursor Forward */ - case 'a': + case 'a': /* HPR -- Cursor Forward */ DEFAULT(csiescseq.arg[0], 1); tmoveto(term.c.x+csiescseq.arg[0], term.c.y); break; @@ -1534,7 +1714,7 @@ csihandle(void) { tmoveto(0, term.c.y-csiescseq.arg[0]); break; case 'g': /* TBC -- Tabulation clear */ - switch (csiescseq.arg[0]) { + switch(csiescseq.arg[0]) { case 0: /* clear current tab stop */ term.tabs[term.c.x] = 0; break; @@ -1554,7 +1734,7 @@ csihandle(void) { case 'f': /* HVP */ DEFAULT(csiescseq.arg[0], 1); DEFAULT(csiescseq.arg[1], 1); - tmoveto(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); break; case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ DEFAULT(csiescseq.arg[0], 1); @@ -1565,17 +1745,19 @@ csihandle(void) { sel.bx = -1; switch(csiescseq.arg[0]) { case 0: /* below */ - tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); - if(term.c.y < term.row-1) - tclearregion(0, term.c.y+1, term.col-1, term.row-1); + tclearregion(term.c.x, term.c.y, term.col-1, term.c.y, 1); + if(term.c.y < term.row-1) { + tclearregion(0, term.c.y+1, term.col-1, + term.row-1, 1); + } break; case 1: /* above */ if(term.c.y > 1) - tclearregion(0, 0, term.col-1, term.c.y-1); - tclearregion(0, term.c.y, term.c.x, term.c.y); + tclearregion(0, 0, term.col-1, term.c.y-1, 1); + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); break; case 2: /* all */ - tclearregion(0, 0, term.col-1, term.row-1); + tclearregion(0, 0, term.col-1, term.row-1, 1); break; default: goto unknown; @@ -1584,13 +1766,14 @@ csihandle(void) { case 'K': /* EL -- Clear line */ switch(csiescseq.arg[0]) { case 0: /* right */ - tclearregion(term.c.x, term.c.y, term.col-1, term.c.y); + tclearregion(term.c.x, term.c.y, term.col-1, + term.c.y, 1); break; case 1: /* left */ - tclearregion(0, term.c.y, term.c.x, term.c.y); + tclearregion(0, term.c.y, term.c.x, term.c.y, 1); break; case 2: /* all */ - tclearregion(0, term.c.y, term.col-1, term.c.y); + tclearregion(0, term.c.y, term.col-1, term.c.y, 1); break; } break; @@ -1615,7 +1798,8 @@ csihandle(void) { break; case 'X': /* ECH -- Erase char */ DEFAULT(csiescseq.arg[0], 1); - tclearregion(term.c.x, term.c.y, term.c.x + csiescseq.arg[0], term.c.y); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y, 1); break; case 'P': /* DCH -- Delete char */ DEFAULT(csiescseq.arg[0], 1); @@ -1628,7 +1812,7 @@ csihandle(void) { break; case 'd': /* VPA -- Move to */ DEFAULT(csiescseq.arg[0], 1); - tmoveto(term.c.x, csiescseq.arg[0]-1); + tmoveato(term.c.x, csiescseq.arg[0]-1); break; case 'h': /* SM -- Set terminal mode */ tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg); @@ -1643,7 +1827,7 @@ csihandle(void) { DEFAULT(csiescseq.arg[0], 1); DEFAULT(csiescseq.arg[1], term.row); tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); - tmoveto(0, 0); + tmoveato(0, 0); } break; case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ @@ -1685,32 +1869,37 @@ csireset(void) { void strhandle(void) { - char *p; + char *p = NULL; + int i, j, narg; - /* - * TODO: make this being useful in case of color palette change. - */ strparse(); - - p = strescseq.buf; + narg = strescseq.narg; switch(strescseq.type) { case ']': /* OSC -- Operating System Command */ - switch(p[0]) { - case '0': - case '1': - case '2': - /* - * TODO: Handle special chars in string, like umlauts. - */ - if(p[1] == ';') { - XStoreName(xw.dpy, xw.win, strescseq.buf+2); - } - break; - case ';': - XStoreName(xw.dpy, xw.win, strescseq.buf+1); + switch(i = atoi(strescseq.args[0])) { + case 0: + case 1: + case 2: + if(narg > 1) + xsettitle(strescseq.args[1]); break; - case '4': /* TODO: Set color (arg0) to "rgb:%hexr/$hexg/$hexb" (arg1) */ + case 4: /* color set */ + if(narg < 3) + break; + p = strescseq.args[2]; + /* fall through */ + case 104: /* color reset, here p = NULL */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + if (!xsetcolorname(j, p)) { + fprintf(stderr, "erresc: invalid color %s\n", p); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(0); + } break; default: fprintf(stderr, "erresc: unknown str "); @@ -1719,7 +1908,7 @@ strhandle(void) { } break; case 'k': /* old title set compatibility */ - XStoreName(xw.dpy, xw.win, strescseq.buf); + xsettitle(strescseq.args[0]); break; case 'P': /* DSC -- Device Control String */ case '_': /* APC -- Application Program Command */ @@ -1738,7 +1927,19 @@ strparse(void) { * TODO: Implement parsing like for CSI when required. * Format: ESC type cmd ';' arg0 [';' argn] ESC \ */ - return; + int narg = 0; + char *start = strescseq.buf, *end = start + strescseq.len; + strescseq.args[0] = start; + while(start < end && narg < LEN(strescseq.args)) { + start = memchr(start, ';', end - start); + if(!start) + break; + *start++ = '\0'; + if(start < end) { + strescseq.args[++narg] = start; + } + } + strescseq.narg = narg + 1; } void @@ -1788,15 +1989,44 @@ tputtab(bool forward) { } void +techo(char *buf, int len) { + for(; len > 0; buf++, len--) { + char c = *buf; + + if(c == '\033') { /* escape */ + tputc("^", 1); + tputc("[", 1); + } else if(c < '\x20') { /* control code */ + if(c != '\n' && c != '\r' && c != '\t') { + c |= '\x40'; + tputc("^", 1); + } + tputc(&c, 1); + } else { + break; + } + } + if(len) + tputc(buf, len); +} + +void tputc(char *c, int len) { uchar ascii = *c; bool control = ascii < '\x20' || ascii == 0177; - if(iofd != -1) - write(iofd, c, len); + if(iofd != -1) { + if(xwrite(iofd, c, len) < 0) { + fprintf(stderr, "Error writing in %s:%s\n", + opt_io, strerror(errno)); + close(iofd); + iofd = -1; + } + } + /* - * STR sequences must be checked before of anything - * because it can use some control codes as part of the sequence + * STR sequences must be checked before anything else + * because it can use some control codes as part of the sequence. */ if(term.esc & ESC_STR) { switch(ascii) { @@ -1808,14 +2038,28 @@ tputc(char *c, int len) { strhandle(); break; default: - strescseq.buf[strescseq.len++] = ascii; - if(strescseq.len+1 >= STR_BUF_SIZ) { - term.esc = 0; - strhandle(); + if(strescseq.len + len < sizeof(strescseq.buf)) { + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + } else { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ } } return; } + /* * Actions of control codes must be performed as soon they arrive * because they can be embedded inside a control sequence, and @@ -1847,20 +2091,23 @@ tputc(char *c, int len) { term.esc = ESC_START; return; case '\016': /* SO */ - term.c.attr.mode |= ATTR_GFX; - return; case '\017': /* SI */ - term.c.attr.mode &= ~ATTR_GFX; + /* + * Different charsets are hard to handle. Applications + * should use the right alt charset escapes for the + * only reason they still exist: line drawing. The + * rest is incompatible history st should not support. + */ return; case '\032': /* SUB */ case '\030': /* CAN */ csireset(); return; - case '\005': /* ENQ (IGNORED) */ - case '\000': /* NUL (IGNORED) */ - case '\021': /* XON (IGNORED) */ - case '\023': /* XOFF (IGNORED) */ - case 0177: /* DEL (IGNORED) */ + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ return; } } else if(term.esc & ESC_START) { @@ -1893,11 +2140,25 @@ tputc(char *c, int len) { fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); } term.esc = 0; + } else if(term.esc & ESC_TEST) { + if(ascii == '8') { /* DEC screen alignment test. */ + char E[UTF_SIZ] = "E"; + int x, y; + + for(x = 0; x < term.col; ++x) { + for(y = 0; y < term.row; ++y) + tsetchar(E, &term.c.attr, x, y); + } + } + term.esc = 0; } else { switch(ascii) { case '[': term.esc |= ESC_CSI; break; + case '#': + term.esc |= ESC_TEST; + break; case 'P': /* DCS -- Device Control String */ case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ @@ -1974,7 +2235,7 @@ tputc(char *c, int len) { } } /* - * All characters which forms part of a sequence are not + * All characters which form part of a sequence are not * printed */ return; @@ -1988,11 +2249,19 @@ tputc(char *c, int len) { sel.bx = -1; if(IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT) tnewline(1); /* always go to first col */ - tsetchar(c); - if(term.c.x+1 < term.col) + + if(IS_SET(MODE_INSERT) && term.c.x+1 < term.col) { + memmove(&term.line[term.c.y][term.c.x+1], + &term.line[term.c.y][term.c.x], + (term.col - term.c.x - 1) * sizeof(Glyph)); + } + + tsetchar(c, &term.c.attr, term.c.x, term.c.y); + if(term.c.x+1 < term.col) { tmoveto(term.c.x+1, term.c.y); - else + } else { term.c.state |= CURSOR_WRAPNEXT; + } } int @@ -2009,9 +2278,11 @@ tresize(int col, int row) { /* free unneeded rows */ i = 0; if(slide > 0) { - /* slide screen to keep cursor where we expect it - + /* + * slide screen to keep cursor where we expect it - * tscrollup would work here, but we can optimize to - * memmove because we're freeing the earlier lines */ + * memmove because we're freeing the earlier lines + */ for(/* i = 0 */; i < slide; i++) { free(term.line[i]); free(term.alt[i]); @@ -2053,37 +2324,49 @@ tresize(int col, int row) { memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); while(--bp > term.tabs && !*bp) /* nothing */ ; - for(bp += TAB; bp < term.tabs + col; bp += TAB) + for(bp += tabspaces; bp < term.tabs + col; bp += tabspaces) *bp = 1; } /* update terminal size */ - term.col = col, term.row = row; - /* make use of the LIMIT in tmoveto */ - tmoveto(term.c.x, term.c.y); + term.col = col; + term.row = row; /* reset scrolling region */ tsetscroll(0, row-1); + /* make use of the LIMIT in tmoveto */ + tmoveto(term.c.x, term.c.y); return (slide > 0); } void xresize(int col, int row) { - xw.tw = MAX(1, 2*BORDER + col * xw.cw); - xw.th = MAX(1, 2*BORDER + row * xw.ch); + xw.tw = MAX(1, col * xw.cw); + xw.th = MAX(1, row * xw.ch); + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h); - XftDrawChange(xw.xft_draw, xw.buf); + XftDrawChange(xw.draw, xw.buf); +} + +static inline ushort +sixd_to_16bit(int x) { + return x == 0 ? 0 : 0x3737 + 0x2828 * x; } void xloadcols(void) { int i, r, g, b; - XRenderColor xft_color = { .alpha = 0 }; + XRenderColor color = { .alpha = 0xffff }; /* load colors [0-15] colors and [256-LEN(colorname)[ (config.h) */ for(i = 0; i < LEN(colorname); i++) { if(!colorname[i]) continue; - if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.xft_col[i])) { + if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, colorname[i], &dc.col[i])) { die("Could not allocate color '%s'\n", colorname[i]); } } @@ -2092,10 +2375,10 @@ xloadcols(void) { for(i = 16, r = 0; r < 6; r++) { for(g = 0; g < 6; g++) { for(b = 0; b < 6; b++) { - xft_color.red = r == 0 ? 0 : 0x3737 + 0x2828 * r; - xft_color.green = g == 0 ? 0 : 0x3737 + 0x2828 * g; - xft_color.blue = b == 0 ? 0 : 0x3737 + 0x2828 * b; - if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &xft_color, &dc.xft_col[i])) { + color.red = sixd_to_16bit(r); + color.green = sixd_to_16bit(g); + color.blue = sixd_to_16bit(b); + if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &dc.col[i])) { die("Could not allocate color %d\n", i); } i++; @@ -2104,20 +2387,52 @@ xloadcols(void) { } for(r = 0; r < 24; r++, i++) { - xft_color.red = xft_color.green = xft_color.blue = 0x0808 + 0x0a0a * r; - if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &xft_color, - &dc.xft_col[i])) { + color.red = color.green = color.blue = 0x0808 + 0x0a0a * r; + if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, + &dc.col[i])) { die("Could not allocate color %d\n", i); } } } +int +xsetcolorname(int x, const char *name) { + XRenderColor color = { .alpha = 0xffff }; + Colour colour; + if (x < 0 || x > LEN(colorname)) + return -1; + if(!name) { + if(16 <= x && x < 16 + 216) { + int r = (x - 16) / 36, g = ((x - 16) % 36) / 6, b = (x - 16) % 6; + color.red = sixd_to_16bit(r); + color.green = sixd_to_16bit(g); + color.blue = sixd_to_16bit(b); + if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &colour)) + return 0; /* something went wrong */ + dc.col[x] = colour; + return 1; + } else if (16 + 216 <= x && x < 256) { + color.red = color.green = color.blue = 0x0808 + 0x0a0a * (x - (16 + 216)); + if(!XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, &colour)) + return 0; /* something went wrong */ + dc.col[x] = colour; + return 1; + } else { + name = colorname[x]; + } + } + if(!XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, &colour)) + return 0; + dc.col[x] = colour; + return 1; +} + void xtermclear(int col1, int row1, int col2, int row2) { - XftDrawRect(xw.xft_draw, - &dc.xft_col[IS_SET(MODE_REVERSE) ? DefaultFG : DefaultBG], - BORDER + col1 * xw.cw, - BORDER + row1 * xw.ch, + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg], + borderpx + col1 * xw.cw, + borderpx + row1 * xw.ch, (col2-col1+1) * xw.cw, (row2-row1+1) * xw.ch); } @@ -2127,14 +2442,14 @@ xtermclear(int col1, int row1, int col2, int row2) { */ void xclear(int x1, int y1, int x2, int y2) { - XftDrawRect(xw.xft_draw, - &dc.xft_col[IS_SET(MODE_REVERSE) ? DefaultFG : DefaultBG], + XftDrawRect(xw.draw, + &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], x1, y1, x2-x1, y2-y1); } void xhints(void) { - XClassHint class = {opt_class ? opt_class : TNAME, TNAME}; + XClassHint class = {opt_class ? opt_class : termname, termname}; XWMHints wm = {.flags = InputHint, .input = 1}; XSizeHints *sizeh = NULL; @@ -2145,8 +2460,8 @@ xhints(void) { sizeh->width = xw.w; sizeh->height_inc = xw.ch; sizeh->width_inc = xw.cw; - sizeh->base_height = 2*BORDER; - sizeh->base_width = 2*BORDER; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; } else { sizeh->flags = PMaxSize | PMinSize; sizeh->min_width = sizeh->max_width = xw.fw; @@ -2157,60 +2472,144 @@ xhints(void) { XFree(sizeh); } -void -xinitfont(Font *f, char *fontstr) { - FcPattern *pattern, *match; +int +xloadfont(Font *f, FcPattern *pattern) { + FcPattern *match; FcResult result; - pattern = FcNameParse((FcChar8 *)fontstr); - if(!pattern) - die("st: can't open font %s\n", fontstr); - - match = XftFontMatch(xw.dpy, xw.scr, pattern, &result); - FcPatternDestroy(pattern); + match = FcFontMatch(NULL, pattern, &result); if(!match) - die("st: can't open font %s\n", fontstr); - if(!(f->xft_set = XftFontOpenPattern(xw.dpy, match))) { + return 1; + + if(!(f->set = FcFontSort(0, match, FcTrue, 0, &result))) { FcPatternDestroy(match); - die("st: can't open font %s.\n", fontstr); + return 1; } - f->ascent = f->xft_set->ascent; - f->descent = f->xft_set->descent; + if(!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(match); + return 1; + } + + f->pattern = FcPatternDuplicate(pattern); + + f->ascent = f->match->ascent; + f->descent = f->match->descent; f->lbearing = 0; - f->rbearing = f->xft_set->max_advance_width; + f->rbearing = f->match->max_advance_width; - f->height = f->xft_set->height; + f->height = f->match->height; f->width = f->lbearing + f->rbearing; + + return 0; } void -initfonts(char *fontstr) { - char *fstr; +xloadfonts(char *fontstr, int fontsize) { + FcPattern *pattern; + FcResult result; + double fontval; + + if(fontstr[0] == '-') { + pattern = XftXlfdParse(fontstr, False, False); + } else { + pattern = FcNameParse((FcChar8 *)fontstr); + } + + if(!pattern) + die("st: can't open font %s\n", fontstr); + + if(fontsize > 0) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + result = FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval); + if(result == FcResultMatch) { + usedfontsize = (int)fontval; + } 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; + } + } + + FcConfigSubstitute(0, pattern, FcMatchPattern); + FcDefaultSubstitute(pattern); + + if(xloadfont(&dc.font, pattern)) + die("st: can't open font %s\n", fontstr); - xinitfont(&dc.font, fontstr); + /* Setting character width and height. */ xw.cw = dc.font.width; xw.ch = dc.font.height; - fstr = smstrcat(fontstr, ":weight=bold", NULL); - xinitfont(&dc.bfont, fstr); - free(fstr); + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if(xloadfont(&dc.ifont, pattern)) + die("st: can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if(xloadfont(&dc.ibfont, pattern)) + die("st: can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if(xloadfont(&dc.bfont, pattern)) + die("st: can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} - fstr = smstrcat(fontstr, ":slant=italic,oblique", NULL); - xinitfont(&dc.ifont, fstr); - free(fstr); +void +xunloadfonts(void) { + int i, ip; - fstr = smstrcat(fontstr, ":weight=bold:slant=italic,oblique", NULL); - xinitfont(&dc.ibfont, fstr); - free(fstr); + /* + * Free the loaded fonts in the font cache. This is done backwards + * from the frccur. + */ + for(i = 0, ip = frccur; i < frclen; i++, ip--) { + if(ip < 0) + ip = LEN(frc) - 1; + XftFontClose(xw.dpy, frc[ip].font); + } + frccur = -1; + frclen = 0; + + XftFontClose(xw.dpy, dc.font.match); + FcPatternDestroy(dc.font.pattern); + FcFontSetDestroy(dc.font.set); + XftFontClose(xw.dpy, dc.bfont.match); + FcPatternDestroy(dc.bfont.pattern); + FcFontSetDestroy(dc.bfont.set); + XftFontClose(xw.dpy, dc.ifont.match); + FcPatternDestroy(dc.ifont.pattern); + FcFontSetDestroy(dc.ifont.set); + XftFontClose(xw.dpy, dc.ibfont.match); + FcPatternDestroy(dc.ibfont.pattern); + FcFontSetDestroy(dc.ibfont.set); +} + +void +xzoom(const Arg *arg) { + xunloadfonts(); + xloadfonts(usedfont, usedfontsize + arg->i); + cresize(0, 0); + redraw(0); } void xinit(void) { XSetWindowAttributes attrs; + XGCValues gcvalues; Cursor cursor; Window parent; - int sw, sh, major, minor; + int sw, sh; if(!(xw.dpy = XOpenDisplay(NULL))) die("Can't open display\n"); @@ -2218,7 +2617,11 @@ xinit(void) { xw.vis = XDefaultVisual(xw.dpy, xw.scr); /* font */ - initfonts((opt_font != NULL)? opt_font : FONT); + if(!FcInit()) + die("Could not init fontconfig.\n"); + + usedfont = (opt_font == NULL)? font : opt_font; + xloadfonts(usedfont, 0); /* colors */ xw.cmap = XDefaultColormap(xw.dpy, xw.scr); @@ -2237,21 +2640,23 @@ xinit(void) { xw.w = xw.fw; } else { /* window - default size */ - xw.h = 2*BORDER + term.row * xw.ch; - xw.w = 2*BORDER + term.col * xw.cw; + xw.h = 2 * borderpx + term.row * xw.ch; + xw.w = 2 * borderpx + term.col * xw.cw; xw.fx = 0; xw.fy = 0; } - attrs.background_pixel = dc.xft_col[DefaultBG].pixel; - attrs.border_pixel = dc.xft_col[DefaultBG].pixel; + /* Events */ + attrs.background_pixel = dc.col[defaultbg].pixel; + attrs.border_pixel = dc.col[defaultbg].pixel; attrs.bit_gravity = NorthWestGravity; attrs.event_mask = FocusChangeMask | KeyPressMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; attrs.colormap = xw.cmap; - parent = opt_embed ? strtol(opt_embed, NULL, 0) : XRootWindow(xw.dpy, xw.scr); + parent = opt_embed ? strtol(opt_embed, NULL, 0) : \ + XRootWindow(xw.dpy, xw.scr); xw.win = XCreateWindow(xw.dpy, parent, xw.fx, xw.fy, xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, xw.vis, @@ -2259,21 +2664,35 @@ xinit(void) { | CWColormap, &attrs); - /* double buffering */ - if(!XdbeQueryExtension(xw.dpy, &major, &minor)) - die("Xdbe extension is not present\n"); - xw.buf = XdbeAllocateBackBufferName(xw.dpy, xw.win, XdbeCopied); + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, + &gcvalues); + xw.buf = XCreatePixmap(xw.dpy, xw.win, xw.w, xw.h, + DefaultDepth(xw.dpy, xw.scr)); + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, xw.w, xw.h); /* Xft rendering context */ - xw.xft_draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); /* input methods */ - xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL); + if((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { + XSetLocaleModifiers("@im=local"); + if((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { + XSetLocaleModifiers("@im="); + if((xw.xim = XOpenIM(xw.dpy, + NULL, NULL, NULL)) == NULL) { + die("XOpenIM failed. Could not open input" + " device.\n"); + } + } + } xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, xw.win, XNFocusWindow, xw.win, NULL); - /* gc */ - dc.gc = XCreateGC(xw.dpy, xw.win, 0, NULL); + if(xw.xic == NULL) + die("XCreateIC failed. Could not obtain input method.\n"); /* white cursor, black outline */ cursor = XCreateFontCursor(xw.dpy, XC_xterm); @@ -2294,27 +2713,33 @@ xinit(void) { void xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) { - int winx = BORDER + x * xw.cw, winy = BORDER + y * xw.ch, - width = charlen * xw.cw; + int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch, + width = charlen * xw.cw, xp, i; + int frp, frcflags; + int u8fl, u8fblen, u8cblen, doesexist; + char *u8c, *u8fs; + long u8char; Font *font = &dc.font; - XGlyphInfo extents; - XftColor *fg = &dc.xft_col[base.fg], *bg = &dc.xft_col[base.bg], + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { NULL }; + FcCharSet *fccharset; + Colour *fg = &dc.col[base.fg], *bg = &dc.col[base.bg], *temp, revfg, revbg; XRenderColor colfg, colbg; - if(base.mode & ATTR_REVERSE) - temp = fg, fg = bg, bg = temp; + frcflags = FRC_NORMAL; if(base.mode & ATTR_BOLD) { if(BETWEEN(base.fg, 0, 7)) { /* basic system colors */ - fg = &dc.xft_col[base.fg + 8]; + fg = &dc.col[base.fg + 8]; } else if(BETWEEN(base.fg, 16, 195)) { /* 256 colors */ - fg = &dc.xft_col[base.fg + 36]; + fg = &dc.col[base.fg + 36]; } else if(BETWEEN(base.fg, 232, 251)) { /* greyscale */ - fg = &dc.xft_col[base.fg + 4]; + fg = &dc.col[base.fg + 4]; } /* * Those ranges will not be brightened: @@ -2323,16 +2748,21 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) { * 252 - 255 – brightest colors in greyscale */ font = &dc.bfont; + frcflags = FRC_BOLD; } - if(base.mode & ATTR_ITALIC) + if(base.mode & ATTR_ITALIC) { font = &dc.ifont; - if(base.mode & (ATTR_ITALIC|ATTR_ITALIC)) + frcflags = FRC_ITALIC; + } + if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD)) { font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } if(IS_SET(MODE_REVERSE)) { - if(fg == &dc.xft_col[DefaultFG]) { - fg = &dc.xft_col[DefaultBG]; + if(fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; } else { colfg.red = ~fg->color.red; colfg.green = ~fg->color.green; @@ -2342,8 +2772,8 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) { fg = &revfg; } - if(bg == &dc.xft_col[DefaultBG]) { - bg = &dc.xft_col[DefaultFG]; + if(bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; } else { colbg.red = ~bg->color.red; colbg.green = ~bg->color.green; @@ -2354,30 +2784,145 @@ xdraws(char *s, Glyph base, int x, int y, int charlen, int bytelen) { } } - XftTextExtentsUtf8(xw.dpy, font->xft_set, (FcChar8 *)s, bytelen, - &extents); - width = extents.xOff; + if(base.mode & ATTR_REVERSE) { + temp = fg; + fg = bg; + bg = temp; + } /* Intelligent cleaning up of the borders. */ if(x == 0) { - xclear(0, (y == 0)? 0 : winy, BORDER, - winy + xw.ch + (y == term.row-1)? xw.h : 0); + xclear(0, (y == 0)? 0 : winy, borderpx, + winy + xw.ch + ((y >= term.row-1)? xw.h : 0)); } - if(x + charlen >= term.col-1) { + if(x + charlen >= term.col) { xclear(winx + width, (y == 0)? 0 : winy, xw.w, - (y == term.row-1)? xw.h : (winy + xw.ch)); + ((y >= term.row-1)? xw.h : (winy + xw.ch))); } if(y == 0) - xclear(winx, 0, winx + width, BORDER); + xclear(winx, 0, winx + width, borderpx); if(y == term.row-1) xclear(winx, winy + xw.ch, winx + width, xw.h); - XftDrawRect(xw.xft_draw, bg, winx, winy, width, xw.ch); - XftDrawStringUtf8(xw.xft_draw, fg, font->xft_set, winx, + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, xw.ch); + + fcsets[0] = font->set; + for(xp = winx; bytelen > 0;) { + /* + * Search for the range in the to be printed string of glyphs + * that are in the main font. Then print that range. If + * some glyph is found that is not in the font, do the + * fallback dance. + */ + u8fs = s; + u8fblen = 0; + u8fl = 0; + for(;;) { + u8c = s; + u8cblen = utf8decode(s, &u8char); + s += u8cblen; + bytelen -= u8cblen; + + doesexist = XftCharIndex(xw.dpy, font->match, u8char); + if(!doesexist || bytelen <= 0) { + if(bytelen <= 0) { + if(doesexist) { + u8fl++; + u8fblen += u8cblen; + } + } + + if(u8fl > 0) { + XftDrawStringUtf8(xw.draw, fg, + font->match, xp, + winy + font->ascent, + (FcChar8 *)u8fs, + u8fblen); + xp += font->width * u8fl; + } + break; + } + + u8fl++; + u8fblen += u8cblen; + } + if(doesexist) + break; + + frp = frccur; + /* Search the font cache. */ + for(i = 0; i < frclen; i++, frp--) { + if(frp <= 0) + frp = LEN(frc) - 1; + + if(frc[frp].c == u8char + && frc[frp].flags == frcflags) { + break; + } + } + + /* Nothing was found. */ + if(i >= frclen) { + /* + * Nothing was found in the cache. Now use + * some dozen of Fontconfig calls to get the + * font for one single character. + */ + fcpattern = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, u8char); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, + FcTrue); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, + FcTrue, fcpattern, &fcres); + + /* + * Overwrite or create the new cache entry + * entry. + */ + frccur++; + frclen++; + if(frccur >= LEN(frc)) + frccur = 0; + if(frclen > LEN(frc)) { + frclen = LEN(frc); + XftFontClose(xw.dpy, frc[frccur].font); + } + + frc[frccur].font = XftFontOpenPattern(xw.dpy, + fontpattern); + frc[frccur].c = u8char; + frc[frccur].flags = frcflags; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + + frp = frccur; + } + + XftDrawStringUtf8(xw.draw, fg, frc[frp].font, + xp, winy + frc[frp].font->ascent, + (FcChar8 *)u8c, u8cblen); + + xp += font->width; + } + + /* + XftDrawStringUtf8(xw.draw, fg, font->set, winx, winy + font->ascent, (FcChar8 *)s, bytelen); + */ if(base.mode & ATTR_UNDERLINE) { - XftDrawRect(xw.xft_draw, fg, winx, winy + font->ascent + 1, + XftDrawRect(xw.draw, fg, winx, winy + font->ascent + 1, width, 1); } } @@ -2386,7 +2931,7 @@ void xdrawcursor(void) { static int oldx = 0, oldy = 0; int sl; - Glyph g = {{' '}, ATTR_NULL, DefaultBG, DefaultCS, 0}; + Glyph g = {{' '}, ATTR_NULL, defaultbg, defaultcs, 0}; LIMIT(oldx, 0, term.col-1); LIMIT(oldy, 0, term.row-1); @@ -2404,12 +2949,12 @@ xdrawcursor(void) { } /* draw the new one */ - if(!(term.c.state & CURSOR_HIDE)) { + if(!(IS_SET(MODE_HIDE))) { if(!(xw.state & WIN_FOCUSED)) - g.bg = DefaultUCS; + g.bg = defaultucs; if(IS_SET(MODE_REVERSE)) - g.mode |= ATTR_REVERSE, g.fg = DefaultCS, g.bg = DefaultFG; + g.mode |= ATTR_REVERSE, g.fg = defaultcs, g.bg = defaultfg; sl = utf8size(g.c); xdraws(g.c, g, term.c.x, term.c.y, 1, sl); @@ -2417,27 +2962,42 @@ xdrawcursor(void) { } } + +void +xsettitle(char *p) { + XTextProperty prop; + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + XSetWMName(xw.dpy, xw.win, &prop); +} + void xresettitle(void) { - XStoreName(xw.dpy, xw.win, opt_title ? opt_title : "st"); + xsettitle(opt_title ? opt_title : "st"); } void -redraw(void) { - struct timespec tv = {0, REDRAW_TIMEOUT * 1000}; +redraw(int timeout) { + struct timespec tv = {0, timeout * 1000}; tfulldirt(); draw(); - XSync(xw.dpy, False); /* necessary for a good tput flash */ - nanosleep(&tv, NULL); + + if(timeout > 0) { + nanosleep(&tv, NULL); + XSync(xw.dpy, False); /* necessary for a good tput flash */ + } } void draw(void) { - XdbeSwapInfo swpinfo[1] = {{xw.win, XdbeCopied}}; - drawregion(0, 0, term.col, term.row); - XdbeSwapBuffers(xw.dpy, swpinfo, 1); + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, xw.w, + xw.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(MODE_REVERSE)? + defaultfg : defaultbg].pixel); } void @@ -2445,10 +3005,11 @@ drawregion(int x1, int y1, int x2, int y2) { int ic, ib, x, y, ox, sl; Glyph base, new; char buf[DRAW_BUF_SIZ]; - bool ena_sel = sel.bx != -1, alt = IS_SET(MODE_ALTSCREEN); + bool ena_sel = sel.bx != -1; - if((sel.alt && !alt) || (!sel.alt && alt)) + if(sel.alt ^ IS_SET(MODE_ALTSCREEN)) ena_sel = 0; + if(!(xw.state & WIN_VISIBLE)) return; @@ -2475,6 +3036,7 @@ drawregion(int x1, int y1, int x2, int y2) { ox = x; base = new; } + sl = utf8size(new.c); memcpy(buf+ib, new.c, sl); ib += sl; @@ -2495,6 +3057,7 @@ expose(XEvent *ev) { if(!e->count) xw.state &= ~WIN_REDRAW; } + redraw(0); } void @@ -2525,28 +3088,87 @@ xseturgency(int add) { void focus(XEvent *ev) { + XFocusChangeEvent *e = &ev->xfocus; + + if(e->mode == NotifyGrab) + return; + if(ev->type == FocusIn) { + XSetICFocus(xw.xic); xw.state |= WIN_FOCUSED; xseturgency(0); } else { + XUnsetICFocus(xw.xic); xw.state &= ~WIN_FOCUSED; } } +inline bool +match(uint mask, uint state) { + state &= ~(ignoremod); + + if(mask == XK_NO_MOD && state) + return false; + if(mask != XK_ANY_MOD && mask != XK_NO_MOD && !state) + return false; + if((state & mask) != state) + return false; + return true; +} + +void +numlock(const Arg *dummy) { + term.numlock ^= 1; +} + char* kmap(KeySym k, uint state) { - int i; uint mask; + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for(i = 0; i < LEN(mappedkeys); i++) { + if(mappedkeys[i] == k) + break; + } + if(i == LEN(mappedkeys)) { + if((k & 0xFFFF) < 0xFD00) + return NULL; + } + + for(kp = key; kp < key + LEN(key); kp++) { + mask = kp->mask; + + if(kp->k != k) + continue; + + if(!match(mask, state)) + continue; + + if(kp->appkey > 0) { + if(!IS_SET(MODE_APPKEYPAD)) + continue; + if(term.numlock && kp->appkey == 2) + continue; + } else if(kp->appkey < 0 && IS_SET(MODE_APPKEYPAD)) { + continue; + } - state &= ~Mod2Mask; - for(i = 0; i < LEN(key); i++) { - mask = key[i].mask; + if((kp->appcursor < 0 && IS_SET(MODE_APPCURSOR)) || + (kp->appcursor > 0 + && !IS_SET(MODE_APPCURSOR))) { + continue; + } - if(key[i].k == k && ((state & mask) == mask - || (mask == XK_NO_MOD && !state))) { - return (char*)key[i].s; + if((kp->crlf < 0 && IS_SET(MODE_CRLF)) || + (kp->crlf > 0 && !IS_SET(MODE_CRLF))) { + continue; } + + return kp->s; } + return NULL; } @@ -2554,63 +3176,52 @@ void kpress(XEvent *ev) { XKeyEvent *e = &ev->xkey; KeySym ksym; - char buf[32]; - char *customkey; + char xstr[31], buf[32], *customkey, *cp = buf; int len; - int meta; - int shift; Status status; + Shortcut *bp; - if (IS_SET(MODE_KBDLOCK)) + if(IS_SET(MODE_KBDLOCK)) return; - meta = e->state & Mod1Mask; - shift = e->state & ShiftMask; - len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status); + len = XmbLookupString(xw.xic, e, xstr, sizeof(xstr), &ksym, &status); + e->state &= ~Mod2Mask; + /* 1. shortcuts */ + for(bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { + if(ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } - /* 1. custom keys from config.h */ + /* 2. custom keys from config.h */ if((customkey = kmap(ksym, e->state))) { - ttywrite(customkey, strlen(customkey)); + len = strlen(customkey); + memcpy(buf, customkey, len); /* 2. hardcoded (overrides X lookup) */ } else { - switch(ksym) { - case XK_Up: - case XK_Down: - case XK_Left: - case XK_Right: - /* XXX: shift up/down doesn't work */ - sprintf(buf, "\033%c%c", - IS_SET(MODE_APPKEYPAD) ? 'O' : '[', - (shift ? "dacb":"DACB")[ksym - XK_Left]); - ttywrite(buf, 3); - break; - case XK_Insert: - if(shift) - selpaste(); - break; - case XK_Return: - if(IS_SET(MODE_CRLF)) { - ttywrite("\r\n", 2); - } else { - ttywrite("\r", 1); - } - break; - /* 3. X lookup */ - default: - if(len > 0) { - if(meta && len == 1) - ttywrite("\033", 1); - ttywrite(buf, len); - } - break; - } + if(len == 0) + return; + + if(len == 1 && e->state & Mod1Mask) + *cp++ = '\033'; + + memcpy(cp, xstr, len); + len = cp - buf + len; } + + ttywrite(buf, len); + if(IS_SET(MODE_ECHO)) + techo(buf, len); } + void cmessage(XEvent *e) { - /* See xembed specs - http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html */ + /* + * 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) { xw.state |= WIN_FOCUSED; @@ -2626,18 +3237,16 @@ cmessage(XEvent *e) { } void -resize(XEvent *e) { +cresize(int width, int height) { int col, row; - if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) - return; + if(width != 0) + xw.w = width; + if(height != 0) + xw.h = height; - xw.w = e->xconfigure.width; - xw.h = e->xconfigure.height; - col = (xw.w - 2*BORDER) / xw.cw; - row = (xw.h - 2*BORDER) / xw.ch; - if(col == term.col && row == term.row) - return; + col = (xw.w - 2 * borderpx) / xw.cw; + row = (xw.h - 2 * borderpx) / xw.ch; tresize(col, row); xresize(col, row); @@ -2645,13 +3254,23 @@ resize(XEvent *e) { } void +resize(XEvent *e) { + if(e->xconfigure.width == xw.w && e->xconfigure.height == xw.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void run(void) { XEvent ev; fd_set rfd; - int xfd = XConnectionNumber(xw.dpy), i; - struct timeval drawtimeout, *tv = NULL; + int xfd = XConnectionNumber(xw.dpy), xev; + struct timeval drawtimeout, *tv = NULL, now, last; - for(i = 0;; i++) { + gettimeofday(&last, NULL); + + for(xev = actionfps;;) { FD_ZERO(&rfd); FD_SET(cmdfd, &rfd); FD_SET(xfd, &rfd); @@ -2661,35 +3280,36 @@ run(void) { die("select failed: %s\n", SERRNO); } - /* - * Stop after a certain number of reads so the user does not - * feel like the system is stuttering. - */ - if(i < 1000 && FD_ISSET(cmdfd, &rfd)) { + gettimeofday(&now, NULL); + drawtimeout.tv_sec = 0; + drawtimeout.tv_usec = (1000/xfps) * 1000; + tv = &drawtimeout; + + if(FD_ISSET(cmdfd, &rfd)) ttyread(); - /* - * Just wait a bit so it isn't disturbing the - * user and the system is able to write something. - */ - drawtimeout.tv_sec = 0; - drawtimeout.tv_usec = 5; - tv = &drawtimeout; - continue; - } - i = 0; - tv = NULL; + if(FD_ISSET(xfd, &rfd)) + xev = actionfps; - while(XPending(xw.dpy)) { - XNextEvent(xw.dpy, &ev); - if(XFilterEvent(&ev, xw.win)) - continue; - if(handler[ev.type]) - (handler[ev.type])(&ev); - } + if(TIMEDIFF(now, last) > \ + (xev ? (1000/xfps) : (1000/actionfps))) { + while(XPending(xw.dpy)) { + XNextEvent(xw.dpy, &ev); + if(XFilterEvent(&ev, None)) + continue; + if(handler[ev.type]) + (handler[ev.type])(&ev); + } + + draw(); + XFlush(xw.dpy); + last = now; - draw(); - XFlush(xw.dpy); + if(xev && !FD_ISSET(xfd, &rfd)) + xev--; + if(!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) + tv = NULL; + } } } @@ -2708,7 +3328,7 @@ main(int argc, char *argv[]) { opt_class = argv[i]; break; case 'e': - /* eat every remaining arguments */ + /* eat all remaining arguments */ if(++i < argc) opt_cmd = &argv[i]; goto run; @@ -2757,11 +3377,13 @@ main(int argc, char *argv[]) { run: setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); tnew(80, 24); - ttynew(); xinit(); + ttynew(); selinit(); run(); + return 0; }