#include <fcntl.h>
#include <limits.h>
#include <locale.h>
+#include <pwd.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#define REDRAW_TIMEOUT (80*1000) /* 80 ms */
+/* macros */
+#define CLEANMASK(mask) (mask & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))
#define SERRNO strerror(errno)
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) < (b) ? (b) : (a))
#define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
#define IS_SET(flag) (term.mode & (flag))
#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"
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 xzoom(const Arg *);
+
+/* Config.h for applying patches and the configuration. */
#include "config.h"
/* Font structure */
static char *kmap(KeySym, uint);
static void kpress(XEvent *);
static void cmessage(XEvent *);
+static void cresize(int width, int height);
static void resize(XEvent *);
static void focus(XEvent *);
static void brelease(XEvent *);
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 (*handler[LASTEvent])(XEvent *) = {
[KeyPress] = kpress,
static char *opt_class = NULL;
static char *opt_font = NULL;
+static char *usedfont = NULL;
+static int usedfontsize = 0;
ssize_t
xwrite(int fd, char *s, size_t len) {
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;
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;
if(b)
*b = e->xbutton.button;
- *x = X2COL(e->xbutton.x);
- *y = 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;
void
mousereport(XEvent *e) {
- int x = X2COL(e->xbutton.x);
- int y = Y2ROW(e->xbutton.y);
+ 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 };
draw();
}
sel.mode = 1;
- sel.ex = sel.bx = X2COL(e->xbutton.x);
- sel.ey = sel.by = Y2ROW(e->xbutton.y);
+ sel.ex = sel.bx = x2col(e->xbutton.x);
+ sel.ey = sel.by = y2row(e->xbutton.y);
}
}
void
selcopy(void) {
- char *str, *ptr;
+ char *str, *ptr, *p;
int x, y, bufsize, is_selected = 0, size;
- Glyph *gp;
+ Glyph *gp, *last;
if(sel.bx == -1) {
str = NULL;
/* 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];
+ gp = &term.line[y][0];
+ last = gp + term.col;
+
+ while(--last >= gp && !(last->state & GLYPH_SET))
+ /* nothing */;
- if(!(is_selected = selected(x, y))
- || !(gp->state & GLYPH_SET)) {
+ for(x = 0; gp <= last; x++, ++gp) {
+ if(!(is_selected = selected(x, y)))
continue;
- }
- size = utf8size(gp->c);
- memcpy(ptr, gp->c, size);
+
+ p = (gp->state & GLYPH_SET) ? gp->c : " ";
+ size = utf8size(p);
+ memcpy(ptr, p, size);
ptr += size;
}
/* \n at the end of every selected line except for the last one */
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 &&
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);
signal(SIGTERM, SIG_DFL);
signal(SIGALRM, SIG_DFL);
- DEFAULT(envshell, SHELL);
- putenv("TERM="TNAME);
+ DEFAULT(envshell, shell);
+ setenv("TERM", termname, 1);
args = opt_cmd ? opt_cmd : (char *[]){envshell, "-i", NULL};
execvp(args[0], args);
exit(EXIT_FAILURE);
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;
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 39:
- term.c.attr.fg = DefaultFG;
+ term.c.attr.fg = defaultfg;
break;
case 48:
if(i + 2 < l && attr[i + 1] == 5) {
}
break;
case 49:
- term.c.attr.bg = DefaultBG;
+ term.c.attr.bg = defaultbg;
break;
default:
if(BETWEEN(attr[i], 30, 37)) {
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;
+ term.col = col;
+ term.row = row;
/* make use of the LIMIT in tmoveto */
tmoveto(term.c.x, term.c.y);
/* reset scrolling region */
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, 2*borderpx + col * xw.cw);
+ xw.th = MAX(1, 2*borderpx + row * xw.ch);
XftDrawChange(xw.xft_draw, xw.buf);
}
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,
+ &dc.xft_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);
}
void
xclear(int x1, int y1, int x2, int y2) {
XftDrawRect(xw.xft_draw,
- &dc.xft_col[IS_SET(MODE_REVERSE) ? DefaultFG : DefaultBG],
+ &dc.xft_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;
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;
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);
if(!match)
- die("st: can't open font %s\n", fontstr);
+ return 1;
if(!(f->xft_set = XftFontOpenPattern(xw.dpy, match))) {
FcPatternDestroy(match);
- die("st: can't open font %s.\n", fontstr);
+ return 1;
}
f->ascent = f->xft_set->ascent;
f->height = f->xft_set->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;
+ }
+ }
+
+ 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_WEIGHT);
+ FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);
+ if(xloadfont(&dc.bfont, pattern))
+ die("st: can't open font %s\n", fontstr);
+
+ FcPatternDel(pattern, FC_SLANT);
+ FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);
+ if(xloadfont(&dc.ibfont, pattern))
+ die("st: can't open font %s\n", fontstr);
+
+ FcPatternDel(pattern, FC_WEIGHT);
+ if(xloadfont(&dc.ifont, pattern))
+ die("st: can't open font %s\n", fontstr);
- fstr = smstrcat(fontstr, ":slant=italic,oblique", NULL);
- xinitfont(&dc.ifont, fstr);
- free(fstr);
+ FcPatternDestroy(pattern);
+}
- fstr = smstrcat(fontstr, ":weight=bold:slant=italic,oblique", NULL);
- xinitfont(&dc.ibfont, fstr);
- free(fstr);
+void
+xzoom(const Arg *arg)
+{
+ xloadfonts(usedfont, usedfontsize + arg->i);
+ cresize(0, 0);
+ draw();
}
void
xw.vis = XDefaultVisual(xw.dpy, xw.scr);
/* font */
- initfonts((opt_font != NULL)? opt_font : FONT);
+ usedfont = (opt_font == NULL)? font : opt_font;
+ xloadfonts(usedfont, 0);
/* colors */
xw.cmap = XDefaultColormap(xw.dpy, xw.scr);
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;
+ attrs.background_pixel = dc.xft_col[defaultbg].pixel;
+ attrs.border_pixel = dc.xft_col[defaultbg].pixel;
attrs.bit_gravity = NorthWestGravity;
attrs.event_mask = FocusChangeMask | KeyPressMask
| ExposureMask | VisibilityChangeMask | StructureNotifyMask
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,
+ int winx = borderpx + x * xw.cw, winy = borderpx + y * xw.ch,
width = charlen * xw.cw;
Font *font = &dc.font;
XGlyphInfo extents;
*temp, revfg, revbg;
XRenderColor colfg, colbg;
- if(base.mode & ATTR_REVERSE)
- temp = fg, fg = bg, bg = temp;
-
if(base.mode & ATTR_BOLD) {
if(BETWEEN(base.fg, 0, 7)) {
/* basic system colors */
if(base.mode & ATTR_ITALIC)
font = &dc.ifont;
- if(base.mode & (ATTR_ITALIC|ATTR_ITALIC))
+ if((base.mode & ATTR_ITALIC) && (base.mode & ATTR_BOLD))
font = &dc.ibfont;
if(IS_SET(MODE_REVERSE)) {
- if(fg == &dc.xft_col[DefaultFG]) {
- fg = &dc.xft_col[DefaultBG];
+ if(fg == &dc.xft_col[defaultfg]) {
+ fg = &dc.xft_col[defaultbg];
} else {
colfg.red = ~fg->color.red;
colfg.green = ~fg->color.green;
fg = &revfg;
}
- if(bg == &dc.xft_col[DefaultBG]) {
- bg = &dc.xft_col[DefaultFG];
+ if(bg == &dc.xft_col[defaultbg]) {
+ bg = &dc.xft_col[defaultfg];
} else {
colbg.red = ~bg->color.red;
colbg.green = ~bg->color.green;
}
}
+ if(base.mode & ATTR_REVERSE)
+ temp = fg, fg = bg, bg = temp;
+
XftTextExtentsUtf8(xw.dpy, font->xft_set, (FcChar8 *)s, bytelen,
&extents);
width = extents.xOff;
/* Intelligent cleaning up of the borders. */
if(x == 0) {
- xclear(0, (y == 0)? 0 : winy, BORDER,
+ xclear(0, (y == 0)? 0 : winy, borderpx,
winy + xw.ch + (y == term.row-1)? xw.h : 0);
}
if(x + charlen >= term.col-1) {
(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);
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);
/* draw the new one */
if(!(term.c.state & CURSOR_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);
void
focus(XEvent *ev) {
if(ev->type == FocusIn) {
+ XSetICFocus(xw.xic);
xw.state |= WIN_FOCUSED;
xseturgency(0);
} else {
+ XUnsetICFocus(xw.xic);
xw.state &= ~WIN_FOCUSED;
}
}
kpress(XEvent *ev) {
XKeyEvent *e = &ev->xkey;
KeySym ksym;
- char buf[32];
- char *customkey;
- int len;
- int meta;
- int shift;
+ char buf[32], *customkey;
+ int len, meta, shift, i;
Status status;
if (IS_SET(MODE_KBDLOCK))
shift = e->state & ShiftMask;
len = XmbLookupString(xw.xic, e, buf, sizeof(buf), &ksym, &status);
- /* 1. custom keys from config.h */
+ /* 1. shortcuts */
+ for(i = 0; i < LEN(shortcuts); i++) {
+ if((ksym == shortcuts[i].keysym)
+ && (CLEANMASK(shortcuts[i].mod) == \
+ CLEANMASK(e->state))
+ && shortcuts[i].func) {
+ shortcuts[i].func(&(shortcuts[i].arg));
+ }
+ }
+
+ /* 2. custom keys from config.h */
if((customkey = kmap(ksym, e->state))) {
ttywrite(customkey, strlen(customkey));
/* 2. hardcoded (overrides X lookup) */
selpaste();
break;
case XK_Return:
+ if(meta)
+ ttywrite("\033", 1);
+
if(IS_SET(MODE_CRLF)) {
ttywrite("\r\n", 2);
} else {
}
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);
ttyresize();
}
+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;
while(XPending(xw.dpy)) {
XNextEvent(xw.dpy, &ev);
- if(XFilterEvent(&ev, xw.win))
+ if(XFilterEvent(&ev, None))
continue;
if(handler[ev.type])
(handler[ev.type])(&ev);
run:
setlocale(LC_CTYPE, "");
+ XSetLocaleModifiers("");
tnew(80, 24);
- ttynew();
xinit();
+ ttynew();
selinit();
run();
return 0;