From ce05175372a9ddca1a225db0765ace1127a39293 Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 12 Nov 2021 09:22:01 -0800 Subject: chore: simplified organizational structure --- src/cmd/cc/ast.c | 2139 +++++++++++++++++ src/cmd/cc/bits.c | 114 + src/cmd/cc/cc.c | 409 ++++ src/cmd/cc/cc.h | 806 +++++++ src/cmd/cc/lex.c | 873 +++++++ src/cmd/cc/pp.c | 1125 +++++++++ src/cmd/cc/rules.mk | 21 + src/cmd/cc/scratch.c | 7 + src/cmd/cc/util.c | 21 + src/cmd/dwm/LICENSE | 37 + src/cmd/dwm/client.c | 657 ++++++ src/cmd/dwm/config.h | 141 ++ src/cmd/dwm/drw.c | 376 +++ src/cmd/dwm/dwm.c | 1185 ++++++++++ src/cmd/dwm/dwm.h | 384 ++++ src/cmd/dwm/hook.c | 489 ++++ src/cmd/dwm/rules.mk | 29 + src/cmd/dwm/util.c | 66 + src/cmd/filter/filter.c | 104 + src/cmd/filter/rules.mk | 14 + src/cmd/ic/LICENSE | 23 + src/cmd/ic/ic.1 | 100 + src/cmd/ic/ic.c | 878 +++++++ src/cmd/ic/rules.mk | 14 + src/cmd/ic/strlcpy.c | 32 + src/cmd/menu/LICENSE | 30 + src/cmd/menu/config.h | 25 + src/cmd/menu/drw.c | 428 ++++ src/cmd/menu/drw.h | 57 + src/cmd/menu/menu.c | 765 +++++++ src/cmd/menu/menu.h | 40 + src/cmd/menu/rules.mk | 27 + src/cmd/menu/util.c | 30 + src/cmd/rc/code.c | 277 +++ src/cmd/rc/exec.c | 1267 ++++++++++ src/cmd/rc/exec.h | 47 + src/cmd/rc/input.c | 1679 ++++++++++++++ src/cmd/rc/io.c | 437 ++++ src/cmd/rc/job.c | 91 + src/cmd/rc/lex.c | 394 ++++ src/cmd/rc/main.c | 66 + src/cmd/rc/parse.c | 2059 +++++++++++++++++ src/cmd/rc/parse.h | 141 ++ src/cmd/rc/prompt.c | 36 + src/cmd/rc/rc.h | 263 +++ src/cmd/rc/rules.mk | 32 + src/cmd/rc/syntax.y | 147 ++ src/cmd/rc/sys.c | 137 ++ src/cmd/rc/tree.c | 111 + src/cmd/rc/util.c | 65 + src/cmd/rc/var.c | 336 +++ src/cmd/rc/wait.c | 247 ++ src/cmd/rules.mk | 32 + src/cmd/term/LICENSE | 34 + src/cmd/term/config.h | 474 ++++ src/cmd/term/hb.c | 147 ++ src/cmd/term/nonspacing.h | 89 + src/cmd/term/rules.mk | 26 + src/cmd/term/term.c | 2417 ++++++++++++++++++++ src/cmd/term/term.h | 316 +++ src/cmd/term/term.info | 250 ++ src/cmd/term/util.c | 30 + src/cmd/term/wide.h | 65 + src/cmd/term/x.c | 2070 +++++++++++++++++ src/cmd/walk/rules.mk | 15 + src/cmd/walk/walk.c | 84 + src/cmd/wm/arg.c | 0 src/cmd/wm/client.c | 274 +++ src/cmd/wm/config.h | 70 + src/cmd/wm/input.c | 316 +++ src/cmd/wm/layer.c | 107 + src/cmd/wm/main.c | 177 ++ src/cmd/wm/monitor.c | 386 ++++ src/cmd/wm/protocol/sync | 6 + .../wm/protocol/wlr-layer-shell-unstable-v1.xml | 390 ++++ src/cmd/wm/render.c | 160 ++ src/cmd/wm/rules.mk | 62 + src/cmd/wm/util.c | 99 + src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c | 93 + src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h | 564 +++++ src/cmd/wm/wm.h | 350 +++ src/cmd/wm/xdg-shell-protocol.c | 181 ++ src/cmd/wm/xdg-shell-protocol.h | 1676 ++++++++++++++ src/cmd/wm/xdg.c | 118 + 84 files changed, 30356 insertions(+) create mode 100644 src/cmd/cc/ast.c create mode 100644 src/cmd/cc/bits.c create mode 100644 src/cmd/cc/cc.c create mode 100644 src/cmd/cc/cc.h create mode 100644 src/cmd/cc/lex.c create mode 100644 src/cmd/cc/pp.c create mode 100644 src/cmd/cc/rules.mk create mode 100644 src/cmd/cc/scratch.c create mode 100644 src/cmd/cc/util.c create mode 100644 src/cmd/dwm/LICENSE create mode 100644 src/cmd/dwm/client.c create mode 100644 src/cmd/dwm/config.h create mode 100644 src/cmd/dwm/drw.c create mode 100644 src/cmd/dwm/dwm.c create mode 100644 src/cmd/dwm/dwm.h create mode 100644 src/cmd/dwm/hook.c create mode 100644 src/cmd/dwm/rules.mk create mode 100644 src/cmd/dwm/util.c create mode 100644 src/cmd/filter/filter.c create mode 100644 src/cmd/filter/rules.mk create mode 100644 src/cmd/ic/LICENSE create mode 100644 src/cmd/ic/ic.1 create mode 100644 src/cmd/ic/ic.c create mode 100644 src/cmd/ic/rules.mk create mode 100644 src/cmd/ic/strlcpy.c create mode 100644 src/cmd/menu/LICENSE create mode 100644 src/cmd/menu/config.h create mode 100644 src/cmd/menu/drw.c create mode 100644 src/cmd/menu/drw.h create mode 100644 src/cmd/menu/menu.c create mode 100644 src/cmd/menu/menu.h create mode 100644 src/cmd/menu/rules.mk create mode 100644 src/cmd/menu/util.c create mode 100644 src/cmd/rc/code.c create mode 100644 src/cmd/rc/exec.c create mode 100644 src/cmd/rc/exec.h create mode 100644 src/cmd/rc/input.c create mode 100644 src/cmd/rc/io.c create mode 100644 src/cmd/rc/job.c create mode 100644 src/cmd/rc/lex.c create mode 100644 src/cmd/rc/main.c create mode 100644 src/cmd/rc/parse.c create mode 100644 src/cmd/rc/parse.h create mode 100644 src/cmd/rc/prompt.c create mode 100644 src/cmd/rc/rc.h create mode 100644 src/cmd/rc/rules.mk create mode 100644 src/cmd/rc/syntax.y create mode 100644 src/cmd/rc/sys.c create mode 100644 src/cmd/rc/tree.c create mode 100644 src/cmd/rc/util.c create mode 100644 src/cmd/rc/var.c create mode 100644 src/cmd/rc/wait.c create mode 100644 src/cmd/rules.mk create mode 100644 src/cmd/term/LICENSE create mode 100644 src/cmd/term/config.h create mode 100644 src/cmd/term/hb.c create mode 100644 src/cmd/term/nonspacing.h create mode 100644 src/cmd/term/rules.mk create mode 100644 src/cmd/term/term.c create mode 100644 src/cmd/term/term.h create mode 100644 src/cmd/term/term.info create mode 100644 src/cmd/term/util.c create mode 100644 src/cmd/term/wide.h create mode 100644 src/cmd/term/x.c create mode 100644 src/cmd/walk/rules.mk create mode 100644 src/cmd/walk/walk.c create mode 100644 src/cmd/wm/arg.c create mode 100644 src/cmd/wm/client.c create mode 100644 src/cmd/wm/config.h create mode 100644 src/cmd/wm/input.c create mode 100644 src/cmd/wm/layer.c create mode 100644 src/cmd/wm/main.c create mode 100644 src/cmd/wm/monitor.c create mode 100755 src/cmd/wm/protocol/sync create mode 100644 src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml create mode 100644 src/cmd/wm/render.c create mode 100644 src/cmd/wm/rules.mk create mode 100644 src/cmd/wm/util.c create mode 100644 src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c create mode 100644 src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h create mode 100644 src/cmd/wm/wm.h create mode 100644 src/cmd/wm/xdg-shell-protocol.c create mode 100644 src/cmd/wm/xdg-shell-protocol.h create mode 100644 src/cmd/wm/xdg.c (limited to 'src/cmd') diff --git a/src/cmd/cc/ast.c b/src/cmd/cc/ast.c new file mode 100644 index 0000000..4330bcc --- /dev/null +++ b/src/cmd/cc/ast.c @@ -0,0 +1,2139 @@ +#include "cc.h" + +// ----------------------------------------------------------------------- +// helper macros + +#define alloc(ptr) (ptr) = mem·arenaalloc(C.heap, 1, sizeof *(ptr)) +#define copyarray(dst, arr, n) (dst) = mem·arenaalloc(C.heap, (n), sizeof *(arr)), memcpy((dst), (arr), n * sizeof *(arr)) +#define movearray(dst, arr, n) copyarray(dst,arr,n), free(arr) + +#define attop(prs) ((uintptr)prs->sp == (uintptr)prs->spstk) +#define peek(p, i) (p->tok[i]) +#define iskw(t, k) (((t).kind == Akeywd) && (t).val.i == (k)) +#define advance(p, l) (p->tok[0] = p->tok[1], p->tok[1] = lex(l), p->tok[0]) + +#define Bit(i) (1 << (i)) + +// ----------------------------------------------------------------------- +// helper functions + +static +string +nameof(Name *n) +{ + switch (n->kind) { + /* 0 corresponds to no state - i.e. an abstract name */ + case Nnil: + return nil; + case Nident: + return n->ident; + case Nparen: + return nameof(n->paren->name); + case Nindex: + case Ncall: + return nameof(n->sfx.name); + } + panicf("unreachable"); + return nil; +} + +static +void +openscope(Parser *p) +{ + if (++p->sp >= arrend(p->spstk)) + panicf("scope stack overflow"); +} + +/* + * TODO: save the symbol table with the ast node + * write a "copy(move)scope" + */ + +static +void +closescope(Parser *p) +{ + if (p->sp <= p->spstk) + panicf("scope stack underflow"); + + forgetall(&p->sp->objs); + forgetall(&p->sp->tags); + p->sp--; +} + +/* temporary stack helpers */ +static +Name* +getname(Parser *p) +{ + if (p->nm >= arrend(p->nmstk)) + panicf("name stack overflow"); + return p->nm++; +} + +static void putdtor(Parser *p, Dtor *dt); + +static +void +putname(Parser *p, Name *n) +{ + if (p->nm <= p->nmstk) + panicf("name stack underflow"); + + switch (n->kind) { + case Nnil: + case Nident: + break; + case Nparen: + putdtor(p, n->paren); + break; + case Nindex: + case Ncall: + putname(p, n->sfx.name); + break; + default: + panicf("unrecognized name kind"); + } + *p->nm-- = (Name){0}; +} + +static +Ptr* +getptr(Parser *p) +{ + if (p->pt >= arrend(p->ptstk)) + panicf("pointer stack overflow"); + + return p->pt++; +} + +static +void +putptr(Parser *p, Ptr *ptr) +{ + if (p->pt <= p->ptstk) + panicf("pointer stack underflow"); + + while ((ptr = ptr->link)) + putptr(p, ptr); + + *p->pt-- = (Ptr){0}; +} + + +static +Dtor* +getdtor(Parser *p) +{ + if (p->dt >= arrend(p->dtstk)) + panicf("dtor stack overflow"); + + p->dt->name = getname(p); + return p->dt++; +} + +static +void +putdtor(Parser *p, Dtor *dt) +{ + if (p->dt <= p->dtstk) + panicf("dtor stack underflow"); + + /* release the pointer overflow if we had to use it */ + if (p->dt->ptr.link) + putptr(p, p->dt->ptr.link); + + /* the dtor could encompass multiple names hierarchically */ + putname(p, dt->name); + *p->dt-- = (Dtor){0}; +} + +/* TODO: This will fail for forward declarations */ +static +void +declareobj(Parser *p, Decl *d) +{ + Sym *sym; + string ident; + uint32 kind; + struct Decls *link; + + switch (d->kind) { + case Dfunc: + case Dvar: + kind = Svar; + goto one; + case Dtype: + kind = Stype; + one: + ident = d->name; + break; + + case Dvars: + kind = Svar; + goto many; + case Dtypes: + kind = Stype; + many: + while (link = &d->list, link != nil) { + ident = link->name; + sym = lookup(&p->sp->objs, ident); + if (sym) { + errorat(peek(p, 0).pos, "redeclaration of name '%s' in object space", ident); + return; + } + sym = define(&p->sp->objs, ident, kind); + if (kind == Svar) + sym->obj = d; + else + sym->type = d->type; + } + break; + + default: + panicf("unrecognized node kind %d. expected declaration", d->kind); + } + sym = lookup(&p->sp->objs, ident); + if (sym) { + errorat(peek(p, 0).pos, "redeclaration of name '%s' in object space", ident); + return; + } + sym = define(&p->sp->objs, ident, kind); + if (kind == Svar) + sym->obj = d; + else + sym->type = d->type; +} + +/* enters the object identifier space */ +static +void +declareenum(Parser *p, int n, string *elts, Expr *vals) +{ + int i; + Sym *s; + + for (i = 0; i < n; i++) { + s = lookup(&p->sp->objs, elts[i]); + if (s) { + errorat(peek(p, 0).pos, "redeclaration of name %s in object space", elts[i]); + continue; + } + s = define(&p->sp->objs, elts[i], Senum); + s->val = vals + i; + } +} + +static +void +declaretag(Parser *p, uint32 t, string name) +{ + Sym *sym; + sym = lookup(&p->sp->tags, name); + if (sym) { + errorat(peek(p, 0).pos, "redeclaration of name '%s' in tag space", name); + return; + } + + sym = define(&p->sp->tags, name, Stype); + sym->type = t; +} + +static +Sym * +lookupobj(Parser *p, string ident) +{ + Sym *sym; + Scope *it; + + it = p->sp; + do { + sym = lookup(&it->objs, ident); + } while (sym == nil && --it >= p->spstk); + + return sym; +} + +static +Sym * +lookuptag(Parser *p, string ident) +{ + Sym *sym; + Scope *it; + + it = p->sp; + do { + sym = lookup(&it->tags, ident); + } while (sym == nil && --it >= p->spstk); + + return sym; +} + +static +int +nomatch(Token t, vlong kind) +{ + if (t.kind == kind) + return 0; + + if (t.kind == Akeywd) + errorat(t.pos, "expected token '%s', instead found keyword '%s'", tokens[kind], keywords[t.val.i]); + else + errorat(t.pos, "expected token '%s', instead found '%s'", tokens[kind], tokens[t.kind]); + return 1; +} + +// ----------------------------------------------------------------------- +// needed forward declarations + +static error spec(Parser *, Lexer *, uint64 *); +static uint32 basetype(Parser *, Lexer *, uint64 *s); +static string namedecl(Parser *, Lexer *, uint32 *, int); +static uint32 typename(Parser *, Lexer *, uint32 *); + +static error dtor(Parser *p, Lexer *lx, Dtor *d, int ab); +static uint32 typeofdtor(Dtor *, uint32); + + +static Decl *decl(Parser *, Lexer *); + +static Expr *ternary(Parser *, Lexer *); +static Expr *expr(Parser *, Lexer *); + +static error blkstmt(Parser *, Lexer *, Stmt **); + + +// ----------------------------------------------------------------------- +// expressions + +#define MAKEX(x, state) alloc((x)), (x)->kind = X##state + +static +Expr* +primary(Parser *p, Lexer *lx) +{ + int k; + Expr *x; + Token t; + Pos b; + + t = peek(p, 0); + b = t.pos; + switch (k = (t.kind & Vmask)) { + case Aident: + MAKEX(x, ident); + x->pos.beg = b; + x->pos.end = lx->pos; + x->name = t.val.s; + break; + + case Alit: + MAKEX(x, lit); + x->pos.beg = b; + x->pos.end = lx->pos; + x->val.kind = t.kind & ~Vmask; + x->val.v = t.val; + break; + + case Alparen: + advance(p, lx); + x = expr(p, lx); + t = peek(p, 0); + if (nomatch(t, Arparen)) { + errorat(lx->pos, "unterminated paren expression"); + goto Bad; + } + break; + + default: + panicf("unreachable"); + } + + advance(p, lx); + return x; +Bad: + errorat(lx->pos, "unable to parse operand expression"); + return nil; +} + +static +int +istypename(Parser *p, Token t) +{ + Sym *sym; + + if (t.kind == Akeywd && (Kconst <= t.val.i && t.val.i <= Kenum)) + return 1; + if (t.kind == Aident) { + sym = lookupobj(p, t.val.s); + return (sym != nil) && sym->kind == Stype; + } + + return 0; +} + +static Expr* initx(Parser *p, Lexer *lx); + +static +Expr* +initlist(Parser *p, Lexer *lx) +{ + Token t; + int c, n; + Expr *x, **a; + struct Key *k; + + MAKEX(x, initlist); + x->pos.beg = lx->pos; + x->init.n = 0; + if (t.kind == Arbrace) { + x->init.k = nil; + x->init.v = nil; + return x; + } + + c = 0; + n = 0; + a = nil; + k = nil; +Key0: + if (n >= c) { + c += 20; + k = realloc(k, c * sizeof(*k)); + a = realloc(a, c * sizeof(*a)); + } +Key1: + switch (t.kind) { + case Adot: + t = advance(p, lx); + if (t.kind != Aident) { + errorat(t.pos, "dot designator must be followed by identifier"); + goto Bad; + } + k[n++] = (struct Key) { + .kind = (uint32)x->init.n, + .s = t.val.s, + }; + t = advance(p, lx); + goto Key0; + + case Albrakt: + t = advance(p, lx); + k[n++] = (struct Key) { + .kind = (uint32)x->init.n | (1ULL << 32), + .x = expr(p, lx), + }; + t = peek(p, 0); + goto Key0; + + case Aeq: + t = advance(p, lx); + /* fallthrough */ + default: + a[x->init.n++] = initx(p, lx); + + t = peek(p, 0); + switch (t.kind) { + case Arbrace: + break; + case Acomma: + advance(p, lx); + /* fallthrough */ + default: + goto Key0; + } + break; + + case Acomma: + t = advance(p, lx); + break; + } + movearray(x->init.k, k, n); + movearray(x->init.v, a, x->init.n); + return x; +Bad: + errorat(t.pos, "could not parse initializer list"); + return nil; +} + +static +Expr* +initx(Parser *p, Lexer *lx) +{ + Expr *x; + Token t; + + t = peek(p, 0); + if (t.kind != Albrace) + return ternary(p, lx); + + advance(p, lx); + x = initlist(p, lx); + t = peek(p, 0); + if (nomatch(t, Arbrace)) { + errorat(t.pos, "unmatched brace in initializer list, found %s instead", tokens[t.kind]); + advance(p, lx); + } + + return x; +} + +static +Expr* +postfix(Parser *p, Lexer *lx) +{ + Pos b; + Token t; + int c, n; + uint32 type, qual; + Expr *x, *y, **a; + + t = peek(p, 0); + if (t.kind == Alparen) + if (istypename(p, peek(p, 1))) { + t = advance(p, lx); + type = typename(p, lx, &qual); + t = peek(p, 0); + if (nomatch(t, Arparen)) { + errorat(lx->pos, "unmatched paren: found %s instead", tokens[t.kind]); + goto Bad; + } + t = advance(p, lx); + if (nomatch(t, Albrace)) { + errorat(lx->pos, "bad initializer list: found %s", tokens[t.kind]); + goto Bad; + } + + x = initlist(p, lx); + + t = peek(p, 0); + if (nomatch(t, Arbrace)) { + errorat(lx->pos, "unmatched brace: found %s instead", tokens[t.kind]); + goto Bad; + } + + x->type = type; + x->qual = qual; + return x; + } + + x = primary(p, lx); + t = peek(p, 0); + for (;;) { + b = x->pos.beg; + switch (t.kind) { + case Ainc: + MAKEX(y, postinc); + goto Postfix; + case Adec: + MAKEX(y, postdec); + Postfix: + y->pos.beg = b; + y->pos.end = lx->pos; + y->unary.post = x; + x = y, y = nil; + break; + + case Adot: + MAKEX(y, self); + goto Select; + case Aarrow: + MAKEX(y, selp); + Select: + t = advance(p, lx); + if (t.kind != Aident) { + errorat(t.pos, "invalid operand of selector expression"); + goto Bad; + } + y->pos.beg = b; + y->pos.end = lx->pos; + + y->idx.f = t.val.s; + y->idx.x = x; + x = y, y = nil; + break; + + case Albrakt: + t = advance(p, lx); + if (t.kind == Arbrakt) { + errorat(t.pos, "empty index expression"); + goto Bad; + } + MAKEX(y, index); + y->idx.x = x; + y->idx.i = expr(p, lx); + + t = peek(p, 0); + if (t.kind != Albrakt) { + errorat(t.pos, "malformed index expression"); + goto Bad; + } + + x = y, y = nil; + break; + + case Alparen: + t = advance(p, lx); + MAKEX(y, call); + y->call.fn = x; + y->pos.beg = b; + y->call.n = 0; + if (t.kind == Arparen) { + y->call.arg = nil; + goto Endfunc; + } + c = 0; + a = nil; + Arg: + if (y->call.n >= c) { + c += 20; + a = realloc(a, c * sizeof(*a)); + } + a[y->call.n++] = expr(p, lx); + t = peek(p, 0); + if (t.kind == Acomma) { + advance(p, lx); + goto Arg; + } + if (t.kind != Arparen) { + errorat(t.pos, "invalid token '%s' found in call argument"); + goto Bad; + } + movearray(y->call.arg, a, y->call.n); + Endfunc: + y->pos.end = lx->pos; + x = y, y = nil; + break; + + default: + return x; + } + t = advance(p, lx); + } + return x; +Bad: + errorat(lx->pos, "failed to parse primary expression"); + return nil; +} + +static +uint32 +typename(Parser *p, Lexer *lx, uint32 *spec) +{ + uint32 base; + uint64 s; + + base = basetype(p, lx, &s); + if (!base) { + errorat(lx->pos, "failed to parse type name specifiers"); + return 0; + } + *spec = (uint32)s; + namedecl(p, lx, &base, 1); + + return base; +} + +static Expr* cast(Parser *p, Lexer *lx); + +static +Expr* +unary(Parser *p, Lexer *lx) +{ + Expr *x; + Token t; + + t = peek(p, 0); + switch (t.kind) { + case Ainc: MAKEX(x, preinc); goto Prefix; + case Adec: MAKEX(x, predec); /* fallthrough */ + Prefix: + advance(p, lx); + x->pos.beg = t.pos; + x->unary.pre = unary(p, lx); + x->pos.end = x->unary.pre->pos.end; + return x; + + case Aneg: MAKEX(x, neg); goto Unary; + case Aand: MAKEX(x, ref); goto Unary; + case Anot: MAKEX(x, not); goto Unary; + case Astar: MAKEX(x, star); goto Unary; + case Aadd: MAKEX(x, plus); goto Unary; + case Asub: MAKEX(x, minus); /* fallthrough */ + Unary: + advance(p, lx); + x->pos.beg = t.pos; + x->unary.pre = cast(p, lx); + x->pos.end = x->unary.pre->pos.end; + return x; + + case Akeywd: + switch (t.val.i) { + case Ksizeof: + MAKEX(x, sizeof); + goto Key; + case Kalignof: + MAKEX(x, alignof); + /* fallthrough */ + Key: + t = advance(p, lx); + if (t.kind == Alparen) + if (istypename(p, peek(p, 1))) { + t = advance(p, lx); + x->info.type = 0; + x->info.of.type = typename(p, lx, &x->info.of.qual); + + t = peek(p, 0); + if (nomatch(t, Arparen)) { + errorat(t.pos, "missing paren for size/alignof statement"); + goto Bad; + } + advance(p, lx); + return x; + } + + x->info.type = 1; + x->info.x = unary(p, lx); + return x; + + default: + ; + } + /* fallthrough */ + default: + return postfix(p, lx); + } +Bad: + return nil; +} + +static +Expr* +cast(Parser *p, Lexer *lx) +{ + Expr *x; + Token t; + + t = peek(p, 0); + if (t.kind == Alparen && istypename(p, peek(p,1))) { + t = advance(p, lx); + MAKEX(x, cast); + + x->pos.beg = t.pos; + x->cast.to.type = typename(p, lx, &x->cast.to.qual); + if (!x->cast.to.type) { + errorat(lx->pos, "invalid type operand of cast"); + goto Bad; + } + + t = peek(p, 0); + if (nomatch(t, Arparen)) { + errorat(lx->pos, "missing closing paren after cast expression"); + goto Bad; + } + advance(p, lx); + + x->cast.x = cast(p, lx); + x->pos.beg = lx->pos; + return x; + } + return unary(p, lx); + +Bad: + errorat(lx->pos, "failed to parse cast expression"); + return nil; +} + +/* static data for binary operators */ +#define OPERATORS \ + OPERATOR(Astar, 10, Xmul) \ + OPERATOR(Adiv, 10, Xdiv) \ + OPERATOR(Amod, 10, Xmod) \ + OPERATOR(Aadd, 9, Xadd) \ + OPERATOR(Asub, 9, Xsub) \ + OPERATOR(Alsft, 8, Xlsft) \ + OPERATOR(Arsft, 8, Xrsft) \ + OPERATOR(Agteq, 7, Xgteq) \ + OPERATOR(Alteq, 7, Xlteq) \ + OPERATOR(Alt, 7, Xlt) \ + OPERATOR(Agt, 7, Xgt) \ + OPERATOR(Aeq, 6, Xeql) \ + OPERATOR(Aneq, 6, Xneq) \ + OPERATOR(Aand, 5, Xand) \ + OPERATOR(Axor, 4, Xxor) \ + OPERATOR(Aor, 3, Xor) \ + OPERATOR(Aandand, 2, Xandand) \ + OPERATOR(Aoror, 1, Xoror) + +static int prectab[NUM_TOKENS] = +{ +#define OPERATOR(a, b, c) [a] = b, + OPERATORS +#undef OPERATOR +}; + +static int optab[NUM_TOKENS] = +{ +#define OPERATOR(a, b, c) [a] = c, + OPERATORS +#undef OPERATOR +}; +#undef OPERATORS + +static +Expr* +binary(Parser *p, Lexer *lx, int prec) +{ + Token t; + int k, np; + Expr *l, *x; + + l = cast(p, lx); + for (;;) { + t = peek(p, 0); + k = t.kind; + np = prectab[k]; + if (np < prec) + return l; + + alloc(x); + t = advance(p, lx); + + x->pos.beg = l->pos.beg; + x->kind = optab[k]; + x->binary.l = l; + x->binary.r = binary(p, lx, np + 1); + x->pos.end = x->binary.r->pos.end; + + l = x; + } + return l; +Bad: + errorat(t.pos, "failed to parse expression"); + return nil; +} + +static +Expr* +ternary(Parser *p, Lexer *lx) +{ + Pos b; + Token t; + Expr *x, *y; + + x = binary(p, lx, 1); + t = peek(p, 0); + b = t.pos; + + switch (t.kind) { + case Aqmark: + t = advance(p, lx); + y = x; + MAKEX(x, ternary); + x->pos.beg = b; + x->kind = Xternary; + x->cond.c = y; + x->cond.t = expr(p, lx); + + t = peek(p, 0); + if (nomatch(t, Acolon)) { + errorat(t.pos, "ternary expression missing ':'"); + goto Bad; + } + t = advance(p, lx); + x->cond.e = expr(p, lx); + x->pos.end = lx->pos; + break; + + case Aasn: MAKEX(y, asn); goto Assign; + case Aorasn: MAKEX(y, orasn); goto Assign; + case Axorasn: MAKEX(y, xorasn); goto Assign; + case Aandasn: MAKEX(y, andasn); goto Assign; + case Asubasn: MAKEX(y, subasn); goto Assign; + case Amulasn: MAKEX(y, mulasn); goto Assign; + case Adivasn: MAKEX(y, divasn); goto Assign; + case Amodasn: MAKEX(y, modasn); goto Assign; + case Alsftasn: MAKEX(y, lsftasn); goto Assign; + case Arsftasn: MAKEX(y, rsftasn); goto Assign; + Assign: + advance(p, lx); + + y->asn.l = x; + y->asn.r = ternary(p, lx); + x = y; + x->pos.beg = b; + x->pos.end = lx->pos; + break; + default: + ; + } + + return x; +Bad: + errorat(lx->pos, "failing expression parse"); + return nil; +} + +static +Expr* +expr(Parser *p, Lexer *lx) +{ + Pos b; + Token t; + Expr *x, *y; + + x = ternary(p, lx); + while (t = peek(p, 0), t.kind == Acomma) { + advance(p, lx); + y = x; + MAKEX(x, comma); + x->pos.beg = y->pos.beg; + x->comma.x[0] = y; + x->comma.x[1] = ternary(p, lx); + x->pos.end = lx->pos; + y = nil; + } + + return x; +} + +// ----------------------------------------------------------------------- +// statements + +static +struct Node* +stmt(Parser *p, Lexer *lx) +{ + int k; + Stmt *s; + Sym *sym; + Token t; + + t = peek(p, 0); + k = t.kind; + + /* intercept decl before allocating a statement */ + if (k == Aident) { + if (peek(p, 1).kind == Acolon) + goto Tlabel; + sym = lookupobj(p, t.val.s); + if (!sym) { + errorat(lx->pos, "unrecognized type identifier '%s'", t.val.s); + goto Bad; + } + + if (sym->kind == Stype) + goto Tdecl; + if (sym->kind == Svar) { + alloc(s); + s->pos.beg = lx->pos; + goto Texpr; + } + + errorat(lx->pos, "bad symbol type used as type identifier"); + goto Bad; + } + + if (k == Akeywd) { + if ((Kauto <= t.val.i && t.val.i <= Ktypedef) || (Kconst <= t.val.i && t.val.i <= Kenum)) { + Tdecl: + return (Node *)decl(p, lx); + } + } + + alloc(s); + s->pos.beg = lx->pos; + + switch (k) { + case Akeywd: + switch (k = t.val.i) { + case Kif: + t = advance(p, lx); + s->kind = Sif; + + if (nomatch(t, Alparen)) { + errorat(lx->pos, "missing opening paren before if conditional"); + goto Bad; + } + s->br.cond = expr(p, lx); + if (nomatch(t, Arparen)) { + errorat(lx->pos, "missing closing paren after if conditional"); + goto Bad; + } + s->br.body = stmt(p, lx); + + t = peek(p, 0); + if (iskw(t, Kelse)) + s->br.orelse = stmt(p, lx); + else + s->br.orelse = nil; + + break; + + case Kswitch: + t = advance(p, lx); + s->kind = Sswitch; + + if (nomatch(t, Alparen)) { + errorat(lx->pos, "missing opening paren before switch conditional"); + goto Bad; + } + s->br.cond = expr(p, lx); + if (nomatch(t, Arparen)) { + errorat(lx->pos, "missing closing paren after switch conditional"); + goto Bad; + } + s->br.body = stmt(p, lx); + s->br.orelse = nil; + + break; + + case Kfor: + t = advance(p, lx); + s->kind = Sfor; + + if (nomatch(t, Alparen)) { + errorat(lx->pos, "missing opening paren before for loop preamble"); + goto Bad; + } + + if (t.kind == Asemi) + s->loop.init = nil; + else { + // TODO: test for declaration + s->loop.init = (Node *)expr(p, lx); + } + + if (nomatch(t, Asemi)) { + errorat(lx->pos, "missing semicolon"); + goto Bad; + } + + if (t.kind == Asemi) + s->loop.cond = nil; + else + s->loop.cond = expr(p, lx); + + if (nomatch(t, Asemi)) { + errorat(lx->pos, "missing semicolon"); + goto Bad; + } + + if (t.kind == Asemi) + s->loop.step = nil; + else + s->loop.step = expr(p, lx); + + if (nomatch(t, Alparen)) { + errorat(lx->pos, "missing closing paren after for loop preamble"); + goto Bad; + } + s->loop.body = stmt(p, lx); + break; + + case Kwhile: + t = advance(p, lx); + s->kind = Swhile; + if (nomatch(t, Alparen)) { + errorat(lx->pos, "missing opening paren before while loop conditional"); + goto Bad; + } + s->loop.cond = expr(p, lx); + if (nomatch(t, Arparen)) { + errorat(t.pos, "missing closing paren after while loop conditional"); + goto Bad; + } + + s->loop.init = nil; + s->loop.step = nil; + s->loop.body = stmt(p, lx); + break; + + case Kdo: + t = advance(p, lx); + s->kind = Sdo; + s->loop.body = stmt(p, lx); + + if (!iskw(t, Kwhile)) { + errorat(t.pos, "missing while statement conditional after do body"); + goto Bad; + } + t = advance(p, lx); + if (nomatch(t, Alparen)) { + errorat(t.pos, "missing open paren after while conditional"); + goto Bad; + } + + s->loop.init = nil; + s->loop.step = nil; + s->loop.cond = expr(p, lx); + break; + + case Kgoto: + t = advance(p, lx); + s->kind = Sgoto; + if (t.kind != Aident) { + errorat(t.pos, "invalid argument to goto"); + goto Bad; + } + s->jmp.lbl = t.val.s; + t = advance(p, lx); + if (nomatch(t, Asemi)) { + errorat(t.pos, "missing semicolon after goto"); + goto Bad; + } + advance(p, lx); + break; + + case Kcontinue: + t = advance(p, lx); + s->kind = Scontin; + s->jmp.lbl = nil; + s->jmp.x = nil; + if (nomatch(t, Asemi)) { + errorat(t.pos, "missing semicolon after continue"); + goto Bad; + } + advance(p, lx); + break; + + case Kbreak: + t = advance(p, lx); + s->kind = Sbreak; + s->jmp.lbl = nil; + s->jmp.x = nil; + if (nomatch(t, Asemi)) { + errorat(t.pos, "missing semicolon after break"); + goto Bad; + } + advance(p, lx); + break; + + case Kreturn: + t = advance(p, lx); + s->kind = Sreturn; + + s->jmp.lbl = nil; + s->jmp.x = (t.kind == Asemi) ? nil : expr(p, lx); + + t = peek(p, 0); + if (nomatch(t, Asemi)) { + errorat(t.pos, "missing semicolon after return statement"); + goto Bad; + } + advance(p, lx); + break; + + case Kcase: + t = advance(p, lx); + s->kind = Scase; + s->lbl.x = expr(p, lx); + if (nomatch(t, Acolon)) { + errorat(t.pos, "missing colon after default label"); + goto Bad; + } + t = advance(p, lx); + s->lbl.stmt = stmt(p, lx); + break; + + case Kdefault: + t = advance(p, lx); + s->kind = Scase; + s->lbl.x = nil; + if (nomatch(t, Acolon)) { + errorat(t.pos, "missing colon after default label"); + goto Bad; + } + t = advance(p, lx); + s->lbl.stmt = stmt(p, lx); + break; + + default: + panicf("unexpected statement keyword %s", keywords[k]); + } + break; + case Albrace: + s->kind = Sblock; + openscope(p); + if (blkstmt(p, lx, &s)) { + errorat(lx->pos, "failed to parse block statement"); + goto Bad; + } + closescope(p); + break; + + case Asemi: + t = advance(p, lx); + s->kind = Sempty; + break; + + case Aident: + Tlabel: + t = advance(p, lx); + s->kind = Slabel; + if (nomatch(t, Acolon)) { + errorat(t.pos, "missing colon after labelled block"); + goto Bad; + } + t = advance(p, lx); + s->lbl.stmt = stmt(p, lx); + break; + + default: + Texpr: + s->kind = Sexpr; + s->x = expr(p, lx); + + t = peek(p, 0); + if (nomatch(t, Asemi)) { + errorat(t.pos, "missing semicolon after statement expression"); + goto Bad; + } + advance(p, lx); + } + + s->pos.end = lx->pos; + return (Node *)s; +Bad: + errorat(lx->pos, "failed to parse statement"); + return nil; +} + +static +error +blkstmt(Parser *p, Lexer *lx, Stmt **s) +{ + Token t; + int len; + int cap; + Node **ns; + + alloc(*s); + (*s)->kind = Sblock; + (*s)->pos.beg = lx->pos; + + t = peek(p, 0); + if (nomatch(t, Albrace)) + goto Bad; + t = advance(p, lx); + + len = 0, cap = 20; + ns = malloc(cap*sizeof(*ns)); + while (t.kind != Arbrace) { + if (cap == len) { + cap += 20; + ns = realloc(ns, cap*sizeof(*ns)); + } + ns[len++] = stmt(p, lx); + t = peek(p, 0); + } + advance(p, lx); + + (*s)->pos.end = lx->pos; + (*s)->blk.n = len; + movearray((*s)->blk.item, ns, len); + return 0; +Bad: + errorat(lx->pos, "failed to parse block statement"); + free(ns); + return 1; +} + +// ----------------------------------------------------------------------- +// types + +uint32 +ptrtype(uint32 base, uint32 qual) +{ + uint32 i; + Type *t; + + i = type(); + t = C.type.info + i; + t->kind = Tptr; + t->ptr.base = base; + t->ptr.qual = qual; + t->size = pointer.size; + t->align = pointer.align; + t->sign = pointer.sign; + + return i; +} + +uint32 +arraytype(uint32 base, uint32 qual, Expr *ix) +{ + int i, n; + Type *t; + + /* TODO: evaluate the length */ + n = 10; + i = type(); + t = C.type.info + i; + t->kind = Tarray; + t->ptr.base = base; + t->size = n * C.type.info[base].size; + t->align = C.type.info[base].align; + t->sign = 0; + + return i; +} + +uint32 +functype(uint32 ret, int n, Field *args, int dots) +{ + uint32 i; + Type *t; + + i = type(); + t = C.type.info + i; + t->kind = Tfunc; + t->size = pointer.size; + t->align = pointer.align; + t->sign = pointer.sign; + + t->func.ret = ret; + t->func.n = n; + t->func.arg = args; + t->func.dots = dots; + + return i; +} + +#define ALIGN_DOWN(n, a) ((n) & ~((a)-1)) +#define ALIGN_UP(n, a) ALIGN_DOWN((n) + (a)-1, (a)) +uint32 +structtype(int n, Field *field, Expr *bits) +{ + uint32 i; + Type *t; + Field *f, *e; + + i = type(); + t = C.type.info + i; + t->kind = Tstruct; + t->size = 0; + t->align = 0; + for (f = field, e = field+n; f != e; ++f) { + t->size += C.type.info[f->type].size + ALIGN_UP(t->size, C.type.info[f->type].align); + t->align = MAX(t->align, C.type.info[f->type].align); + } + t->aggr.len = n; + t->aggr.f = field; + t->aggr.x = bits; + + return i; +} + +uint32 +uniontype(int n, Field *field, Expr *bits) +{ + uint32 i; + Type *t; + Field *f, *e; + + i = type(); + t = C.type.info + i; + t->kind = Tstruct; + t->size = 0; + t->align = 0; + for (f = field, e = field+n; f != e; ++f) { + t->size = MAX(t->size, C.type.info[f->type].size); + t->align = MAX(t->align, C.type.info[f->type].align); + } + t->aggr.len = n; + t->aggr.f = field; + t->aggr.x = bits; + + return i; +} + +uint32 +enumtype(int n, string *elts, Expr *vals) +{ + uint32 i; + Type *t; + Field *f, *e; + + i = type(); + t = C.type.info + i; + t->kind = Tenum; + /* TODO: dont hardcode int32 */ + t->size = 4; + t->align = 4; + t->enm.len = n; + t->enm.elt = elts; + t->enm.val = vals; + + return i; +} +#undef ALIGN_UP +#undef ALIGN_DOWN + +/* unpacking C declarations into sensible types */ +static +uint32 +typeofname(Name *name, uint32 base) +{ + switch (name->kind) { + /* Nnil corresponds to an abstract declarator (i.e. no identifier) */ + case Nnil: + case Nident: + return base; + case Nparen: + return typeofdtor(name->paren, base); + case Nindex: + return typeofname(name->sfx.name, arraytype(base, name->sfx.idx.q, name->sfx.idx.x)); + case Ncall: + return typeofname(name->sfx.name, functype(base, name->sfx.call.n, name->sfx.call.arg, name->sfx.call.dots)); + default: + panicf("unreachable"); + } + return 0; +} + +static +uint32 +typeofdtor(Dtor *decl, uint32 base) +{ + int n; + Ptr *p; + uint64 b, tmp; + + n = 0; + p = &decl->ptr; + b = p->kind; + while (b & 1) { + base = ptrtype(base, b >> 1); + if (++n >= 8) { + p = p->link; + b = p->kind; + } else { + b >>= 6; + } + } + + return typeofname(decl->name, base); +} + +static +uint32 +basetype(Parser *p, Lexer *lx, uint64 *s) +{ + int n; + uint64 m; + + if (spec(p, lx, s)) { + errorat(lx->pos, "failed to parse type specifier"); + return 0; + } + + m = (((*s<<32)>>32) & ~(MaskQul|MaskMem|MaskFcn)); + for (n = 0; n < arrlen(validtypespec); n++) { + if (validtypespec[n] == m) { + if (indextypespec[n] < 0) { + m = *s >> 32; + if (!m) { + errorat(lx->pos, "not a valid type identifier"); + return 0; + } + return m; + } + return indextypespec[n]; + } + } + + errorat(lx->pos, "invalid type specifier"); + return 0; +} + +static +string +namedecl(Parser *p, Lexer *lx, uint32 *base, int noname) +{ + Dtor *dt; + string name; + Type *t; + + dt = getdtor(p); + name = nil; + if (dtor(p, lx, dt, noname)) { + errorat(lx->pos, "invalid declarator"); + goto End; + } + if (!noname || noname == 2 && dt->name->kind) + name = nameof(dt->name); + + *base = typeofdtor(dt, *base); + putdtor(p, dt); + return name; +End: + putdtor(p, dt); + return nil; +} + +// ----------------------------------------------------------------------- +// declarations + +static +uint32 +enumerate(Parser *p, Lexer *lx, string name, int kind) +{ + int i, n; + uint64 s; + uint32 t; + Token tk; + /* TODO: think of a better soln */ + string nm[1024], *elts; + Expr *cx[1024], *vals; + + for (n = 0; tk.kind != Arbrace && n < arrlen(nm); n++) { + if (tk.kind != Aident) { + errorat(tk.pos, "invalid token %s in enum declaration", tokens[tk.kind]); + goto Bad; + } + nm[n] = tk.val.s; + cx[n] = nil; + + tk = advance(p, lx); + switch(tk.kind) { + case Aeq: + advance(p, lx); + cx[n] = expr(p, lx); + tk = peek(p, 0); + if (tk.kind != Acomma) + continue; + /* fallthrough */ + case Acomma: + tk = advance(p, lx); + } + } + copyarray(elts, nm, n); + copyarray(vals, cx, n); + + t = enumtype(n, elts, vals); + declareenum(p, n, elts, vals); + return t; +Bad: + errorat(tk.pos, "failed to parse enum declaration"); + return 0; +} + +static +uint32 +aggregate(Parser *p, Lexer *lx, string name, int kind) +{ + int n; + uint64 s; + Token tk; + /* TODO: think of a better soln */ + static Field fs[1024]; + Field *f; + static Expr *cx[1024]; + Expr *x; + + for (n = 0, tk = peek(p, 0); tk.kind != Arbrace && n < arrlen(fs); n++) { + fs[n].type = basetype(p, lx, &s); + fs[n].qual = (uint32)(s & ~(MaskTyp|MaskInt|MaskFlt)); + Field: + fs[n].name = namedecl(p, lx, &fs[n].type, 0); + tk = peek(p, 0); + switch (tk.kind) { + case Acolon: + advance(p, lx); + cx[n] = expr(p, lx); + tk = peek(p, 0); + if (tk.kind == Asemi) { + tk = advance(p, lx); + continue; + } + if (tk.kind != Acomma) { + errorat(tk.pos, "unrecognized token %s in struct field declaration", tokens[tk.kind]); + goto Bad; + } + /* fallthrough */ + case Acomma: + advance(p, lx); + n++; + goto Field; + + case Asemi: + tk = advance(p, lx); + continue; + + default: + errorat(tk.pos, "unrecognized token %s in struct field declaration", tokens[tk.kind]); + goto Bad; + } + } + copyarray(f, fs, n); + copyarray(x, cx, n); + return (kind == Tstruct) ? structtype(n, f, x) : uniontype(n, f, x); +Bad: + errorat(tk.pos, "failed to parse aggregate declaration"); + return 0; +} + +static +error +spec(Parser *p, Lexer *lx, uint64 *spec) +{ + Token t; + int n, i; + Sym *typ; + string name; + uint32 tag; + uint64 s, sm; + static uint32 (*aggrfunc[2])(Parser *, Lexer *, string , int) = {aggregate, enumerate}; + + s = 0; + while (t = peek(p, 0), t.kind >= Aident) { + /* typename */ + if (t.kind == Aident) { + typ = lookupobj(p, t.val.s); + if (!typ || (typ && typ->kind != Stype)) + break; + + sm = typ->type; + s |= (sm << 32 | Tname); + advance(p, lx); + continue; + } + + /* keyword */ + switch (n = t.val.i) { + case Kauto: case Kregister: case Kstatic: case Kextern: case Ktypedef: case Ktls: + if (s & MaskMem) { + errorat(lx->pos, "multiple storage class specifiers: second was %s", keywords[n]); + goto Bad; + } + break; + + case Kinline: case Knoret: + if (s & Bit(n)) + warnat(lx->pos, "duplicate %s function specifier", keywords[n]); + break; + + case Kconst: case Kvolatile: + if (s & Bit(n)) + warnat(lx->pos, "duplicate %s specifier found in declaration", keywords[n]); + break; + + case Ksigned: case Kunsigned: + if (s & MaskSgn) { + if (s & Bit(n)) { + warnat(lx->pos, "duplicated storage class specifier: second was %s", keywords[n]); + break; + } + errorat(lx->pos, "multiple storage class specifiers"); + goto Bad; + } + break; + + case Kshort: + if (s & Tshort) { + warnat(lx->pos, "duplicated short specifier"); + break; + } + break; + + case Klong: + if ((s >> Klong) & 2) { + errorat(lx->pos, "cannot chain three or more long specifiers"); + goto Bad; + } + s += Bit(n); + t = advance(p, lx); + continue; + + case Kvoid: case Kchar: case Kint: case Kfloat: case Kdouble: + if (s & MaskTyp) { + errorat(lx->pos, "more than one base type specified"); + goto Bad; + } + break; + + case Kstruct: case Kunion: + i = 0; + goto Aggr; + case Kenum: + i = 1; + Aggr: + if (s & (Tstruct | Tunion | Tenum)) { + errorat(lx->pos, "more than one aggregate/enum type specified"); + goto Bad; + } + t = advance(p, lx); + if (t.kind != Aident && t.kind != Albrace) { + errorat(t.pos, "enum specifier missing valid declaration"); + goto Bad; + } + + /* NOTE: This offset is needed to correctly obtain Tstruct */ + n++; + name = nil; + tag = 0; + if (t.kind == Aident) { + name = t.val.s; + t = advance(p, lx); + } + if (t.kind == Albrace) { + /* TODO: we need check if the name exists. */ + t = advance(p, lx); + /* NOTE: This depends on the enum order. KEEP IN SYNC */ + tag = aggrfunc[i](p, lx, name, Bit(n)); + if (t = peek(p, 0), nomatch(t, Arbrace)) { + errorat(t.pos, "invalid token %s in aggregate/enum declaration", tokens[t.kind]); + goto Bad; + } + /* high bits encode the type index */ + s |= (uint64)tag << 32; + } + /* TODO: if name does not exist, enter in an incomplete type! */ + if (name) + declaretag(p, tag, name); + + break; + + default: + errorat(t.pos, "invalid keyword '%s' found in declaration specifier", keywords[n]); + } + + s |= Bit(n); + advance(p, lx); + } + + *spec = s; + return 0; + +Bad: + /* TODO: serialize bitflags to string for nice error message */ + errorat(lx->pos, "ignoring specifier"); + *spec = Sbad; + return 1; +} + +/* + * name declaration + * see dtor for valid values of ab + */ +static +error +name(Parser *p, Lexer *lx, Name **nmp, int ab) +{ + Token t; + int n, k; + uint64 s; + Sym *sym; + Name *nm, *tmp; + + /* max args = 100 */ + struct Field args[100]; + + nm = *nmp; + t = peek(p, 0); + switch (k = t.kind) { + case Aident: + if (ab == 1) { + errorat(t.pos, "identifier not allowed in abstract declarator"); + goto Bad; + } + nm->kind = Nident; + nm->ident = t.val.s; + break; + + case Alparen: + advance(p, lx); + nm->kind = Nparen; + nm->paren = getdtor(p); + if (dtor(p, lx, nm->paren, ab)) { + putdtor(p, nm->paren); + nm->paren = nil; + errorat(lx->pos, "invalid declarator in parenthesis"); + goto Bad; + } + + t = peek(p, 0); + if (nomatch(t, Arparen)) { + putdtor(p, nm->paren); + nm->paren = nil; + errorat(lx->pos, "missing closing paren in declarator"); + goto Bad; + } + break; + + case Albrakt: + if (ab) + goto Sfx; + errorat(lx->pos, "missing identifier in non-abstract declarator"); + /* fallthrough */ + default: + if (ab) + goto Sfx; + errorat(lx->pos, "invalid token '%s' in name declaration", tokens[k]); + goto Bad; + } + + t = advance(p, lx); +Sfx: + for (;;) { + switch (k = t.kind) { + case Albrakt: + tmp = getname(p); + tmp->kind = Nindex; + tmp->sfx.name = nm; + + nm = tmp, tmp = nil; + + t = advance(p, lx); + if (t.kind == Arbrakt) { + nm->sfx.idx.q = 0; + Iend: + nm->sfx.idx.x = nil; + t = advance(p, lx); + break; + } + if (t.kind == Astar) { + nm->sfx.idx.q = -1; + IStar: + nm->sfx.idx.x = nil; + t = advance(p, lx); + if (t.kind != Arbrakt) { + errorat(t.pos, "invalid '*' syntax in index expression"); + goto Bad; + } + t = advance(p, lx); + break; + } + + if (spec(p, lx, &s)) { + errorat(lx->pos, "invalid type qualifier list in index expression"); + goto Bad; + } + + nm->sfx.idx.q = (uint32)s; + t = peek(p, 0); + + if (t.kind == Astar) + goto IStar; + + if (t.kind == Arbrakt) + goto Iend; + + nm->sfx.idx.x = expr(p, lx); + + t = peek(p, 0); + if (nomatch(t, Arbrakt)) { + errorat(t.pos, "unterminated index expression"); + goto Bad; + } + + t = advance(p, lx); + continue; + + case Alparen: + tmp = getname(p); + tmp->kind = Ncall; + tmp->sfx.name = nm; + + nm = tmp, tmp = nil; + + t = advance(p, lx); + nm->sfx.call.n = 0; + switch (t.kind) { + case Arparen: + nm->sfx.call.arg = nil; + break; + + case Aident: + sym = lookupobj(p, t.val.s); + if (!sym || (sym && sym->kind != Stype)) { + while (t.kind == Aident) { + if (nm->sfx.call.n >= arrlen(args)) + panicf("ident stack overflow"); + args[nm->sfx.call.n++] = (struct Field) { + .qual = 0, + .type = 0, + .name = t.val.s, + }; + t = advance(p, lx); + } + if (nomatch(t, Arparen)) { + errorat(t.pos, "token '%s' found in function parameter identifier list"); + goto Bad; + } + copyarray(nm->sfx.call.arg, args, nm->sfx.call.n); + break; + } + goto ParamLoop; + + case Akeywd: + if (t.val.i < Kconst || t.val.i > Kenum) { + errorat(t.pos, "invalid keyword %s inside function signature"); + goto Bad; + } + + ParamLoop: + if (nm->sfx.call.n >= arrlen(args)-1) + panicf("out of argument buffer"); + + args[nm->sfx.call.n].type = basetype(p, lx, &s); + if (!args[nm->sfx.call.n].type) { + errorat(lx->pos, "could not parse base type in function call"); + goto Bad; + } + + args[nm->sfx.call.n].qual = (uint32)s & ~(MaskTyp|MaskInt|MaskFlt); + args[nm->sfx.call.n].name = namedecl(p, lx, &args[nm->sfx.call.n].type, 2); + + nm->sfx.call.n++; + if ((t = peek(p, 0)).kind == Acomma) { + advance(p, lx); + goto ParamLoop; + } + + if (t.kind == Aellip) { + nm->sfx.call.dots = 1; + t = advance(p, lx); + } + + if (nomatch(t, Arparen)) { + errorat(t.pos, "token '%s' found in function parameter list"); + goto Bad; + } + copyarray(nm->sfx.call.arg, args, nm->sfx.call.n); + break; + + default: + errorat(t.pos, "invalid token %s inside function call signature", tokens[t.kind]); + goto Bad; + } + + t = advance(p, lx); + continue; + + default: + break; + } + break; + } + + *nmp = nm; + return 0; +Bad: + return 1; +} + +/* pointer kind is partitioned into 8x6 regions + * ab => abstract + * @ 0: must have identifier + * @ 1: must not have identifier + * @ 2: don't care + * else: undefined + */ +static +error +dtor(Parser *p, Lexer *lx, Dtor *d, int ab) +{ + int n, k; + error err; + Token t; + Dtor *link; + Ptr *ptr, *x; + + err = 1; + + ptr = &d->ptr; + ptr->kind = 0; + ptr->link = nil; + + t = peek(p, 0); + if (t.kind != Astar) { + if (ab || t.kind == Aident || t.kind == Arparen) + goto Name; + goto Bad; + } + n = 0; +Ptr: + ptr->kind |= Bit(n); + advance(p, lx); +Key: + t = peek(p, 0); + switch (k = t.kind) { + case Akeywd: + if (Kconst <= t.val.i && t.val.i <= Katomic) + ptr->kind |= Bit(6*n + (t.val.i - Kconst + 1)); + else { + errorat(lx->pos, "invalid keyword '%s' modifies pointer", keywords[t.val.i]); + goto Bad; + } + advance(p, lx); + goto Key; + + case Astar: + if (++n >= 8) { + x = getptr(p); + x->kind = 0; + x->link = nil; + ptr->link = x; + ptr = x; + n = 0; + } + goto Ptr; + + case Aident: + case Alparen: + goto Name; + + default: + if (ab) + goto Name; + errorat(lx->pos, "invalid token '%s' modifies pointer specification", tokens[t.kind]); + goto Bad; + } +Name: + return name(p, lx, &d->name, ab); +Bad: + return err; +} + +static +Decl * +decl(Parser *p, Lexer *lx) +{ + uint64 s; + Token t; + Decl *d; + Expr *x; + string name; + struct Decls *ds; + uint32 base, type; + + alloc(d); + + d->kind = 0; + d->pos.beg = lx->pos; + + base = basetype(p, lx, &s); + if (!base) { + errorat(lx->pos, "could not parse type declaration"); + goto Bad; + } + + x = nil; + d->spec = (uint32)s & ~(MaskInt|MaskFlt|MaskTyp); + d->type = base; + d->name = namedecl(p, lx, &d->type, 0); + /* TODO: think about functions (both decls and defs) */ + d->kind = (s & Mtype) ? Dtype : Dvar; + + switch (t = peek(p, 0), t.kind) { + case Aeq: + if (s & Mtype) { + errorat(d->pos.beg, "initialization of type not allowed"); + goto Bad; + } + t = advance(p, lx); + x = initx(p, lx); + d->kind = Dvar; + if (t.kind != Acomma) { + d->init = x; + goto Semi; + } + /* fallthrough */ + case Acomma: + d->kind |= Dlist; + d->list.init = x; + /* move singleton data over */ + name = d->name; + type = d->type; + d->list.name = name; + d->list.type = type; + ds = &d->list; + /* iterate until we hit end of list */ + while (t.kind == Acomma) { + t = advance(p, lx); + + alloc(ds->link); + ds = ds->link; + ds->type = base; + ds->name = namedecl(p, lx, &ds->type, 0); + + t = peek(p, 0); + if (t.kind == Aeq) { + t = advance(p, lx); + ds->init = initx(p, lx); + } else + ds->init = nil; + } + goto Semi; + + case Albrace: + d->kind = Dfunc; + alloc(d->body); + + if (!attop(p)) { + errorat(lx->pos, "nested function declarations are illegal"); + goto Bad; + } + + if (C.type.info[d->type].kind != Tfunc) { + errorat(lx->pos, "attempted to define function body for non function type"); + goto Bad; + } + + openscope(p); + if (blkstmt(p, lx, &d->body)) { + errorat(lx->pos, "failed to parse function body"); + goto Bad; + } + closescope(p); + break; + + default: + Semi: + if (nomatch(t, Asemi)) { + errorat(t.pos, "no semicolon after declaration"); + goto Bad; + } + t = advance(p, lx); + } + + d->pos.end = lx->pos; + declareobj(p, d); + return d; +Bad: + errorat(lx->pos, "failed to parse top level declaration"); + return nil; +} + +// ----------------------------------------------------------------------- +// top level api + +void +setup(Parser *p, Lexer *lx) +{ + advance(p,lx); + advance(p,lx); + + /* define all builtin typedefs */ + declareobj(p, &C.builtin.vargs); +} + +error +parse(Parser *p, Lexer *lx) +{ + Token tok; + + setup(p, lx); + while ((tok = peek(p, 0)), tok.kind > Aeof) { + if (p->ast.len >= p->ast.cap) { + p->ast.cap += 20; + p->ast.decls = realloc(p->ast.decls, p->ast.cap*sizeof(*p->ast.decls)); + } + p->ast.decls[p->ast.len++] = decl(p, lx); + } + + return 0; +} diff --git a/src/cmd/cc/bits.c b/src/cmd/cc/bits.c new file mode 100644 index 0000000..4b405dc --- /dev/null +++ b/src/cmd/cc/bits.c @@ -0,0 +1,114 @@ +#include "cc.h" + +// ----------------------------------------------------------------------- +// Architecture + +enum +{ + archx64, + numarch, +}; + +// ----------------------------------------------------------------------- +// Types + +/* + * enumerated type specifers + * see https://en.wikipedia.org/wiki/C_data_types + */ +#define VOID X(Tvoid, 2) + +#define BOOL X(Tbool, 3) +#define CHAR X(Tchar, 4) +#define SCHAR X(Tsign|Tchar, 5) +#define UCHAR X(Tunsign|Tchar, 6) + +#define SHORT X(Tshort, 7), X(Tshort|Tint, 7) +#define SSHORT X(Tsign|Tshort, 8), X(Tsign|Tshort|Tint, 8) +#define USHORT X(Tunsign|Tshort, 9), X(Tunsign|Tshort|Tint, 9) + +#define INT X(0, 10), X(Tint, 10) +#define SINT X(Tsign, 11), X(Tsign|Tint, 11) +#define UINT X(Tunsign, 12), X(Tunsign|Tint, 12) + +#define LONG X(Tlong, 13), X(Tlong|Tint, 13) +#define SLONG X(Tsign|Tlong, 14), X(Tsign|Tlong|Tint, 14) +#define ULONG X(Tunsign|Tlong, 15), X(Tunsign|Tlong|Tint, 15) + +#define VLONG X(Tvlong, 16), X(Tvlong|Tint, 16) +#define SVLONG X(Tsign|Tvlong, 17), X(Tsign|Tvlong|Tint, 17) +#define UVLONG X(Tunsign|Tvlong, 18), X(Tunsign|Tvlong|Tint, 18) + +#define FLOAT X(Tfloat, 19) +#define DOUBLE X(Tdouble, 20) +#define LONGDB X(Tlong|Tdouble, 21) +#define COMPLEX X(Tcmplx, 22) +#define IMAGINARY X(Timag, 23) + +/* fixed width definitions */ +#define DEF(sz, aln, mx, sgn) {.size=sz, .align=aln, .max=mx, .sign=sgn } + +#define INT8 DEF(1, 1, 0x7fff, 0) +#define UINT8 DEF(1, 1, 0xffff, 1) + +#define INT16 DEF(2, 2, 0x7fff, 0) +#define UINT16 DEF(2, 2, 0xffff, 1) + +#define INT32 DEF(4, 4, 0x7fffffff, 0) +#define UINT32 DEF(4, 4, 0xffffffff, 1) + +#define INT64 DEF(8, 8, 0x7fffffffffffffff, 0) +#define UINT64 DEF(8, 8, 0xffffffffffffffff, 1) + +/* architecture specific definitions */ +// TODO: max value should be able to take floats +#define TYPES \ + TYPE(DEF(0, 0, 0, 0), VOID) \ + TYPE(INT8, BOOL) \ + TYPE(UINT8, CHAR) \ + TYPE(INT8, SCHAR) \ + TYPE(UINT8, UCHAR) \ + TYPE(INT16, SHORT) \ + TYPE(INT16, SSHORT) \ + TYPE(UINT16, USHORT) \ + TYPE(INT32, INT) \ + TYPE(INT32, SINT) \ + TYPE(UINT32, UINT) \ + TYPE(INT64, LONG) \ + TYPE(INT64, SLONG) \ + TYPE(UINT64, ULONG) \ + TYPE(INT64, VLONG) \ + TYPE(INT64, SVLONG) \ + TYPE(UINT64, UVLONG) \ + TYPE(DEF(4, 4, 0, 0), FLOAT) \ + TYPE(DEF(8, 8, 0, 0), DOUBLE) \ + TYPE(DEF(16, 16, 0, 0), LONGDB) \ + TYPE(DEF(8, 8, 0, 0), COMPLEX) \ + TYPE(DEF(4, 4, 0, 0), IMAGINARY) \ + +Type pointer = {.size=8, .align=8, .max=0xffffffffffffffff, .sign=0}; + +/* pack architecture specific definitions into exported arrays */ +#define TYPE(a, ...) a, +Type basetypes[] = { + { 0 }, /* sentinel value for bad types */ + { 0 }, /* sentinel value for variadic args */ + TYPES +}; +#undef TYPE + +#define TYPE(a, ...) __VA_ARGS__, +#define X(a, b) a +uint64 validtypespec[38] = { + TYPES + Tstruct, Tunion, Tenum, Tname, +}; +#undef X + +#define X(a, b) b +int indextypespec[38] = { + TYPES + -1, -1, -1, -1, +}; +#undef X +#undef TYPE diff --git a/src/cmd/cc/cc.c b/src/cmd/cc/cc.c new file mode 100644 index 0000000..8ad0022 --- /dev/null +++ b/src/cmd/cc/cc.c @@ -0,0 +1,409 @@ +#include "cc.h" +#include + +// ----------------------------------------------------------------------- +// string interning + +/* jenkins' one at a time hash */ +static +int32 +hash_string(byte* s) +{ + int32 h; + + h = 0; + if (s != nil) { + for (; *s; ++s) { + h += *s; + h = (h << 10); + h = (h >> 6); + } + } + + h += (h << 3); + h ^= (h >> 11); + h += (h >> 11); + + return h; +} + +static +int +streq(byte *s, byte *t) +{ + if (s == nil) { + if (t == nil) + return 1; + else + return 0; + } + + return (t == nil) ? 0 : strcmp(s, t) == 0; +} + +#define HASH(s) hash_string(s) +#define EQUAL(s, t) (streq(s, t)) +static +int +getstr(string key, int *ok) +{ + int idx; + MAP_GET(idx, (&C.strs), key, HASH, EQUAL); + + *ok = idx < C.strs.n_buckets; + return idx; +} + +static +void +·free(void* _, void* ptr) { + return free(ptr); +} + +static +void * +·alloc(void* _, uint n, ulong size) { + return malloc(n*size); +} + +static +void * +·calloc(void* _, uint n, ulong size) { + return calloc(n, size); +} + +static +int +morestrtab(StrTab *tab, int n) +{ + MAP_GROW(tab, string, int32, n, HASH, ·calloc, ·free, nil); +} + +static +int +putstr(byte *s, error *err) +{ + int sz; + sz = C.strs.size; + MAP_PUT((&C.strs), s, sz, HASH, EQUAL, morestrtab, err); +} +#undef HASH +#undef EQUAL + +int32 +intern(byte **s) +{ + int i, ok; + + i = getstr(*s, &ok); + if (ok) { + *s = C.strs.keys[i]; + goto END; + } + + *s = str·make(*s); + i = putstr(*s, &ok); + C.strs.vals[i] = C.strs.size - 1; + +END: + return C.strs.vals[i]; +} + +// ----------------------------------------------------------------------- +// type interning + +/* TODO: intern types for memory savings */ +int +type() +{ + if (C.type.len >= C.type.cap) { + C.type.cap += 100; + C.type.info = realloc(C.type.info, C.type.cap * sizeof(*C.type.info)); + } + + return C.type.len++; +} + +// ----------------------------------------------------------------------- +// universal compiler builtins + +#define KEYWORD(a, b) b, +byte *keywords[NUM_KEYWORDS] = { KEYWORDS }; +#undef KEYWORD + +#define DIRECTIVE(a, b, c) b, +byte *directives[NUM_DIRECTIVES] = { DIRECTIVES }; +#undef DIRECTIVE + +struct Compiler C = { 0 }; + +// ----------------------------------------------------------------------- +// cli flag handlers + +void +pushinclude(byte *dirs) +{ + string d, s, *it, *end; + + while (*dirs != '\0') { + d = strchr(dirs, ' '); + if (d != nil) + *d = '\0'; + + s = dirs; + intern(&s); + for (it = C.inc.dir, end = it + C.inc.len; it != end; ++it) { + if ((uintptr)s == (uintptr)(*it)) + goto Nextdir; + } + + if (C.inc.len == C.inc.cap) { + C.inc.cap += 20; + C.inc.dir = realloc(C.inc.dir, C.inc.cap*sizeof(*C.inc.dir)); + } + C.inc.dir[C.inc.len++] = s; +Nextdir: + if (d == nil) + break; + dirs = d + 1; + } +} + +// ----------------------------------------------------------------------- +// error reporting + +void +errorat(Pos x, byte *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + printf("error:%s:%d:%d: ", os·basename(x.path), x.line, x.col); + vprintf(fmt, args); + printf("\n"); + + va_end(args); + assert(0); +} + +void +warnat(Pos x, byte *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + printf("warning:%s:%d:%d: ", os·basename(x.path), x.line, x.col); + vprintf(fmt, args); + printf("\n"); + + va_end(args); +} + +// ----------------------------------------------------------------------- +// main point of entry + +void +init(void) +{ + int i; + + for (i = 0; i < arrlen(keywords); i++) + intern(&keywords[i]); + + for (i = 0; i < arrlen(directives); i++) + intern(&directives[i]); + + C.heap = mem·makearena(mem·sys, nil); + + /* compiler definitions */ + C.def.len = 0; + C.def.cap = 100; + C.def.val = calloc(C.def.cap, sizeof(*C.def.val)); + + /* compiler include paths */ + C.inc.len = 0; + C.inc.cap = 100; + C.inc.dir = calloc(C.inc.cap, sizeof(*C.inc.dir)); + C.inc.dir[C.inc.len++] = "."; + + C.outfile = nil; + + /* type info */ + C.type.len = arrlen(basetypes); + C.type.cap = 100 + arrlen(basetypes); + C.type.info = calloc(C.type.cap, sizeof(*C.type.info)); + + memcpy(C.type.info, basetypes, C.type.len * sizeof(*C.type.info)); + + /* builtins */ + C.builtin.vargs = (Decl) { + .pos = (Range) { + .beg = { + .col = 0, + .line = 0, + .path = "", + }, + .end = { + .col = 0, + .line = 0, + .path = "", + }, + }, + .kind = Dtype, + .spec = Mtype, + .type = 1, + .name = "__builtin_va_list", + }; + + intern(&C.builtin.vargs.name); +} + +void +initlx(Lexer *lx) +{ + int i; + + memset(lx, 0, sizeof(*lx)); + lx->b = lx->buf; + + /* predefine macros */ + dodefine(lx, "__LINE__"); + dodefine(lx, "__FILE__"); + lx->macline = (uintptr)lookup(&lx->sym, "__LINE__"); + lx->macfile = (uintptr)lookup(&lx->sym, "__FILE__"); + + for (i = 0; i < C.def.len; i++) + dodefine(lx, C.def.val[i]); + + lx->omit.len = 0; + lx->omit.cap = 100; + lx->omit.path = calloc(lx->omit.cap, sizeof(*C.inc.dir)); + + lx->new = lx->iostk; + lx->new->link = nil; + memset(lx->iostk, 0, sizeof(lx->iostk)); + + lx->sym = (SymTab){ 0 }; +} + +void +freelx(Lexer *lx) +{ + free(lx->omit.path); +} + +void +initp(Parser *p) +{ + /* initialize temporary buffers */ + memset(p->spstk, 0, sizeof(p->spstk)); + memset(p->nmstk, 0, sizeof(p->nmstk)); + memset(p->dtstk, 0, sizeof(p->dtstk)); + memset(p->ptstk, 0, sizeof(p->ptstk)); + + p->sp = p->spstk; + p->nm = p->nmstk; + p->dt = p->dtstk; + p->pt = p->ptstk; + + /* initialize ast */ + p->ast.cap = 0; + p->ast.len = 0; + p->ast.decls = nil; +} + +error +compile(byte *path) +{ + Lexer lx; + Parser p; + error err; + byte *sep, out[400]; + + intern(&path); + strcpy(out, path); + + sep = utf8·findrrune(out, '/'); + if (sep) + *sep++ = '\0'; + else + sep = out; + + if (!C.outfile) { + C.outfile = sep; + if (C.outfile) { + if ((sep = utf8·findrrune(C.outfile, '.'))) { + sep[0] = '.'; + sep[1] = 'o'; + sep[2] = '\0'; + } + } else { + C.outfile = "/dev/null"; + } + } + + initlx(&lx); + initp(&p); + + lx.io = openio(&lx, path); + lx.pos = (Pos){ + .path = path, + .line = 1, + .col = 1, + }; + + err = parse(&p, &lx); + freelx(&lx); + return err; +} + +error +main(int argc, byte *argv[]) +{ + byte *a, *src; + int err; + + init(); + + ARGBEGIN { + case 'o': + C.outfile = ARGF(); + break; + + case 'D': + a = ARGF(); + if (a) { + intern(&a); + if (C.def.len >= C.def.cap) { + C.def.cap += 20; + C.def.val = realloc(C.def.val, C.def.cap * sizeof(*C.def.val)); + } + C.def.val[C.def.len++] = a; + } + break; + + case 'I': + a = ARGF(); + if (a) + pushinclude(a); + break; + } ARGEND + + if (argc < 1 && C.outfile == nil) { + printf("usage: cc [-options] files\n"); + exit(1); + } + + // NOTE: This is just for my comfort during debugging. + pushinclude("/home/nolln/root/include"); + pushinclude("/home/nolln/root/include/vendor/libc"); + + src = (argc == 0) ? "" : argv[0]; + intern(&src); + + if ((err = compile(src)), err) { + exit(2); + } + + exit(0); +} diff --git a/src/cmd/cc/cc.h b/src/cmd/cc/cc.h new file mode 100644 index 0000000..8fc5f73 --- /dev/null +++ b/src/cmd/cc/cc.h @@ -0,0 +1,806 @@ +#pragma once + +#include +#include + +#define iota(x) 1 << (x) + +/* core types */ +typedef struct Io Io; +typedef struct Pos Pos; +typedef struct Range Range; +typedef struct Token Token; + +typedef struct Lexer Lexer; + +typedef struct Sym Sym; +typedef struct Type Type; +typedef struct Scope Scope; + +typedef struct Parser Parser; + +typedef struct Ptr Ptr; +typedef struct Name Name; +typedef struct Dtor Dtor; +typedef struct Field Field; + +typedef struct Node Node; +typedef struct Decl Decl; +typedef struct Stmt Stmt; +typedef struct Expr Expr; + +typedef struct SymTab SymTab; +typedef struct StrTab StrTab; + +typedef struct Compiler Compiler; + +/* keywords of language */ +#define KEYWORDS \ + KEYWORD(Kauto,"auto") \ + KEYWORD(Kregister,"register") \ + KEYWORD(Kstatic,"static") \ + KEYWORD(Kextern,"extern") \ + KEYWORD(Ktls,"thread_local") \ + KEYWORD(Ktypedef,"typedef") \ + KEYWORD(Kinline,"inline") \ + KEYWORD(Knoret,"_Noreturn") \ + KEYWORD(Kconst,"const") \ + KEYWORD(Kvolatile,"volatile") \ + KEYWORD(Krestrict,"restrict") \ + KEYWORD(Katomic,"_Atomic") \ + KEYWORD(Ksigned,"signed") \ + KEYWORD(Kunsigned,"unsigned") \ + KEYWORD(Kvoid,"void") \ + KEYWORD(Kbool,"_Bool") \ + KEYWORD(Kchar,"char") \ + KEYWORD(Kfloat,"float") \ + KEYWORD(Kdouble,"double") \ + KEYWORD(Kcomplex,"complex") \ + KEYWORD(Kimaginary,"imaginary") \ + KEYWORD(Kint,"int") \ + KEYWORD(Kshort,"short") \ + KEYWORD(Klong,"long") \ + KEYWORD(Kstruct,"struct") \ + KEYWORD(Kunion,"union") \ + KEYWORD(Kenum,"enum") \ + KEYWORD(Kfor,"for") \ + KEYWORD(Kdo,"do") \ + KEYWORD(Kwhile,"while") \ + KEYWORD(Kcontinue,"continue") \ + KEYWORD(Kif,"if") \ + KEYWORD(Kelse,"else") \ + KEYWORD(Kswitch,"switch") \ + KEYWORD(Kcase,"case") \ + KEYWORD(Kdefault,"default") \ + KEYWORD(Kbreak,"break") \ + KEYWORD(Kgoto,"goto") \ + KEYWORD(Kreturn,"return") \ + KEYWORD(Ksizeof,"sizeof") \ + KEYWORD(Kalignof,"alignof") \ + KEYWORD(Kalignas,"alignas") + +#define KEYWORD(a, b) a, +enum { KEYWORDS NUM_KEYWORDS }; +#undef KEYWORD + +extern byte *keywords[NUM_KEYWORDS]; + +// ----------------------------------------------------------------------- +// lexing: byte stream -> tokens +// pre-processor built in + +/* source position: error reporting */ +struct Pos +{ + int col; + int line; + string path; +}; + + +struct Range +{ + Pos beg; + Pos end; +}; + +void errorat(Pos x, byte *fmt, ...); +void warnat(Pos x, byte *fmt, ...); + +/* pre-processor */ +#define DIRECTIVES \ + DIRECTIVE(Dpragma,"pragma", ppprag) \ + DIRECTIVE(Dinclude,"include", ppinc) \ + DIRECTIVE(Ddefine,"define", ppdef) \ + DIRECTIVE(Dundef,"undef", ppund) \ + DIRECTIVE(Dif,"if", ppif0) \ + DIRECTIVE(Delif,"elif", ppif1) \ + DIRECTIVE(Delse, "else", ppif1) \ + DIRECTIVE(Difdef,"ifdef", ppif2) \ + DIRECTIVE(Difndef,"ifndef", ppif3) \ + DIRECTIVE(Dendif,"endif", ppend) + +#define DIRECTIVE(a, b, c) a, +enum { DIRECTIVES NUM_DIRECTIVES }; +#undef DIRECTIVE + +extern byte *directives[NUM_DIRECTIVES]; + +error domacro(Lexer*); +error dodefine(Lexer *lx, string s); +int expandmacro(Lexer *lx, Sym *s, byte *dst); + +extern error (*macros[NUM_DIRECTIVES])(Lexer*); + +/* tokenization of byte stream */ +#define TOKENS \ + TOK(Anil,"nil") \ + TOK(Aeof,"eof") \ + TOK(Aeq, "==") \ + TOK(Aneq, "!=") \ + TOK(Anot, "!") \ + TOK(Aneg, "~") \ + TOK(Axor, "^") \ + TOK(Aor, "|") \ + TOK(Aand, "&") \ + TOK(Aoror, "||") \ + TOK(Aandand, "&&") \ + TOK(Aadd,"+") \ + TOK(Asub,"-") \ + TOK(Astar,"*") \ + TOK(Adiv,"/") \ + TOK(Amod,"%") \ + TOK(Agt,">") \ + TOK(Alt,"<") \ + TOK(Agteq,">=") \ + TOK(Alteq,"<=") \ + TOK(Alsft,"<<") \ + TOK(Arsft,">>") \ + TOK(Ainc,"++") \ + TOK(Adec,"--") \ + TOK(Aasn,"=") \ + TOK(Aorasn,"|=") \ + TOK(Axorasn,"^=") \ + TOK(Aandasn,"&=") \ + TOK(Aaddasn,"+=") \ + TOK(Asubasn,"-=") \ + TOK(Amulasn,"*=") \ + TOK(Adivasn,"/=") \ + TOK(Amodasn,"%=") \ + TOK(Alsftasn,"<<=") \ + TOK(Arsftasn,">>=") \ + TOK(Acomma,",") \ + TOK(Acolon,":") \ + TOK(Asemi,";") \ + TOK(Alparen,"(") \ + TOK(Arparen,")") \ + TOK(Albrace,"{") \ + TOK(Arbrace,"}") \ + TOK(Albrakt,"[") \ + TOK(Arbrakt,"]") \ + TOK(Adot,".") \ + TOK(Aarrow,"->") \ + TOK(Aqmark,"?") \ + TOK(Aellip,"...") \ + TOK(Alit,"") \ + TOK(Aident,"") \ + TOK(Akeywd,"") \ + +#define TOK(a, b) a, +enum +{ + TOKENS + NUM_TOKENS, + + Vchar = iota(8), + Vrune = iota(9), + Vint = iota(10), + Vlong = iota(11), + Vvlong = iota(12), + Vun = iota(13), + Vfloat = iota(14), + Vstr = iota(15), + Vwstr = iota(16), + + Vmask = Vchar - 1, +}; +#undef TOK + +extern byte *tokens[NUM_TOKENS]; + +/* TODO: store literals in a big val */ +union Val +{ + byte *s; + double f; + vlong i; + uvlong ui; + int32 c; + uint32 uc; + rune r; +}; + +struct Token +{ + uint32 kind; + Pos pos; + union Val val; +}; + +enum +{ + Svar = iota(1), + Sfunc = iota(2), + Stype = iota(3), + Stag = iota(4), + Senum = iota(5), + Slabl = iota(6), + Smacro = iota(7), +}; + +struct Sym +{ + uint32 kind; + string name; + union { + string macro; + Decl *obj; + int32 type; + Stmt *blk; + Expr *val; + }; +}; + +struct SymTab +{ + int32 n_buckets; + int32 size; + int32 n_occupied; + int32 upper_bound; + int32 *flags; + string *keys; + Sym **vals; +}; + +Sym *define(SymTab *tab, string ident, uint32 kind); +Sym *lookup(SymTab *tab, string ident); +error forget(SymTab *tab, string ident); +void forgetall(SymTab *tab); + +enum +{ + IOnil = iota(0), + IOfile = iota(1), + IObuff = iota(2), +}; + +struct Io +{ + io·Buffer rdr; + string path; + uint32 kind; + union { + Stream *f; + byte *b; + }; + + Pos store; + struct Io *link; +}; + +struct Lexer +{ + Pos pos; + SymTab sym; + byte *b; + byte buf[2*1024]; + + /* predefined dynamic macros */ + uintptr macfile; + uintptr macline; + + /* i/o data */ + Io *io, *new; + Io iostk[100]; + struct { + int cap; + int len; + string *path; + } omit; +}; + +/* lex.c functions */ +Token lex(Lexer *); + +int getbyte(Lexer *); +int getnsbyte(Lexer *l); +rune getrune(Lexer *); +byte ungetbyte(Lexer *); +rune ungetrune(Lexer *, rune r); + +Io* openio(Lexer *lx, byte *path); +void pushio(Lexer *lx, Io *new); +void popio(Lexer *lx); + +void puttok(Token); + +// ----------------------------------------------------------------------- +// parsing & type resolution +// tokens -> ast + +/* parent data */ +struct Node +{ + Range pos; + uint32 kind; +}; + +/* ast types */ +enum +{ + Nbad, + /* labels */ + Sempty, Slabel, Scase, + Sblock, + Sexpr, Sdecl, + Sselect, + /* loops */ + Sfor, Swhile, Sdo, + /* jumps */ + Sgoto, Scontin, Sbreak, Sreturn, + /* forks */ + Sif, Sswitch, + + + /* assignments */ + Xasn, Xmulasn, Xdivasn, Xmodasn, Xsubasn, Xaddasn, + Xlsftasn, Xrsftasn, Xandasn, Xxorasn, Xorasn, + /* conditional */ + Xternary, + /* unary prefix ops */ + Xref, Xstar, Xplus, Xminus, Xneg, Xnot, Xsizeof, Xalignof, Xpreinc, Xpredec, + Xcast, + /* unary postfix ops */ + Xpostinc, Xpostdec, Xindex, Xcall, Xselp, Xself, Xinitlist, + /* binary ops */ + Xoror, Xandand, Xor, Xxor, Xand, Xneq, Xeql, Xgt, Xlt, Xgteq, Xlteq, Xlsft, Xrsft, + Xadd, Xsub, Xmul, Xdiv, Xmod, + /* primary */ + Xparen, Xident, Xlit, + /* lists */ + Xcomma, + + + Dvar, + Dfunc, + Dtype, + Dlist = iota(20), + Dvars = Dvar | Dlist, + Dtypes = Dtype | Dlist, + + /* names (don't interact w/ final AST) */ + Nnil = 0, + Nident, + Nparen, + Nindex, + Ncall, +}; + +/* expressions */ +enum +{ + Keynil, + Keyidx, + Keysel, +}; + +struct Key +{ + uint kind : 2; + union { + Expr *x; + string s; + }; +}; + +struct Expr +{ + struct Node; + uint32 qual; + uint32 type; + union { + string name; + struct { + uint64 kind; + union { + union Val; + union Val v; + }; + } val; + struct { + int n; + struct Key *k; + Expr *v; + } init; + Expr *x; + struct { + Expr *l; + Expr *r; + } asn; + struct { + Expr *c; + Expr *t; + Expr *e; + } cond; + struct { + Expr *x; + union { + Expr *i; + string f; + }; + } idx; + struct { + Expr *fn; + int n; + Expr **arg; + } call; + union { + Expr *pre; + Expr *post; + } unary; + struct { + int type : 1; + union { + struct { + uint32 qual; + uint32 type; + } of; + Expr *x; + }; + } info; + struct { + struct { + uint32 qual; + uint32 type; + } to; + Expr *x; + } cast; + struct { + Expr *l; + Expr *r; + } binary; + struct { + Expr *x[2]; + } comma; + }; +}; + + +/* statements */ +struct Stmt +{ + struct Node; + union { + struct { + union { + string ident; + Expr *x; + }; + Node *stmt; + } lbl; + struct { + long n; + struct Node **item; + } blk; + Expr *x; + struct { + Node *init; + Expr *cond; + Expr *step; + Node *body; + } loop; + union{ + string lbl; + Expr *x; + } jmp; + struct { + Expr *cond; + Node *body; + Node *orelse; + } br; + }; +}; + +/* declarations */ + +/* + * specifiers + * the design is the following: + * type info is held w/in a 64 bit integer. + * the bottom 32 bits are associated to specializations + * the top 32 bits index into a type-info array held by the compiler. + */ +enum +{ + /* memory */ + Mauto = iota(Kauto), + Mstatic = iota(Kstatic), + Mreg = iota(Kregister), + Mtls = iota(Ktls), + Mtype = iota(Ktypedef), + Mextern = iota(Kextern), + + MaskMem = Mauto | Mstatic | Mreg | Mtls | Mtype | Mextern, + + /* qualifiers */ + Qconst = iota(Kconst), + Qrestr = iota(Krestrict), + Qvoltl = iota(Kvolatile), + Qatom = iota(Katomic), + + MaskQul = Qconst | Qrestr | Qvoltl | Qatom, + + Finlne = iota(Kinline), + Fnoret = iota(Knoret), + + MaskFcn = Finlne | Fnoret, + + /* types */ + Tsign = iota(Ksigned), + Tunsign = iota(Kunsigned), + + MaskSgn = Tsign | Tunsign, + + Tvoid = iota(Kvoid), + Tfloat = iota(Kfloat), + Tdouble = iota(Kdouble), + Tcmplx = iota(Kcomplex), + Timag = iota(Kimaginary), + + MaskFlt = Tfloat | Tdouble | Tcmplx | Timag, + + Tchar = iota(Kchar), + Tbool = iota(Kbool), + + Tshort = iota(Kshort), + Tint = iota(Kint), + Tlong = iota(Klong), + Tvlong = iota(Klong+1), + + MaskInt = Tshort | Tint | Tlong | Tvlong, + MaskTyp = Tvoid | Tbool | Tchar | Tint | Tfloat | Timag | Tcmplx, + /* + * NOTE IMPORTANT: vlong takes over the struct bit place + * DON'T MOVE KEYWORDS WITHOUT REORGANIZING + */ + Tstruct = iota(Kstruct+1), + Tunion = iota(Kunion+1), + Tenum = iota(Kenum+1), + Tname = iota(Kenum+2), + + Sbad = -1, +}; + +/* intermediate nodes */ +struct Ptr +{ + uint64 kind; + Ptr *link; +}; + +struct Name +{ + uint32 kind; + union { + string ident; + struct Dtor *paren; + struct { + Name *name; + union { + struct { + uint32 q; + Expr *x; + } idx; + struct { + int n; + int dots : 1; + Field *arg; + } call; + }; + } sfx; + }; +}; + +struct Dtor +{ + Ptr ptr; + Name *name; +}; + +/* final ast node */ + +struct Field +{ + uint32 qual; + uint32 type; + string name; +}; + +struct Decls +{ + string name; + uint32 type; + Expr *init; + struct Decls *link; +}; + + +struct Decl +{ + struct Node; + uint32 spec; + union { + struct { + string name; + uint32 type; + union { + Stmt *body; + Expr *init; + }; + }; + struct Decls list; + }; +}; + +enum +{ + Tbad, + Tbase, + Tdef, + Tptr, + Tarray, + Tfunc, +}; + +/* types */ +struct Type +{ + uint32 kind; + Sym *sym; + uintptr size; + uintptr max; + uint16 align : 8; + uint8 sign : 2; + union { + struct { + uint32 qual; + uint32 base; + } ptr; + struct { + int len; + uint32 qual; + uint32 *elt; + } arr; + struct { + int len; + Field *f; + Expr *x; + } aggr; + struct { + int len; + string *elt; + Expr *val; + } enm; + struct { + uint32 ret; + int n; + int dots : 1; + Field *arg; + } func; + }; +}; + +/* platform specific */ +extern Type pointer; +extern Type basetypes[24]; +/* mandated by C standard */ +extern uint64 validtypespec[38]; +extern int indextypespec[38]; + +struct Scope +{ + SymTab tags; + SymTab objs; +}; + +struct Parser +{ + Token tok[2]; + struct { + int cap; + int len; + Decl **decls; + } ast; + + /* static buffers/stacks */ + Scope *sp; + Scope spstk[40]; + + Name *nm; + Name nmstk[40]; + + Ptr *pt; + Ptr ptstk[10]; + + Dtor *dt; + Dtor dtstk[40]; +}; + +/* ast.c functions */ +error parse(Parser *, Lexer *); + +// ----------------------------------------------------------------------- +// global compiler data + +struct StrTab +{ + int32 n_buckets; + int32 size; + int32 n_occupied; + int32 upper_bound; + int32 *flags; + string *keys; + int32 *vals; +}; + +#if 0 +struct TypeSet +{ + int32 n_buckets; + int32 size; + int32 n_occupied; + int32 upper_bound; + int32 *flags; + Type **keys; +}; +#endif + +/* main data */ +struct Compiler +{ + mem·Arena *heap; + StrTab strs; + string outfile; + + struct { + int cap; + int len; + string *val; + } def; + + struct { + int cap; + int len; + string *dir; + } inc; + + struct { + int cap; + int len; + Type *info; + } type; + + /* TODO: make array */ + struct { + Decl vargs; + } builtin; +}; + +extern Compiler C; + +/* cc.c functions */ +void init(); +int32 intern(byte **str); +int32 type(); + +#undef iota diff --git a/src/cmd/cc/lex.c b/src/cmd/cc/lex.c new file mode 100644 index 0000000..33fc5d0 --- /dev/null +++ b/src/cmd/cc/lex.c @@ -0,0 +1,873 @@ +#include "cc.h" +#include + +// ----------------------------------------------------------------------- +// printing functions + +void +puttok(Token tok) +{ + if (tok.kind < Alit) + printf("%s", tokens[tok.kind]); + else if (tok.kind & Alit) { + if (tok.kind & Vchar) + if (tok.kind & Vint) + if (tok.kind & Vlong) + if (tok.kind & Vvlong) + printf("literal <%lld>", tok.val.i); + if (tok.kind & Vfloat) + printf("literal <%f>", tok.val.f); + printf("literal <%s>", tok.val.s); + } else + printf("ident <%s>", tok.val.s); +} + +// ----------------------------------------------------------------------- +// io buffer management + +#define asrdr(x) (io·Reader){(int (*)(void *, int, int, void *))x} + +// path should be absolute +Io* +openio(Lexer *lx, byte *path) +{ + string *it, *end; + + intern(&path); + + // See if we have already opened file; + // If so, and it hasn't been flagged return it + for (it = lx->omit.path, end = it + lx->omit.len; it < end; ++it) { + if ((uintptr)(*it) == (uintptr)(path)) + return nil; + } + + // TODO: See if we have already loaded the file + + if ((lx->new - lx->iostk) >= arrlen(lx->iostk)-1) + panicf("out of I/O space!"); + + lx->new->f = io·open(path, "r"); + if (!lx->new->f) + panicf("file %s not found", path); + + lx->new->kind = IOfile; + lx->new->path = path; + bufio·initreader(&lx->new->rdr, asrdr(io·read), lx->new->f); + + return lx->new++; +} + +static +Io* +makeio(Lexer *lx, byte *name) +{ + if ((lx->new - lx->iostk) >= arrlen(lx->iostk)-1) + panicf("out of I/O space!"); + + lx->new->path = name; + lx->new->rdr = (io·Buffer) { + .state = bufio·rdr | bufio·end, + .runesize = 0, + .h = nil, + .size = bufio·size, + .beg = lx->new->rdr.buf + bufio·ungets, + .pos = lx->new->rdr.buf + bufio·ungets, + .end = lx->new->rdr.buf + bufio·ungets, + }; + lx->new->b = lx->new->rdr.beg; + + return lx->new++; +} +#undef asrdr + +static +void +freeio(Lexer *lx, Io *io) +{ + if (io->kind & IOfile) { + io·close(io->f); + } + + io->rdr.state = 0; + io->kind = 0; + io->link = nil; + io->path = nil; + io->store = (Pos){ 0 }; + io->path = ""; +} + +void +pushio(Lexer *lx, Io *new) +{ + new->link = lx->io; + lx->io->store = lx->pos; + lx->io = new; + + lx->pos = (Pos){ + .line = 1, + .col = 1, + .path = new->path, + }; +} + +void +popio(Lexer *lx) +{ + Io *prev; + + assert(lx->io == lx->new-1); + --lx->new; + + prev = lx->io->link; + freeio(lx, lx->io); + + lx->io = prev; + if (!prev) { + return; + } + + lx->pos = prev->store; +} + +// ----------------------------------------------------------------------- +// simple wrappers + +int +getbyte(Lexer *lx) +{ + return bufio·getbyte(&lx->io->rdr); +} + +int +getnsbyte(Lexer *lx) +{ + int b; + b = getbyte(lx); + for (;;) { + if (b == EOF) { + if (lx->io->link) { + popio(lx); + assert(lx->io); + b = getbyte(lx); + continue; + } else + return b; + } + if (b >= RuneSelf || !isspace(b)) + return b; + if (b == '\n') + return b; + b = getbyte(lx); + } + return b; +} + +rune +getrune(Lexer *lx) +{ + return bufio·getrune(&lx->io->rdr); +} + +byte +ungetbyte(Lexer *lx) +{ + byte b; + return bufio·ungetbyte(&lx->io->rdr, b); +} + +rune +ungetrune(Lexer *l, rune r) +{ + return bufio·ungetrune(&l->io->rdr, r); +} + +// ----------------------------------------------------------------------- +// main lexer + +#define TOK(a, b) b, +byte *tokens[NUM_TOKENS] = { TOKENS }; +#undef TOK + +static uint8 Atoi[256] = +{ + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, + ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['a'] = 10, ['A'] = 10, + ['b'] = 11, ['B'] = 11, ['c'] = 12, ['C'] = 12, ['d'] = 13, ['D'] = 13, + ['e'] = 14, ['E'] = 14, ['f'] = 15, ['F'] = 15, +}; + +static +error +escapechar(Lexer *lx, int x, int islong, int esc, vlong *val) +{ + int i, u, c; + vlong l; + + c = getrune(lx); + + switch (c) { + case '\\': + break; + case EOF: + errorat(lx->pos, "EOF in string"); + return 1; + case '\n': + errorat(lx->pos, "newline in string"); + return 1; + default: + if (c == x) + return 1; + *val = c; + return 0; + } + + u = 0; + c = getrune(lx); + + switch(c) { + case 'x': + i = islong ? 4 : 2; + goto hex; + + case 'u': + i = islong ? 8 : 4; + u = 1; + goto hex; + + case 'U': + i = 8; + u = 1; + goto hex; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + i = islong ? 4 : 2; + goto oct; + + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\':c = '\\'; break; + + default: + if(c != x) errorat(lx->pos, "unknown escape sequence: %c", c); + } + *val = c; + return 0; + +hex: + l = 0; + for(; i > 0; i--) { + c = getbyte(lx); + if (c >= '0' && c <= '9') { + l = l*16 + c-'0'; + continue; + } + if (c >= 'a' && c <= 'f') { + l = l*16 + c-'a' + 10; + continue; + } + if (c >= 'A' && c <= 'F') { + l = l*16 + c-'A' + 10; + continue; + } + ungetbyte(lx); + break; + } + if (u && (l > RuneMax || (0xd800 <= l && l < 0xe000))) { + errorat(lx->pos, "invalid unicode code point in escape sequence: %#llx", l); + l = RuneErr; + } + *val = l; + if (esc) + *val |= RuneMask + 1; + return 0; + +oct: + l = c - '0'; + for (; i > 0; i--) { + c = getbyte(lx); + if (c >= '0' && c <= '7') { + l = l*8 + c-'0'; + continue; + } + ungetbyte(lx); + break; + } + if (l > 255) errorat(lx->pos, "octal escape value > 255: %d", l); + + *val = l; + if (esc) + *val |= RuneMask + 1; + return 0; +} + +#define CASE1(stmt1, kind1) \ + case stmt1: \ + tok.kind = kind1; \ + goto Return + +#define CASE2(stmt1, kind1, b1, kind2) \ + case stmt1: \ + tok.kind = kind1; \ + b = getbyte(lx); \ + if (b == b1) \ + tok.kind = kind2; \ + else \ + ungetbyte(lx); \ + goto Return + +#define CASE3(stmt1, kind1, b1, kind2, b2, kind3) \ + case stmt1: \ + tok.kind = kind1; \ + b = getbyte(lx); \ + if (b == b1) \ + tok.kind = kind2; \ + else if (b == b2) \ + tok.kind = kind3; \ + else \ + ungetbyte(lx); \ + goto Return + +#define CASE4(stmt1, kind1, b1, kind2, b2, kind3, b3, type4) \ + case stmt1: \ + tok.kind = kind1; \ + b = getbyte(lx); \ + if (b == b1) \ + tok.kind = kind2; \ + else if (b == b2) \ + tok.kind = kind3; \ + else if (b == b3) \ + tok.kind = type4; \ + else \ + ungetbyte(lx); \ + goto Return + + +Token +lex(Lexer *lx) +{ + int b, n, f; + vlong v, _; + rune r; + string s; + double d; + byte *e; + Token tok; + Sym *sym; + Io *io; + +GetByte: + b = getbyte(lx); +Dispatch: + tok.pos = lx->pos; + + if ((b != EOF && b >= RuneSelf) || b == '_') + goto Talpha; + if (isalpha(b)) { + if (b != 'L') + goto Talpha; + + n = b; + b = getbyte(lx); + if (b == '\'') { + if (escapechar(lx, '\'', 1, 0, &v)) + b = '\''; + if (!escapechar(lx, '\'', 1, 0, &_)) { + errorat(lx->pos, "missing ' at end of character constant"); + } + tok.kind = Alit | Vrune; + tok.val.r = v; + goto Return; + } + if (b == '"') + goto TLstr; + ungetbyte(lx); + b = n; + + goto Talpha; + } + if (isdigit(b)) + goto Tnum; + + switch (b) { + case '\n': + lx->pos.line++; + case ' ': case '\r': case '\t': case '\v': case '\f': + while (b = getbyte(lx), isspace(b)) + if (b == '\n') + lx->pos.line++; + goto Dispatch; + + case '\\': + b = getbyte(lx); + if (b != '\n') + errorat(lx->pos, "'\\' without a trailing newline"); + goto GetByte; + + Tchar: + case '\'': + if (escapechar(lx, '\'', 0, 0, &v)) { + errorat(lx->pos, "empty literal or escaped ' in char literal"); + v = '\''; + } + if (!escapechar(lx, '\'', 0, 0, &_)) { + errorat(lx->pos, "missing '"); + ungetbyte(lx); + } + + if (v > 0xff) { + errorat(lx->pos, "overflowed character literal"); + v = 0; + } + tok.kind = Alit | Vchar; + tok.val.c = v; + goto Return; + + case '"': + s = str·makecap("", 0, 8); + for (;;) { + if (escapechar(lx, '"', 0, 1, &v)) + break; + + if (v & (RuneMask + 1)) + str·appendbyte(&s, v); + else { + r = v; + b = utf8·runelen(r); + utf8·runetobyte(lx->buf, &r); + str·appendlen(&s, b, lx->buf); + } + } + tok.kind = Alit | Vstr; + tok.val.s = s; + intern(&tok.val.s); + + str·free(s); + goto Return; + + TLstr: + s = str·makecap("", 0, 8); + // NOTE: this violates strict aliasing + for (;;) { + if (escapechar(lx, '"', 1, 0, &v)) + break; + str·appendlen(&s, sizeof(wchar_t), (byte*)&v); + } + tok.kind = Alit | Vwstr; + tok.val.s = s; + intern(&tok.val.s); + + str·free(s); + goto Return; + + case '.': + tok.kind = Adot; + b = getbyte(lx); + + if (isdigit(b)) { + // *lx->b++ = b; + goto Tflt; + } else if (b == '.') { + b = getbyte(lx); + if (b != '.') { + errorat(lx->pos, "invalid token '..'"); + tok.kind = Aellip; + break; + } + } + ungetbyte(lx); + goto Return; + + case '<': + tok.kind = Alt; + b = getbyte(lx); + + if (b == '<') { + tok.kind = Alsft; + b = getbyte(lx); + if (b == '=') + tok.kind = Alsftasn; + else + ungetbyte(lx); + } else if (b == '=') + tok.kind = Alteq; + else + ungetbyte(lx); + goto Return; + + case '>': + tok.kind = Agt; + b = getbyte(lx); + + if (b == '>') { + tok.kind = Arsft; + b = getbyte(lx); + if (b == '=') + tok.kind = Arsftasn; + else + ungetbyte(lx); + } else if (b == '=') + tok.kind = Agteq; + else + ungetbyte(lx); + goto Return; + + case '/': + tok.kind = Adiv; + b = getbyte(lx); + + if (b == '=') + tok.kind = Adivasn; + else if (b == '/') { + while (b != EOF && b != '\n') + b = getbyte(lx); + goto Dispatch; + } else if (b == '*') { + int level = 1; + b = getbyte(lx); + while (b != EOF && level > 0) { + if (b == '/') { + b = getbyte(lx); + if (b == '*') + level++; + } else if (b == '*') { + b = getbyte(lx); + if (b == '/') + level--; + } + if (b == '\n') + lx->pos.line++; + b = getbyte(lx); + } + goto Dispatch; + } else + ungetbyte(lx); + goto Return; + + case '#': + if (domacro(lx)) { + tok.kind = Anil; + errorat(lx->pos, "failed to perform preprocessor directive"); + return tok; + } + goto GetByte; + + case EOF: + popio(lx); + if (lx->io) + goto GetByte; + tok.kind = Aeof; + goto Return; + + CASE1('(', Alparen); + CASE1(')', Arparen); + CASE1('{', Albrace); + CASE1('}', Arbrace); + CASE1('[', Albrakt); + CASE1(']', Arbrakt); + CASE1(',', Acomma); + CASE1('?', Aqmark); + CASE1(';', Asemi); + CASE1('~', Aneg); + CASE1(':', Acolon); + CASE2('^', Axor, '=', Axorasn); + CASE2('!', Anot, '=', Aneq); + CASE2('*', Astar,'=', Amulasn); + CASE2('=', Aasn, '=', Aeq); + CASE2('%', Amod, '=', Amodasn); + CASE3('+', Aadd, '=', Aaddasn, '+', Ainc); + CASE3('&', Aand, '=', Aandasn, '&', Aandand); + CASE3('|', Aor, '=', Aorasn, '|', Aoror); + CASE4('-', Asub, '=', Asubasn, '-', Adec, '>', Aarrow); + + Tnum: + e = lx->buf + arrlen(lx->buf); + do { + if (lx->b >= e) { + errorat(lx->pos, "number overflows lexer buffer"); + goto Nospace; + } + *lx->b++ = b; + } while (b = getbyte(lx), isdigit(b) || b == '_'); + + if (b == '.' || tolower(b) == 'e') + goto Tflt; + Tint: + n = 10; + s = lx->buf; + if (*s == '0') { + switch (b) { + case 'x': n = 16; break; + case 'b': n = 2; break; + case 'o': n = 8; break; + default: goto Rint; + } + lx->b = s; + /* reparse number, now with base info */ + while (b = getbyte(lx), (isdigit(b) || + ('a' <= b && b <= 'f') || + ('A' <= b && b <= 'F') || + b == '_')) + *lx->b++ = b; + } + Rint: + v = 0; + r = b; + for (; s != lx->b ; s++) { + b = *s; + if (b == '_') continue; + + f = Atoi[b]; + if (f == 0 && b != '0') + break; + + if (f >= n) { + errorat(lx->pos, "digit '%c' out of range for base %d", b, n); + f = 0; + } + + if (v > (UINT64_MAX - f) / n) { + errorat(lx->pos, "integer literal overflow"); + v = 0; + break; + } + + v = v * n + f; + } + + b = r; + tok.kind = Alit; + tok.val.i = v; + + if (b == 'u' || b == 'U') { + tok.kind |= Vun; + b = getbyte(lx); + } + if (b == 'l' || b == 'L') { + r = getbyte(lx); + if (r == 'l' || r == 'L') { + if (r != b) + errorat(lx->pos, "mismatched case on long long integer suffix"); + tok.kind |= Vvlong; + r = getbyte(lx); + } else + tok.kind |= Vlong; + + if (r == 'u' || r == 'U') { + if (tok.kind & Vun) + errorat(lx->pos, "multiple unsigned designators on integer suffix"); + tok.kind |= Vun; + goto Return; + } + + ungetbyte(lx); + goto Return; + } + + tok.kind |= Vint; + ungetbyte(lx); + goto Return; + + Tflt: + if (b == '.') { + *lx->b++ = b; + b = getbyte(lx); + } + + while (isdigit(b)) { + *lx->b++ = b; + + if (lx->b >= e) { + errorat(lx->pos, "number overflows lexer buffer"); + goto Nospace; + } + } + + if (tolower(b) == 'e') { + b = getbyte(lx); + if (b == '-' || b == '+') + b = getbyte(lx); + + if (!isdigit(b)) + errorat(lx->pos, "expected number after exponent, found %c", b); + + do { + *lx->b++ = b; + } while (b = getbyte(lx), isdigit(b)); + } + *lx->b = '\0'; + d = strtod(lx->buf, nil); + ungetbyte(lx); + + tok.kind = Alit | Vfloat; + tok.val.f = d; + + goto Return; + + Talpha: + s = lx->buf; + e = lx->buf + arrlen(lx->buf); + for (;;) { + if (s >= e) { + errorat(lx->pos, "identifier too long for buffer: %s", s); + goto Nospace; + } + if (b != EOF && b >= RuneSelf) { + ungetbyte(lx); + r = getrune(lx); + if (!utf8·isletter(r) && !utf8·isdigit(r) && r != 0xb7) { + errorat(lx->pos, "invalid identifier character %d", r); + } + s += utf8·runetobyte(s, &r); + } else if (!isalnum(b) && b != '_') + break; + else + *s++ = b; + b = getbyte(lx); + } + *s = '\0'; + ungetbyte(lx); + + tok.kind = Aident; + tok.val.s = lx->buf; + + n = intern(&tok.val.s); + if (n < arrlen(keywords)) { + tok.kind = Akeywd; + tok.val.i = n; + goto Return; + } + + sym = lookup(&lx->sym, tok.val.s); + if (sym && ((uintptr)sym->name != (uintptr)lx->io->path)) { + if ((uintptr)sym == lx->macline) { + tok.kind = Alit | Vint; + tok.val.i = lx->pos.line; + goto Return; + } + if ((uintptr)sym == lx->macfile) { + tok.kind = Alit | Vstr; + tok.val.s = lx->pos.path; + goto Return; + } + io = makeio(lx, sym->name); + io->rdr.end += expandmacro(lx, sym, io->b); + printf("EXPANDED %s: %s\n", sym->name, io->rdr.beg); + *io->rdr.end++ = EOF; + pushio(lx, io); + goto GetByte; + } + goto Return; + + default: + tok.kind = Anil; + errorat(lx->pos, "invalid token, crashing"); + abort(); + } + +Return: + lx->b = lx->buf; + return tok; + +Nospace: + panicf("aborting compilation"); + exit(1); +} + +#undef CASE4 +#undef CASE3 +#undef CASE2 +#undef CASE1 + +// ----------------------------------------------------------------------- +// symbol tables + +#define PTR_HASH(p) (uintptr)(p) +#define PTR_EQUAL(p1, p2) ((uintptr)(p1) == (uintptr)(p2)) + +static +void +·free(void* _, void* ptr) { + return free(ptr); +} + +static +void * +·alloc(void* _, uint n, ulong size) { + return malloc(n*size); +} + +static +void * +·calloc(void* _, uint n, ulong size) { + return calloc(n, size); +} + +static +int +moresymtab(SymTab *tab, int n) +{ + MAP_GROW(tab, string, Sym*, n, PTR_HASH, sys·Memory, nil); +} + +static +int +putsym(SymTab *tab, Sym *sym, error *err) +{ + MAP_PUT(tab, sym->name, sym, PTR_HASH, PTR_EQUAL, moresymtab, err); +} + +Sym* +define(SymTab *tab, string name, uint32 kind) +{ + int i; + Sym *sym; + error err; + + sym = mem·arenaalloc(C.heap, 1, sizeof(*sym)); + sym->name = name; + sym->kind = kind; + + i = putsym(tab, sym, &err); + tab->vals[i] = sym; + + return sym; +} + +Sym* +lookup(SymTab *tab, string ident) +{ + int idx; + MAP_GET(idx, tab, ident, PTR_HASH, PTR_EQUAL); + + if (idx < tab->n_buckets) + return tab->vals[idx]; + + return nil; +} + + +error +forget(SymTab *tab, string ident) +{ + int idx; + MAP_GET(idx, tab, ident, PTR_HASH, PTR_EQUAL); + + if (idx < tab->n_buckets) { + MAP_DEL(tab, idx); + return 0; + } + return 1; +} + +void +forgetall(SymTab *tab) +{ + MAP_RESET(tab); +} diff --git a/src/cmd/cc/pp.c b/src/cmd/cc/pp.c new file mode 100644 index 0000000..57c3501 --- /dev/null +++ b/src/cmd/cc/pp.c @@ -0,0 +1,1125 @@ +#include "cc.h" + +// ----------------------------------------------------------------------- +// helper functions + +static +void +pushomit(Lexer *lx, string omit) +{ + if (lx->omit.len == lx->omit.cap) { + lx->omit.cap += 20; + lx->omit.path = realloc(lx->omit.path, lx->omit.cap*sizeof(*lx->omit.path)); + } + lx->omit.path[lx->omit.len++] = omit; +} + +// NOTE: The iterator of lexer lx->b IS NOT reset. +// Its the caller's responsibility. +static +string +ident(Lexer *lx) +{ + int b; + byte *s; + + b = getnsbyte(lx); + if (!isalpha(b) && b != '_' && b < RuneSelf) { + ungetbyte(lx); + return ""; + } + + s = lx->b; + for (;;) { + *lx->b++ = b; + b = getbyte(lx); + if (isalnum(b) || b == '_' || b >= RuneSelf) + continue; + ungetbyte(lx); + break; + } + *lx->b++ = '\0'; + + return s; +} + +static +string +identdots(Lexer *lx, int *dots) +{ + int c; + byte *s; + + s = ident(lx); + if (*s != '\0') + return s; + + c = getnsbyte(lx); + if (c != '.') { + ungetbyte(lx); + return s; + } + + if (getbyte(lx) != '.' || getbyte(lx) != '.') + errorat(lx->pos, "incorrect '...' token in macro"); + + *dots = 1; + // TODO: should only run intern once... + s = "__VA_ARGS__"; + intern(&s); + return s; +} + +static +Sym* +defmacro(Lexer *lx, string name, string macro) +{ + Sym *mac; + + // printf("DEFINING MACRO %s ON LINE %d, file %s\n", name, lx->pos.line, os·basename(lx->pos.path)); + mac = define(&lx->sym, name, Smacro); + mac->macro = macro; + + return mac; +} + +static vlong evalmacro(Lexer *lx, byte prec); + +static +vlong +opand(Lexer *lx) +{ + int b; + vlong v; + string s; + Token tok; + Sym *sym; + + b = getnsbyte(lx); + if (b == '\n') { + errorat(lx->pos, "new line in macro expression"); + return 0; + } + ungetbyte(lx); + + tok = lex(lx); + + switch (tok.kind & Vmask) { + case Aneg: + return ~opand(lx); + + case Anot: + return !opand(lx); + + case Alparen: + v = evalmacro(lx, 1); + tok = lex(lx); + if (!(tok.kind & Arparen)) { + errorat(lx->pos, "unbalanced parenthesis in macro expression"); + return 0; + } + return v; + + case Alit: + switch (tok.kind & ~Vmask) { + case Vint: case Vlong: case Vvlong: + return tok.val.i; + case Vun|Vint : case Vun|Vlong : case Vun|Vvlong: + return tok.val.ui; + case Vrune: + return tok.val.r; + case Vchar: + return tok.val.c; + default: + errorat(lx->pos, "invalid literal of type '%s' in conditional macro", tokens[tok.kind & ~Vmask]); + return 0; + } + + case Aident: + sym = lookup(&lx->sym, tok.val.s); + if (!sym) { + /* calling lex directly would expand the operand here + * manually lex the result + */ + if (strcmp(tok.val.s, "defined") == 0) { + b = getnsbyte(lx); + if (b == '\n') { + errorat(lx->pos, "new line in defined operand"); + return 0; + } + s = lx->buf; + if (b == '(') { + b = getnsbyte(lx); + while (b != ')') { + if (b == '\n') { + errorat(lx->pos, "new line inside defined operand"); + return 0; + } + if (b == '(') { + errorat(lx->pos, "nested parens not allowed inside defined operator"); + return 0; + } + if (!isspace(b)) + *s++ = b; + b = getbyte(lx); + } + } else { + while (!isspace(b)) { + *s++ = b; + b = getbyte(lx); + + if (b == '\n') { + errorat(lx->pos, "new line inside defined operand"); + return 0; + } + } + } + *s = '\0'; + s = lx->buf; + intern(&s); + return lookup(&lx->sym, s) != nil; + } + return 0; + } + panicf("unreachable"); + return 1; + + default: + errorat(lx->pos, "opand: invalid token found in macro conditional: '%s'", tokens[tok.kind & Vmask]); + return 0; + } +} + +// recursively evaluates a macro +// reduced set of operators allowed here +static +vlong +evalmacro(Lexer *lx, byte prec) +{ + int b; + vlong l, r; + Token tok; + + l = opand(lx); + for (;;) { + b = getnsbyte(lx); + // NOTE: Either this or we pass in what are stopping byte is + // New line should always stop us... + // Is there any case where we SHOULDN'T STOP ON ')'? + if (b == '\n' || b == ')') { + ungetbyte(lx); + break; + } + ungetbyte(lx); + + tok = lex(lx); + // simplified jump table of precedence + // unpacked to evaluate inline + switch (tok.kind & Vmask) { + case Astar: + if (prec > 10) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 10 + 1); + l = l * r; + continue; + + case Adiv: + if (prec > 10) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 10 + 1); + l = l / r; + continue; + + case Amod: + if (prec > 10) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 10 + 1); + l = l % r; + continue; + + case Aadd: + if (prec > 9) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 9 + 1); + l = l + r; + continue; + + case Asub: + if (prec > 9) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 9 + 1); + l = l - r; + continue; + + case Alsft: + if (prec > 8) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 8 + 1); + l = l << r; + continue; + + case Arsft: + if (prec > 8) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 8 + 1); + l = l >> r; + continue; + + case Alt: + if (prec > 7) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 7 + 1); + l = l < r; + continue; + + case Agt: + if (prec > 7) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 7 + 1); + l = l > r; + continue; + + case Agteq: + if (prec > 7) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 7 + 1); + l = l >= r; + continue; + + case Alteq: + if (prec > 7) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 7 + 1); + l = l >= r; + continue; + + case Aeq: + if (prec > 6) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 6 + 1); + l = l == r; + continue; + + case Aneq: + if (prec > 6) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 6 + 1); + l = l != r; + continue; + + case Aand: + if (prec > 5) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 5 + 1); + l = l & r; + continue; + + case Axor: + if (prec > 4) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 4 + 1); + l = l ^ r; + continue; + + case Aor: + if (prec > 3) { + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 3 + 1); + l = l | r; + continue; + + case Aandand: + if (prec > 2) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 2 + 1); + l = l && r; + continue; + + case Aoror: + if (prec > 1) { + ungetbyte(lx); + ungetbyte(lx); + return l; + } + r = evalmacro(lx, 1 + 1); + l = l || r; + continue; + + default: + errorat(lx->pos, "eval: invalid token found in macro conditional '%s'", tokens[tok.kind & Vmask]); + abort(); + return 0; + } + } + + return l; +} + +// ----------------------------------------------------------------------- +// preprocessor magic numbers + +enum +{ + PPbeg = 0x02, + PParg = 0x03, + PPcat = 0x04, + PPstr = 0x05, + + PPnarg = 30, +}; + +#define PPvar 0x80u + +// ----------------------------------------------------------------------- +// preprocessor functions + +/* #endif */ +static +error +ppend(Lexer *lx) +{ + int b; + do { + b = getnsbyte(lx); + } while (b > 0 && b != '\n'); + + if (b == '\n') + lx->pos.line++; + + return 0; +} + + +/* #undef */ +static +error +ppund(Lexer *lx) +{ + string s; + error err; + + s = ident(lx); + intern(&s); + lx->b = lx->buf; + + err = forget(&lx->sym, s); + if (err) + warnat(lx->pos, "attempting to undefine unrecognized symbol '%s'", s); + + ppend(lx); + return 0; +} + +/* #define */ +static +error +ppdef(Lexer *lx) +{ + int b; + Sym *sym; + int i, j, n, dot; + string s, a, base, end, buf, args[PPnarg]; + + s = ident(lx); + if (!s) { + errorat(lx->pos, "failed to parse defined identifer"); + goto Bad; + } + intern(&s); + printf("DEFINING %s\n", s); + lx->b = lx->buf; + + sym = lookup(&lx->sym, s); + if (sym) + warnat(lx->pos, "macro redefined: '%s'", sym->name); + + n = 0; + dot = 0; + b = getbyte(lx); + if (b == '(') { + b = getnsbyte(lx); + if (b != ')') { + ungetbyte(lx); + for (;;) { + // NOTE: This is a pointer into the lx->buffer. + // Can't reset lx->b while we hold the args! + a = identdots(lx, &dot); + if (a == nil) { + errorat(lx->pos, "macro syntax error: improper argument"); + goto Bad; + } + if (n >= PPnarg) { + errorat(lx->pos, "macro syntax error: too many arguments: %d > %d", n, PPnarg); + goto Bad; + } + + args[n++] = a; + b = getnsbyte(lx); + + if (b == ')') + break; + if (b != ',') { + errorat(lx->pos, "macro syntax error: bad token in argument '%b'", b); + goto Bad; + } + } + } + b = getbyte(lx); + } + + if (isspace(b)) + if (b != '\n') + b = getnsbyte(lx); + + base = lx->b; + end = lx->buf + arrlen(lx->buf); + if (base >= end) { + errorat(lx->pos, "out of macro buffer space!"); + goto Bad; + } + buf = str·makef("%c%c", n, PPbeg); + for (;;) { + if (isalpha(b) || b == '_') { + lx->b = base; + *lx->b++ = b; + + b = getbyte(lx); + while (isalnum(b) || b == '_') { + *lx->b++ = b; + if (lx->b >= end) { + errorat(lx->pos, "out of macro buffer space!"); + goto Bad; + } + b = getbyte(lx); + } + *lx->b++ = '\0'; + + for (i = 0; i < n; i++) { + if (strcmp(base, args[i]) == 0) { + goto Arg; + } + } + str·appendlen(&buf, (lx->b - base - 1), base); + continue; + Arg: + str·appendbyte(&buf, PParg); + str·appendbyte(&buf, 'a' + i); + continue; + } + + if (b == '/') { + b = getbyte(lx); + if (b == '/') { + while (b = getbyte(lx), b != '\n'); + continue; + } + if (b == '*') { + b = getbyte(lx); + for (;;) { + if (b == '*') { + b = getbyte(lx); + if (b != '/') + continue; + b = getbyte(lx); + break; + } + if (b == '\n') { + errorat(lx->pos, "comment and newline found in define statement of %s", s); + break; + } + b = getbyte(lx); + } + continue; + } + str·appendbyte(&buf, '/'); + continue; + } + + if (b == '\\') { + b = getbyte(lx); + /* unix */ + if (b == '\n') { + lx->pos.line++; + b = getbyte(lx); + continue; + } + /* windows */ + if (b == '\r') { + b = getbyte(lx); + if (b == '\n') { + lx->pos.line++; + b = getbyte(lx); + continue; + } + } + str·appendbyte(&buf, '\\'); + } + if (b == '\n') { + lx->pos.line++; + break; + } + + if (b == '#') { + b = getnsbyte(lx); + if (b == '#') { + str·appendbyte(&buf, PPcat); + b = getbyte(lx); + continue; + } + + lx->b = base; + while (isalnum(b) || b == '_') { + *lx->b++ = b; + b = getbyte(lx); + } + *lx->b = '\0'; + + for (i = 0; i < n; i++) { + if (strcmp(base, args[i]) == 0) + goto Str; + } + errorat(lx->pos, "macro operator '#' must be followed by a valid variable identifier"); + goto Bad; + Str: + str·appendbyte(&buf, PPstr); + str·appendbyte(&buf, 'a' + i); + continue; + } + + str·appendbyte(&buf, b); + b = getbyte(lx); + if (b == EOF) { + errorat(lx->pos, "eof found in macro '%s'", s); + goto Bad; + } + } + if (dot) + *buf |= PPvar; + + lx->b = lx->buf; + sym = defmacro(lx, s, buf); + return 0; +Bad: + errorat(lx->pos, "failed parse of #define macro '%s'", s); + lx->b = lx->buf; + ppend(lx); + return 1; +} + +/* macro expansion */ +int +expandmacro(Lexer *lx, Sym *s, byte *dst) +{ + int n, lv, nargs, dots; + byte b, *it, *e, *arg[PPnarg]; + + /* not a function macro */ + if (s->macro[0] == '\0') { + if (s->macro[1] != PPbeg) { + errorat(lx->pos, "malformed macro"); + goto Bad; + } + strcpy(dst, s->macro + 2); + return str·len(s->macro)-2; + } + dots = (ubyte)s->macro[0] & PPvar; + nargs = (ubyte)s->macro[0] & (~PPvar); + + b = getnsbyte(lx); + if (b != '(') { + errorat(lx->pos, "macro function not given arguments"); + goto Bad; + } + + n = 0; + b = getbyte(lx); + if (b != ')') { + ungetbyte(lx); + lv = 0; + lx->b = lx->buf; + e = lx->buf + arrlen(lx->buf) - 4; + arg[n++] = lx->buf; + for (;;) { + if (lx->b >= e) + goto Nospace; + b = getbyte(lx); + if (b == '"') + for (;;) { + if (lx->b >= e) + goto Nospace; + *lx->b++ = b; + b = getbyte(lx); + if (b == '\\') { + *lx->b++ = b; + b = getbyte(lx); + continue; + } + if (b == '\n') { + errorat(lx->pos, "newline found in arguments: macro '%s'", s->name); + goto Bad; + } + if (b == '"') + break; + } + if (b == '\'') + for (;;) { + if (lx->b >= e) + goto Nospace; + *lx->b++ = b; + b = getbyte(lx); + if (b == '\\') { + *lx->b++ = b; + b = getbyte(lx); + continue; + } + if (b == '\n') { + errorat(lx->pos, "newline found in arguments: macro '%s'", s->name); + goto Bad; + } + if (b == '"') + break; + } + if (b == '/') { + b = getbyte(lx); + switch(b) { + case '*': + for (;;) { + b = getbyte(lx); + if (b == '*') { + b = getbyte(lx); + if (b == '/') + break; + } + } + *lx->b++ = ' '; + continue; + case '/': + while ((b = getbyte(lx)) != '\n') + ; + break; + + default: + ungetbyte(lx); + b = '/'; + } + } + if (lv == 0) { + if (b == ',') { + if (n == nargs && dots) { + *lx->b++ = ','; + continue; + } + *lx->b++ = '\0'; + arg[n++] = lx->b; + if (n > nargs) + break; + continue; + } + if (b == ')') + break; + } + if (b == '\n') + b = ' '; + *lx->b++ = b; + if (b == '(') + lv++; + if (b == ')') + lv--; + } + *lx->b = '\0'; + } + + if (n != nargs) { + errorat(lx->pos, "number of arguments don't match macro definition: %s", s->name); + *dst = '\0'; + goto Bad; + } + + if (s->macro[1] != PPbeg) { + errorat(lx->pos, "corrupted macro buffer: %s", s->name); + *dst = '\0'; + goto Bad; + } + + it = s->macro+2; + e = dst; + for (;;) { + b = *it++; + if (b == '\n') + b = ' '; + switch (b) { + case PParg: + b = *it++; + b -= 'a'; + if (b < 0 && b > n) { + errorat(lx->pos, "malformed macro index: %s", s->name); + goto Bad; + } + strcpy(dst, arg[b]); + dst += strlen(arg[b]); + + break; + + case PPstr: + b = *it++; + b -= 'a'; + if (b < 0 && b > n) { + errorat(lx->pos, "malformed macro index: %s", s->name); + goto Bad; + } + *dst++ = '"'; + strcpy(dst, arg[b]); + *dst++ = '"'; + + break; + + case PPcat: + continue; + + case '\0': + goto End; + + default: + *dst++ = b; + continue; + } + } +End: + *dst = '\0'; + return dst - e; +Nospace: + errorat(lx->pos, "out of memory during macro expansion %s", s->name); +Bad: + ppend(lx); + lx->b = lx->buf; + errorat(lx->pos, "failed to expand macro %s", s->name); + return -1; +} + +/* #include */ +static +error +ppinc(Lexer *lx) +{ + int i; + byte b, end; + string s; + + Stream *f; + Io *io; + + b = getnsbyte(lx); + if (b != '"') { + end = b; + if (b != '<') { + errorat(lx->pos, "unrecognized token '%c' in include directive", b); + goto Bad; + } + end = '>'; + } else + end = '"'; + + lx->b = lx->buf; + for (;;) { + b = getbyte(lx); + if (b == end) + break; + if (b == '\n') { + errorat(lx->pos, "hit end of line before include directive completed"); + goto Bad; + } + *lx->b++ = b; + } + *lx->b = '\0'; + s = lx->buf; + intern(&s); // NOTE: we could use this to see if we already have the file + + lx->b = lx->buf; + for (i = 0; i < C.inc.len; i++) { + if (i == 0 && end == '>') + continue; + + strcpy(lx->buf, C.inc.dir[i]); + strcat(lx->buf, "/"); + + if (strcmp(lx->buf, "./") == 0) + lx->buf[0] = '\0'; + strcat(lx->buf, s); + + if (os·exists(lx->buf, ReadOK)) { + break; + } + } + if (i == C.inc.len) { + errorat(lx->pos, "could not find file '%s' on standard include search path", s); + goto Bad; + } + + io = openio(lx, lx->buf); + if (io != nil) { + pushio(lx, io); + } + + return 0; + +Bad: + ungetbyte(lx); + lx->b = lx->buf; + errorat(lx->pos, "failed include"); + ppend(lx); + return 1; +} + +/* #pragma */ +static +error +ppprag(Lexer *lx) +{ + string s; + + s = ident(lx); + if (s == nil) { + errorat(lx->pos, "failed to parse pragma identifier"); + goto Bad; + } + lx->b = lx->buf; + if (strcmp(s, "once") == 0) { + pushomit(lx, lx->io->path); + return 0; + } +Bad: + lx->b = lx->buf; + errorat(lx->pos, "unrecognized pragma '%s'", s); + ppend(lx); + return 1; +} + +/* all #if statements */ +static +error +ppif(Lexer *lx, int f) +{ + Sym *sym; + string s; + int c, l, b; + +Eval: + if (f == 0) { + b = evalmacro(lx, 1); + if (b) { + ppend(lx); + return 0; + } + goto Skip; + } + + if (f == 1) + goto Skip; + + s = ident(lx); + if (s == nil) { + errorat(lx->pos, "failed to parse preprocessor identifier"); + goto Bad; + } + intern(&s); + lx->b = lx->buf; + + sym = lookup(&lx->sym, s); + if ((!sym && (f == 3)) || (sym && (f == 2))) + return 0; + +Skip: + b = 1; + l = 0; + for (;;) { + c = getbyte(lx); + if (c != '#') { + if (!isspace(c)) + b = 0; + if (c == '\n') { + lx->pos.line++; + b = 1; + } + if (c == EOF) { + errorat(lx->pos, "EOF hit while skipping if block. Missing endif"); + goto Bad; + } + continue; + } + if (!b) + continue; + s = ident(lx); + lx->b = lx->buf; + if (!s) + continue; + + if (l == 0 && (strcmp(s, "elif") == 0)) { + f = 0; + goto Eval; + } + + if (strcmp(s, "endif") == 0) { + if (l) { + l--; + continue; + } + ppend(lx); + return 0; + } + if (strcmp(s, "if") == 0 || + strcmp(s, "ifdef") == 0 || + strcmp(s, "ifndef") == 0) { + l++; + continue; + } + + if (l == 0 && f != 1 && strcmp(s, "else") == 0) { + return 0; + } + } + +Bad: + lx->b = lx->buf; + errorat(lx->pos, "bad syntax in preprocessor conditional directive"); + ppend(lx); + return 1; +} + +/* #if */ +static +error +ppif0(Lexer *lx) +{ + return ppif(lx, 0); +} + +/* #else */ +static +error +ppif1(Lexer *lx) +{ + return ppif(lx, 1); +} + +/* #ifdef */ +static +error +ppif2(Lexer *lx) +{ + return ppif(lx, 2); +} + +/* #ifndef */ +static +error +ppif3(Lexer *lx) +{ + return ppif(lx, 3); +} + +// ----------------------------------------------------------------------- +// dispatch function + +#define DIRECTIVE(a, b, c) c, +error (*macros[NUM_DIRECTIVES])(Lexer*) = { DIRECTIVES }; +#undef DIRECTIVE + +/* reads an identifier into the lexer's buffer */ +/* caller must intern */ + +error +domacro(Lexer *lx) +{ + int n; + error err; + string s; + + s = ident(lx); + intern(&s); + lx->b = lx->buf; + for (n = 0; n < NUM_DIRECTIVES; n++) { + if ((uintptr)s == (uintptr)directives[n]) { + goto Do; + } + } + errorat(lx->pos, "unrecognized directive name '%s'", s); + return 1; +Do: + err = macros[n](lx); + return err; +} + +error +dodefine(Lexer *lx, string s) +{ + int n; + byte *c, *def; + Sym *sym; + + strcpy(lx->buf, s); + c = strchr(lx->buf, '='); + if (c) { + *c++ = '\0'; + sym = lookup(&lx->sym, lx->buf); + if (sym) { + errorf("redefinition of symbol '%s'", sym->name); + return 1; + } + sym = define(&lx->sym, lx->buf, Smacro); + n = strlen(c) + 2; + sym->macro = str·makelen("", n); + str·appendbyte(&sym->macro, '\0'); + str·append(&sym->macro, c); + } else { + sym = lookup(&lx->sym, lx->buf); + if (sym) { + errorf("redefinition of symbol '%s'", sym->name); + return 1; + } + sym = define(&lx->sym, s, Smacro); + sym->macro = "\00\02"; + } + + return 0; +} diff --git a/src/cmd/cc/rules.mk b/src/cmd/cc/rules.mk new file mode 100644 index 0000000..b7b4688 --- /dev/null +++ b/src/cmd/cc/rules.mk @@ -0,0 +1,21 @@ +include share/push.mk + +# local sources +SRCS_$(d):=\ + $(d)/pp.c\ + $(d)/lex.c\ + $(d)/ast.c\ + $(d)/bits.c\ + $(d)/cc.c +TEST_$(d) := + +# outputs +BINS_$(d) := $(d)/cc + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/cc/scratch.c b/src/cmd/cc/scratch.c new file mode 100644 index 0000000..b37d9a5 --- /dev/null +++ b/src/cmd/cc/scratch.c @@ -0,0 +1,7 @@ +#define XXX(a, b, c) a ## b ## c + +int +main() +{ + XXX(d, e, f); +} diff --git a/src/cmd/cc/util.c b/src/cmd/cc/util.c new file mode 100644 index 0000000..cca16f2 --- /dev/null +++ b/src/cmd/cc/util.c @@ -0,0 +1,21 @@ +#include "cc.h" + +void +·free(void* _, void* ptr) { + return free(ptr); +} + +void * +·alloc(void* _, uint n, ulong size) { + return malloc(n*size); +} + +void * +·calloc(void* _, uint n, ulong size) { + return calloc(n, size); +} + +void * +·realloc(void* _, void *ptr, uint n, ulong size) { + return realloc(ptr, n*size); +} diff --git a/src/cmd/dwm/LICENSE b/src/cmd/dwm/LICENSE new file mode 100644 index 0000000..d221f09 --- /dev/null +++ b/src/cmd/dwm/LICENSE @@ -0,0 +1,37 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2009 Jukka Salmi +© 2006-2007 Sander van Dijk +© 2007-2011 Peter Hartlich +© 2007-2009 Szabolcs Nagy +© 2007-2009 Christof Musik +© 2007-2009 Premysl Hruby +© 2007-2008 Enno Gottox Boland +© 2008 Martin Hurton +© 2008 Neale Pickett +© 2009 Mate Nagy +© 2010-2016 Hiltjo Posthuma +© 2010-2012 Connor Lane Smith +© 2011 Christoph Lohmann <20h@r-36.net> +© 2015-2016 Quentin Rameau +© 2015-2016 Eric Pruitt +© 2016-2017 Markus Teich + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/cmd/dwm/client.c b/src/cmd/dwm/client.c new file mode 100644 index 0000000..fa04f5f --- /dev/null +++ b/src/cmd/dwm/client.c @@ -0,0 +1,657 @@ +#include "dwm.h" + +void +applyrules(Client *c) +{ + char *class, *instance; + uint i; + Rule *r; + Monitor *m; + XClassHint ch = { nil, nil }; + + c->isfloating = 0; + c->tags = 0; + c->noswallow = -1; + + /* rule matching */ + XGetClassHint(dpy, c->win, &ch); + class = ch.res_class ? ch.res_class : broken; + instance = ch.res_name ? ch.res_name : broken; + + for (i = 0; i < arrlen(rules); i++) { + r = &rules[i]; + if ((!r->title || strstr(c->name, r->title)) + && (!r->class || strstr(class, r->class)) + && (!r->instance || strstr(instance, r->instance))) + { + c->isterm = r->isterm; + c->noswallow = r->noswallow; + c->isfloating = r->isfloating; + c->tags |= r->tags; + for (m = mons; m && m->num != r->monitor; m = m->next) + ; + if (m) + c->mon = m; + } + } + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; +} + +int +applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) +{ + int baseismin; + Monitor *m = c->mon; + + /* set minimum possible */ + *w = MAX(1, *w); + *h = MAX(1, *h); + if (interact) { + if (*x > sw) + *x = sw - WIDTH(c); + if (*y > sh) + *y = sh - HEIGHT(c); + if (*x + *w + 2 * c->bw < 0) + *x = 0; + if (*y + *h + 2 * c->bw < 0) + *y = 0; + } else { + if (*x >= m->wx + m->ww) + *x = m->wx + m->ww - WIDTH(c); + if (*y >= m->wy + m->wh) + *y = m->wy + m->wh - HEIGHT(c); + if (*x + *w + 2 * c->bw <= m->wx) + *x = m->wx; + if (*y + *h + 2 * c->bw <= m->wy) + *y = m->wy; + } + if (*h < bh) + *h = bh; + if (*w < bh) + *w = bh; + if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + /* see last two sentences in ICCCM 4.1.2.3 */ + baseismin = c->basew == c->minw && c->baseh == c->minh; + if (!baseismin) { /* temporarily remove base dimensions */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for aspect limits */ + if (c->mina > 0 && c->maxa > 0) { + if (c->maxa < (float)*w / *h) + *w = *h * c->maxa + 0.5; + else if (c->mina < (float)*h / *w) + *h = *w * c->mina + 0.5; + } + if (baseismin) { /* increment calculation requires this */ + *w -= c->basew; + *h -= c->baseh; + } + /* adjust for increment value */ + if (c->incw) + *w -= *w % c->incw; + if (c->inch) + *h -= *h % c->inch; + /* restore base dimensions */ + *w = MAX(*w + c->basew, c->minw); + *h = MAX(*h + c->baseh, c->minh); + if (c->maxw) + *w = MIN(*w, c->maxw); + if (c->maxh) + *h = MIN(*h, c->maxh); + } + return *x != c->x || *y != c->y || *w != c->w || *h != c->h; +} + +void +attach(Client *c) +{ + c->next = c->mon->clients; + c->mon->clients = c; +} + +void +enqueue(Client *c) +{ + Client *l; + + for (l = c->mon->clients; l && l->next; l = l->next) + ; + + if (l) { + l->next = c; + c->next = nil; + } +} + +void +attachbottom(Client *c) +{ + Client **tc; + c->next = nil; + for (tc = &c->mon->clients; *tc; tc = &(*tc)->next) + ; + *tc = c; +} + +void +attachstack(Client *c) +{ + c->snext = c->mon->stack; + c->mon->stack = c; +} + +void +enqueuestack(Client *c) +{ + Client *l; + for (l = c->mon->clients; l && l->next; l = l->next) + ; + + if (l) { + l->snext = c; + c->snext = nil; + } +} + +void +configure(Client *c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->bw; + ce.above = None; + ce.override_redirect = False; + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); +} + +void +detach(Client *c) +{ + Client **tc; + + for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next) + ; + *tc = c->next; +} + +void +detachstack(Client *c) +{ + Client **tc, *t; + + for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext) + ; + + *tc = c->snext; + + if (c == c->mon->sel) { + for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext) + ; + c->mon->sel = t; + } +} + +void +focus(Client *c) +{ + if (!c || !ISVISIBLE(c)) + for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext) + ; + if (selmon->sel && selmon->sel != c) + unfocus(selmon->sel, 0); + if (c) { + if (c->mon != selmon) + selmon = c->mon; + if (c->isurgent) + seturgent(c, 0); + detachstack(c); + attachstack(c); + grabbuttons(c, 1); + XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); + setfocus(c); + } else { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } + selmon->sel = c; + drawbars(); +} + +Atom +getatomprop(Client *c, Atom prop) +{ + int di; + unsigned long dl; + uchar *p = nil; + Atom da, atom = None; + + if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, + &da, &di, &dl, &dl, &p) == Success && p) { + atom = *(Atom *)p; + XFree(p); + } + return atom; +} + +void +grabbuttons(Client *c, int focused) +{ + updatenumlockmask(); + { + uint i, j; + uint modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + if (!focused) + XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, + BUTTONMASK, GrabModeSync, GrabModeSync, None, None); + for (i = 0; i < arrlen(buttons); i++) + if (buttons[i].click == ClkClientWin) + for (j = 0; j < arrlen(modifiers); j++) + XGrabButton(dpy, buttons[i].button, + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + } +} + +Client * +nexttiled(Client *c) +{ + for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next) + ; + return c; +} + +void +pop(Client *c) +{ + detach(c); + attach(c); + focus(c); + arrange(c->mon); +} + +void +resize(Client *c, int x, int y, int w, int h, int interact) +{ + if (applysizehints(c, &x, &y, &w, &h, interact)) + resizeclient(c, x, y, w, h); +} + + +void +resizeclient(Client *c, int x, int y, int w, int h) +{ + XWindowChanges wc; + + c->oldx = c->x; c->x = wc.x = x; + c->oldy = c->y; c->y = wc.y = y; + c->oldw = c->w; c->w = wc.width = w; + c->oldh = c->h; c->h = wc.height = h; + wc.border_width = c->bw; + XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + configure(c); + XSync(dpy, False); +} + +void +sendtomon(Client *c, Monitor *m) +{ + if (c->mon == m) + return; + unfocus(c, 1); + detach(c); + detachstack(c); + c->mon = m; + c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ + /* attach(c); */ + attachbottom(c); + attachstack(c); + focus(nil); + arrange(nil); +} + +void +setclientstate(Client *c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (uchar *)data, 2); +} + +int +sendevent(Client *c, Atom proto) +{ + int n; + Atom *protocols; + int exists = 0; + XEvent ev; + + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + while (!exists && n--) + exists = protocols[n] == proto; + XFree(protocols); + } + if (exists) { + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = proto; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + } + return exists; +} + +void +setfocus(Client *c) +{ + if (!c->neverfocus) { + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XChangeProperty(dpy, root, netatom[NetActiveWindow], + XA_WINDOW, 32, PropModeReplace, + (uchar *) &(c->win), 1); + } + sendevent(c, wmatom[WMTakeFocus]); +} + +void +setfullscreen(Client *c, int fullscreen) +{ + static uint32 opacity = 0xFFFFFFFFul; + if (fullscreen && !c->isfullscreen) { + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (uchar*)&netatom[NetWMFullscreen], 1); + XChangeProperty(dpy, c->win, netatom[NetWMWindowOpacity], XA_CARDINAL, 32, PropModeReplace, (uchar *)&opacity, 1L); + + c->isfullscreen = 1; + c->oldstate = c->isfloating; + c->oldbw = c->bw; + c->bw = 0; + c->isfloating = 1; + + resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh); + + XRaiseWindow(dpy, c->win); + } else if (!fullscreen && c->isfullscreen){ + XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, + PropModeReplace, (uchar*)nil, 0); + XDeleteProperty(dpy, c->win, netatom[NetWMWindowOpacity]); + + c->isfullscreen = 0; + c->isfloating = c->oldstate; + c->bw = c->oldbw; + c->x = c->oldx; + c->y = c->oldy; + c->w = c->oldw; + c->h = c->oldh; + resizeclient(c, c->x, c->y, c->w, c->h); + arrange(c->mon); + } +} + +void +seturgent(Client *c, int urg) +{ + XWMHints *wmh; + + c->isurgent = urg; + if (!(wmh = XGetWMHints(dpy, c->win))) + return; + wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint); + XSetWMHints(dpy, c->win, wmh); + XFree(wmh); +} + +void +showhide(Client *c) +{ + if (!c) + return; + if (ISVISIBLE(c)) { + /* show clients top down */ + XMoveWindow(dpy, c->win, c->x, c->y); + if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + resize(c, c->x, c->y, c->w, c->h, 0); + showhide(c->snext); + } else { + /* hide clients bottom up */ + showhide(c->snext); + XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y); + } +} + +void +swallow(Client *p, Client *c) +{ + Client *s; + + + if (c->noswallow > 0 || c->isterm) + return; + if (c->noswallow < 0 && !swallowfloating && c->isfloating) + return; + + detach(c); + detachstack(c); + + setclientstate(c, WithdrawnState); + XUnmapWindow(dpy, p->win); + + p->swallowing = c; + c->mon = p->mon; + + Window w = p->win; + p->win = c->win; + c->win = w; + + XChangeProperty(dpy, c->win, netatom[NetClientList], XA_WINDOW, 32, PropModeReplace, + (unsigned char *) &(p->win), 1); + + updatetitle(p); + s = scanner ? c : p; + XMoveResizeWindow(dpy, p->win, s->x, s->y, s->w, s->h); + arrange(p->mon); + configure(p); + updateclientlist(); +} + +Client * +termof(Client *w) +{ + Client *c; + Monitor *m; + + if (!w->pid || w->isterm) + return NULL; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->isterm && !c->swallowing && c->pid && isdescendent(c->pid, w->pid)) + return c; + } + } + + return NULL; +} + +void +unfocus(Client *c, int setfocus) +{ + if (!c) + return; + grabbuttons(c, 0); + XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); + if (setfocus) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); + } +} + +void +unmanage(Client *c, int destroyed) +{ + Client *s; + Monitor *m = c->mon; + XWindowChanges wc; + + if (c->swallowing) { + unswallow(c); + return; + } + + s = swallowing(c->win); + if (s) { + free(s->swallowing); + s->swallowing = nil; + arrange(m); + focus(nil); + return; + } + + + detach(c); + detachstack(c); + + if (!destroyed) { + wc.border_width = c->oldbw; + XGrabServer(dpy); /* avoid race conditions */ + XSetErrorHandler(xerrordummy); + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, c->win); + setclientstate(c, WithdrawnState); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } + free(c); + focus(nil); + updateclientlist(); + arrange(m); + + if (!s) { + // arrange(m); + focus(nil); + updateclientlist(); + } +} + +void +unswallow(Client *c) +{ + c->win = c->swallowing->win; + + free(c->swallowing); + c->swallowing = nil; + + XDeleteProperty(dpy, c->win, netatom[NetClientList]); + + /* unfullscreen the client */ + setfullscreen(c, 0); + updatetitle(c); + arrange(c->mon); + XMapWindow(dpy, c->win); + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + setclientstate(c, NormalState); + focus(nil); + arrange(c->mon); +} + + +void +updatesizehints(Client *c) +{ + long msize; + XSizeHints size; + + if (!XGetWMNormalHints(dpy, c->win, &size, &msize)) + /* size is uninitialized, ensure that size.flags aren't used */ + size.flags = PSize; + if (size.flags & PBaseSize) { + c->basew = size.base_width; + c->baseh = size.base_height; + } else if (size.flags & PMinSize) { + c->basew = size.min_width; + c->baseh = size.min_height; + } else + c->basew = c->baseh = 0; + if (size.flags & PResizeInc) { + c->incw = size.width_inc; + c->inch = size.height_inc; + } else + c->incw = c->inch = 0; + if (size.flags & PMaxSize) { + c->maxw = size.max_width; + c->maxh = size.max_height; + } else + c->maxw = c->maxh = 0; + if (size.flags & PMinSize) { + c->minw = size.min_width; + c->minh = size.min_height; + } else if (size.flags & PBaseSize) { + c->minw = size.base_width; + c->minh = size.base_height; + } else + c->minw = c->minh = 0; + if (size.flags & PAspect) { + c->mina = (float)size.min_aspect.y / size.min_aspect.x; + c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + } else + c->maxa = c->mina = 0.0; + c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh); +} + +void +updatetitle(Client *c) +{ + if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if (c->name[0] == '\0') /* hack to mark broken clients */ + strcpy(c->name, broken); +} + +void +updatewindowtype(Client *c) +{ + Atom state = getatomprop(c, netatom[NetWMState]); + Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + + if (state == netatom[NetWMFullscreen]) + setfullscreen(c, 1); + if (wtype == netatom[NetWMWindowTypeDialog]) + c->isfloating = 1; +} + +void +updatewmhints(Client *c) +{ + XWMHints *wmh; + + if ((wmh = XGetWMHints(dpy, c->win))) { + if (c == selmon->sel && wmh->flags & XUrgencyHint) { + wmh->flags &= ~XUrgencyHint; + XSetWMHints(dpy, c->win, wmh); + } else + c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0; + if (wmh->flags & InputHint) + c->neverfocus = !wmh->input; + else + c->neverfocus = 0; + XFree(wmh); + } +} diff --git a/src/cmd/dwm/config.h b/src/cmd/dwm/config.h new file mode 100644 index 0000000..1f82b1f --- /dev/null +++ b/src/cmd/dwm/config.h @@ -0,0 +1,141 @@ +/* See LICENSE file for copyright and license details. */ +#define VERSION "1" + +/* appearance */ +static uint borderpx = 2; /* border pixel of windows */ +static uint gapx = 4; /* gaps between windows */ +static uint snap = 32; /* snap pixel */ +static int swallowfloating = 0; /* 1 will swallow floating by default */ +static int showbar = 1; /* 0 means no bar */ +static int topbar = 1; /* 0 means bottom bar */ +static char *fonts[] = { "consolas:size=16" }; +static char col_gray1[] = "#504945"; +static char col_gray2[] = "#282828"; +static char col_gray3[] = "#fbf1c7"; +static char col_gray4[] = "#504945"; +static char col_cyan[] = "#83a598"; +static char *colors[][3] = +{ + /* fg bg border */ + [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, + [SchemeSel] = { col_gray4, col_cyan, col_cyan }, +}; + +/* tagging */ +static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + +static Rule rules[] = { + /* xprop(1): + * WM_CLASS(STRING) = instance, class + * WM_NAME(STRING) = title + */ + /* class instance title tags mask isfloating isterminal noswallow monitor */ + { "Gimp", nil, nil, 0, 1, 0, 0, -1 }, + { "Inkscape", nil, nil, 0, 1, 0, 0, -1 }, + { "zoom", nil, nil, 0, 1, 0, 0, -1 }, + { "qutebrowser", nil, nil, 0, 0, 0, 0, -1 }, + { "term-256color", nil, nil, 0, 0, 1, -1, -1 }, +}; + +/* layout(s) */ +static float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +static int nmaster = 1; /* number of clients in master area */ +static int resizehints = 1; /* 1 means respect size hints in tiled resizals */ + +static Layout layouts[] = { + /* symbol arrange function */ + { "[]=", tile }, /* first entry is default */ + { "><>", nil }, /* no layout function means floating behavior */ + { "[M]", monocle }, +}; + +/* key definitions */ +#define MODKEY Mod4Mask +#define TAGKEYS(KEY,TAG) \ + { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + +/* commands */ +static char *menucmd[] = { "menu_run", nil }; +static char *termcmd[] = { "term", nil }; +static char *webscmd[] = { "qutebrowser", nil }; +static char scratchname[] = "scratchpad"; +static char *scratchcmd[] = { "term", "-t", scratchname, "-g", "120x34", nil }; +static char *upvolcmd[] = { "vol", "+5%", nil }; +static char *lovolcmd[] = { "vol", "-5%", nil }; +static char *novolcmd[] = { "vol", "mute", nil }; + +#define XK_lovol XF86XK_AudioLowerVolume +#define XK_upvol XF86XK_AudioRaiseVolume +#define XK_novol XF86XK_AudioMute + +static Key keys[] = { + /* modifier key function argument */ + { MODKEY, XK_d, spawn, {.v = menucmd } }, + { MODKEY, XK_Return, spawn, {.v = termcmd } }, + { MODKEY, XK_q, spawn, {.v = webscmd } }, + { 0, XK_upvol, spawn, {.v = upvolcmd} }, + { 0, XK_lovol, spawn, {.v = lovolcmd} }, + { 0, XK_novol, spawn, {.v = novolcmd} }, + { MODKEY, XK_s, togglescratch, {.v = scratchcmd} }, + { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_f, togglefocus, {0} }, + { MODKEY, XK_Up, focusstack, {.i = +1 } }, + { MODKEY, XK_Down, focusstack, {.i = -1 } }, + { MODKEY|ShiftMask, XK_Up, rotatestack, {.i = +1 } }, + { MODKEY|ShiftMask, XK_Down, rotatestack, {.i = -1 } }, + { MODKEY, XK_i, incnmaster, {.i = +1 } }, + { MODKEY, XK_o, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, focusdirection, {.i = 'l'} }, + { MODKEY, XK_l, focusdirection, {.i = 'r'} }, + { MODKEY, XK_k, focusdirection, {.i = 'u'} }, + { MODKEY, XK_j, focusdirection, {.i = 'd'} }, + { MODKEY|ShiftMask, XK_h, setmfact, {.f = -0.05} }, + { MODKEY|ShiftMask, XK_l, setmfact, {.f = +0.05} }, + { MODKEY|ShiftMask, XK_k, rotatestack, {.i = -1 } }, + { MODKEY|ShiftMask, XK_j, rotatestack, {.i = +1 } }, + { MODKEY|ShiftMask, XK_Return, zoom, {0} }, + { MODKEY, XK_Tab, view, {0} }, + { MODKEY|ShiftMask, XK_q, killclient, {0} }, + { MODKEY|ShiftMask, XK_t, setlayout, {.v = &layouts[0]} }, + { MODKEY|ShiftMask, XK_f, setlayout, {.v = &layouts[1]} }, + { MODKEY|ShiftMask, XK_m, setlayout, {.v = &layouts[2]} }, + { MODKEY, XK_space, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, + { MODKEY, XK_0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, focusmon, {.i = -1 } }, + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + { MODKEY|ShiftMask, XK_e, quit, {0} }, +}; + +/* button definitions */ +/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ +static Button buttons[] = { + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} }, + { ClkWinTitle, 0, Button2, zoom, {0} }, + { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, + { ClkTagBar, MODKEY, Button3, toggletag, {0} }, +}; + diff --git a/src/cmd/dwm/drw.c b/src/cmd/dwm/drw.c new file mode 100644 index 0000000..a6d6902 --- /dev/null +++ b/src/cmd/dwm/drw.c @@ -0,0 +1,376 @@ +/* See LICENSE file for copyright and license details. */ +#include "dwm.h" + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + fatal("no font specified."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + fatal("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + rune utf8codepoint = 0; + char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8·decode(text, &utf8codepoint); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + fatal("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/src/cmd/dwm/dwm.c b/src/cmd/dwm/dwm.c new file mode 100644 index 0000000..0567650 --- /dev/null +++ b/src/cmd/dwm/dwm.c @@ -0,0 +1,1185 @@ +#include "dwm.h" + +/* global variables */ +char broken[] = ""; +char stext[256]; +int scanner; +int screen; +int sw, sh; /* X display screen geometry width, height */ +int bh, blw = 0; /* bar geometry */ +int lrpad; /* sum of left and right padding for text */ +int (*xerrorxlib)(Display *, XErrorEvent *); +uint numlockmask = 0; +void (*handler[LASTEvent]) (XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureRequest] = configurerequest, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MappingNotify] = mappingnotify, + [MapRequest] = maprequest, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, + [UnmapNotify] = unmapnotify +}; + +xcb_connection_t *xcon; + +Atom wmatom[WMLast] = {0}, netatom[NetLast] = {0}; +int running = 1; +Cur *cursor[MouseLast] = {0}; +Clr **scheme = nil; +Display *dpy = nil; +Drw *drw = nil; +Monitor *mons = nil, *selmon = nil; +Window root = {0}, wmcheckwin = {0}; + +/* compile-time check if all tags fit into an uint bit array. */ +struct NumTags { char limitexceeded[arrlen(tags) > 31 ? -1 : 1]; }; + +/* function implementations */ +void +arrange(Monitor *m) +{ + if (m) + showhide(m->stack); + else for (m = mons; m; m = m->next) + showhide(m->stack); + if (m) { + arrangemon(m); + restack(m); + } else for (m = mons; m; m = m->next) + arrangemon(m); +} + +void +arrangemon(Monitor *m) +{ + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); + if (m->lt[m->sellt]->arrange) + m->lt[m->sellt]->arrange(m); +} + +void +buttonpress(XEvent *e) +{ + uint i, x, click; + Arg arg = {0}; + Client *c; + Monitor *m; + XButtonPressedEvent *ev = &e->xbutton; + + click = ClkRootWin; + /* focus monitor if necessary */ + if ((m = wintomon(ev->window)) && m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(nil); + } + if (ev->window == selmon->barwin) { + i = x = 0; + do + x += TEXTW(tags[i]); + while (ev->x >= x && ++i < arrlen(tags)); + if (i < arrlen(tags)) { + click = ClkTagBar; + arg.ui = 1 << i; + } else if (ev->x < x + blw) + click = ClkLtSymbol; + else if (ev->x > selmon->ww - TEXTW(stext)) + click = ClkStatusText; + else + click = ClkWinTitle; + } else if ((c = wintoclient(ev->window))) { + focus(c); + restack(selmon); + XAllowEvents(dpy, ReplayPointer, CurrentTime); + click = ClkClientWin; + } + for (i = 0; i < arrlen(buttons); i++) + if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) + buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); +} + +void +checkotherwm(void) +{ + xerrorxlib = XSetErrorHandler(xerrorstart); + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask); + XSync(dpy, False); + XSetErrorHandler(xerror); + XSync(dpy, False); +} + +void +cleanup(void) +{ + Arg a = {.ui = ~0}; + Layout foo = { "", nil }; + Monitor *m; + size_t i; + + view(&a); + selmon->lt[selmon->sellt] = &foo; + for (m = mons; m; m = m->next) + while (m->stack) + unmanage(m->stack, 0); + XUngrabKey(dpy, AnyKey, AnyModifier, root); + while (mons) + cleanupmon(mons); + for (i = 0; i < MouseLast; i++) + drw_cur_free(drw, cursor[i]); + for (i = 0; i < arrlen(colors); i++) + free(scheme[i]); + XDestroyWindow(dpy, wmcheckwin); + drw_free(drw); + XSync(dpy, False); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XDeleteProperty(dpy, root, netatom[NetActiveWindow]); +} + +void +cleanupmon(Monitor *mon) +{ + Monitor *m; + + if (mon == mons) + mons = mons->next; + else { + for (m = mons; m && m->next != mon; m = m->next); + m->next = mon->next; + } + XUnmapWindow(dpy, mon->barwin); + XDestroyWindow(dpy, mon->barwin); + free(mon); +} + +void +clientmessage(XEvent *e) +{ + XClientMessageEvent *cme = &e->xclient; + Client *c = wintoclient(cme->window); + + if (!c) + return; + if (cme->message_type == netatom[NetWMState]) { + if (cme->data.l[1] == netatom[NetWMFullscreen] + || cme->data.l[2] == netatom[NetWMFullscreen]) + setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + } else if (cme->message_type == netatom[NetActiveWindow]) { + if (c != selmon->sel && !c->isurgent) + seturgent(c, 1); + } +} + +void +configurenotify(XEvent *e) +{ + Monitor *m; + Client *c; + XConfigureEvent *ev = &e->xconfigure; + int dirty; + + /* TODO: updategeom handling sucks, needs to be simplified */ + if (ev->window == root) { + dirty = (sw != ev->width || sh != ev->height); + sw = ev->width; + sh = ev->height; + if (updategeom() || dirty) { + drw_resize(drw, sw, bh); + updatebars(); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) + if (c->isfullscreen) + resizeclient(c, m->mx, m->my, m->mw, m->mh); + XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } + focus(nil); + arrange(nil); + } + } +} + +void +configurerequest(XEvent *e) +{ + Client *c; + Monitor *m; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if ((c = wintoclient(ev->window))) { + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { + m = c->mon; + if (ev->value_mask & CWX) { + c->oldx = c->x; + c->x = m->mx + ev->x; + } + if (ev->value_mask & CWY) { + c->oldy = c->y; + c->y = m->my + ev->y; + } + if (ev->value_mask & CWWidth) { + c->oldw = c->w; + c->w = ev->width; + } + if (ev->value_mask & CWHeight) { + c->oldh = c->h; + c->h = ev->height; + } + if ((c->x + c->w) > m->mx + m->mw && c->isfloating) + c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */ + if ((c->y + c->h) > m->my + m->mh && c->isfloating) + c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ + if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) + configure(c); + if (ISVISIBLE(c)) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); + } else { + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + } + XSync(dpy, False); +} + +Monitor * +createmon(void) +{ + Monitor *m; + + m = ecalloc(1, sizeof(Monitor)); + m->tagset[0] = m->tagset[1] = 1; + m->mfact = mfact; + m->nmaster = nmaster; + m->showbar = showbar; + m->topbar = topbar; + m->lt[0] = &layouts[0]; + m->lt[1] = &layouts[1 % arrlen(layouts)]; + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + return m; +} + +void +destroynotify(XEvent *e) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if ((c = wintoclient(ev->window))) + unmanage(c, 1); + else if ((c = swallowing(ev->window))) + unmanage(c->swallowing, 1); +} + +Monitor * +dirtomon(int dir) +{ + Monitor *m = nil; + + if (dir > 0) { + if (!(m = selmon->next)) + m = mons; + } else if (selmon == mons) + for (m = mons; m->next; m = m->next); + else + for (m = mons; m->next != selmon; m = m->next); + return m; +} + +void +drawbar(Monitor *m) +{ + int x, w, tw = 0; + int boxs = drw->fonts->h / 9; + int boxw = drw->fonts->h / 6 + 2; + uint i, occ = 0, urg = 0; + Client *c; + + /* draw status first so it can be overdrawn by tags later */ + if(m == selmon) { /* status is only drawn on selected monitor */ + drw_setscheme(drw, scheme[SchemeNorm]); + tw = TEXTW(stext) - lrpad + 2; /* 2px right padding */ + drw_text(drw, m->ww - tw, 0, tw, bh, 0, stext, 0); + } + + for(c = m->clients; c; c = c->next) { + occ |= c->tags; + if (c->isurgent) + urg |= c->tags; + } + x = 0; + for(i = 0; i < arrlen(tags); i++) { + w = TEXTW(tags[i]); + drw_setscheme(drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i); + if (occ & 1 << i) + drw_rect(drw, x + boxs, boxs, boxw, boxw, + m == selmon && selmon->sel && selmon->sel->tags & 1 << i, + urg & 1 << i); + x += w; + } + w = blw = TEXTW(m->ltsymbol); + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + + if((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0); + if (m->sel->isfloating) + drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0); + } else { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x, 0, w, bh, 1, 1); + } + } + drw_map(drw, m->barwin, 0, 0, m->ww, bh); +} + +void +drawbars(void) +{ + Monitor *m; + + for (m = mons; m; m = m->next) + drawbar(m); +} + +void +enternotify(XEvent *e) +{ + Client *c; + Monitor *m; + XCrossingEvent *ev = &e->xcrossing; + + if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) + return; + c = wintoclient(ev->window); + m = c ? c->mon : wintomon(ev->window); + if (m != selmon) { + unfocus(selmon->sel, 1); + selmon = m; + } else if (!c || c == selmon->sel) + return; + focus(c); +} + +void +expose(XEvent *e) +{ + Monitor *m; + XExposeEvent *ev = &e->xexpose; + + if (ev->count == 0 && (m = wintomon(ev->window))) + drawbar(m); +} + +/* there are some broken focus acquiring clients needing extra handling */ +void +focusin(XEvent *e) +{ + XFocusChangeEvent *ev = &e->xfocus; + + if (selmon->sel && ev->window != selmon->sel->win) + setfocus(selmon->sel); +} + +int +getrootptr(int *x, int *y) +{ + int di; + uint dui; + Window dummy; + + return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui); +} + +long +getstate(Window w) +{ + int format; + long result = -1; + unsigned char *p = nil; + unsigned long n, extra; + Atom real; + + if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **)&p) != Success) + return -1; + if (n != 0) + result = *p; + XFree(p); + return result; +} + +int +gettextprop(Window w, Atom atom, char *text, uint size) +{ + char **list = nil; + int n; + XTextProperty name; + + if (!text || size == 0) + return 0; + text[0] = '\0'; + if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) + return 0; + if (name.encoding == XA_STRING) + strncpy(text, (char *)name.value, size - 1); + else { + if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + } + text[size - 1] = '\0'; + XFree(name.value); + return 1; +} + +void +grabkeys(void) +{ + updatenumlockmask(); + { + uint i, j; + uint modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < arrlen(keys); i++) + if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) + for (j = 0; j < arrlen(modifiers); j++) + XGrabKey(dpy, code, keys[i].mod | modifiers[j], root, + True, GrabModeAsync, GrabModeAsync); + } +} + +static +int +isuniquegeom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info) +{ + while (n--) + if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org + && unique[n].width == info->width && unique[n].height == info->height) + return 0; + return 1; +} + +void +keypress(XEvent *e) +{ + uint i; + KeySym keysym; + XKeyEvent *ev; + + ev = &e->xkey; + keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); + for (i = 0; i < arrlen(keys); i++) + if (keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) + keys[i].func(&(keys[i].arg)); +} + +void +manage(Window w, XWindowAttributes *wa) +{ + Client *c, *t = nil, *term = nil; + Window trans = None; + XWindowChanges wc; + + c = ecalloc(1, sizeof(Client)); + c->win = w; + c->pid = winpid(w); + /* geometry */ + c->x = c->oldx = wa->x; + c->y = c->oldy = wa->y; + c->w = c->oldw = wa->width; + c->h = c->oldh = wa->height; + c->oldbw = wa->border_width; + + updatetitle(c); + if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { + c->mon = t->mon; + c->tags = t->tags; + } else { + c->mon = selmon; + applyrules(c); + term = termof(c); + } + + if (c->x + WIDTH(c) > c->mon->mx + c->mon->mw) + c->x = c->mon->mx + c->mon->mw - WIDTH(c); + if (c->y + HEIGHT(c) > c->mon->my + c->mon->mh) + c->y = c->mon->my + c->mon->mh - HEIGHT(c); + c->x = MAX(c->x, c->mon->mx); + /* only fix client y-offset, if the client center might cover the bar */ + c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) + && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); + c->bw = borderpx; + + selmon->tagset[selmon->seltags] &= ~scratchtag; + if(!strcmp(c->name, scratchname)) { + c->mon->tagset[c->mon->seltags] |= c->tags = scratchtag; + c->isfloating = 1; + c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2); + c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2); + } + + wc.border_width = c->bw; + XConfigureWindow(dpy, w, CWBorderWidth, &wc); + XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel); + configure(c); /* propagates border_width, if size doesn't change */ + updatewindowtype(c); + updatesizehints(c); + updatewmhints(c); + XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + grabbuttons(c, 0); + + if (!c->isfloating) + c->isfloating = c->oldstate = trans != None || c->isfixed; + if (c->isfloating) + XRaiseWindow(dpy, c->win); + + /* attach(c); */ + attachbottom(c); + attachstack(c); + + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */ + setclientstate(c, NormalState); + if (c->mon == selmon) + unfocus(selmon->sel, 0); + c->mon->sel = c; + arrange(c->mon); + XMapWindow(dpy, c->win); + if (term) + swallow(term, c); + focus(nil); +} + +void +mappingnotify(XEvent *e) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if (ev->request == MappingKeyboard) + grabkeys(); +} + +void +maprequest(XEvent *e) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) + return; + if (!wintoclient(ev->window)) + manage(ev->window, &wa); +} + +void +monocle(Monitor *m) +{ + uint n = 0; + Client *c; + + for (c = m->clients; c; c = c->next) + if (ISVISIBLE(c)) + n++; + if (n > 0) /* override layout symbol */ + snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); + for (c = nexttiled(m->clients); c; c = nexttiled(c->next)) + resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0); +} + +void +motionnotify(XEvent *e) +{ + static Monitor *mon = nil; + Monitor *m; + XMotionEvent *ev = &e->xmotion; + + if (ev->window != root) + return; + if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) { + unfocus(selmon->sel, 1); + selmon = m; + focus(nil); + } + mon = m; +} + +void +propertynotify(XEvent *e) +{ + Client *c; + Window trans; + XPropertyEvent *ev = &e->xproperty; + + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) + updatestatus(); + else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { + default: break; + case XA_WM_TRANSIENT_FOR: + if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && + (c->isfloating = (wintoclient(trans)) != nil)) + arrange(c->mon); + break; + case XA_WM_NORMAL_HINTS: + updatesizehints(c); + break; + case XA_WM_HINTS: + updatewmhints(c); + drawbars(); + break; + } + if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); + } +} + +Monitor * +recttomon(int x, int y, int w, int h) +{ + Monitor *m, *r = selmon; + int a, area = 0; + + for (m = mons; m; m = m->next) + if ((a = INTERSECT(x, y, w, h, m)) > area) { + area = a; + r = m; + } + return r; +} + +void +restack(Monitor *m) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawbar(m); + if (!m->sel) + return; + if (m->sel->isfloating || !m->lt[m->sellt]->arrange) + XRaiseWindow(dpy, m->sel->win); + if (m->lt[m->sellt]->arrange) { + wc.stack_mode = Below; + wc.sibling = m->barwin; + for (c = m->stack; c; c = c->snext) + if (!c->isfloating && ISVISIBLE(c)) { + XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(dpy, False); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); +} + +void +run(void) +{ + XEvent ev; + /* main event loop */ + XSync(dpy, False); + while (running && !XNextEvent(dpy, &ev)) + if (handler[ev.type]) + handler[ev.type](&ev); /* call handler */ +} + +void +scan(void) +{ + uint i, num; + Window d1, d2, *wins = nil; + XWindowAttributes wa; + char swin[256]; + + scanner = 1; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) + manage(wins[i], &wa); + else if (gettextprop(wins[i], netatom[NetClientList], swin, sizeof swin)) + manage(wins[i], &wa); + } + for (i = 0; i < num; i++) { /* now the transients */ + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (XGetTransientForHint(dpy, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) + manage(wins[i], &wa); + } + if (wins) + XFree(wins); + } + + scanner = 0; +} + +void +setup(void) +{ + int i; + XSetWindowAttributes wa; + Atom utf8string; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + sw = DisplayWidth(dpy, screen); + sh = DisplayHeight(dpy, screen); + root = RootWindow(dpy, screen); + drw = drw_create(dpy, screen, root, sw, sh); + if (!drw_fontset_create(drw, fonts, arrlen(fonts))) + fatal("no fonts could be loaded."); + + lrpad = drw->fonts->h; + bh = drw->fonts->h + 2; + updategeom(); + + /* init atoms */ + utf8string = XInternAtom(dpy, "UTF8_STRING", False); + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False); + wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); + + netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False); + netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + netatom[NetWMWindowOpacity] = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False); + netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False); + netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + + /* init cursors */ + cursor[MouseNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[MouseResize] = drw_cur_create(drw, XC_sizing); + cursor[MouseMove] = drw_cur_create(drw, XC_fleur); + + /* init appearance */ + scheme = ecalloc(arrlen(colors), sizeof(Clr *)); + for (i = 0; i < arrlen(colors); i++) + scheme[i] = drw_scm_create(drw, colors[i], 3); + + /* init bars */ + updatebars(); + updatestatus(); + + /* supporting window for NetWMCheck */ + wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (uchar *) &wmcheckwin, 1); + XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8, + PropModeReplace, (uchar *) "dwm", 3); + XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32, + PropModeReplace, (uchar *) &wmcheckwin, 1); + /* EWMH support per view */ + XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (uchar *) netatom, NetLast); + XDeleteProperty(dpy, root, netatom[NetClientList]); + /* select events */ + wa.cursor = cursor[MouseNormal]->cursor; + wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask + |ButtonPressMask|PointerMotionMask|EnterWindowMask + |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); + XSelectInput(dpy, root, wa.event_mask); + grabkeys(); + focus(nil); +} + + +void +sigchld(int unused) +{ + if (signal(SIGCHLD, sigchld) == SIG_ERR) + fatal("can't install SIGCHLD handler:"); + while (0 < waitpid(-1, nil, WNOHANG)); +} + +Client * +swallowing(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->swallowing && c->swallowing->win == w) + return c; + } + } + + return nil; +} + +void +tile(Monitor *m) +{ + uint i, n, h, r, mw, my, ty; + Client *c; + + for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++) + ; + + if (n == 0) + return; + + if (n > m->nmaster) + mw = m->nmaster ? (m->ww+gapx) * m->mfact : 0; + else + mw = m->ww - gapx; + + for (i = 0, my = ty = gapx, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + if (i < m->nmaster) { + r = MIN(n, m->nmaster) - i; + h = (m->wh - my)/r - gapx; + resize(c, m->wx + gapx, m->wy + my, mw - (2*c->bw) - gapx, h - (2*c->bw), 0); + if (my + HEIGHT(c) + gapx < m->wh) + my += HEIGHT(c) + gapx; + } else { + r = (n-i); + h = (m->wh - ty)/r - gapx; + resize(c, m->wx + mw + gapx, m->wy + ty, m->ww - mw - (2*c->bw) - (2*gapx), h - (2*c->bw), 0); + if (ty + HEIGHT(c) + gapx < m->wh) + ty += HEIGHT(c) + gapx; + } +} + +void +unmapnotify(XEvent *e) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if ((c = wintoclient(ev->window))) { + if (ev->send_event) + setclientstate(c, WithdrawnState); + else + unmanage(c, 0); + } +} + +void +updatebars(void) +{ + Monitor *m; + XSetWindowAttributes wa = { + .override_redirect = True, + .background_pixmap = ParentRelative, + .event_mask = ButtonPressMask|ExposureMask + }; + XClassHint ch = {"dwm", "dwm"}; + for (m = mons; m; m = m->next) { + if (m->barwin) + continue; + m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->barwin, cursor[MouseNormal]->cursor); + XMapRaised(dpy, m->barwin); + XSetClassHint(dpy, m->barwin, &ch); + } +} + +void +updatebarpos(Monitor *m) +{ + m->wy = m->my; + m->wh = m->mh; + if (m->showbar) { + m->wh -= bh; + m->by = m->topbar ? m->wy : m->wy + m->wh; + m->wy = m->topbar ? m->wy + bh : m->wy; + } else + m->by = -bh; +} + +void +updateclientlist() +{ + Client *c; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); +} + +int +updategeom(void) +{ + int dirty = 0; + + if (XineramaIsActive(dpy)) { + int i, j, n, nn; + Client *c; + Monitor *m; + XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); + XineramaScreenInfo *unique = nil; + + for (n = 0, m = mons; m; m = m->next, n++); + /* only consider unique geometries as separate screens */ + unique = ecalloc(nn, sizeof(XineramaScreenInfo)); + for (i = 0, j = 0; i < nn; i++) + if (isuniquegeom(unique, j, &info[i])) + memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo)); + XFree(info); + nn = j; + if (n <= nn) { /* new monitors available */ + for (i = 0; i < (nn - n); i++) { + for (m = mons; m && m->next; m = m->next); + if (m) + m->next = createmon(); + else + mons = createmon(); + } + for (i = 0, m = mons; i < nn && m; m = m->next, i++) + if (i >= n + || unique[i].x_org != m->mx || unique[i].y_org != m->my + || unique[i].width != m->mw || unique[i].height != m->mh) + { + dirty = 1; + m->num = i; + m->mx = m->wx = unique[i].x_org; + m->my = m->wy = unique[i].y_org; + m->mw = m->ww = unique[i].width; + m->mh = m->wh = unique[i].height; + updatebarpos(m); + } + } else { /* less monitors available nn < n */ + for (i = nn; i < n; i++) { + for (m = mons; m && m->next; m = m->next); + while ((c = m->clients)) { + dirty = 1; + m->clients = c->next; + detachstack(c); + c->mon = mons; + /* attach(c); */ + attachbottom(c); + attachstack(c); + } + if (m == selmon) + selmon = mons; + cleanupmon(m); + } + } + free(unique); + } else + { /* default monitor setup */ + if (!mons) + mons = createmon(); + if (mons->mw != sw || mons->mh != sh) { + dirty = 1; + mons->mw = mons->ww = sw; + mons->mh = mons->wh = sh; + updatebarpos(mons); + } + } + if (dirty) { + selmon = mons; + selmon = wintomon(root); + } + return dirty; +} + +void +updatenumlockmask(void) +{ + uint i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for (i = 0; i < 8; i++) + for (j = 0; j < modmap->max_keypermod; j++) + if (modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, XK_Num_Lock)) + numlockmask = (1 << i); + XFreeModifiermap(modmap); +} + +void +updatestatus(void) +{ + if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext))) + strcpy(stext, "dwm-"VERSION); + drawbar(selmon); +} + +pid_t +winpid(Window w) +{ + pid_t result = 0; + + xcb_res_client_id_spec_t spec = {0}; + spec.client = w; + spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; + + xcb_generic_error_t *e = NULL; + xcb_res_query_client_ids_cookie_t c = xcb_res_query_client_ids(xcon, 1, &spec); + xcb_res_query_client_ids_reply_t *r = xcb_res_query_client_ids_reply(xcon, c, &e); + + if (!r) + return (pid_t)0; + + xcb_res_client_id_value_iterator_t i = xcb_res_query_client_ids_ids_iterator(r); + for (; i.rem; xcb_res_client_id_value_next(&i)) { + spec = i.data->spec; + if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { + uint32_t *t = xcb_res_client_id_value_value(i.data); + result = *t; + break; + } + } + + free(r); + + if (result == (pid_t)-1) + result = 0; + + return result; +} + +Client * +wintoclient(Window w) +{ + Client *c; + Monitor *m; + + for (m = mons; m; m = m->next) + for (c = m->clients; c; c = c->next) + if (c->win == w) + return c; + return nil; +} + +Monitor * +wintomon(Window w) +{ + int x, y; + Client *c; + Monitor *m; + + if (w == root && getrootptr(&x, &y)) + return recttomon(x, y, 1, 1); + for (m = mons; m; m = m->next) + if (w == m->barwin) + return m; + if ((c = wintoclient(w))) + return c->mon; + return selmon; +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) + return 0; + fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +int +xerrordummy(Display *dpy, XErrorEvent *ee) +{ + return 0; +} + +/* Startup Error handler to check if another window manager + * is already running. */ +int +xerrorstart(Display *dpy, XErrorEvent *ee) +{ + fatal("dwm: another window manager is already running"); + return -1; +} + +int +main(int argc, char *argv[]) +{ + if (argc == 2 && !strcmp("-v", argv[1])) + fatal("dwm-"VERSION); + else if (argc != 1) + fatal("usage: dwm [-v]"); + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(nil))) + fatal("dwm: cannot open display"); + if (!(xcon = XGetXCBConnection(dpy))) + fatal("dwm: cannot get xcb connection"); + + checkotherwm(); + setup(); + +#ifdef __OpenBSD__ + if (pledge("stdio rpath proc exec", nil) == -1) + fatal("pledge"); +#endif /* __OpenBSD__ */ + + scan(); + run(); + cleanup(); + + XCloseDisplay(dpy); + return 0; +} diff --git a/src/cmd/dwm/dwm.h b/src/cmd/dwm/dwm.h new file mode 100644 index 0000000..afec1f2 --- /dev/null +++ b/src/cmd/dwm/dwm.h @@ -0,0 +1,384 @@ +/* See LICENSE file for copyright and license details. */ +#pragma once +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* macros */ +#define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ + * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define MOUSEMASK (BUTTONMASK|PointerMotionMask) +#define WIDTH(X) ((X)->w + 2 * (X)->bw) +#define HEIGHT(X) ((X)->h + 2 * (X)->bw) +#define TAGMASK ((1 << arrlen(tags)) - 1) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + + +/* enums */ +enum +{ + MouseNormal, + MouseResize, + MouseMove, + MouseLast, +}; /* mouse states */ + +enum +{ + SchemeNorm, + SchemeSel +}; /* color schemes */ + +enum +{ + NetSupported, + NetWMName, + NetWMState, + NetWMCheck, + NetWMFullscreen, + NetActiveWindow, + NetWMWindowType, + NetWMWindowTypeDialog, + NetWMWindowOpacity, + NetClientList, + NetLast +}; /* EWMH atoms */ + +enum +{ + WMProtocols, + WMDelete, + WMState, + WMTakeFocus, + WMLast +}; /* default atoms */ + +enum +{ + ClkTagBar, + ClkLtSymbol, + ClkStatusText, + ClkWinTitle, + ClkClientWin, + ClkRootWin, + ClkLast +}; /* clicks */ + +enum +{ + ColFg, + ColBg, + ColBorder +}; /* color scheme index */ + +typedef struct Monitor Monitor; +typedef struct Layout Layout; +typedef struct Client Client; +typedef struct Keyboard Keyboard; +typedef struct Button Button; +typedef struct Key Key; +typedef struct Rule Rule; +typedef union Arg Arg; + +union Arg { + int i; + uint ui; + float f; + void *v; +}; + +struct Button { + uint click; + uint mask; + uint button; + void (*func)(Arg *arg); + Arg arg; +}; + +struct Client { + char name[256]; + float mina, maxa; + int x, y, w, h; + int oldx, oldy, oldw, oldh; + int basew, baseh, incw, inch, maxw, maxh, minw, minh; + int bw, oldbw; + uint tags; + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, isterm, noswallow; + pid_t pid; + Client *next; + Client *snext; + Client *swallowing; + Monitor *mon; + Window win; +}; + +struct Key { + uint mod; + KeySym keysym; + void (*func)(Arg *); + Arg arg; +}; + +struct Layout { + char *symbol; + void (*arrange)(Monitor *); +}; + +struct Monitor { + char ltsymbol[16]; + float mfact; + int nmaster; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + uint seltags; + uint sellt; + uint tagset[2]; + int showbar; + int topbar; + Client *clients; + Client *sel; + Client *stack; + Monitor *next; + Window barwin; + Layout *lt[2]; +}; + +struct Rule { + char *class; + char *instance; + char *title; + uint tags; + int isfloating; + int isterm; + int noswallow; + int monitor; +}; + +/* draw.c */ +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + uint h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +typedef XftColor Clr; + +typedef struct { + uint w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* global state */ + +extern char broken[]; +extern char stext[256]; +extern int scanner; +extern int screen; +extern int sw, sh; +extern int bh, blw; +extern int lrpad; +extern int (*xerrorxlib)(Display *, XErrorEvent *); +extern uint numlockmask; +extern void (*handler[LASTEvent]) (XEvent *); +extern int scratchtag; + +extern xcb_connection_t *xcon; + +extern Atom wmatom[WMLast], netatom[NetLast]; +extern int running; +extern Cur *cursor[MouseLast]; +extern Clr **scheme; +extern Display *dpy; +extern Drw *drw; +extern Monitor *mons, *selmon; +extern Window root, wmcheckwin; + +// ----------------------------------------------------------------------- +// function declarations + +// TODO: remove declarations that don't require global existence... +void applyrules(Client *c); +int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact); +void arrange(Monitor *m); +void arrangemon(Monitor *m); +void attach(Client *c); +void enqueue(Client *c); +void attachbottom(Client *c); +void attachstack(Client *c); +void enqueuestack(Client *c); +void buttonpress(XEvent *e); +void checkotherwm(void); +void cleanup(void); +void cleanupmon(Monitor *mon); +void clientmessage(XEvent *e); +void configure(Client *c); +void configurenotify(XEvent *e); +void configurerequest(XEvent *e); +Monitor *createmon(void); +void destroynotify(XEvent *e); +void detach(Client *c); +void detachstack(Client *c); +Monitor *dirtomon(int dir); +void drawbar(Monitor *m); +void drawbars(void); +void enternotify(XEvent *e); +void expose(XEvent *e); +void focus(Client *c); +void focusin(XEvent *e); +void focusmon(Arg *arg); +void focusstack(Arg *arg); +void focusdirection(Arg *arg); +void rotatestack(Arg *arg); +Atom getatomprop(Client *c, Atom prop); +int getrootptr(int *x, int *y); +long getstate(Window w); +int gettextprop(Window w, Atom atom, char *text, uint size); +void grabbuttons(Client *c, int focused); +void grabkeys(void); +void incnmaster(Arg *arg); +void keypress(XEvent *e); +void killclient(Arg *arg); +void manage(Window w, XWindowAttributes *wa); +void mappingnotify(XEvent *e); +void maprequest(XEvent *e); +void monocle(Monitor *m); +void motionnotify(XEvent *e); +void movemouse(Arg *arg); +Client *nexttiled(Client *c); +void pop(Client *); +void propertynotify(XEvent *e); +void quit(Arg *arg); +Monitor *recttomon(int x, int y, int w, int h); +void resize(Client *c, int x, int y, int w, int h, int interact); +void resizeclient(Client *c, int x, int y, int w, int h); +void resizemouse(Arg *arg); +void restack(Monitor *m); +void run(void); +void scan(void); +int sendevent(Client *c, Atom proto); +void sendtomon(Client *c, Monitor *m); +void setclientstate(Client *c, long state); +void setfocus(Client *c); +void setfullscreen(Client *c, int fullscreen); +void setlayout(Arg *arg); +void setmfact(Arg *arg); +void setup(void); +void seturgent(Client *c, int urg); +void showhide(Client *c); +void sigchld(int unused); +void swallow(Client *p, Client *c); +Client *swallowing(Window w); +void spawn(Arg *arg); +void tag(Arg *arg); +void tagmon(Arg *arg); +Client *termof(Client *c); +void tile(Monitor *); +void togglebar(Arg *arg); +void togglefocus(Arg *arg); +void togglefloating(Arg *arg); +void togglescratch(Arg *arg); +void toggletag(Arg *arg); +void toggleview(Arg *arg); +void unfocus(Client *c, int setfocus); +void unmanage(Client *c, int destroyed); +void unmapnotify(XEvent *e); +void unswallow(Client *c); +void updatebarpos(Monitor *m); +void updatebars(void); +void updateclientlist(void); +int updategeom(void); +void updatenumlockmask(void); +void updatesizehints(Client *c); +void updatestatus(void); +void updatetitle(Client *c); +void updatewindowtype(Client *c); +void updatewmhints(Client *c); +void view(Arg *arg); +pid_t winpid(Window w); +Client *wintoclient(Window w); +Monitor *wintomon(Window w); +int xerror(Display *dpy, XErrorEvent *ee); +int xerrordummy(Display *dpy, XErrorEvent *ee); +int xerrorstart(Display *dpy, XErrorEvent *ee); +void zoom(Arg *arg); + +#include "config.h" + +/* draw.c */ + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, uint w, uint h); +void drw_resize(Drw *drw, uint w, uint h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +uint drw_fontset_getwidth(Drw *drw, char *text); +void drw_font_getexts(Fnt *font, char *text, uint len, uint *w, uint *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, char *clrname); +Clr *drw_scm_create(Drw *drw, char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, uint w, uint h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, uint w, uint h, uint lpad, char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, uint w, uint h); + +/* util.c */ +void fatal(char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); +pid_t getparentproc(pid_t p); +pid_t isdescendent(pid_t p, pid_t c); diff --git a/src/cmd/dwm/hook.c b/src/cmd/dwm/hook.c new file mode 100644 index 0000000..9758965 --- /dev/null +++ b/src/cmd/dwm/hook.c @@ -0,0 +1,489 @@ +#include "dwm.h" + +int scratchtag = 1 << arrlen(tags); + +void +focusmon(Arg *arg) +{ + Monitor *m; + + if (!mons->next) + return; + if ((m = dirtomon(arg->i)) == selmon) + return; + unfocus(selmon->sel, 0); + selmon = m; + focus(nil); +} + +void +focusstack(Arg *arg) +{ + Client *c = nil, *i; + + if (!selmon->sel) + return; + if (arg->i > 0) { + for(c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + if(!c) + for(c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + } else { + for(i = selmon->clients; i != selmon->sel; i = i->next) + if(ISVISIBLE(i)) + c = i; + if(!c) + for(; i; i = i->next) + if (ISVISIBLE(i)) + c = i; + } + if(c) { + focus(c); + restack(selmon); + } +} + +void +focusdirection(Arg *arg) +{ + Monitor *m; + Client *it, *c; + int x, y, cx, cy; + + if(!selmon || !selmon->sel) + return; + + c = selmon->sel; + x = c->x, y = c->y; + + c = nil; + switch(arg->i) { + case 'l': + cx = INT_MIN; + cy = y; + for(m=mons; m; m=m->next) { + for(it=m->clients; it; it = it->next) { + if(ISVISIBLE(it) && (it->x < x)) { + if((it->x > cx) || ((it->x == cx) && abs(y-it->y) < abs(y-cy))) { + c = it; + cx = it->x; + cy = it->y; + } + } + } + } + break; + + case 'r': + cx = INT_MAX; + cy = y; + for(m=mons; m; m=m->next) { + for(it=m->clients; it; it = it->next) { + if(ISVISIBLE(it) && (it->x > x)) { + if((it->x < cx) || ((it->x == cx) && abs(y-it->y) < abs(y-cy))) { + c = it; + cx = it->x; + cy = it->y; + } + } + } + } + break; + + case 'u': + cx = x; + cy = INT_MIN; + for(m=mons; m; m=m->next) { + for(it=m->clients; it; it = it->next) { + if(ISVISIBLE(it) && (it->y < y)) { + if((it->y > cy) || ((it->y == cy) && abs(x-it->x) < abs(x-cx))) { + c = it; + cx = it->x; + cy = it->y; + } + } + } + } + break; + + case 'd': + cx = x; + cy = INT_MAX; + for(m=mons; m; m=m->next) { + for(it=m->clients; it; it = it->next) { + if(ISVISIBLE(it) && (it->y > y)) { + if((it->y < cy) || ((it->y == cy) && abs(x-it->x) < abs(x-cx))) { + c = it; + cx = it->x; + cy = it->y; + } + } + } + } + break; + + default: + ; + } + + if(c) { + focus(c); + restack(selmon); + if(c->mon != selmon) + restack(c->mon); + } +} + +void +rotatestack(Arg *arg) +{ + Client *c = nil, *f; + + if (!selmon->sel) + return; + + f = selmon->sel; + if (arg->i > 0) { + for (c = nexttiled(selmon->clients); c && nexttiled(c->next); c = nexttiled(c->next)) + ; + + if (c) { + detach(c); + attach(c); + detachstack(c); + attachstack(c); + } + } else { + if ((c = nexttiled(selmon->clients))) { + detach(c); + enqueue(c); + detachstack(c); + enqueuestack(c); + } + } + + if (c) { + arrange(selmon); + focus(f); + restack(selmon); + } +} + + +void +incnmaster(Arg *arg) +{ + selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + arrange(selmon); +} + +void +killclient(Arg *arg) +{ + if (!selmon->sel) + return; + if (!sendevent(selmon->sel, wmatom[WMDelete])) { + XGrabServer(dpy); + XSetErrorHandler(xerrordummy); + XSetCloseDownMode(dpy, DestroyAll); + XKillClient(dpy, selmon->sel->win); + XSync(dpy, False); + XSetErrorHandler(xerror); + XUngrabServer(dpy); + } +} + +void +movemouse(Arg *arg) +{ + int x, y, ocx, ocy, nx, ny; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support moving fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[MouseMove]->cursor, CurrentTime) != GrabSuccess) + return; + if (!getrootptr(&x, &y)) + return; + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nx = ocx + (ev.xmotion.x - x); + ny = ocy + (ev.xmotion.y - y); + if (abs(selmon->wx - nx) < snap) + nx = selmon->wx; + else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap) + nx = selmon->wx + selmon->ww - WIDTH(c); + if (abs(selmon->wy - ny) < snap) + ny = selmon->wy; + else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap) + ny = selmon->wy + selmon->wh - HEIGHT(c); + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) + togglefloating(nil); + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, nx, ny, c->w, c->h, 1); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendtomon(c, m); + selmon = m; + focus(nil); + } +} + +void +quit(Arg *arg) +{ + running = 0; +} + +void +resizemouse(Arg *arg) +{ + int ocx, ocy, nw, nh; + Client *c; + Monitor *m; + XEvent ev; + Time lasttime = 0; + + if (!(c = selmon->sel)) + return; + if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */ + return; + restack(selmon); + ocx = c->x; + ocy = c->y; + if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[MouseResize]->cursor, CurrentTime) != GrabSuccess) + return; + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type](&ev); + break; + case MotionNotify: + if ((ev.xmotion.time - lasttime) <= (1000 / 60)) + continue; + lasttime = ev.xmotion.time; + + nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1); + nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1); + if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww + && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh) + { + if (!c->isfloating && selmon->lt[selmon->sellt]->arrange + && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) + togglefloating(nil); + } + if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) + resize(c, c->x, c->y, nw, nh, 1); + break; + } + } while (ev.type != ButtonRelease); + XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); + XUngrabPointer(dpy, CurrentTime); + while (XCheckMaskEvent(dpy, EnterWindowMask, &ev)); + if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) { + sendtomon(c, m); + selmon = m; + focus(nil); + } +} + +void +setlayout(Arg *arg) +{ + if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; + strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); + if (selmon->sel) + arrange(selmon); + else + drawbar(selmon); +} + +/* arg > 1.0 will set mfact absolutely */ +void +setmfact(Arg *arg) +{ + float f; + + if (!arg || !selmon->lt[selmon->sellt]->arrange) + return; + f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; + if (f < 0.05 || f > 0.95) + return; + selmon->mfact = f; + arrange(selmon); +} + +void +spawn(Arg *arg) +{ + selmon->tagset[selmon->seltags] &= ~scratchtag; + + if (fork() == 0) { + if (dpy) + close(ConnectionNumber(dpy)); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]); + perror(" failed"); + exit(EXIT_SUCCESS); + } +} + +void +tag(Arg *arg) +{ + if (selmon->sel && arg->ui & TAGMASK) { + selmon->sel->tags = arg->ui & TAGMASK; + focus(nil); + arrange(selmon); + } +} + +void +tagmon(Arg *arg) +{ + if (!selmon->sel || !mons->next) + return; + sendtomon(selmon->sel, dirtomon(arg->i)); +} + +void +togglebar(Arg *arg) +{ + selmon->showbar = !selmon->showbar; + updatebarpos(selmon); + XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); + arrange(selmon); +} + +void +togglefloating(Arg *arg) +{ + if (!selmon->sel) + return; + if (selmon->sel->isfullscreen) /* no support for fullscreen windows */ + return; + selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; + if (selmon->sel->isfloating) + resize(selmon->sel, selmon->sel->x, selmon->sel->y, + selmon->sel->w, selmon->sel->h, 0); + arrange(selmon); +} + +void +togglescratch(Arg *arg) +{ + Client *c; + uint f = 0; + + for(c = selmon->clients; c && !(f = (c->tags & scratchtag)); c = c->next) + ; + + if(f) { + f = selmon->tagset[selmon->seltags] ^ scratchtag; + if(f) { + selmon->tagset[selmon->seltags] = f; + focus(nil); + arrange(selmon); + } + if(ISVISIBLE(c)) { + focus(c); + restack(selmon); + } + } else + spawn(arg); + +} + +void +toggletag(Arg *arg) +{ + uint newtags; + if (!selmon->sel) + return; + + newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + if (newtags) { + selmon->sel->tags = newtags; + focus(nil); + arrange(selmon); + } +} + +void +togglefocus(Arg *arg) +{ + if (selmon->sel) + setfullscreen(selmon->sel, !selmon->sel->isfullscreen); + + togglebar(arg); +} + +void +toggleview(Arg *arg) +{ + uint newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + + if (newtagset) { + selmon->tagset[selmon->seltags] = newtagset; + focus(nil); + arrange(selmon); + } +} + +void +view(Arg *arg) +{ + if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) + return; + selmon->seltags ^= 1; /* toggle sel tagset */ + if (arg->ui & TAGMASK) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focus(nil); + arrange(selmon); +} + +void +zoom(Arg *arg) +{ + Client *c = selmon->sel; + + if (!selmon->lt[selmon->sellt]->arrange + || (selmon->sel && selmon->sel->isfloating)) + return; + if (c == nexttiled(selmon->clients)) + if (!c || !(c = nexttiled(c->next))) + return; + pop(c); +} diff --git a/src/cmd/dwm/rules.mk b/src/cmd/dwm/rules.mk new file mode 100644 index 0000000..a840217 --- /dev/null +++ b/src/cmd/dwm/rules.mk @@ -0,0 +1,29 @@ +include share/push.mk + +# local sources +SRCS_$(d):=\ + $(d)/drw.c\ + $(d)/hook.c\ + $(d)/client.c\ + $(d)/util.c\ + $(d)/dwm.c + +# outputs +BINS_$(d) := $(d)/dwm + +include share/paths.mk + +# Local rules +include share/dynamic.mk +$(BINS_$(d)): TCFLAGS=\ + `$(PKG) --cflags fontconfig`\ + `$(PKG) --cflags freetype2` +$(BINS_$(d)): TCLIBS=\ + `$(PKG) --libs fontconfig`\ + `$(PKG) --libs freetype2`\ + -lX11 -lXinerama -lXft -lX11-xcb -lxcb -lxcb-res + +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libutf/libutf.a $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/dwm/util.c b/src/cmd/dwm/util.c new file mode 100644 index 0000000..0db71cc --- /dev/null +++ b/src/cmd/dwm/util.c @@ -0,0 +1,66 @@ +/* See LICENSE file for copyright and license details. */ +#include "dwm.h" + +void +fatal(char *fmt, ...) { + va_list args; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + if(fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + fatal("calloc:"); + return p; +} + +pid_t +getparentprocess(pid_t p) +{ + uint v = 0; + +#if defined(__linux__) + io·Stream *f; + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p); + + if (!(f = fopen(buf, "r"))) + return (pid_t)0; + + if (fscanf(f, "%*u %*s %*c %u", (unsigned *)&v) != 1) + v = (pid_t)0; + fclose(f); +#elif defined(__FreeBSD__) + struct kinfo_proc *proc = kinfo_getproc(p); + if (!proc) + return (pid_t)0; + + v = proc->ki_ppid; + free(proc); +#endif + return (pid_t)v; +} + +int +isdescendent(pid_t p, pid_t c) +{ + while (p != c && c != 0) + c = getparentprocess(c); + + return (int)c; +} diff --git a/src/cmd/filter/filter.c b/src/cmd/filter/filter.c new file mode 100644 index 0000000..abc9a88 --- /dev/null +++ b/src/cmd/filter/filter.c @@ -0,0 +1,104 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include + +#define FLAG(x) (flag[(x)-'a']) + +static void filter(const char *, const char *); +static void usage(void); + +static int match = 0; +static int flag[26]; +static struct stat old, new; + +static +void +filter(const char *path, const char *name) +{ + struct stat st, ln; + + if ((!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */ + && (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */ + && (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */ + && (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */ + && (!FLAG('e') || access(path, F_OK) == 0) /* exists */ + && (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */ + && (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */ + && (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */ + && (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */ + && (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */ + && (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */ + && (!FLAG('r') || access(path, R_OK) == 0) /* readable */ + && (!FLAG('s') || st.st_size > 0) /* not empty */ + && (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */ + && (!FLAG('w') || access(path, W_OK) == 0) /* writable */ + && (!FLAG('x') || access(path, X_OK) == 0)) != FLAG('v')) { /* executable */ + if (FLAG('q')) + exit(0); + match = 1; + puts(name); + } +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-abcdefghlpqrsuvwx] " + "[-n file] [-o file] [file...]\n", argv0); + exit(2); /* like test(1) return > 1 on error */ +} + +int +main(int argc, char *argv[]) +{ + struct dirent *d; + char path[PATH_MAX], *line = NULL, *file; + size_t linesiz = 0; + ssize_t n; + DIR *dir; + int r; + + ARGBEGIN { + case 'n': /* newer than file */ + case 'o': /* older than file */ + file = EARGF(usage()); + if (!(FLAG(ARGC()) = !stat(file, (ARGC() == 'n' ? &new : &old)))) + perror(file); + break; + default: + /* miscellaneous operators */ + if (strchr("abcdefghlpqrsuvwx", ARGC())) + FLAG(ARGC()) = 1; + else + usage(); /* unknown flag */ + } ARGEND; + + if (!argc) { + /* read list from stdin */ + while ((n = getline(&line, &linesiz, stdin)) > 0) { + if (n && line[n - 1] == '\n') + line[n - 1] = '\0'; + filter(line, line); + } + free(line); + } else { + for (; argc; argc--, argv++) { + if (FLAG('l') && (dir = opendir(*argv))) { + /* filter directory contents */ + while ((d = readdir(dir))) { + r = snprintf(path, sizeof path, "%s/%s", + *argv, d->d_name); + if (r >= 0 && (size_t)r < sizeof path) + filter(path, d->d_name); + } + closedir(dir); + } else { + filter(*argv, *argv); + } + } + } + return match ? 0 : 1; +} diff --git a/src/cmd/filter/rules.mk b/src/cmd/filter/rules.mk new file mode 100644 index 0000000..13ddd56 --- /dev/null +++ b/src/cmd/filter/rules.mk @@ -0,0 +1,14 @@ +include share/push.mk + +# local sources +SRCS_$(d):=$(d)/filter.c +# outputs +BINS_$(d):=$(d)/filter + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/ic/LICENSE b/src/cmd/ic/LICENSE new file mode 100644 index 0000000..a5816a8 --- /dev/null +++ b/src/cmd/ic/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +(C)opyright 2014-2018 Hiltjo Posthuma +(C)opyright 2005-2006 Anselm R. Garbe +(C)opyright 2005-2011 Nico Golde + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/cmd/ic/ic.1 b/src/cmd/ic/ic.1 new file mode 100644 index 0000000..3302dad --- /dev/null +++ b/src/cmd/ic/ic.1 @@ -0,0 +1,100 @@ +.TH II 1 ic\-VERSION +.SH NAME +ic \- irc it or irc improved +.SH DESCRIPTION +.B ic +is a minimalistic FIFO and filesystem based IRC client. +It creates an irc directory tree with server, channel and +nick name directories. +In every directory a FIFO file (in) and normal file (out) +is placed. This will be for example ~/irc/irc.freenode.net/. +The in file is used to communicate with the servers and the out +files includes the server messages. For every channel and every nick +name there will be new in and out files. +The basic idea of this is to be able to communicate with an IRC +server with basic command line tools. +For example if you will join a channel just do echo "/j #channel" > in +and ic creates a new channel directory with in and out file. +.SH SYNOPSIS +.B ic +.RB < \-s +.IR servername > +.RB [ \-p +.IR port ] +.RB [ \-k +.IR "environment variable" ] +.RB [ \-i +.IR prefix ] +.RB [ \-n +.IR nickname ] +.RB [ \-f +.IR realname ] +.RB < \-u +.IR sockname > +.SH OPTIONS +.TP +.BI \-s " servername" +server to connect to, for example: irc.freenode.net +.TP +.BI \-u " sockname" +connect to a UNIX domain socket instead of directly to a server. +.TP +.BI \-p " port" +lets you override the default port (6667) +.TP +.BI \-k " environment variable" +lets you specify an environment variable that contains your IRC password, e.g. IIPASS="foobar" ic -k IIPASS. +This is done in order to prevent other users from eavesdropping the server password via the process list. +.TP +.BI \-i " prefix" +lets you override the default irc path (~/irc) +.TP +.BI \-n " nickname" +lets you override the default nick ($USER) +.TP +.BI \-f " realname" +lets you specify your real name associated with your nick +.SH DIRECTORIES +.TP +.B ~/irc +In this directory the irc tree will be created. In this directory you +will find a directory for your server (default: irc.freenode.net) in +which the FIFO and the output file will be stored. +If you join a channel a new directory with the name of the channel +will be created in the ~/irc/$servername/ directory. +.SH COMMANDS +.TP +.BI /a " []" +mark yourself as away +.TP +.BI /j " #channel/nickname []" +join a channel or open private conversation with user +.TP +.BI /l " [reason]" +leave a channel or query +.TP +.BI /n " nick" +change the nick name +.TP +.BI /q " [reason]" +quit ic +.TP +.BI /t " topic" +set the topic of a channel +.SH RAW COMMANDS +.LP +Everything which is not a command will be posted into the channel or to the server. +So if you need /who just write /WHO as described in RFC#1459 to the server in FIFO. +.SH SSL PROTOCOL SUPPORT +.LP +For TLS/SSL protocol support you can connect to a local tunnel, for example with stunnel or socat. +.SH CONTACT +.LP +Subscribe to the mailinglist and write to dev (at) suckless (dot) org for suggestions, fixes, etc. +.SH AUTHORS +ic engineers, see LICENSE file +.SH SEE ALSO +.BR echo (1), +.BR tail (1) +.SH BUGS +Please report them! diff --git a/src/cmd/ic/ic.c b/src/cmd/ic/ic.c new file mode 100644 index 0000000..7fc37d8 --- /dev/null +++ b/src/cmd/ic/ic.c @@ -0,0 +1,878 @@ +/* See LICENSE file for license details. */ +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +size_t strlcpy(char *, const char *, size_t); + +#define IRC_CHANNEL_MAX 200 +#define IRC_MSG_MAX 512 /* guaranteed to be <= than PIPE_BUF */ +#define PING_TIMEOUT 300 + +enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST }; + +typedef struct Channel Channel; +struct Channel { + int fdin; + char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */ + char inpath[PATH_MAX]; /* input path */ + char outpath[PATH_MAX]; /* output path */ + Channel *next; +}; + +static Channel * channel_add(const char *); +static Channel * channel_find(const char *); +static Channel * channel_join(const char *); +static void channel_leave(Channel *); +static Channel * channel_new(const char *); +static void channel_normalize_name(char *); +static void channel_normalize_path(char *); +static int channel_open(Channel *); +static void channel_print(Channel *, const char *); +static int channel_reopen(Channel *); +static void channel_rm(Channel *); + +static void create_dirtree(const char *); +static void create_filepath(char *, size_t, const char *, const char *, const char *); +static void ewritestr(int, const char *); +static void handle_channels_input(int, Channel *); +static void handle_server_output(int); +static int isnumeric(const char *); +static void loginkey(int, const char *); +static void loginuser(int, const char *, const char *); +static void proc_channels_input(int, Channel *, char *); +static void proc_channels_privmsg(int, Channel *, char *); +static void proc_server_cmd(int, char *); +static int read_line(int, char *, size_t); +static void run(int, const char *); +static void setup(void); +static void sighandler(int); +static int tcpopen(const char *, const char *); +static size_t tokenize(char **, size_t, char *, int); +static int udsopen(const char *); +static void usage(void); + +static int isrunning = 1; +static time_t last_response = 0; +static Channel *channels = nil; +static Channel *channelmaster = nil; +static char nick[32], _nick[arrlen(nick)]; /* active nickname at runtime */ +static char ircpath[PATH_MAX]; /* irc dir (-i) */ +static char msg[IRC_MSG_MAX]; /* message buf used for communication */ + +static +void +usage(void) +{ + fprintf(stderr, "usage: %s <-s host> [-i ] [-p ] " + "[-u ] [-n ] [-k ] " + "[-f ]\n", argv0); + exit(1); +} + +static +void +ewritestr(int fd, const char *s) +{ + size_t len, off = 0; + int w = -1; + + len = strlen(s); + for (off = 0; off < len; off += w) { + if ((w = write(fd, s + off, len - off)) == -1) + break; + off += w; + } + if (w == -1) { + fprintf(stderr, "%s: write: %s\n", argv0, strerror(errno)); + exit(1); + } +} + +/* creates directories bottom-up, if necessary */ +static +void +create_dirtree(const char *dir) +{ + char tmp[PATH_MAX], *p; + struct stat st; + size_t len; + + strlcpy(tmp, dir, sizeof(tmp)); + len = strlen(tmp); + if (len > 0 && tmp[len - 1] == '/') + tmp[len - 1] = '\0'; + + if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode)) + return; /* dir exists */ + + for (p = tmp + 1; *p; p++) { + if (*p != '/') + continue; + *p = '\0'; + mkdir(tmp, S_IRWXU); + *p = '/'; + } + mkdir(tmp, S_IRWXU); +} + +static +void +channel_normalize_path(char *s) +{ + for (; *s; s++) { + if (isalpha((unsigned char)*s)) + *s = tolower((unsigned char)*s); + else if (!isdigit((unsigned char)*s) && !strchr(".#&+!-", *s)) + *s = '_'; + } +} + +static +void +channel_normalize_name(char *s) +{ + char *p; + + while (*s == '&' || *s == '#') + s++; + for (p = s; *s; s++) { + if (!strchr(" ,&#\x07", *s)) { + *p = *s; + p++; + } + } + *p = '\0'; +} + +static +void +create_filepath(char *filepath, size_t len, const char *path, + const char *channel, const char *suffix) +{ + int r; + + if (channel[0]) { + r = snprintf(filepath, len, "%s/%s", path, channel); + if (r < 0 || (size_t)r >= len) + goto error; + create_dirtree(filepath); + r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix); + if (r < 0 || (size_t)r >= len) + goto error; + } else { + r = snprintf(filepath, len, "%s/%s", path, suffix); + if (r < 0 || (size_t)r >= len) + goto error; + } + return; + +error: + fprintf(stderr, "%s: path to irc directory too long\n", argv0); + exit(1); +} + +static +int +channel_open(Channel *c) +{ + int fd; + struct stat st; + + /* make "in" fifo if it doesn't exist already. */ + if (lstat(c->inpath, &st) != -1) { + if (!(st.st_mode & S_IFIFO)) + return -1; + } else if (mkfifo(c->inpath, S_IRWXU)) { + return -1; + } + c->fdin = -1; + fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0); + if (fd == -1) + return -1; + c->fdin = fd; + + return 0; +} + +static +int +channel_reopen(Channel *c) +{ + if (c->fdin > 2) { + close(c->fdin); + c->fdin = -1; + } + return channel_open(c); +} + +static +Channel * +channel_new(const char *name) +{ + Channel *c; + char channelpath[PATH_MAX]; + + strlcpy(channelpath, name, sizeof(channelpath)); + channel_normalize_path(channelpath); + + if (!(c = calloc(1, sizeof(Channel)))) { + fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno)); + exit(1); + } + + strlcpy(c->name, name, sizeof(c->name)); + channel_normalize_name(c->name); + + create_filepath(c->inpath, sizeof(c->inpath), ircpath, + channelpath, "in"); + create_filepath(c->outpath, sizeof(c->outpath), ircpath, + channelpath, "out"); + return c; +} + +static +Channel * +channel_find(const char *name) +{ + Channel *c; + char chan[IRC_CHANNEL_MAX]; + + strlcpy(chan, name, sizeof(chan)); + channel_normalize_name(chan); + for (c = channels; c; c = c->next) { + if (!strcmp(chan, c->name)) + return c; /* already handled */ + } + return nil; +} + +static +Channel * +channel_add(const char *name) +{ + Channel *c; + + c = channel_new(name); + if (channel_open(c) == -1) { + fprintf(stderr, "%s: cannot create channel: %s: %s\n", + argv0, name, strerror(errno)); + free(c); + return nil; + } + if (!channels) { + channels = c; + } else { + c->next = channels; + channels = c; + } + return c; +} + +static +Channel * +channel_join(const char *name) +{ + Channel *c; + + if (!(c = channel_find(name))) + c = channel_add(name); + return c; +} + +static +void +channel_rm(Channel *c) +{ + Channel *p; + + if (channels == c) { + channels = channels->next; + } else { + for (p = channels; p && p->next != c; p = p->next) + ; + if (p && p->next == c) + p->next = c->next; + } + free(c); +} + +static +void +channel_leave(Channel *c) +{ + if (c->fdin > 2) { + close(c->fdin); + c->fdin = -1; + } + /* remove "in" file on leaving the channel */ + unlink(c->inpath); + channel_rm(c); +} + +static +void +loginkey(int ircfd, const char *key) +{ + snprintf(msg, sizeof(msg), "PASS %s\r\n", key); + ewritestr(ircfd, msg); +} + +static +void +loginuser(int ircfd, const char *host, const char *fullname) +{ + snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n", + nick, nick, host, fullname); + puts(msg); + ewritestr(ircfd, msg); +} + +static +int +udsopen(const char *uds) +{ + struct sockaddr_un sun; + size_t len; + int fd; + + if((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno)); + exit(1); + } + + sun.sun_family = AF_UNIX; + if(strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) { + fprintf(stderr, "%s: UNIX domain socket path truncation\n", argv0); + exit(1); + } + len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family); + if (connect(fd, (struct sockaddr *)&sun, len) == -1) { + fprintf(stderr, "%s: connect: %s\n", argv0, strerror(errno)); + exit(1); + } + return fd; +} + +static +int +tcpopen(const char *host, const char *service) +{ + struct addrinfo hints, *res = nil, *rp; + int fd = -1, e; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */ + hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */ + hints.ai_socktype = SOCK_STREAM; + + if ((e = getaddrinfo(host, service, &hints, &res))) { + fprintf(stderr, "%s: getaddrinfo: %s\n", argv0, gai_strerror(e)); + exit(1); + } + + for (rp = res; rp; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) + continue; + if (connect(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + close(fd); + fd = -1; + continue; + } + break; /* success */ + } + if (fd == -1) { + fprintf(stderr, "%s: could not connect to %s:%s: %s\n", + argv0, host, service, strerror(errno)); + exit(1); + } + + freeaddrinfo(res); + return fd; +} + +static +int +isnumeric(const char *s) +{ + errno = 0; + strtol(s, nil, 10); + return errno == 0; +} + +static +size_t +tokenize(char **result, size_t reslen, char *str, int delim) +{ + char *p = nil, *n = nil; + size_t i = 0; + + for (n = str; *n == ' '; n++) + ; + p = n; + while (*n != '\0') { + if (i >= reslen) + return 0; + if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0])) + delim = ':'; /* workaround non-RFC compliant messages */ + if (*n == delim) { + *n = '\0'; + result[i++] = p; + p = ++n; + } else { + n++; + } + } + /* add last entry */ + if (i < reslen && p < n && p && *p) + result[i++] = p; + return i; /* number of tokens */ +} + +static +void +channel_print(Channel *c, const char *buf) +{ + FILE *fp = nil; + time_t t = time(nil); + + if (!(fp = fopen(c->outpath, "a"))) + return; + fprintf(fp, "%lu %s\n", (unsigned long)t, buf); + fclose(fp); +} + +static +void +proc_channels_privmsg(int ircfd, Channel *c, char *buf) +{ + snprintf(msg, sizeof(msg), "<%s> %s", nick, buf); + channel_print(c, msg); + snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf); + ewritestr(ircfd, msg); +} + +static +void +proc_channels_input(int ircfd, Channel *c, char *buf) +{ + char *p = nil; + size_t buflen; + + if (buf[0] == '\0') + return; + if (buf[0] != '/') { + proc_channels_privmsg(ircfd, c, buf); + return; + } + + msg[0] = '\0'; + if ((buflen = strlen(buf)) < 2) + return; + if (buf[2] == ' ' || buf[2] == '\0') { + switch (buf[1]) { + case 'j': /* join */ + if (buflen < 3) + return; + if ((p = strchr(&buf[3], ' '))) /* password parameter */ + *p = '\0'; + if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '+') || + (buf[3] == '!')) + { + /* password protected channel */ + if (p) + snprintf(msg, sizeof(msg), "JOIN %s %s\r\n", &buf[3], p + 1); + else + snprintf(msg, sizeof(msg), "JOIN %s\r\n", &buf[3]); + channel_join(&buf[3]); + } else if (p) { + if ((c = channel_join(&buf[3]))) + proc_channels_privmsg(ircfd, c, p + 1); + return; + } + break; + case 't': /* topic */ + if (buflen >= 3) + snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n", c->name, &buf[3]); + break; + case 'a': /* away */ + if (buflen >= 3) { + snprintf(msg, sizeof(msg), "-!- %s is away \"%s\"", nick, &buf[3]); + channel_print(c, msg); + } + if (buflen >= 3) + snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &buf[3]); + else + snprintf(msg, sizeof(msg), "AWAY\r\n"); + break; + case 'n': /* change nick */ + if (buflen >= 3) { + strlcpy(_nick, &buf[3], sizeof(_nick)); + snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf[3]); + } + break; + case 'l': /* leave */ + if (c == channelmaster) + return; + if (buflen >= 3) + snprintf(msg, sizeof(msg), "PART %s :%s\r\n", c->name, &buf[3]); + else + snprintf(msg, sizeof(msg), + "PART %s :leaving\r\n", c->name); + ewritestr(ircfd, msg); + channel_leave(c); + return; + break; + case 'q': /* quit */ + if (buflen >= 3) + snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &buf[3]); + else + snprintf(msg, sizeof(msg), + "QUIT %s\r\n", "bye"); + ewritestr(ircfd, msg); + isrunning = 0; + return; + break; + default: /* raw IRC command */ + snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]); + break; + } + } else { + /* raw IRC command */ + snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]); + } + if (msg[0] != '\0') + ewritestr(ircfd, msg); +} + +static +void +proc_server_cmd(int fd, char *buf) +{ + Channel *c; + const char *channel; + char *argv[TOK_LAST], *cmd = nil, *p = nil; + unsigned int i; + + if (!buf || buf[0] == '\0') + return; + + /* clear tokens */ + for (i = 0; i < TOK_LAST; i++) + argv[i] = nil; + + /* check prefix */ + if (buf[0] == ':') { + if (!(p = strchr(buf, ' '))) + return; + *p = '\0'; + for (++p; *p == ' '; p++) + ; + cmd = p; + argv[TOK_NICKSRV] = &buf[1]; + if ((p = strchr(buf, '!'))) { + *p = '\0'; + argv[TOK_USER] = ++p; + } + } else { + cmd = buf; + } + + /* remove CRLFs */ + for (p = cmd; p && *p != '\0'; p++) { + if (*p == '\r' || *p == '\n') + *p = '\0'; + } + + if ((p = strchr(cmd, ':'))) { + *p = '\0'; + argv[TOK_TEXT] = ++p; + } + + tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' '); + + if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) { + return; + } else if (!strcmp("PING", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]); + ewritestr(fd, msg); + return; + } else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) { + /* server command */ + snprintf(msg, sizeof(msg), "%s%s", + argv[TOK_ARG] ? argv[TOK_ARG] : "", + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + channel_print(channelmaster, msg); + return; /* don't process further */ + } else if (!strcmp("ERROR", argv[TOK_CMD])) + snprintf(msg, sizeof(msg), "-!- error %s", + argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown"); + else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK_TEXT])) { + if (argv[TOK_TEXT]) + argv[TOK_CHAN] = argv[TOK_TEXT]; + snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s", + argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); + } else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) { + snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s", + argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]); + /* if user itself leaves, don't write to channel (don't reopen channel). */ + if (!strcmp(argv[TOK_NICKSRV], nick)) + return; + } else if (!strcmp("MODE", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s", + argv[TOK_NICKSRV], + argv[TOK_CHAN] ? argv[TOK_CHAN] : "", + argv[TOK_ARG] ? argv[TOK_ARG] : "", + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("QUIT", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- %s(%s) has quit \"%s\"", + argv[TOK_NICKSRV], argv[TOK_USER], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] && + !strcmp(_nick, argv[TOK_TEXT])) { + strlcpy(nick, _nick, sizeof(nick)); + snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick); + channel_print(channelmaster, msg); + } else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) { + snprintf(msg, sizeof(msg), "-!- %s changed nick to %s", + argv[TOK_NICKSRV], argv[TOK_TEXT]); + } else if (!strcmp("TOPIC", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"", + argv[TOK_NICKSRV], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) { + snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")", + argv[TOK_NICKSRV], argv[TOK_ARG], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("NOTICE", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "-!- \"%s\"", + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else if (!strcmp("PRIVMSG", argv[TOK_CMD])) { + snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV], + argv[TOK_TEXT] ? argv[TOK_TEXT] : ""); + } else { + return; /* can't read this message */ + } + if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick)) + channel = argv[TOK_NICKSRV]; + else + channel = argv[TOK_CHAN]; + + if (!channel || channel[0] == '\0') + c = channelmaster; + else + c = channel_join(channel); + if (c) + channel_print(c, msg); +} + +static +int +read_line(int fd, char *buf, size_t bufsiz) +{ + size_t i = 0; + char c = '\0'; + + do { + if (read(fd, &c, sizeof(char)) != sizeof(char)) + return -1; + buf[i++] = c; + } while (c != '\n' && i < bufsiz); + buf[i - 1] = '\0'; /* eliminates '\n' */ + return 0; +} + +static +void +handle_channels_input(int ircfd, Channel *c) +{ + char buf[IRC_MSG_MAX]; + + if(read_line(c->fdin, buf, sizeof(buf)) == -1) { + if(channel_reopen(c) == -1) + channel_rm(c); + return; + } + proc_channels_input(ircfd, c, buf); +} + +static +void +handle_server_output(int ircfd) +{ + char buf[IRC_MSG_MAX]; + + if (read_line(ircfd, buf, sizeof(buf)) == -1) { + fprintf(stderr, "%s: remote host closed connection: %s\n", + argv0, strerror(errno)); + exit(1); + } + fprintf(stdout, "%lu %s\n", (unsigned long)time(nil), buf); + fflush(stdout); + proc_server_cmd(ircfd, buf); +} + +static +void +sighandler(int sig) +{ + if (sig == SIGTERM || sig == SIGINT) + isrunning = 0; +} + +static +void +setup(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = sighandler; + sigaction(SIGTERM, &sa, nil); + sigaction(SIGINT, &sa, nil); +} + +static +void +run(int ircfd, const char *host) +{ + Channel *c, *tmp; + fd_set rdset; + struct timeval tv; + char ping_msg[IRC_MSG_MAX]; + int r, maxfd; + + snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host); + while(isrunning) { + maxfd = ircfd; + FD_ZERO(&rdset); + FD_SET(ircfd, &rdset); + for (c = channels; c; c = c->next) { + if (c->fdin > maxfd) + maxfd = c->fdin; + FD_SET(c->fdin, &rdset); + } + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 120; + r = select(maxfd + 1, &rdset, 0, 0, &tv); + if(r < 0){ + if (errno == EINTR) + continue; + fprintf(stderr, "%s: select: %s\n", argv0, strerror(errno)); + exit(1); + }else if(r == 0){ + if (time(nil) - last_response >= PING_TIMEOUT) { + channel_print(channelmaster, "-!- ii shutting down: ping timeout"); + exit(2); /* status code 2 for timeout */ + } + ewritestr(ircfd, ping_msg); + continue; + } + if(FD_ISSET(ircfd, &rdset)) { + handle_server_output(ircfd); + last_response = time(nil); + } + for(c = channels; c; c = tmp) { + tmp = c->next; + if (FD_ISSET(c->fdin, &rdset)) + handle_channels_input(ircfd, c); + } + } +} + +int +main(int argc, char *argv[]) +{ + Channel *c, *tmp; + struct passwd *spw; + const char *key = nil, *fullname = nil, *host = ""; + const char *uds = nil, *service = "6667"; + char prefix[PATH_MAX]; + int ircfd, r; + + /* use nickname and home dir of user by default */ + if(!(spw = getpwuid(getuid()))) { + fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno)); + exit(1); + } + strlcpy(nick, spw->pw_name, sizeof(nick)); + snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir); + + ARGBEGIN { + case 'f': + fullname = EARGF(usage()); + break; + case 'i': + strlcpy(prefix, EARGF(usage()), sizeof(prefix)); + break; + case 'k': + key = getenv(EARGF(usage())); + break; + case 'n': + strlcpy(nick, EARGF(usage()), sizeof(nick)); + break; + case 'p': + service = EARGF(usage()); + break; + case 's': + host = EARGF(usage()); + break; + case 'u': + uds = EARGF(usage()); + break; + default: + usage(); + break; + } ARGEND + + if(!*host) + usage(); + + if(uds) + ircfd = udsopen(uds); + else + ircfd = tcpopen(host, service); + +#ifdef __OpenBSD__ + /* OpenBSD pledge(2) support */ + if (pledge("stdio rpath wpath cpath dpath", nil) == -1) { + fprintf(stderr, "%s: pledge: %s\n", argv0, strerror(errno)); + exit(1); + } +#endif + + r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host); + if (r < 0 || (size_t)r >= sizeof(ircpath)) { + fprintf(stderr, "%s: path to irc directory too long\n", argv0); + exit(1); + } + create_dirtree(ircpath); + + channelmaster = channel_add(""); /* master channel */ + if(key) + loginkey(ircfd, key); + loginuser(ircfd, host, fullname && *fullname ? fullname : nick); + setup(); + run(ircfd, host); + if(channelmaster) + channel_leave(channelmaster); + + for(c = channels; c; c = tmp) { + tmp = c->next; + channel_leave(c); + } + + return 0; +} diff --git a/src/cmd/ic/rules.mk b/src/cmd/ic/rules.mk new file mode 100644 index 0000000..d001c01 --- /dev/null +++ b/src/cmd/ic/rules.mk @@ -0,0 +1,14 @@ +include share/push.mk +# Iterate through subdirectory tree + +# Local sources +SRCS_$(d):=$(d)/strlcpy.c $(d)/ic.c +BINS_$(d):=$(d)/ic + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/ic/strlcpy.c b/src/cmd/ic/strlcpy.c new file mode 100644 index 0000000..5af7906 --- /dev/null +++ b/src/cmd/ic/strlcpy.c @@ -0,0 +1,32 @@ +/* Taken from OpenBSD */ +#include +#include + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t siz) +{ + char *d = dst; + const char *s = src; + size_t n = siz; + + /* Copy as many bytes as will fit */ + if(n != 0) { + while(--n != 0) { + if((*d++ = *s++) == '\0') + break; + } + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if(n == 0) { + if(siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while(*s++) + ; + } + return s - src - 1; /* count does not include NUL */ +} diff --git a/src/cmd/menu/LICENSE b/src/cmd/menu/LICENSE new file mode 100644 index 0000000..9762166 --- /dev/null +++ b/src/cmd/menu/LICENSE @@ -0,0 +1,30 @@ +MIT/X Consortium License + +© 2006-2019 Anselm R Garbe +© 2006-2008 Sander van Dijk +© 2006-2007 Michał Janeczek +© 2007 Kris Maglione +© 2009 Gottox +© 2009 Markus Schnalke +© 2009 Evan Gates +© 2010-2012 Connor Lane Smith +© 2014-2019 Hiltjo Posthuma +© 2015-2019 Quentin Rameau + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/cmd/menu/config.h b/src/cmd/menu/config.h new file mode 100644 index 0000000..9bfd5b3 --- /dev/null +++ b/src/cmd/menu/config.h @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +/* Default settings; can be overriden by command line. */ +#define VERSION "1.0" + +static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */ +/* -fn option overrides fonts[0]; default X11 font or font set */ +static const char *fonts[] = { + "consolas:size=16" +}; + +static const char *prompt = "cmds"; /* -p option; prompt to the left of input field */ +static const char *colors[SchemeLast][2] = { + /* fg bg */ + [SchemeNorm] = { "#fbf1c7", "#504945" }, + [SchemeSel] = { "#504945", "#83a598" }, + [SchemeOut] = { "#000000", "#00ffff" }, +}; +/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ +static unsigned int lines = 0; + +/* + * Characters not considered part of a word while deleting words + * e.g. " /?\"&[]" + */ +static const char worddelimiters[] = " "; diff --git a/src/cmd/menu/drw.c b/src/cmd/menu/drw.c new file mode 100644 index 0000000..162fe40 --- /dev/null +++ b/src/cmd/menu/drw.c @@ -0,0 +1,428 @@ +/* See LICENSE file for copyright and license details. */ +#include "menu.h" + +#define UTF_INVALID 0xFFFD +#define UTF_SIZ 4 + +static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; +static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; +static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; +static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; + +static long +utf8decodebyte(const char c, size_t *i) +{ + for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) + if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) + return (unsigned char)c & ~utfmask[*i]; + return 0; +} + +static size_t +utf8validate(long *u, size_t i) +{ + if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) + *u = RuneErr; + for (i = 1; *u > utfmax[i]; ++i) + ; + return i; +} + +static size_t +utf8decode(const char *c, long *u, size_t clen) +{ + size_t i, j, len, type; + long udecoded; + + *u = RuneErr; + if (!clen) + return 0; + udecoded = utf8decodebyte(c[0], &len); + if (!BETWEEN(len, 1, UTF_SIZ)) + return 1; + for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { + udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); + if (type) + return j; + } + if (j < len) + return 0; + *u = udecoded; + utf8validate(u, len); + + return len; +} + +Drw * +drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) +{ + Drw *drw = ecalloc(1, sizeof(Drw)); + + drw->dpy = dpy; + drw->screen = screen; + drw->root = root; + drw->w = w; + drw->h = h; + drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); + drw->gc = XCreateGC(dpy, root, 0, NULL); + XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); + + return drw; +} + +void +drw_resize(Drw *drw, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + drw->w = w; + drw->h = h; + if (drw->drawable) + XFreePixmap(drw->dpy, drw->drawable); + drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); +} + +void +drw_free(Drw *drw) +{ + XFreePixmap(drw->dpy, drw->drawable); + XFreeGC(drw->dpy, drw->gc); + free(drw); +} + +/* This function is an implementation detail. Library users should use + * drw_fontset_create instead. + */ +static Fnt * +xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) +{ + Fnt *font; + XftFont *xfont = NULL; + FcPattern *pattern = NULL; + + if (fontname) { + /* Using the pattern found at font->xfont->pattern does not yield the + * same substitution results as using the pattern returned by + * FcNameParse; using the latter results in the desired fallback + * behaviour whereas the former just results in missing-character + * rectangles being drawn, at least with some fonts. */ + if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { + fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); + return NULL; + } + if (!(pattern = FcNameParse((FcChar8 *) fontname))) { + fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); + XftFontClose(drw->dpy, xfont); + return NULL; + } + } else if (fontpattern) { + if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { + fprintf(stderr, "error, cannot load font from pattern.\n"); + return NULL; + } + } else { + fatal("no font specified."); + } + + /* Do not allow using color fonts. This is a workaround for a BadLength + * error from Xft with color glyphs. Modelled on the Xterm workaround. See + * https://bugzilla.redhat.com/show_bug.cgi?id=1498269 + * https://lists.suckless.org/dev/1701/30932.html + * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349 + * and lots more all over the internet. + */ + FcBool iscol; + if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) { + XftFontClose(drw->dpy, xfont); + return NULL; + } + + font = ecalloc(1, sizeof(Fnt)); + font->xfont = xfont; + font->pattern = pattern; + font->h = xfont->ascent + xfont->descent; + font->dpy = drw->dpy; + + return font; +} + +static void +xfont_free(Fnt *font) +{ + if (!font) + return; + if (font->pattern) + FcPatternDestroy(font->pattern); + XftFontClose(font->dpy, font->xfont); + free(font); +} + +Fnt* +drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) +{ + Fnt *cur, *ret = NULL; + size_t i; + + if (!drw || !fonts) + return NULL; + + for (i = 1; i <= fontcount; i++) { + if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { + cur->next = ret; + ret = cur; + } + } + return (drw->fonts = ret); +} + +void +drw_fontset_free(Fnt *font) +{ + if (font) { + drw_fontset_free(font->next); + xfont_free(font); + } +} + +void +drw_clr_create(Drw *drw, Clr *dest, const char *clrname) +{ + if (!drw || !dest || !clrname) + return; + + if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen), + clrname, dest)) + fatal("error, cannot allocate color '%s'", clrname); +} + +/* Wrapper to create color schemes. The caller has to call free(3) on the + * returned color scheme when done using it. */ +Clr * +drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) +{ + size_t i; + Clr *ret; + + /* need at least two colors for a scheme */ + if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) + return NULL; + + for (i = 0; i < clrcount; i++) + drw_clr_create(drw, &ret[i], clrnames[i]); + return ret; +} + +void +drw_setfontset(Drw *drw, Fnt *set) +{ + if (drw) + drw->fonts = set; +} + +void +drw_setscheme(Drw *drw, Clr *scm) +{ + if (drw) + drw->scheme = scm; +} + +void +drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) +{ + if (!drw || !drw->scheme) + return; + XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); + if (filled) + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + else + XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); +} + +int +drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) +{ + char buf[1024]; + int ty; + unsigned int ew; + XftDraw *d = NULL; + Fnt *usedfont, *curfont, *nextfont; + size_t i, len; + int utf8strlen, utf8charlen, render = x || y || w || h; + long utf8codepoint = 0; + const char *utf8str; + FcCharSet *fccharset; + FcPattern *fcpattern; + FcPattern *match; + XftResult result; + int charexists = 0; + + if (!drw || (render && !drw->scheme) || !text || !drw->fonts) + return 0; + + if (!render) { + w = ~w; + } else { + XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); + XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); + d = XftDrawCreate(drw->dpy, drw->drawable, + DefaultVisual(drw->dpy, drw->screen), + DefaultColormap(drw->dpy, drw->screen)); + x += lpad; + w -= lpad; + } + + usedfont = drw->fonts; + while (1) { + utf8strlen = 0; + utf8str = text; + nextfont = NULL; + while (*text) { + utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); + for (curfont = drw->fonts; curfont; curfont = curfont->next) { + charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); + if (charexists) { + if (curfont == usedfont) { + utf8strlen += utf8charlen; + text += utf8charlen; + } else { + nextfont = curfont; + } + break; + } + } + + if (!charexists || nextfont) + break; + else + charexists = 0; + } + + if (utf8strlen) { + drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); + /* shorten text if necessary */ + for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) + drw_font_getexts(usedfont, utf8str, len, &ew, NULL); + + if (len) { + memcpy(buf, utf8str, len); + buf[len] = '\0'; + if (len < utf8strlen) + for (i = len; i && i > len - 3; buf[--i] = '.') + ; /* NOP */ + + if (render) { + ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; + XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], + usedfont->xfont, x, ty, (XftChar8 *)buf, len); + } + x += ew; + w -= ew; + } + } + + if (!*text) { + break; + } else if (nextfont) { + charexists = 0; + usedfont = nextfont; + } else { + /* Regardless of whether or not a fallback font is found, the + * character must be drawn. */ + charexists = 1; + + fccharset = FcCharSetCreate(); + FcCharSetAddChar(fccharset, utf8codepoint); + + if (!drw->fonts->pattern) { + /* Refer to the comment in xfont_create for more information. */ + fatal("the first font in the cache must be loaded from a font string."); + } + + fcpattern = FcPatternDuplicate(drw->fonts->pattern); + FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); + FcPatternAddBool(fcpattern, FC_COLOR, FcFalse); + + FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); + FcDefaultSubstitute(fcpattern); + match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); + + FcCharSetDestroy(fccharset); + FcPatternDestroy(fcpattern); + + if (match) { + usedfont = xfont_create(drw, NULL, match); + if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { + for (curfont = drw->fonts; curfont->next; curfont = curfont->next) + ; /* NOP */ + curfont->next = usedfont; + } else { + xfont_free(usedfont); + usedfont = drw->fonts; + } + } + } + } + if (d) + XftDrawDestroy(d); + + return x + (render ? w : 0); +} + +void +drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) +{ + if (!drw) + return; + + XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); + XSync(drw->dpy, False); +} + +unsigned int +drw_fontset_getwidth(Drw *drw, const char *text) +{ + if (!drw || !drw->fonts || !text) + return 0; + return drw_text(drw, 0, 0, 0, 0, 0, text, 0); +} + +void +drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) +{ + XGlyphInfo ext; + + if (!font || !text) + return; + + XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); + if (w) + *w = ext.xOff; + if (h) + *h = font->h; +} + +Cur * +drw_cur_create(Drw *drw, int shape) +{ + Cur *cur; + + if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) + return NULL; + + cur->cursor = XCreateFontCursor(drw->dpy, shape); + + return cur; +} + +void +drw_cur_free(Drw *drw, Cur *cursor) +{ + if (!cursor) + return; + + XFreeCursor(drw->dpy, cursor->cursor); + free(cursor); +} diff --git a/src/cmd/menu/drw.h b/src/cmd/menu/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/src/cmd/menu/drw.h @@ -0,0 +1,57 @@ +/* See LICENSE file for copyright and license details. */ + +typedef struct { + Cursor cursor; +} Cur; + +typedef struct Fnt { + Display *dpy; + unsigned int h; + XftFont *xfont; + FcPattern *pattern; + struct Fnt *next; +} Fnt; + +enum { ColFg, ColBg }; /* Clr scheme index */ +typedef XftColor Clr; + +typedef struct { + unsigned int w, h; + Display *dpy; + int screen; + Window root; + Drawable drawable; + GC gc; + Clr *scheme; + Fnt *fonts; +} Drw; + +/* Drawable abstraction */ +Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); +void drw_resize(Drw *drw, unsigned int w, unsigned int h); +void drw_free(Drw *drw); + +/* Fnt abstraction */ +Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); +void drw_fontset_free(Fnt* set); +unsigned int drw_fontset_getwidth(Drw *drw, const char *text); +void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); + +/* Colorscheme abstraction */ +void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); +Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); + +/* Cursor abstraction */ +Cur *drw_cur_create(Drw *drw, int shape); +void drw_cur_free(Drw *drw, Cur *cursor); + +/* Drawing context manipulation */ +void drw_setfontset(Drw *drw, Fnt *set); +void drw_setscheme(Drw *drw, Clr *scm); + +/* Drawing functions */ +void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); +int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); + +/* Map functions */ +void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); diff --git a/src/cmd/menu/menu.c b/src/cmd/menu/menu.c new file mode 100644 index 0000000..e6e4bb2 --- /dev/null +++ b/src/cmd/menu/menu.c @@ -0,0 +1,765 @@ +#include "menu.h" + +static char text[BUFSIZ] = ""; +static char *embed; +static int bh, mw, mh; +static int inputw = 0, promptw, passwd = 0; +static int lrpad; /* sum of left and right padding */ +static size_t cursor; +static struct item *items = nil; +static struct item *matches, *matchend; +static struct item *prev, *curr, *next, *sel; +static int mon = -1, screen; + +static Atom clip, utf8; +static Display *dpy; +static Window root, parentwin, win; +static XIC xic; + +static Drw *drw; +static Clr *scheme[SchemeLast]; + +#include "config.h" + +static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; +static char *(*fstrstr)(const char *, const char *) = strstr; + +static +void +appenditem(struct item *item, struct item **list, struct item **last) +{ + if (*last) + (*last)->right = item; + else + *list = item; + + item->left = *last; + item->right = nil; + *last = item; +} + +static +void +calcoffsets(void) +{ + int i, n; + + if (lines > 0) + n = lines * bh; + else + n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); + /* calculate which items will begin the next page and previous page */ + for (i = 0, next = curr; next; next = next->right) + if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n) + break; + for (i = 0, prev = curr; prev && prev->left; prev = prev->left) + if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n) + break; +} + +static +void +cleanup(void) +{ + size_t i; + + XUngrabKey(dpy, AnyKey, AnyModifier, root); + for (i = 0; i < SchemeLast; i++) + free(scheme[i]); + drw_free(drw); + XSync(dpy, False); + XCloseDisplay(dpy); +} + +static +char * +cistrstr(const char *s, const char *sub) +{ + size_t len; + + for (len = strlen(sub); *s; s++) + if (!strncasecmp(s, sub, len)) + return (char *)s; + return nil; +} + +static +int +drawitem(struct item *item, int x, int y, int w) +{ + if (item == sel) + drw_setscheme(drw, scheme[SchemeSel]); + else if (item->out) + drw_setscheme(drw, scheme[SchemeOut]); + else + drw_setscheme(drw, scheme[SchemeNorm]); + + return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0); +} + +static +void +drawmenu(void) +{ + uint curpos; + struct item *item; + int x = 0, y = 0, w; + char *censort; + + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, 0, 0, mw, mh, 1, 1); + + if (prompt && *prompt) { + drw_setscheme(drw, scheme[SchemeSel]); + x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); + } + /* draw input field */ + w = (lines > 0 || !matches) ? mw - x : inputw; + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); + if(passwd){ + censort = ecalloc(1, sizeof(text)); + memset(censort, '.', strlen(text)); + drw_text(drw, x, 0, w, bh, lrpad / 2, censort, 0); + free(censort); + } + + curpos = TEXTW(text) - TEXTW(&text[cursor]); + if ((curpos += lrpad / 2 - 1) < w) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); + } + + if (lines > 0) { + /* draw vertical list */ + for (item = curr; item != next; item = item->right) + drawitem(item, x, y += bh, mw - x); + } else if (matches) { + /* draw horizontal list */ + x += inputw; + w = TEXTW("<"); + if (curr->left) { + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); + } + x += w; + for (item = curr; item != next; item = item->right) + x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">"))); + if (next) { + w = TEXTW(">"); + drw_setscheme(drw, scheme[SchemeNorm]); + drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0); + } + } + drw_map(drw, win, 0, 0, mw, mh); +} + +static +void +grabfocus(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; + Window focuswin; + int i, revertwin; + + for (i = 0; i < 100; ++i) { + XGetInputFocus(dpy, &focuswin, &revertwin); + if (focuswin == win) + return; + XSetInputFocus(dpy, win, RevertToParent, CurrentTime); + nanosleep(&ts, nil); + } + fatal("cannot grab focus"); +} + +static +void +grabkeyboard(void) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; + int i; + + if (embed) + return; + /* try to grab keyboard, we may have to wait for another process to ungrab */ + for (i = 0; i < 1000; i++) { + if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, + GrabModeAsync, CurrentTime) == GrabSuccess) + return; + nanosleep(&ts, nil); + } + fatal("cannot grab keyboard"); +} + +static +void +match(void) +{ + static char **tokv = nil; + static int tokn = 0; + + char buf[sizeof text], *s; + int i, tokc = 0; + size_t len, textsize; + struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; + + strcpy(buf, text); + /* separate input text into tokens to be matched individually */ + for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(nil, " ")) + if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) + fatal("cannot realloc %u bytes:", tokn * sizeof *tokv); + len = tokc ? strlen(tokv[0]) : 0; + + matches = lprefix = lsubstr = matchend = prefixend = substrend = nil; + textsize = strlen(text) + 1; + for (item = items; item && item->text; item++) { + for (i = 0; i < tokc; i++) + if (!fstrstr(item->text, tokv[i])) + break; + if (i != tokc) /* not all tokens match */ + continue; + /* exact matches go first, then prefixes, then substrings */ + if (!tokc || !fstrncmp(text, item->text, textsize)) + appenditem(item, &matches, &matchend); + else if (!fstrncmp(tokv[0], item->text, len)) + appenditem(item, &lprefix, &prefixend); + else + appenditem(item, &lsubstr, &substrend); + } + if (lprefix) { + if (matches) { + matchend->right = lprefix; + lprefix->left = matchend; + } else + matches = lprefix; + matchend = prefixend; + } + if (lsubstr) { + if (matches) { + matchend->right = lsubstr; + lsubstr->left = matchend; + } else + matches = lsubstr; + matchend = substrend; + } + curr = sel = matches; + calcoffsets(); +} + +static +void +insert(const char *str, ssize_t n) +{ + if (strlen(text) + n > sizeof text - 1) + return; + /* move existing text out of the way, insert new text, and update cursor */ + memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0)); + if (n > 0) + memcpy(&text[cursor], str, n); + cursor += n; + match(); +} + +static +size_t +nextrune(int inc) +{ + ssize_t n; + + /* return location of next utf8 rune in the given direction (+1 or -1) */ + for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc) + ; + return n; +} + +static +void +movewordedge(int dir) +{ + if (dir < 0) { /* move cursor to the start of the word*/ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + cursor = nextrune(-1); + } else { /* move cursor to the end of the word */ + while (text[cursor] && strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + while (text[cursor] && !strchr(worddelimiters, text[cursor])) + cursor = nextrune(+1); + } +} + +static +void +keypress(XKeyEvent *ev) +{ + char buf[32]; + int len; + KeySym ksym; + Status status; + + len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); + switch (status) { + default: /* XLookupNone, XBufferOverflow */ + return; + case XLookupChars: + goto insert; + case XLookupKeySym: + case XLookupBoth: + break; + } + + if (ev->state & ControlMask) { + switch(ksym) { + case XK_a: ksym = XK_Home; break; + case XK_b: ksym = XK_Left; break; + case XK_c: ksym = XK_Escape; break; + case XK_d: ksym = XK_Delete; break; + case XK_e: ksym = XK_End; break; + case XK_f: ksym = XK_Right; break; + case XK_g: ksym = XK_Escape; break; + case XK_h: ksym = XK_BackSpace; break; + case XK_i: ksym = XK_Tab; break; + case XK_j: /* fallthrough */ + case XK_J: /* fallthrough */ + case XK_m: /* fallthrough */ + case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break; + case XK_n: ksym = XK_Down; break; + case XK_p: ksym = XK_Up; break; + + case XK_k: /* delete right */ + text[cursor] = '\0'; + match(); + break; + case XK_u: /* delete left */ + insert(nil, 0 - cursor); + break; + case XK_w: /* delete word */ + while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) + insert(nil, nextrune(-1) - cursor); + while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) + insert(nil, nextrune(-1) - cursor); + break; + case XK_y: /* paste selection */ + case XK_Y: + XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, + utf8, utf8, win, CurrentTime); + return; + case XK_Left: + movewordedge(-1); + goto draw; + case XK_Right: + movewordedge(+1); + goto draw; + case XK_Return: + case XK_KP_Enter: + break; + case XK_bracketleft: + cleanup(); + exit(1); + default: + return; + } + } else if (ev->state & Mod1Mask) { + switch(ksym) { + case XK_b: + movewordedge(-1); + goto draw; + case XK_f: + movewordedge(+1); + goto draw; + case XK_g: ksym = XK_Home; break; + case XK_G: ksym = XK_End; break; + case XK_h: ksym = XK_Up; break; + case XK_j: ksym = XK_Next; break; + case XK_k: ksym = XK_Prior; break; + case XK_l: ksym = XK_Down; break; + default: + return; + } + } + + switch(ksym) { + default: +insert: + if (!iscntrl(*buf)) + insert(buf, len); + break; + case XK_Delete: + if (text[cursor] == '\0') + return; + cursor = nextrune(+1); + /* fallthrough */ + case XK_BackSpace: + if (cursor == 0) + return; + insert(nil, nextrune(-1) - cursor); + break; + case XK_End: + if (text[cursor] != '\0') { + cursor = strlen(text); + break; + } + if (next) { + /* jump to end of list and position items in reverse */ + curr = matchend; + calcoffsets(); + curr = prev; + calcoffsets(); + while (next && (curr = curr->right)) + calcoffsets(); + } + sel = matchend; + break; + case XK_Escape: + cleanup(); + exit(1); + case XK_Home: + if (sel == matches) { + cursor = 0; + break; + } + sel = curr = matches; + calcoffsets(); + break; + case XK_Left: + if (cursor > 0 && (!sel || !sel->left || lines > 0)) { + cursor = nextrune(-1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Up: + if (sel && sel->left && (sel = sel->left)->right == curr) { + curr = prev; + calcoffsets(); + } + break; + case XK_Next: + if (!next) + return; + sel = curr = next; + calcoffsets(); + break; + case XK_Prior: + if (!prev) + return; + sel = curr = prev; + calcoffsets(); + break; + case XK_Return: + case XK_KP_Enter: + puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); + if (!(ev->state & ControlMask)) { + cleanup(); + exit(0); + } + if (sel) + sel->out = 1; + break; + case XK_Right: + if (text[cursor] != '\0') { + cursor = nextrune(+1); + break; + } + if (lines > 0) + return; + /* fallthrough */ + case XK_Down: + if (sel && sel->right && (sel = sel->right) == next) { + curr = next; + calcoffsets(); + } + break; + case XK_Tab: + if (!sel) + return; + strncpy(text, sel->text, sizeof text - 1); + text[sizeof text - 1] = '\0'; + cursor = strlen(text); + match(); + break; + } + +draw: + drawmenu(); +} + +static +void +paste(void) +{ + char *p, *q; + int di; + unsigned long dl; + Atom da; + + /* we have been given the current selection, now insert it into input */ + if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, + utf8, &da, &di, &dl, &dl, (unsigned char **)&p) + == Success && p) { + insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); + XFree(p); + } + drawmenu(); +} + +static +void +readstdin(void) +{ + char buf[sizeof text], *p; + size_t i, imax = 0, size = 0; + uint tmpmax = 0; + if(passwd){ + inputw = lines = 0; + return; + } + + /* read each line from stdin and add it to the item list */ + for (i = 0; fgets(buf, sizeof buf, stdin); i++) { + if (i + 1 >= size / sizeof *items) + if (!(items = realloc(items, (size += BUFSIZ)))) + fatal("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(items[i].text = strdup(buf))) + fatal("cannot strdup %u bytes:", strlen(buf) + 1); + items[i].out = 0; + drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, nil); + if (tmpmax > inputw) { + inputw = tmpmax; + imax = i; + } + } + if (items) + items[i].text = nil; + inputw = items ? TEXTW(items[imax].text) : 0; + lines = MIN(lines, i); +} + +static +void +run(void) +{ + XEvent ev; + + while (!XNextEvent(dpy, &ev)) { + if (XFilterEvent(&ev, win)) + continue; + switch(ev.type) { + case DestroyNotify: + if (ev.xdestroywindow.window != win) + break; + cleanup(); + exit(1); + case Expose: + if (ev.xexpose.count == 0) + drw_map(drw, win, 0, 0, mw, mh); + break; + case FocusIn: + /* regrab focus from parent window */ + if (ev.xfocus.window != win) + grabfocus(); + break; + case KeyPress: + keypress(&ev.xkey); + break; + case SelectionNotify: + if (ev.xselection.property == utf8) + paste(); + break; + case VisibilityNotify: + if (ev.xvisibility.state != VisibilityUnobscured) + XRaiseWindow(dpy, win); + break; + } + } +} + +static +void +setup(void) +{ + int x, y, i, j; + uint du; + XSetWindowAttributes swa; + XIM xim; + Window w, dw, *dws; + XWindowAttributes wa; + XClassHint ch = {"menu", "menu"}; + XineramaScreenInfo *info; + Window pw; + int a, di, n, area = 0; + + /* init appearance */ + for (j = 0; j < SchemeLast; j++) + scheme[j] = drw_scm_create(drw, colors[j], 2); + + clip = XInternAtom(dpy, "CLIPBOARD", False); + utf8 = XInternAtom(dpy, "UTF8_STRING", False); + + /* calculate menu geometry */ + bh = drw->fonts->h + 2; + lines = MAX(lines, 0); + mh = (lines + 1) * bh; + i = 0; + if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { + XGetInputFocus(dpy, &w, &di); + if (mon >= 0 && mon < n) + i = mon; + else if (w != root && w != PointerRoot && w != None) { + /* find top-level window containing current input focus */ + do { + if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) + XFree(dws); + } while (w != root && w != pw); + /* find xinerama screen with which the window intersects most */ + if (XGetWindowAttributes(dpy, pw, &wa)) + for (j = 0; j < n; j++) + if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { + area = a; + i = j; + } + } + /* no focused window is on screen, so use pointer location instead */ + if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) + for (i = 0; i < n; i++) + if (INTERSECT(x, y, 1, 1, info[i])) + break; + + x = info[i].x_org; + y = info[i].y_org + (topbar ? 0 : info[i].height - mh); + mw = info[i].width; + XFree(info); + } else + { + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + fatal("could not get embedding window attributes: 0x%lx", + parentwin); + x = 0; + y = topbar ? 0 : wa.height - mh; + mw = wa.width; + } + promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; + inputw = MIN(inputw, mw/3); + match(); + + /* create menu window */ + swa.override_redirect = True; + swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; + swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; + win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, + CopyFromParent, CopyFromParent, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); + XSetClassHint(dpy, win, &ch); + + + /* input methods */ + if ((xim = XOpenIM(dpy, nil, nil, nil)) == nil) + fatal("XOpenIM failed: could not open input device"); + + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, win, XNFocusWindow, win, nil); + + XMapRaised(dpy, win); + if (embed) { + XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); + if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { + for (i = 0; i < du && dws[i] != win; ++i) + XSelectInput(dpy, dws[i], FocusChangeMask); + XFree(dws); + } + grabfocus(); + } + drw_resize(drw, mw, mh); + drawmenu(); +} + +static +void +usage(void) +{ + fputs("usage: menu [-bfivP] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" + " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + XWindowAttributes wa; + int i, fast = 0; + + for (i = 1; i < argc; i++) + /* these options take no arguments */ + if (!strcmp(argv[i], "-v")) { /* prints version information */ + puts("menu-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */ + topbar = 0; + else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */ + fast = 1; + else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */ + fstrncmp = strncasecmp; + fstrstr = cistrstr; + } else if (!strcmp(argv[i], "-P")) { + passwd = 1; + } else if (i + 1 == argc) + usage(); + /* these options take one argument */ + else if (!strcmp(argv[i], "-l")) /* number of lines in vertical list */ + lines = atoi(argv[++i]); + else if (!strcmp(argv[i], "-m")) + mon = atoi(argv[++i]); + else if (!strcmp(argv[i], "-p")) /* adds prompt to left of input field */ + prompt = argv[++i]; + else if (!strcmp(argv[i], "-fn")) /* font or font set */ + fonts[0] = argv[++i]; + else if (!strcmp(argv[i], "-nb")) /* normal background color */ + colors[SchemeNorm][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ + colors[SchemeNorm][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-sb")) /* selected background color */ + colors[SchemeSel][ColBg] = argv[++i]; + else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ + colors[SchemeSel][ColFg] = argv[++i]; + else if (!strcmp(argv[i], "-w")) /* embedding window id */ + embed = argv[++i]; + else + usage(); + + if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fputs("warning: no locale support\n", stderr); + if (!(dpy = XOpenDisplay(nil))) + fatal("cannot open display"); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + if (!embed || !(parentwin = strtol(embed, nil, 0))) + parentwin = root; + if (!XGetWindowAttributes(dpy, parentwin, &wa)) + fatal("could not get embedding window attributes: 0x%lx", + parentwin); + drw = drw_create(dpy, screen, root, wa.width, wa.height); + if (!drw_fontset_create(drw, fonts, arrlen(fonts))) + fatal("no fonts could be loaded."); + lrpad = drw->fonts->h; + +#ifdef __OpenBSD__ + if (pledge("stdio rpath", nil) == -1) + fatal("pledge"); +#endif + + if (fast && !isatty(0)) { + grabkeyboard(); + readstdin(); + } else { + readstdin(); + grabkeyboard(); + } + setup(); + run(); + + return 1; /* unreachable */ +} diff --git a/src/cmd/menu/menu.h b/src/cmd/menu/menu.h new file mode 100644 index 0000000..f4345bb --- /dev/null +++ b/src/cmd/menu/menu.h @@ -0,0 +1,40 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "drw.h" + +/* macros */ +#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ + * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) +#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) +#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) + + +/* enums */ +enum { + SchemeNorm, + SchemeSel, + SchemeOut, + SchemeLast +}; /* color schemes */ + +struct item { + char *text; + struct item *left, *right; + int out; +}; + +/* util.c */ +void fatal(const char *fmt, ...); +void *ecalloc(size_t nmemb, size_t size); diff --git a/src/cmd/menu/rules.mk b/src/cmd/menu/rules.mk new file mode 100644 index 0000000..f9b59aa --- /dev/null +++ b/src/cmd/menu/rules.mk @@ -0,0 +1,27 @@ +include share/push.mk +# Iterate through subdirectory tree + +# Local sources +SRCS_$(d):=\ + $(d)/menu.c\ + $(d)/drw.c\ + $(d)/util.c + +#outputs +BINS_$(d):=$(d)/menu + +include share/paths.mk + +# Local rules +include share/dynamic.mk + +$(BINS_$(d)): TCLIBS=\ + -lfontconfig -lXft -lXinerama -lX11 +$(BINS_$(d)): TCINCS=\ + `$(PKG) --cflags fontconfig`\ + `$(PKG) --cflags freetype2` + +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/menu/util.c b/src/cmd/menu/util.c new file mode 100644 index 0000000..14bfe1c --- /dev/null +++ b/src/cmd/menu/util.c @@ -0,0 +1,30 @@ +/* See LICENSE file for copyright and license details. */ +#include "menu.h" + +void * +ecalloc(size_t nmemb, size_t size) +{ + void *p; + + if (!(p = calloc(nmemb, size))) + fatal("calloc:"); + return p; +} + +void +fatal(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt)-1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + + exit(1); +} diff --git a/src/cmd/rc/code.c b/src/cmd/rc/code.c new file mode 100644 index 0000000..786f284 --- /dev/null +++ b/src/cmd/rc/code.c @@ -0,0 +1,277 @@ +#include "rc.h" +#include "parse.h" +#include "exec.h" + +// ----------------------------------------------------------------------- +// types + +struct Interpreter +{ + int i, cap; + Code *code; +}; + +Code *compiled = nil; +static struct Interpreter interpreter; +#define emiti(x) ((void)(interpreter.i != interpreter.cap || grow()), interpreter.code[interpreter.i].i = (x), interpreter.i++) +#define emitf(x) ((void)(interpreter.i != interpreter.cap || grow()), interpreter.code[interpreter.i].f = (x), interpreter.i++) +#define emits(x) ((void)(interpreter.i != interpreter.cap || grow()), interpreter.code[interpreter.i].s = (x), interpreter.i++) + +static +int +grow(void) +{ + interpreter.cap += 100; + interpreter.code = erealloc(interpreter.code, sizeof(*interpreter.code)*interpreter.cap); + memset(interpreter.code+interpreter.cap-100, 0, 100*sizeof(*interpreter.code)); + + return 0; +} + +static +void +storepc(int a) +{ + if(interpreter.i <= a || a < 0) + fatal("bad address %d in interpreter", a); + + interpreter.code[a].i = interpreter.i; +} + +static +void +walk(Tree *node) +{ + Tree *n; + int addr1, addr2; + + if(!node) + return; + + switch(node->type){ + default: + print(shell.err, "bad type %d in interpreter walk\n", node->type); + fatal("crashing\n"); + break; + + case '$': + emitf(Xmark); + walk(node->child[0]); + emitf(Xdollar); + break; + + case Tcount: + emitf(Xmark); + walk(node->child[0]); + emitf(Xcount); + break; + + case Tjoin: + emitf(Xmark); + walk(node->child[0]); + emitf(Xjoin); + break; + + case Tindex: + emitf(Xmark); + walk(node->child[1]); + emitf(Xmark); + walk(node->child[0]); + emitf(Xindex); + break; + + case ';': + walk(node->child[0]); + walk(node->child[1]); + break; + + case '^': + emitf(Xmark); + walk(node->child[1]); + emitf(Xmark); + walk(node->child[0]); + emitf(Xconcatenate); + break; + + case Tandand: + walk(node->child[0]); + emitf(Xtrue); + addr1 = emiti(0); + walk(node->child[1]); + storepc(addr1); + break; + + case Toror: + walk(node->child[0]); + emitf(Xfalse); + addr1 = emiti(0); + walk(node->child[1]); + storepc(addr1); + break; + + case Targs: + walk(node->child[1]); + walk(node->child[0]); + break; + + case Tparen: case Tblock: + walk(node->child[0]); + break; + + case Tbasic: + emitf(Xmark); + walk(node->child[0]); + emitf(Xbasic); + break; + + case Tbang: + walk(node->child[0]); + emitf(Xbang); + + case Tword: + emitf(Xword); + emits(strdup(node->str)); + break; + + case Twords: + walk(node->child[1]); + walk(node->child[0]); + break; + + case '=': + for(n=node; node && node->type == '='; node = node->child[2]) + ; + if(node){ + for(node=n; node->type=='='; node = node->child[2]){ + emitf(Xmark); + walk(node->child[1]); + emitf(Xmark); + walk(node->child[0]); + emitf(Xlocal); + } + walk(node); + for(node=n; node->type=='='; node = node->child[2]) + emitf(Xunlocal); + }else{ + for(node=n; node; node=node->child[2]){ + emitf(Xmark); + walk(node->child[1]); + emitf(Xmark); + walk(node->child[0]); + emitf(Xassign); + } + } + node = n; + break; + /* control structures */ + case Twhile: + addr1 = interpreter.i; // head of loop + walk(node->child[0]); + if(addr1 == interpreter.i) + fatal("TODO"); + emitf(Xtrue); + addr2 = emiti(0); // goto end of loop + + walk(node->child[1]); + emitf(Xgoto); + emiti(addr1); // goto top of loop + storepc(addr2); + break; + + case Tfor: + emitf(Xmark); + if(node->child[1]){ // for( x in X ) + walk(node->child[1]); + // emitf(Xglob) + }else{ // for(X) + fatal("TODO"); + } + emitf(Xmark); // null initial value for Xlocal + emitf(Xmark); + walk(node->child[0]); + emitf(Xlocal); + + addr1 = emitf(Xfor); + addr2 = emiti(0); + + walk(node->child[2]); + emitf(Xgoto); + emiti(addr1); + storepc(addr2); + emitf(Xunlocal); + break; + + /* forks */ + case '&': + emitf(Xasync); + addr1 = emiti(0); + walk(node->child[0]); + emitf(Xexit); + storepc(addr1); + break; + + case Tsubshell: + emitf(Xsubshell); + addr1 = emiti(0); + walk(node->child[0]); + emitf(Xexit); + storepc(addr1); + break; + + case Tpipe: + emitf(Xpipe); + + emiti(node->redir.fd[0]); + emiti(node->redir.fd[1]); + addr1 = emiti(0); + addr2 = emiti(0); + + walk(node->child[0]); + emitf(Xexit); + storepc(addr1); + + walk(node->child[1]); + emitf(Xreturn); + storepc(addr2); + + emitf(Xpipewait); + + break; + } +} + +// ----------------------------------------------------------------------- +// main exports + +void +freecode(Code *c) +{ + if(--c[0].i!=0) + return; + efree(c); +} + +Code * +copycode(Code *c) +{ + c[0].i++; + return c; +} + +int +compile(Tree *node) +{ + flush(shell.err); + + interpreter.i = 0; + interpreter.code = emalloc(100*sizeof(*interpreter.code)); + emiti(0); // reference count: no thread owns code yet + + walk(node); + + emitf(Xreturn); + emitf(nil); + + compiled = interpreter.code; + return 0; +} diff --git a/src/cmd/rc/exec.c b/src/cmd/rc/exec.c new file mode 100644 index 0000000..5baaf1a --- /dev/null +++ b/src/cmd/rc/exec.c @@ -0,0 +1,1267 @@ +#include "rc.h" +#include "exec.h" + +#include + +int yyparse(void); + +struct Builtin{ + char *name; + void (*func)(void); +}; + +struct State { + int async; +}; + +static struct State state; + +// ----------------------------------------------------------------------- +// globals + +static Word nullpath = { .str="", .link=nil }; + +struct Builtin builtin[]={ + {"cd", xcd}, + {".", xdot}, + {"echo", xecho}, + {"exit", xexit}, + {"fg", xfg}, + {"jobs", xjob}, + 0, +}; + +// ----------------------------------------------------------------------- +// internal + +/* words and lists */ + +static +void +pushword(char *str) +{ + if(!runner->args) + fatal("attempt to push on empty argument stack\n"); + + runner->args->word = makeword(str, runner->args->word); +} + +static +void +popword(void) +{ + Word *w; + if(!runner->args) + fatal("tried to pop word on empty argument stack\n"); + + w = runner->args->word; + if(!w) + fatal("tried to pop word but nothing there\n"); + + runner->args->word = w->link; + efree(w->str); + efree(w); +} + +static +Word* +copywords(Word *a, Word *tail) +{ + Word *v = nil, **end; + + for(end=&v; a; a = a->link,end=&(*end)->link) + *end = makeword(a->str, nil); + *end = tail; + + return v; +} + +static +void +freewords(Word *w) +{ + Word *n; + while(w){ + efree(w->str); + n = w->link; + efree(w); + w = n; + } +} + +static +void +freelist(Word *w) +{ + Word *n; + while(w){ + n = w->link; + efree(w->str); + efree(w); + w = n; + } + +} + +static +void +pushlist(void) +{ + List *stack = emalloc(sizeof(*stack)); + + stack->word = nil; + stack->link = runner->args; + + runner->args = stack; +} + +static +void +poplist(void) +{ + List *stack = runner->args; + if(!stack) + fatal("attempted to pop an empty argument stack\n"); + + freelist(stack->word); + runner->args = stack->link; + efree(stack); +} + +/* system interop */ +static +Word* +path(char *w) +{ + Word *path; + + if(strncmp(w, "/", 1)==0 + || strncmp(w, "./", 2)==0 + || strncmp(w, "../", 3)==0 + || (path = var("path")->val)==0) + path=&nullpath; + + return path; +} + +static inline +void +undoredirs(void) +{ + while(runner->redir.end != runner->redir.start) + Xpopredir(); +} + +static inline +int +exitsnext(void) +{ + Code *c = &runner->code.exe[runner->code.i]; + while(c->f == Xpopredir) + c++; + + return c->f == Xexit; +} + +static inline +void +defaultsignal(void) +{ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + signal(SIGCHLD, SIG_DFL); +} + +static inline +void +setpid(Thread *job, int pid) +{ + job->pid = pid; + if(job->pgid <= 0){ + job->pgid = pid; + addjob(job); + } + + setpgid(pid, job->pgid); +} + +/* fork/execute helpers */ + +static inline +void +initchild(Thread *job, int fg) +{ + int pid = getpid(); + setpid(job, pid); + + if(job->flag.user){ + if(fg) + tcsetpgrp(0, job->pgid); + else + job->flag.user = 0; + defaultsignal(); + } + + clearwait(job); +} + +static inline +void +initparent(Thread *job, int pid, int fg) +{ + setpid(job, pid); + + if(job->flag.user){ + if(!fg){ + tcsetpgrp(0, job->pgid); + job->flag.user = 0; + } + } + addwait(job, pid); +} + +static +void +xx(void) +{ + popword(); // "exec" + if(!runner->args->word){ + Xerror("empty argument list"); + return; + } + + redirect(runner->redir.end); + execute(runner->args->word, path(runner->args->word->str)); + poplist(); +} + +static +int +xforkx(void) +{ + int n, pid; + + switch(pid=fork()){ + case -1: + Xerror("try again\n"); + return -1; + case 0: // child + initchild(runner, 1); + + pushword("exec"); + xx(); + + exit(2); // NOTE: unreachable: xx does not return + default: // parent + initparent(runner, pid, 0); + + return pid; + } +} + +/* redirections */ +void +pushredir(int type, int from, int to) +{ + Redir *r = emalloc(sizeof(*r)); + + r->type = type; + r->from = from; + r->to = to; + + r->link = runner->redir.end, runner->redir.end = r; +} + +/* byte code */ +static +void +run(Code *c, int pc, Var *local, int inherit) +{ + Thread *new = emalloc(sizeof(*new)); + + new->code.i = pc; + new->code.exe = copycode(c); + + new->cmd.path = nil; + new->cmd.io = nil; + + new->args = nil; + new->local = local; + + new->flag.eof = 0; + if(runner){ + new->pid = runner->pid; + new->flag.user = runner->flag.user; + new->redir.end = new->redir.start = runner->redir.end; + }else{ + new->pid = shell.pid; + new->flag.user = shell.interactive; + new->redir.end = new->redir.start = nil; + } + + new->wait.status = 0; + new->wait.len = 0; + new->wait.cap = 0; + new->wait.on = nil; + + new->status = 0; + if(inherit) + new->pgid = runner->pgid; + else + new->pgid = -1; + + new->line = 0; + new->caller = runner; + new->link = nil; + + runner = new; +} + +// ----------------------------------------------------------------------- +// exported builtins + +// XXX: find a better place for these +Word* +makeword(char *str, Word *link) +{ + Word *w = emalloc(sizeof(*w)); + + w->str = strdup(str); + w->link = link; + + return w; +} + +void +freeword(Word *word) +{ + Word *n; + + while(word){ + efree(word->str); + n = word->link; + efree(word); + word = n; + } +} + +int +count(Word *w) +{ + int n; + for(n = 0; w; n++) + w = w->link; + return n; +} + +// ----------------------------------------------------------------------- +// builtins + +void +xecho(void) +{ + int fd; + Word *arg; + char *b, *s, buf[128]; + + fd = mapfd(1); + b = buf; + + popword(); // echo + + // TODO: controllable flags here + arg = runner->args->word; +printword: + s = arg->str; + while(*s){ + *b++ = *s++; + if(b == arrend(buf)-2) // always have 2 bytes available + write(fd, buf, arrlen(buf)-2), b = buf; + } + + arg = arg->link; + if(arg){ + *b++ = ' '; + goto printword; + }else{ + *b++ = '\n'; + *b++ = 0; + /* fallthrough */ + } + write(fd, buf, b-buf); + + poplist(); +} + +void +xexit(void) +{ + Word *arg; + + popword(); // exit + arg = runner->args->word; + switch(count(arg)){ + default: + print(shell.err, "invalid number of arguments to exit, exiting anyways\n"); + case 0: + Xexit(); + } + /* unreachable */ +} + +void +xcd(void) +{ + Word *arg; + Word *cdpath; + char dir[512]; + + popword(); // cd + + arg = runner->args->word; + switch(count(arg)){ + default: + print(shell.err, "usage: cd [directory]\n"); + break; + case 0: + arg = var("home")->val; + if(count(arg) >= 1){ + if(chdir(arg->str) < 0) + print(shell.err, "failed cd: %s\n", strerror(errno)); + }else{ + print(shell.err, "ambiguous cd: $home empty\n"); + } + break; + + case 1: + // TODO: add cdpath + cdpath = &nullpath; + for(; cdpath; cdpath = cdpath->link){ + strcpy(dir, cdpath->str); + if(dir[0]) + strcat(dir,"/"); + strcat(dir, arg->str); + if(chdir(dir) < 0){ + print(shell.err, "failed cd %s: %s\n", dir, strerror(errno)); + } + break; + } + break; + } + + poplist(); +} + +static Code dotcmd[14] = +{ + [0] = {.i = 0}, + [1] = {.f = Xmark}, + [2] = {.f = Xword}, + [3] = {.s = "0"}, + [4] = {.f = Xlocal}, + [5] = {.f = Xmark}, + [6] = {.f = Xword}, + [7] = {.s = "*"}, + [8] = {.f = Xlocal}, + [9] = {.f = Xreadcmd}, + [10] = {.f = Xunlocal}, + [11] = {.f = Xunlocal}, + [12] = {.f = Xreturn}, +}; + +void +xdot(void) +{ + Word *p; + List *argv; + char *base; + int fd, iflag = 0; + Thread *old; + char file[512]; + + popword(); // "." +#if 0 + if(proc->args->word && strcmp(proc->args->word->str, "-i")==0){ + iflag = 1; + popword(); + } +#endif + /* get input file */ + if(!runner->args->word){ + Xerror("usage: . [-i] file [arg ...]\n"); + return; + } + + base = strdup(runner->args->word->str); + popword(); + for(fd=-1, p=path(base); p; p = p->link){ + strcpy(file, p->str); + + if(file[0]) + strcat(file, "/"); + strcat(file, base); + + if((fd = open(file, 0))>=0) + break; + } + + if(fd<0){ + print(shell.err, "failed open: %s: ", base); + return; + } + /* set up for a new command loop */ + old = runner; // store pointer to old code + run(dotcmd, 1, nil, 0); + + /* operations on new command stack */ + pushredir(Rclose, fd, 0); + runner->cmd.path = base; + runner->cmd.io = openfd(fd); + + /* push $* value */ + pushlist(); + runner->args->word = old->args->word; + + /* free caller's copy of $* */ + argv = old->args; + old->args = argv->link; + efree(argv); + + /* push $0 value */ + pushlist(); + pushword(base); + //ndot++; +} + +void +xjob(void) +{ + int i; + Thread *job; + + for(i=0, job = shell.jobs; job; job = job->link, i++) + report(job,i); + + poplist(); +} + +void +xfg(void) +{ + int i; + Thread *job, *old; + + popword(); // fg + + /* get input job id */ + if(!runner->args->word){ + print(shell.err, "usage: fg [pid|\%num]\n"); + poplist(); + return; + } + + i = atoi(runner->args->word->str); + popword(); // [pid|num] + + for(job=shell.jobs; i > 0; job=job->link, --i) + ; + + poplist(); // this goes here? + + wakeup(job); + job->caller = runner, runner = job; // XXX: can this leave zombies? + foreground(job, 1); +} + +void +xboot(int argc, char *argv[]) +{ + int i; + Code bootstrap[32]; + char num[12]; + + i = 0; + bootstrap[i++].i = 1; + bootstrap[i++].f = Xmark; + bootstrap[i++].f = Xword; + bootstrap[i++].s="*"; + bootstrap[i++].f = Xassign; + bootstrap[i++].f = Xmark; + bootstrap[i++].f = Xmark; + bootstrap[i++].f = Xword; + bootstrap[i++].s="*"; + bootstrap[i++].f = Xdollar; + bootstrap[i++].f = Xword; + bootstrap[i++].s = "/dev/stdin"; + bootstrap[i++].f = Xword; + bootstrap[i++].s="."; + bootstrap[i++].f = Xbasic; + bootstrap[i++].f = Xexit; + bootstrap[i].i = 0; + + run(bootstrap, 1, nil, 0); + runner->pid = runner->pgid = shell.pid; + pushlist(); // prime bootstrap argv + + argv0 = strdup(argv[0]); + for(i = argc-1; i > 0; --i) + pushword(argv[i]); + + /* main interpreter loop */ + for(;;){ + runner->code.i++; + (*runner->code.exe[runner->code.i-1].f)(); + } +} + +// ----------------------------------------------------------------------- +// exported interpreter bytecode + +void +Xmark(void) +{ + pushlist(); +} + +void +Xword(void) +{ + pushword(runner->code.exe[runner->code.i++].s); +} + +void +Xtrue(void) +{ + if(!runner->status){ + assert(runner->wait.status == Pdone); + runner->code.i++; + deljob(runner); + runner->pgid = -1; + }else + runner->code.i = runner->code.exe[runner->code.i].i; +} + +void +Xfalse(void) +{ + if(runner->status){ + assert(runner->wait.status == Pdone); + runner->code.i++; + deljob(runner); + runner->pgid = -1; + } else + runner->code.i = runner->code.exe[runner->code.i].i; +} + +void +Xgoto(void) +{ + runner->code.i = runner->code.exe[runner->code.i].i; +} + +void +Xfor(void) +{ + if(!runner->args->word){ + poplist(); + runner->code.i = runner->code.exe[runner->code.i].i; + }else{ + freelist(runner->local->val); + + runner->local->val = runner->args->word; + runner->local->new = 1; + runner->args->word = runner->args->word->link; + + runner->local->val->link = nil; + runner->code.i++; + } + +} + +static +Word* +catlist(Word *l, Word *r, Word *tail) +{ + Word *w; + char *buf; + + if(l->link || r->link) + tail = catlist( (!l->link)?l:l->link, (!r->link)?r:r->link, tail); + + buf = emalloc(strlen(l->str)+strlen(r->str)+1); + strcpy(buf, l->str); + strcat(buf, r->str); + + w = makeword(buf, tail); + efree(buf); + + return w; +} + +void +Xconcatenate(void) +{ + int rn, ln; + Word *l = runner->args->word; + Word *r = runner->args->link->word; + Word *w = runner->args->link->link->word; + + ln = count(l), rn = count(r); + if(ln != 0 || rn != 0) { + if(ln == 0 || rn == 0){ + Xerror("null list in concatenation\n"); + return; + } + if(ln != 1 && rn != 1 && ln != rn) { + Xerror("mismatched list lengths in concatenation\n"); + return; + } + w = catlist(l, r, w); + } + + poplist(); + poplist(); + runner->args->word = w; +} + +void +Xdollar(void) +{ + int n; + char *s, *t; + Word *a, *star; + + if(count(runner->args->word)!=1){ + Xerror("variable name not singleton!\n"); + return; + } + s = runner->args->word->str; + // deglob(s); + n = 0; + + for(t = s;'0'<=*t && *t<='9';t++) + n = n*10+*t-'0'; + + a = runner->args->link->word; + + if(n==0 || *t) + a = copywords(var(s)->val, a); + else{ + star = var("*")->val; + if(star && 1<=n && n<=count(star)){ + while(--n) + star = star->link; + + a = makeword(star->str, a); + } + } + + poplist(); + runner->args->word = a; +} + +static +Word* +cpwords(Word *array, Word *tail, int n) +{ + Word *cp, **end; + + cp = nil, end = &cp; + while(n-- > 0){ + *end = makeword(array->str, nil); + end = &(*end)->link; + array = array->link; + } + *end = tail; + + return cp; +} + + +static +Word* +getindex(Word *array, int len, Word *index, Word *tail) +{ + char *s; + int n, m; + if(!index) + return tail; + + tail = getindex(array, len, index->link, tail); + + s = index->str; + //deglob(s) + + m = 0, n = 0; + while('0' <= *s && *s <= '9') + n = 10*n + (*s++ - '0'); + if(*s == '-'){ + if(*++s == 0) + m = len - n; + else{ + while('0' <= *s && *s <= '9') + m = 10*m + (*s++ - '0'); + m -= n; + } + } + + if(n<1 || n > len || m < 0) + return tail; + if(n+m > len) + m = len-n; + while(--n > 0) + array = array->link; + return cpwords(array, tail, m+1); +} + +void +Xindex(void) +{ + char *s; + Word *val, *ret; + + if(count(runner->args->word) != 1){ + Xerror("variable name not a singleton"); + return; + } + s = runner->args->word->str; + //deglob(s) + val = var(s)->val; + poplist(); + + ret = runner->args->link->word; // pointer to next stack frame + ret = getindex(val, count(val), runner->args->word, ret); + poplist(); + + // push result back on stack + runner->args->word = ret; +} + +void +Xjoin(void) +{ + int n; + char *s; + Word *arg, *elt; + + if(count(runner->args->word) != 1){ + Xerror("variable name is not singleton\n"); + return; + } + + s = runner->args->word->str; + // deglob(s) + + arg = var(s)->val; + poplist(); + + n = count(arg); + if(n==0){ + pushword(""); + return; + } + + for(elt = arg; elt; elt=elt->link) + n += strlen(elt->str); + + s = emalloc(n); + if(arg){ + strcpy(s, arg->str); + for(elt = arg->link; elt; elt = elt->link){ + strcat(s, " "); + strcat(s, elt->str); + } + }else + s[0] = 0; + + pushword(s); + efree(s); +} + +void +Xassign(void) +{ + Var *v; + + if(count(runner->args->word)!=1){ + Xerror("variable name not singleton!\n"); + return; + } + //deglob(runq->argv->words->word); + v = var(runner->args->word->str); + poplist(); + + //globlist(); + freewords(v->val); + v->val = runner->args->word; + v->new = 1; + if(v->update) + v->update(v); + + runner->args->word = nil; + poplist(); +} + +void +Xreadcmd(void) +{ + Thread *root; + Word *prompt; + + flush(shell.err); + root = runner; + + resetprompt(); + + if(yyparse()){ + // resource cleanup? + if(runner->flag.eof) + Xreturn(); + else + --root->code.i; + }else{ + --root->code.i; /* re-execute Xreadcmd after codebuf runs */ + run(compiled, 1, root->local, 0); + } + + killzombies(); + freeparsetree(); +} + +void +Xlocal(void) +{ + if(count(runner->args->word)!=1){ + Xerror("variable name must be singleton\n"); + return; + } + //deglob(shell->args->word->str); + + runner->local = makevar(strdup(runner->args->word->str), runner->local); + runner->local->val = copywords(runner->args->link->word, nil); + runner->local->new = 1; + + poplist(); + poplist(); +} + +void +Xunlocal(void) +{ + Var *v = runner->local, *hide; + if(!v) + fatal("Xunlocal: no locals!\n", 0); + + runner->local = v->link; + hide = var(v->name); + hide->new = 1; + + efree(v->name); + freewords(v->val); + efree(v); +} + +void +Xasync(void) +{ + int pid; + /* + int null = open("/dev/null", 0); + if(!null){ + Xerror("can not open /dev/null\n"); + return; + } + */ + + switch(pid=fork()){ + case -1: + // close(null); + Xerror("fork failed: try again"); + break; + + case 0: // child in background + initchild(runner,0); + /* pushredir(Ropen, null, 0); */ + + run(runner->code.exe, runner->code.i+1, runner->local, 0); + runner->caller = nil; + runner->flag.user = 0; + break; + + default: // parent in foreground + initparent(runner,pid,1); + // close(null); + + runner->code.i = runner->code.exe[runner->code.i].i; /* jump to end of async command */ + /* don't wait: continue running */ + } +} + +void +Xsubshell(void) +{ + int pid, user; + + user = runner->flag.user; + switch(pid=fork()){ + case -1: + Xerror("fork failed: try again"); + break; + + case 0: // child + initchild(runner, 1); + run(runner->code.exe, runner->code.i+1, runner->local, 1); + runner->caller = nil; + break; + + default: // parent + initparent(runner, pid, 0); // relinquish control + waitfor(runner, pid); // wait until child finishes + if(user){ + tcsetpgrp(0, shell.pid); + runner->flag.user = 1; // take control + } + + runner->code.i = runner->code.exe[runner->code.i].i; // jump to end of subshell command and continue execution + } +} + +void +Xpipewait(void) +{ + foreground(runner, 0); +} + +void +Xpipe(void) +{ + Thread *orig; + int pc, pid, lfd, rfd, pfd[2]; + + orig = runner; + pc = orig->code.i; + lfd = orig->code.exe[pc++].i; + rfd = orig->code.exe[pc++].i; + + if(pipe(pfd)<0){ + Xerror("can't get pipe\n"); + return; + } + + switch(pid=fork()){ + case -1: + Xerror("try again"); + break; + case 0: // child + initchild(runner,1); + + /* child 0 (writer) forked process */ + run(runner->code.exe, pc+2, runner->local, 1); + runner->caller = nil; + + close(pfd[0]); + pushredir(Ropen, pfd[1], lfd); + break; + + default: // parent + initparent(runner,pid,0); + + /* child 1 (reader) subprocess*/ + run(runner->code.exe, runner->code.exe[pc].i, runner->local, 1); + + close(pfd[1]); + pushredir(Ropen, pfd[0], rfd); + + orig->code.i = orig->code.exe[pc+1].i; + break; + } +} + +void +Xbasic(void) +{ + Var *v; + Word *arg; + int pid, status; + struct Builtin *b; + + arg = runner->args->word; + if(!arg){ + Xerror("empty argument list\n"); + return; + } + + v = var(arg->str); + if(v->func){ + return; + } + + // see if it matches a builtin + for(b = builtin; b->name; b++){ + if(strcmp(b->name, arg->str)==0){ + b->func(); + return; + } + } + + /* if we are here then it's an external command */ + if(exitsnext()){ // if we exit immediately, no need to fork + pushword("exec"); + xx(); + Xexit(); + } + + // run the external command + if((pid = xforkx()) < 0) { + Xerror("try again"); + return; + } + + poplist(); + foreground(runner, 0); // waits for child +} + +void +Xcount(void) +{ + Word *arg; + char *str, num[12]; + + if(count(runner->args->word) != 1){ + Xerror("variable name not a singleton\n"); + return; + } + + str = runner->args->word->str; + arg = var(str)->val; + poplist(); + + itoa(num, count(arg)); + pushword(num); +} + +void +Xflat(void) +{ + int len; + char *str; + Word *arg, *a; + + if(count(runner->args->word)!=1){ + Xerror("variable name is not a singleton\n"); + return; + } + + str = runner->args->word->str; + arg = var(str)->val; + poplist(); + + len = count(arg); + if(!len){ + pushword(""); + return; + } + + for(a=arg; a; a=a->link) + len += strlen(a->str); + + str = emalloc(len); + if(arg){ + strcpy(str, arg->str); + for(a = arg->link; a; a = a->link){ + strcat(str," "); + strcat(str,a->str); + } + }else + str[0] = 0; + + pushword(str); + efree(str); +} + +void +Xbang(void) +{ + if(runner->status) + runner->status = 0; + else + runner->status = 1; +} + +void +Xpopredir(void) +{ + Redir *r = runner->redir.end; + if(!r) + fatal("attempted to pop a nil redir\n"); + + runner->redir.end = runner->redir.end->link; + if(r->type==Ropen) + close(r->from); + + efree(r); +} + +void +Xreturn(void) +{ + Thread *curr = runner; + + switch(curr->wait.status){ + /* + * If our job is still running or suspended we must: + * 1. move program one step back to rerun Xreturn upon recall + * 2. return to our calling thread + * 3. don't free! + */ + case Prun: + report(curr, 0); + curr->flag.user = 0; + case Pstop: + curr->code.i--; + runner = curr->caller; + curr->caller = nil; // detach job + return; + /* + * If our job has finished: + * 1. remove from our list + * 2. continue to clean up its memory + */ + case Pdone: + deljob(curr); + /* fallthrough */ + default: + ; + } + + undoredirs(); + + while(curr->args) + poplist(); + freecode(curr->code.exe); + efree(curr->wait.on); + + runner = curr->caller; + efree(curr); + if(!runner) + exit(0); +} + +void +Xexit(void) +{ + exit(runner->status); +} + +void +Xerror(char *msg) +{ + print(shell.err, "rc: %s", msg); + flush(shell.err); + while(!runner->flag.user) + Xreturn(); +} + diff --git a/src/cmd/rc/exec.h b/src/cmd/rc/exec.h new file mode 100644 index 0000000..a3a6ae9 --- /dev/null +++ b/src/cmd/rc/exec.h @@ -0,0 +1,47 @@ +#pragma once + +/* + * opcode routines + * arguments on stack (...) + * arguments in line [...] + * code in line with jump around {...} + */ + +void Xmark(void); // Xmark marks stack location for word +void Xindex(void); // Xindex +void Xlocal(void); // Xlocal(name,val) create local variable, assign value +void Xunlocal(void); // Xunlocal delete local variable +void Xdollar(void); // Xdollar(name) get value of name +void Xtrue(void); // Xtrue{...} execute {} if true +void Xfalse(void); // Xfalse{...} execute {} if false +void Xgoto(void); // Xgoto[addr] goto address +void Xfor(void); // Xfor(var, list){... Xreturn} +void Xreadcmd(void); // +void Xassign(void); +void Xbang(void); +void Xasync(void); +void Xbasic(void); // Xbasic(args) run command and wait for result +void Xsubshell(void); +void Xword(void); +void Xjoin(void); +void Xconcatenate(void); +void Xcount(void); +void Xflat(void); +void Xpipe(void); +void Xpipewait(void); +void Xpopredir(void); + +void Xreturn(void); +void Xexit(void); + +void Xerror(char*); + +/* builtin commands */ +void xcd(void); +void xdot(void); +void xecho(void); +void xexit(void); +void xfg(void); +void xjob(void); + +void xboot(int argc, char *argv[]); diff --git a/src/cmd/rc/input.c b/src/cmd/rc/input.c new file mode 100644 index 0000000..cc2383d --- /dev/null +++ b/src/cmd/rc/input.c @@ -0,0 +1,1679 @@ +#include "rc.h" + +#include +#include + +/* don't change order of these without modifying matrix */ +enum +{ + NonPrintable, + Alnum, + Punctuation, + Space +}; + +static int ascii[256] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, + 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +struct Mode +{ + ushort raw : 1; + ushort multiline : 1; + ushort mask : 1; + ushort defer : 1; + struct { + ushort on : 1; + ushort insert : 1; + } vi ; +}; + +/* + * the structure represents the state during line editing. + * we pass this state to functions implementing specific editing functionalities + */ +struct TerminalState +{ + int ifd; /* terminal stdin file descriptor. */ + int ofd; /* terminal stdout file descriptor. */ + + struct{ + char *s; /* raw UTF-8 bytes */ + int len; /* number of bytes in prompt */ + int size; /* number of (printed) runes in prompt */ + } prompt; + + struct{ + intptr cap; /* capacity of edit buffer */ + intptr len; /* current number of bytes stored */ + intptr pos; /* position within edit buffer */ + char *buf; + } edit; /* edit buffer */ + + struct{ + intptr cap; /* number of columns in terminal */ + intptr len; /* current edited line length (in runes) */ + intptr pos; /* current cursor position (in runes) */ + intptr old; /* previous refresh cursor position (in runes) */ + } cursor; + + struct{ + intptr cap; + intptr len; + char *buf; + } yank; /* yank buffer */ + + intptr maxrows; /* maximum num of rows used so far (multiline mode) */ + intptr history; /* index of history we are currently editing */ +}; + +/* + * line history (circular buffer) + */ +struct History +{ + char **bot, **top, *entry[1024]; +}; + +/* globals */ +static struct Mode mode; +static struct History history; +static struct termios originalterm; + +enum +{ + KeyNil = 0, /* nil */ + KeyCtrlA = 1, /* Ctrl+a */ + KeyCtrlB = 2, /* Ctrl-b */ + KeyCtrlC = 3, /* Ctrl-c */ + KeyCtrlD = 4, /* Ctrl-d */ + KeyCtrlE = 5, /* Ctrl-e */ + KeyCtrlF = 6, /* Ctrl-f */ + KeyCtrlH = 8, /* Ctrl-h */ + KeyTab = 9, /* Tab */ + KeyCtrlK = 11, /* Ctrl+k */ + KeyCtrlL = 12, /* Ctrl+l */ + KeyEnter = 13, /* Enter */ + KeyCtrlN = 14, /* Ctrl-n */ + KeyCtrlP = 16, /* Ctrl-p */ + KeyCtrlT = 20, /* Ctrl-t */ + KeyCtrlU = 21, /* Ctrl+u */ + KeyCtrlW = 23, /* Ctrl+w */ + KeyEsc = 27, /* Escape */ + KeyBackspace = 127 /* Backspace */ +}; + +static void doatexit(void); + +/* vi operations */ +typedef struct +{ + intptr buffer; + intptr cursor; +} Position; + +typedef Position (*Noun)(struct TerminalState*, int); +typedef void (*Verb)(struct TerminalState*, Position); + +static +int +runetype(rune r) +{ + if(r<128) + return ascii[r]; + if(utf8·isspace(r)) + return Space; + if(utf8·isdigit(r) || utf8·isalpha(r)) + return Alnum; + if(utf8·ispunct(r)) + return Punctuation; + + return NonPrintable; +} + +static +void +normalcursor(int fd) +{ + write(fd,"\e[2 q",5); +} + +static +void +insertcursor(int fd) +{ + write(fd,"\e[6 q",5); +} + +/* raw mode: 1960 magic shit. */ +static +int +enterraw(int fd) +{ + struct termios raw; + + if(!shell.interactive) + goto fatal; + + if(!mode.defer){ + atexit(doatexit); + mode.defer = 1; + } + if(tcgetattr(fd,&originalterm) == -1) + goto fatal; + + raw = originalterm; /* modify the original mode */ + + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if(tcsetattr(fd,TCSAFLUSH,&raw) < 0) + goto fatal; + + mode.raw = 1; + return 1; + +fatal: + errno = ENOTTY; + return 0; +} + +static +void +exitraw(int fd) +{ + /* don't even check the return value as it's too late. */ + if(mode.raw && tcsetattr(fd,TCSAFLUSH,&originalterm) != -1) + mode.raw = 0; +} + +/* use the esc [6n escape sequence to query the horizontal cursor position + * and return it. on error -1 is returned, on success the position of the + * cursor. */ +static +int +cursorposition(int ifd, int ofd) +{ + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if(write(ofd, "\x1b[6n", 4) != 4) + return -1; + + /* Read the response: ESC [ rows ; cols R */ + while(i < sizeof(buf)-1) { + if(read(ifd,buf+i,1) != 1) + break; + if(buf[i] == 'R') + break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if(buf[0] != KeyEsc || buf[1] != '[') + return -1; + if(sscanf(buf+2,"%d;%d",&rows,&cols) != 2) + return -1; + + return cols; +} + +/* try to get the number of columns in the current terminal, or assume 80 if it fails. */ +static +int +columns(int ifd, int ofd) +{ + struct winsize ws; + + if(ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0){ + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = cursorposition(ifd,ofd); + if(start == -1) + goto failed; + + /* Go to right margin and get position. */ + if(write(ofd,"\x1b[999C",6) != 6) + goto failed; + cols = cursorposition(ifd,ofd); + if(cols == -1) + goto failed; + + /* Restore position. */ + if(cols > start){ + char esc[32]; + snprintf(esc,32,"\x1b[%dD",cols-start); + if(write(ofd,esc,strlen(esc)) == -1) + ; + } + return cols; + }else + return ws.ws_col; + +failed: + return 80; +} + +static +void +clear(void) +{ + if(write(1,"\x1b[H\x1b[2J",7) <= 0) + ; +} + +/* beep: used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static +void +beep(void) +{ + fprintf(stderr, "\x7"); + fflush(stderr); +} + +// ----------------------------------------------------------------------- +// command history + +void +inithistory(void) +{ + history.bot = history.top = history.entry; +} + +int +addhistory(char *line) +{ + char *copy; + + copy = strdup(line); + if(!copy) + return 0; + + *history.top++ = copy; + if(history.top == arrend(history.entry)) + history.top = history.entry; + + if(history.top == history.bot){ + efree(history.bot); + history.bot++; + } + + return 1; +} + +static +void +pophistory(void) +{ + if(--history.top < history.entry) + history.top = arrend(history.entry)-1; + efree(*history.top); +} + +static void refreshline(struct TerminalState *); + +static +char ** +currenthistory(struct TerminalState *term, intptr *size) +{ + char **entry; + intptr len, head; + + if(history.top > history.bot){ + len = history.top - history.bot; + entry = history.top - term->history - 1; + }else if(history.top < history.bot){ + len = (arrend(history.entry) - history.bot) + (history.top - history.entry); + if((head=history.top - history.entry) < term->history) + entry = arrend(history.entry) - head; + else + entry = history.top - term->history - 1; + }else + return nil; + + *size = len; + return entry; +} + +static +void +usehistory(struct TerminalState *term, int d) +{ + rune r; + intptr w, len; + char *b, *e, **entry; + + if(!(entry = currenthistory(term, &len))) + return; + + efree(*entry); + *entry = strdup(term->edit.buf); + + term->history += d; + if(term->history < 0){ + term->history = 0; + return; + }else if(term->history >= len){ + term->history = len - 1; + return; + } + entry = currenthistory(term, &len); + + strncpy(term->edit.buf, *entry, term->edit.cap); + term->edit.buf[term->edit.cap-1] = 0; + + /* update cursor/buffer positions */ + term->edit.len = term->edit.pos = strlen(term->edit.buf); + for(w=0, b=term->edit.buf, e=term->edit.buf+term->edit.len; b < e; ){ + b += utf8·decode(b, &r); + w += utf8·runewidth(r); + } + term->cursor.len = term->cursor.pos = w; + + refreshline(term); +} + +// ----------------------------------------------------------------------- +// line editing + +/* + * we define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. this is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. + */ + +struct Buffer +{ + int len; + char *b; +}; + +static +void +initbuffer(struct Buffer *ab) +{ + ab->b = nil; + ab->len = 0; +} + +static +void +append(struct Buffer *ab, const char *s, int len) +{ + char *new = realloc(ab->b,ab->len+len); + + if (new == nil) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static +void +freebuffer(struct Buffer *ab) +{ + free(ab->b); +} + +/* single line low level line refresh. + * + * rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static +void +refreshsingleline(struct TerminalState *term) +{ + char esc[64]; + struct Buffer ab; + + int n, w; + rune r; + int fd = term->ofd; + intptr off = term->prompt.size; + char *buf = term->edit.buf; + intptr len = term->edit.len; + intptr pos = term->cursor.pos; + intptr col = term->cursor.len; + + while((off+pos) >= term->cursor.cap){ + n = utf8·decode(buf, &r); + w = utf8·runewidth(r); + + buf+=n, len-=n; + pos-=w, col-=w; + } + + assert(buf <= term->edit.buf + len); + + while(off+col > term->cursor.cap){ + n = utf8·decodeprev(buf+len-1, &r); + w = utf8·runewidth(r); + + len-=n, col-=w; + } + assert(len >= 0); + + initbuffer(&ab); // TODO: do we need so much malloc pressure? + + /* move cursor to left edge */ + snprintf(esc,64,"\r"); + append(&ab,"\r",1); + + /* write the prompt and the current buffer content */ + append(&ab, term->prompt.s, term->prompt.len); + + if(mode.mask == 1) + while(len--) + append(&ab,"*",1); + else + append(&ab,buf,len); + + snprintf(esc,64,"\x1b[0K"); // erase to right + append(&ab,esc,strlen(esc)); + + snprintf(esc,64,"\r\x1b[%dC", (int)(off+pos)); // move cursor to original position + append(&ab,esc,strlen(esc)); + + if(write(fd,ab.b,ab.len) == -1) /* can't recover from write error. */ + ; + + freebuffer(&ab); +} + +/* multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static +void +refreshmultilines(struct TerminalState *term) +{ +#if 0 + char esc[64]; + int plen = term->plen; + int rows = (plen+term->len+term->cols-1)/term->cols; /* rows used by current buf. */ + int rpos = (plen+term->oldpos+term->cols)/term->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int i; + int old_rows = term->maxrows; + int fd = term->ofd, j; + struct Buffer ab; + + /* Update maxrows if needed. */ + if(rows > (int)term->maxrows) + term->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + initbuffer(&ab); + if(old_rows-rpos > 0){ + snprintf(esc,64,"\x1b[%dB", old_rows-rpos); + append(&ab,esc,strlen(esc)); + } + + /* Now for every row clear it, go up. */ + for(j = 0; j < old_rows-1; j++){ + snprintf(esc,64,"\r\x1b[0K\x1b[1A"); + append(&ab,esc,strlen(esc)); + } + + /* clean the top line. */ + snprintf(esc,64,"\r\x1b[0K"); + append(&ab,esc,strlen(esc)); + + /* Write the prompt and the current buffer content */ + append(&ab,term->prompt,strlen(term->prompt)); + if(mode.mask == 1){ + for(i = 0; i < term->len; i++) append(&ab,"*",1); + }else + append(&ab,term->buf,term->len); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if(term->pos && term->pos == term->len && (term->pos+plen) % term->cols == 0) { + append(&ab,"\n",1); + snprintf(esc,64,"\r"); + append(&ab,esc,strlen(esc)); + rows++; + if(rows > (int)term->maxrows) + term->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+term->pos+term->cols)/term->cols; /* current cursor relative row. */ + + /* Go up till we reach the expected positon. */ + if(rows-rpos2 > 0){ + snprintf(esc,64,"\x1b[%dA", rows-rpos2); + append(&ab,esc,strlen(esc)); + } + + /* Set column. */ + col = (plen+(int)term->pos) % (int)term->cols; + if(col) + snprintf(esc,64,"\r\x1b[%dC", col); + else + snprintf(esc,64,"\r"); + append(&ab,esc,strlen(esc)); + + term->oldpos = term->pos; + + if(write(fd,ab.b,ab.len) == -1) /* Can't recover from write error. */ + ; + + freebuffer(&ab); +#endif +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static +void +refreshline(struct TerminalState *term) +{ + if(mode.multiline) + refreshmultilines(term); + else + refreshsingleline(term); +} + +/* insert the rune 'c' at cursor current position. + * on error writing to the terminal -1 is returned, otherwise 0. */ +int +insertrune(struct TerminalState *term, int n, char *c) +{ + int w; + rune r; + + utf8·decode(c, &r); + w = utf8·runewidth(r); + + if(term->edit.len + n <= term->edit.cap){ + if(term->edit.pos == term->edit.len){ + memcpy(term->edit.buf+term->edit.pos, c, n); + + term->edit.pos += n, term->edit.len += n; + term->cursor.pos += w, term->cursor.len += w; + + term->edit.buf[term->edit.len] = '\0'; + + if(!mode.multiline && ((term->prompt.size+term->cursor.pos+n) <= term->cursor.cap)){ + if(mode.mask){ + c = "*"; + n = 1; + } + if(write(term->ofd, c, n) == -1) + return 0; + } + refreshline(term); + }else{ + memmove(term->edit.buf+term->edit.pos+n, term->edit.buf+term->edit.pos, term->edit.len-term->edit.pos); + memcpy(term->edit.buf+term->edit.pos, c, n); + + term->edit.pos += n, term->edit.len += n; + term->cursor.pos += w, term->cursor.len += w; + + term->edit.buf[term->edit.len] = '\0'; + refreshline(term); + } + } + + return 1; +} + +int +insertbytes(struct TerminalState *term, int len, char *buf) +{ + int nr; + if(term->edit.len + len > term->edit.cap){ + len = term->edit.cap - term->edit.len; + buf[len] = 0; + } + nr = utf8·len(buf); + + if(term->edit.pos == term->cursor.len){ + memcpy(term->edit.buf+term->edit.len, buf, len); + + term->edit.pos += len, term->edit.len += len; + term->cursor.pos += nr, term->cursor.len += nr; + + // XXX: transfer the modeline here? + term->edit.buf[term->edit.len] = '\0'; + refreshline(term); + }else{ + memmove(term->edit.buf+term->edit.pos+len,term->edit.buf+term->edit.pos,term->edit.len-term->edit.pos); + memcpy(term->edit.buf+term->edit.pos, buf, len); + + term->edit.pos += len, term->edit.len += len; + term->cursor.pos += nr, term->cursor.len += nr; + + term->edit.buf[term->edit.len] = '\0'; + refreshline(term); + } + + return 1; +} + +// ----------------------------------------------------------------------- +// vi functionality + +/* modes */ + +static +void +normalmode(int fd) +{ + mode.vi.insert = 0; + normalcursor(fd); +} + +static +void +insertmode(int fd) +{ + mode.vi.insert = 1; + insertcursor(fd); +} + +/* actions */ + +static +void +move(struct TerminalState *term, Position to) +{ + if(to.buffer != term->edit.pos){ + term->edit.pos = to.buffer; + term->cursor.pos = to.cursor; + refreshline(term); + } +} + +static +void +yank(struct TerminalState *term, Position to) +{ + intptr len, off; + + if(to.buffer == term->edit.pos) + return; // noop + + if(to.buffer > term->edit.pos){ + len = to.buffer - term->edit.pos; + off = term->edit.pos; + }else{ + len = term->edit.pos - to.buffer; + off = to.buffer; + } + + if(term->yank.cap < len+1){ + efree(term->yank.buf); + term->yank.cap = len+1; + term->yank.buf = emalloc(len+1); + } + term->yank.len = len; + memcpy(term->yank.buf, term->edit.buf+off, len); + term->yank.buf[len] = 0; +} + +static +void +delete(struct TerminalState *term, Position to) +{ + intptr diff; + + // delete characters in front of us (exclusive) + if(to.buffer > term->edit.pos){ + diff = to.buffer - term->edit.pos; + memmove(term->edit.buf+term->edit.pos, term->edit.buf+to.buffer, term->edit.len-to.buffer+1); + term->edit.len -= diff; + + diff = to.cursor - term->cursor.pos; + goto refresh; + } + + // delete characters behind us + if(to.buffer < term->edit.pos){ + diff = term->edit.pos - to.buffer; + memmove(term->edit.buf+to.buffer, term->edit.buf+term->edit.pos, term->edit.len-term->edit.pos+1); + term->edit.pos = to.buffer; + term->edit.len -= diff; + + diff = term->cursor.pos - to.cursor; + term->cursor.pos = to.cursor; + goto refresh; + } + // do nothing + return; + +refresh: + term->cursor.len -= diff; + refreshline(term); +} +/* movements */ + +#define CURRENT(term) (Position){ .buffer=(term)->edit.pos, .cursor=(term)->cursor.pos }; + +// move cursor to the left n boxes +static +Position +left(struct TerminalState *term, int n) +{ + rune r; + int w, d; + Position pos = CURRENT(term); + char *buf = term->edit.buf + term->edit.pos; + + d = 0; + while(n > 0 && buf > term->edit.buf){ + buf -= utf8·decodeprev(buf-1, &r); + + w = utf8·runewidth(r); + n -= w; + d += w; + } + + pos.cursor = MAX(pos.cursor-d, 0); + pos.buffer = MAX(buf-term->edit.buf, 0); + return pos; +} + +// move cursor to the right n boxes +static +Position +right(struct TerminalState *term, int n) +{ + rune r; + int w, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + char *end = term->edit.buf + term->edit.len; + + d = 0; + while(n > 0 && buf < end){ + buf += utf8·decode(buf, &r); + + w = utf8·runewidth(r); + n -= w; + d += w; + } + + pos.cursor = MIN(pos.cursor+d, term->cursor.len); + pos.buffer = MIN(buf-term->edit.buf, term->edit.len); + return pos; +} + +static +Position +prevword(struct TerminalState *term, int n) +{ + rune r; + int c, w, b, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + + d = 0; + while(n-- > 0 && buf > term->edit.buf){ + eatspace: + b = utf8·decodeprev(buf-1, &r); + w = utf8·runewidth(r); + if((c=runetype(r)) == Space){ + buf -= b; + d += w; + + if(buf <= term->edit.buf) + break; + + goto eatspace; + } + + eatword: + if(runetype(r) == c){ + buf -= b; + d += w; + + if(buf <= term->edit.buf) + break; + + b = utf8·decodeprev(buf-1, &r); + w = utf8·runewidth(r); + + goto eatword; + } + } + + pos.cursor = MAX(pos.cursor-d, 0); + pos.buffer = MAX(buf-term->edit.buf, 0); + return pos; +} + +static +Position +nextword(struct TerminalState *term, int n) +{ + rune r; + int c, b, w, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + char *end = term->edit.buf + term->edit.len; + + d = 0; + while(n-- > 0 && buf < end){ + b = utf8·decode(buf, &r); + w = utf8·runewidth(r); + c = runetype(r); + eatword: + if(runetype(r) == c){ + buf += b; + d += w; + + if(buf >= end) + break; + + b = utf8·decode(buf, &r); + w = utf8·runewidth(r); + goto eatword; + } + eatspace: + while((c=runetype(r)) == Space){ + buf += b; + d += w; + + if(buf >= end) + break; + + b = utf8·decode(buf, &r); + w = utf8·runewidth(r); + goto eatspace; + } + } + + pos.cursor = MIN(pos.cursor+d, term->cursor.len); + pos.buffer = MIN(buf-term->edit.buf, term->edit.len); + return pos; +} + + +static +Position +prevWord(struct TerminalState *term, int n) +{ + rune r; + int c, w, b, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + + d = 0; + while(n-- > 0 && buf > term->edit.buf){ + eatspace: + b = utf8·decodeprev(buf-1, &r); + w = utf8·runewidth(r); + if((c=runetype(r)) == Space){ + buf -= b; + d += w; + + if(buf <= term->edit.buf) + break; + + goto eatspace; + } + + eatword: + if((c=runetype(r)) != Space){ + buf -= b; + d += w; + + if(buf <= term->edit.buf) + break; + + b = utf8·decodeprev(buf-1, &r); + w = utf8·runewidth(r); + + goto eatword; + } + } + + pos.cursor = MAX(pos.cursor-d, 0); + pos.buffer = MAX(buf-term->edit.buf, 0); + return pos; +} + +static +Position +nextWord(struct TerminalState *term, int n) +{ + rune r; + int b, w, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + char *end = term->edit.buf + term->edit.len; + + d = 0; + while(n-- > 0 && buf < end){ + eatword: + b = utf8·decode(buf, &r); + w = utf8·runewidth(r); + if(runetype(r) != Space){ + buf += b; + d += w; + + if(buf > end) + break; + + goto eatword; + } + + eatspace: + if(runetype(r) == Space){ + buf += b; + d += w; + + if(buf > end) + break; + + b = utf8·decode(buf, &r); + w = utf8·runewidth(r); + + goto eatspace; + } + } + + pos.cursor = MIN(pos.cursor+d, term->cursor.len); + pos.buffer = MIN(buf-term->edit.buf, term->edit.len); + return pos; +} + +static +Position +nextend(struct TerminalState *term, int n) +{ + rune r; + int c, b, w, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + char *end = term->edit.buf + term->edit.len; + + d = 0; + while(n-- > 0 && buf+1 < end){ + eatspace: + b = utf8·decode(buf+1, &r); + w = utf8·runewidth(r); + while((c=runetype(r)) == Space){ + buf += b; + d += w; + + if(buf+1 >= end) + break; + + goto eatspace; + } + eatword: + if(runetype(r) == c){ + buf += b; + d += w; + + if(buf+1 >= end) + break; + + b = utf8·decode(buf+1, &r); + w = utf8·runewidth(r); + goto eatword; + } + } + + pos.cursor = MIN(pos.cursor+d, term->cursor.len); + pos.buffer = MIN(buf-term->edit.buf, term->edit.len); + return pos; +} + +static +Position +nextEnd(struct TerminalState *term, int n) +{ + rune r; + int b, w, d; + Position pos = CURRENT(term); + + char *buf = term->edit.buf + term->edit.pos; + char *end = term->edit.buf + term->edit.len; + + d = 0; + while(n-- > 0 && buf+1 < end){ + eatspace: + b = utf8·decode(buf+1, &r); + w = utf8·runewidth(r); + if(runetype(r) == Space){ + buf += b; + d += w; + + if(buf+1 > end) + break; + + goto eatspace; + } + + eatword: + if(runetype(r) != Space){ + buf += b; + d += w; + + if(buf+1 > end) + break; + + b = utf8·decode(buf+1, &r); + w = utf8·runewidth(r); + + goto eatword; + } + } + + pos.cursor = MIN(pos.cursor+d, term->cursor.len); + pos.buffer = MIN(buf-term->edit.buf, term->edit.len); + return pos; +} + +#define HOME(term) (Position){0} +#define END(term) (Position){(term)->edit.len, (term)->cursor.len} + +static +int +vi(struct TerminalState *term, char c) +{ + int n = 1; + Verb verb = move; + +action: + switch(c){ + /* # of repeats */ + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + n = 0; + while('0' <= c && c <= '9'){ + n = 10*n + (c-'0'); + if(read(term->ifd, &c, 1)<1) + return -1; + } + goto action; + + /* composable actions */ + case 'l': verb(term, right(term, n)); break; + case 'h': verb(term, left(term, n)); break; + case '0': verb(term, HOME(term)); break; + case '$': verb(term, END(term)); break; + case 'b': verb(term, prevword(term,n)); break; + case 'B': verb(term, prevWord(term,n)); break; + case 'w': verb(term, nextword(term,n)); break; + case 'W': verb(term, nextWord(term,n)); break; + case 'e': verb(term, nextend(term,n)); break; + case 'E': verb(term, nextEnd(term,n)); break; + + /* verb switches */ + case 'd': // delete + verb = delete; + if(read(term->ifd, &c, 1)<1) + return -1; + /* special cases */ + switch(c){ + case 'd': + move(term, HOME(term)); + delete(term, END(term)); + return 0; + default: + goto action; + } + case 'y': // yank + verb = yank; + if(read(term->ifd, &c, 1)<1) + return -1; + /* special cases */ + switch(c){ + case 'y': + if(term->yank.cap < term->edit.len+1){ + efree(term->yank.buf); + term->yank.len = term->edit.len; + term->yank.cap = term->edit.len+1; + term->yank.buf = emalloc(term->yank.cap); + } + memcpy(term->yank.buf, term->edit.buf, term->edit.len+1); + break; + default: + goto action; + } + break; + + case 'p': // put + insertbytes(term, term->yank.len, term->yank.buf); + refreshline(term); + return 0; + + /* special cases + * sadly I don't know a better way than to have these checks for move + * the vi language doesn't fully compose + */ + case 'i': insertmode: + if(verb != move) goto unrecognized; + insertmode(term->ofd); + break; + + case 'I': + if(verb != move) goto unrecognized; + move(term, HOME(term)); + goto insertmode; + + case 'a': + if(verb != move) goto unrecognized; + if(term->edit.pos < term->edit.len){ + term->edit.pos++; + refreshline(term); + } + goto insertmode; + + case 'A': + if(verb != move) goto unrecognized; + move(term, END(term)); + goto insertmode; + + case 'x': + if(verb != move) goto unrecognized; + delete(term, right(term, 1)); + break; + + case 'X': + if(verb != move) goto unrecognized; + delete(term, left(term, 1)); + break; + + case 'r': + if(verb != move) goto unrecognized; + if(read(term->ifd, &c, 1)<1) + return -1; + if(c < ' ') + break; + term->edit.buf[term->edit.pos] = c; + refreshline(term); + break; + + // TODO: replace mode? + + case 'c': + if(verb != move) goto unrecognized; + insertmode(term->ofd); + verb = delete; + if(read(term->ifd, &c, 1)<1) + return -1; + goto action; + + case 'C': + if(verb != move) goto unrecognized; + insertmode(term->ofd); + goto deleteln; + + case 'D': + if(verb != move) goto unrecognized; + deleteln: + term->edit.len = term->edit.pos; + term->edit.buf[term->edit.pos] = 0; + refreshline(term); + break; + + default: unrecognized: + beep(); + break; + } + + return 0; +} +#undef END + +#define END(term) (Position){(term).edit.len, (term).cursor.len} + +static +int +size(char *s) +{ + rune c; + int n, len = 0;; + while((c=*s)){ + if(c == '\033'){ + n = 1; + esccode: + c = s[n]; + if(!c) // we hit end of string in the middle of parsing an escape code! + return len; + if(c == 'm'){ + s += n + 1; + continue; // outer loop + } + n++; + goto esccode; + } + n = utf8·decode(s, &c); + s += n; + len += utf8·runewidth(c); + } + return len; +} + +/* this function is the core of the line editing capability of linenoise. + * it expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned asap to read(). + * + * the resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * the function returns the length of the current buffer. */ +static +int +interact(int ifd, int ofd, char *buf, intptr len, char *prompt) +{ + int n, aux; + char esc[3]; + char c[UTFmax+1] = { 0 }; + rune r; + + struct TerminalState term; + /* + * populate the state that we pass to functions implementing + * specific editing functionalities + */ + term.ifd = ifd; + term.ofd = ofd; + + term.edit.buf = buf; + term.edit.cap = len; + term.edit.len = 0; + term.edit.pos = 0; + + term.prompt.s = prompt; + term.prompt.len = strlen(prompt); + term.prompt.size = size(prompt); + + term.cursor.pos = 0; + term.cursor.len = 0; + term.cursor.cap = columns(ifd, ofd); + + term.maxrows = 0; + term.history = 0; + + term.yank.buf = nil; + term.yank.cap = term.yank.len = 0; + + /* buffer starts empty. */ + term.edit.buf[0] = '\0'; + term.edit.cap--; /* make sure there is always space for the nulterm */ + + /* push current (empty) command onto history stack */ + addhistory(""); + + if(write(term.ofd,prompt,term.prompt.len) == -1) + return -1; + + for(;;){ + n = read(term.ifd,c,1); + if(n <= 0) + goto finish; + + /* partition input by rune */ + if(utf8·onebyte(c[0])){ + r = c[0]; + }else if(utf8·twobyte(c[0])){ + n = read(term.ifd,c+1,1); + if(n < 1 || (n=utf8·decode(c, &r)) != 2) + goto finish; + }else if(utf8·threebyte(c[0])){ + n = read(term.ifd,c+1,2); + if(n < 2 || (n=utf8·decode(c, &r)) != 3) + goto finish; + }else if(utf8·fourbyte(c[0])){ + n = read(term.ifd,c+1,3); + if(n < 3 || (n=utf8·decode(c, &r)) != 4) + goto finish; + }else + goto finish; + + switch(r){ + case KeyEnter: + pophistory(); + if(mode.multiline) + move(&term, END(term)); + goto finish; + + case KeyCtrlC: + errno = EAGAIN; + return -1; + + case KeyBackspace: + case KeyCtrlH: + delete(&term, left(&term, 1)); + break; + + case KeyCtrlD: + if(term.edit.len > 0) + delete(&term, right(&term, 1)); + break; + + case KeyCtrlT: + if(term.edit.pos > 0 && term.edit.pos < term.edit.len){ + aux = buf[term.edit.pos-1]; + + buf[term.edit.pos-1] = buf[term.edit.pos]; + buf[term.edit.pos] = aux; + + if(term.edit.pos != term.edit.len-1) + term.edit.pos++; + + refreshline(&term); + } + break; + + case KeyCtrlB: + move(&term, left(&term, 1)); + break; + + case KeyCtrlF: /* ctrl-f */ + move(&term, right(&term, 1)); + break; + + case KeyCtrlP: /* ctrl-p */ + usehistory(&term, +1); + break; + + case KeyCtrlN: /* ctrl-n */ + usehistory(&term, -1); + break; + + case KeyEsc: /* escape sequence */ + /* + * try to read two bytes representing the escape sequence. + * if we read less than 2 and we are in vi mode, interpret as command + * + * NOTE: we could do a timed read here + */ + switch(read(term.ifd,esc,2)){ + case 0: + if(mode.vi.on){ + if(mode.vi.insert){ + normalmode(term.ofd); + if(term.edit.pos > 0){ + --term.edit.pos; + refreshline(&term); + } + continue; + } + } + case 1: + if(mode.vi.on){ + if(mode.vi.insert){ + normalmode(term.ofd); + if(vi(&term,esc[0]) < 0){ + term.edit.len = -1; + goto finish; + } + continue; + } + } + default: // 2 + ; + } + + /* ESC [ sequences. */ + if(esc[0] == '['){ + if(0 <= esc[1] && esc[1] <= '9'){ + /* extended escape, read additional byte. */ + if(read(term.ifd,esc+2,1) == -1) + break; + + if(esc[2] == '~'){ + switch(esc[1]){ + case '3': /* delete key. */ + delete(&term, left(&term,1)); + break; + } + } + }else{ + switch(esc[1]) { + case 'A': /* up */ + usehistory(&term, +1); + break; + case 'B': /* down */ + usehistory(&term, -1); + break; + case 'C': /* right */ + move(&term, right(&term, 1)); + break; + case 'D': /* left */ + move(&term, left(&term, 1)); + break; + case 'H': /* home */ + move(&term, HOME(term)); + break; + case 'F': /* end*/ + move(&term, END(term)); + break; + } + } + } + /* ESC O sequences. */ + else if(esc[0] == 'O'){ + switch(esc[1]) { + case 'H': /* home */ + move(&term, HOME(term)); + break; + case 'F': /* end*/ + move(&term, END(term)); + break; + } + } + break; + + default: + if(mode.vi.on && !mode.vi.insert && n == 1){ + if(vi(&term,c[0]) < 0){ + term.edit.len = -1; + goto finish; + } + }else if(!insertrune(&term,n,c)){ + term.edit.len = -1; + goto finish; + } + + break; + + case KeyCtrlU: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + term.edit.pos = term.edit.len = 0; + term.cursor.pos = term.cursor.len = 0; + refreshline(&term); + break; + + case KeyCtrlK: /* Ctrl+k, delete from current to end of line. */ + buf[term.edit.pos] = '\0'; + term.edit.len = term.edit.pos; + term.cursor.len = term.cursor.pos; + refreshline(&term); + break; + + case KeyCtrlA: /* Ctrl+a, go to the start of the line */ + move(&term, HOME(term)); + break; + + case KeyCtrlE: /* ctrl+e, go to the end of the line */ + move(&term, END(term)); + break; + + case KeyCtrlL: /* ctrl+term, clear screen */ + clear(); + refreshline(&term); + break; + + case KeyCtrlW: /* ctrl+w, delete previous word */ + delete(&term, prevword(&term,1)); + break; + } + } +finish: + efree(term.yank.buf); + return term.edit.len; +} + +/* + * this special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. + */ +void +printkeycode(void) +{ + int n; + char c, quit[4]; + + printf("entering debugging mode. printing key codes.\n" + "press keys to see scan codes. type 'quit' at any time to exit.\n"); + + if(!enterraw(0)) + return; + + memset(quit,' ',4); + + for(;;){ + n = read(0,&c,1); + if(n <= 0) + continue; + memmove(quit,quit+1,sizeof(quit)-1); // shift string to left + quit[arrlen(quit)-1] = c; /* Insert current char on the right. */ + + if(memcmp(quit,"quit",sizeof(quit)) == 0) + break; + + printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* go to left edge manually, we are in raw mode. */ + fflush(stdout); + } + exitraw(0); +} + +/* + * this function calls the line editing function edit() using the stdin set in raw mode + */ +static +int +raw(char *buf, intptr len, char *prompt) +{ + int n; + + if(!len){ + errno = EINVAL; + return -1; + } + + // XXX: should we not hardcode stdin and stdout fd? + if(!enterraw(0)) return -1; + n = interact(0, 1, buf, len, prompt); + exitraw(0); + + return n; +} + +/* + * called when readline() is called with the standard + * input file descriptor not attached to a TTY. For example when the + * program is called in pipe or with a file redirected to its standard input + * in this case, we want to be able to return the line regardless of its length + */ +static +int +notty(void) +{ + int c; + + for(;;){ + c = fgetc(stdin); + put(&runner->cmd.io, c); + } +} + +void +enablevi(void) +{ + mode.vi.on = 1; + insertmode(1); +} + +/* + * The high level function that is the main API. + * This function checks if the terminal has basic capabilities and later + * either calls the line editing function or uses dummy fgets() so that + * you will be able to type something even in the most desperate of the + * conditions. + */ +int +readline(char *prompt) +{ + int n; + + // reset the command buffer + runner->cmd.io->e = runner->cmd.io->b = runner->cmd.io->buf; + + if(!shell.interactive) + return notty(); + + if((n = raw(runner->cmd.io->e, runner->cmd.io->cap-1, prompt)) == -1) + return 0; + runner->cmd.io->e += n; + + /* insert a newline character at the end */ + put(&runner->cmd.io, '\n'); + + return 1; +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static +void +doatexit(void) +{ + exitraw(0); + normalcursor(1); +} diff --git a/src/cmd/rc/io.c b/src/cmd/rc/io.c new file mode 100644 index 0000000..dc81c2e --- /dev/null +++ b/src/cmd/rc/io.c @@ -0,0 +1,437 @@ +#include "rc.h" +#include "parse.h" + +#define CAP0 512 + +Io* +openfd(int fd) +{ + Io *io = emalloc(sizeof(*io) + CAP0); + + io->fd = fd; + io->cap = CAP0; + io->b = io->e = io->buf; + io->s = nil; + + return io; +} + +Io* +openstr(void) +{ + char *s; + Io *io = emalloc(sizeof(*io) + CAP0); + + io->fd = -1; + io->cap = CAP0; + io->b = io->s = emalloc(101); + io->e = io->b+100; + + for(s = io->b; s<=io->e; s++) + *s=0; + + return io; +} + +#if 0 +/* + * open a corebuffer to read. EOF occurs after reading len characters from buf + */ + +Io* +opencore(char *s, int len) +{ + Io *io = emalloc(sizeof(*io)); + char *buf = emalloc(len); + io->fd = -1 /*open("/dev/null", 0)*/; + io->b = io->s = buf; + io->e = buf+len; + memcpy(buf, s, len); + + return io; +} +#endif + +void +iorewind(Io *io) +{ + if(io->fd==-1) + io->b = io->s; + else{ + io->b = io->e = io->buf; + lseek(io->fd, 0L, 0); + } +} + +void +terminate(Io *io) +{ + if(io->fd>=0) + close(io->fd); + if(io->s) + efree(io->s); + + efree((char *)io); +} + +static +int +refill(Io *io) +{ + int n; + + if(io->fd==-1 || (n = read(io->fd, io->buf, io->cap))<=0) + return EOF; + + io->b = io->buf; + io->e = io->buf+n; + + return *io->b++&0xff; +} + + +void +flush(Io *io) +{ + int n; + char *s; + + if(io->s){ + n = io->e-io->s; + io->s = realloc(io->s, n+101); + if(io->s==0) + panicf("Can't realloc %d bytes in flush!", n+101); + io->b = io->s+n; + io->e = io->b+100; + for(s = io->b;s<=io->e;s++) *s='\0'; + }else{ + n = io->b-io->buf; + if(n && write(io->fd, io->buf, n) < 0) + write(3, "write error\n", 12); + io->b = io->buf; + io->e = io->buf + io->cap; + } +} + + +static +void +printchar(Io *io, int c) +{ + if(io->b==io->e) + flush(io); + + *io->b++=c; +} + +void +printquote(Io *io, char *s) +{ + printchar(io, '\''); + for(;*s;s++) + if(*s=='\'') + print(io, "''"); + else printchar(io, *s); + printchar(io, '\''); +} + +void +printstr(Io *io, char *s) +{ + if(s==0) + s="(null)"; + while(*s) printchar(io, *s++); +} + +void +printword(Io *io, char *s) +{ + char *t; + + for(t = s;*t;t++) + if(!iswordchar(*t)) + break; + + if(t==s || *t) + printquote(io, s); + else + printstr(io, s); +} + +void +printptr(Io *io, void *v) +{ + int n; + uintptr p; + + p = (uintptr)v; + if(sizeof(uintptr) == sizeof(uvlong) && p>>32) + for(n = 60;n>=32;n-=4) printchar(io, "0123456789ABCDEF"[(p>>n)&0xF]); + + for(n = 28;n>=0;n-=4) printchar(io, "0123456789ABCDEF"[(p>>n)&0xF]); +} + +static +void +printint(Io *io, int n) +{ + if(n<0){ + if(n!=INT_MIN){ + printchar(io, '-'); + printint(io, -n); + return; + } + /* n is two's complement minimum integer */ + n = -(INT_MIN+1); + printchar(io, '-'); + printint(io, n/10); + printchar(io, n%10+'1'); + return; + } + if(n>9) + printint(io, n/10); + printchar(io, n%10+'0'); +} + +static +void +printoct(Io *io, unsigned n) +{ + if(n>7) + printoct(io, n>>3); + printchar(io, (n&7)+'0'); +} + +static +void +printval(Io *io, Word *a) +{ + if(a){ + while(a->link && a->link->str){ + printword(io, a->str); + printchar(io, ' '); + a = a->link; + } + printword(io, a->str); + } +} + +#define C0 t->child[0] +#define C1 t->child[1] +#define C2 t->child[2] + +static +void +printtree(Io *io, Tree *t) +{ + if(!t) + return; + + switch(t->type){ + default: print(io, "bad(%d)[%p %p %p]", t->type, C0, C1, C2); break; + case '$': print(io,"$%t",C0); break; + case '&': print(io,"%t&",C0); break; + case '^': print(io,"%t^%t",C0,C1); break; + case '`': print(io,"`%t",C0); break; + + case Tbasic: print(io, "%t", C0); break; + case Tbang: print(io, "!%t", C0); break; + case Tblock: print(io, "{%t}", C0); break; + case Tcount: print(io, "$#%t", C0); break; + case Tparen: print(io, "(%t)", C0); break; + case Tjoin: print(io,"$\"%t",C0); break; + case Tindex: print(io, "%t(%t)",C0); break; + case Tsubshell: print(io, "@ %t",C0); break; + //case Ttwiddle: print(io, "~ %t %t", C0, C1); break; + + case Toror: + case Tandand: + + case Targs: + if(!C0) + print(io, "%t", C1); + else if(!C1) + print(io, "%t", C0); + else + print(io, "%t %t", C0, C1); + break; + + case ';': + if(C0){ + if(C1) + print(io, "%t;%t", C0, C1); + else + print(io, "%t", C0); + }else + print(io, "%t", C1); + break; + + case Twords: + if(C0) + print(io, "%t", C0); + print(io, "%t", C1); + + case Tword: + if(t->quoted) + print(io, "%Q", t->str); + print(io, "%q", t->str); + break; + + case '=': + print(io, "%t=%t", C0, C1); + if(C2) + print(io, " %t", C2); + break; + + case Tdup: + if(t->redir.type == Rdupfd) + print(io, ">[%d=%d]", t->redir.fd[1], t->redir.fd[0]); + else + print(io, ">[%d=]", t->redir.fd[0]); + print(io, "%t", C1); + break; + + case Tredir: + switch(t->redir.type){ + case Rhere: + printchar(io, '<'); + case Rread: + printchar(io, '<'); + goto readfd; + case Rrdwr: + printchar(io, '<'); + printchar(io, '>'); + readfd: + if(t->redir.fd[0]!=0) + print(io, "[%d]", t->redir.fd[0]); + break; + case Rappend: + printchar(io, '>'); + goto writefd; + case Rwrite: + printchar(io, '>'); + printchar(io, '>'); + writefd: + if(t->redir.fd[0]!=1) + print(io, "[%d]", t->redir.fd[0]); + break; + } + print(io, "%t", C0); + if(C1) + print(io, " %t", C1); + break; + + case Tpipe: + print(io, "%t|", C0); + if(t->redir.fd[1]==0){ + if(t->redir.fd[0]!=1) + print(io, "[%d]", t->redir.fd[0]); + } + else + print(io, "[%d=%d]", t->redir.fd[0], t->redir.fd[1]); + print(io, "%t", C1); + break; + } +} + +#undef C0 +#undef C1 +#undef C2 + +// ----------------------------------------------------------------------- +// exports + +/* readers */ +int +get(Io *io) +{ + if(io->b==io->e) + return refill(io); + + return *io->b++ & 0xFF; +} + +/* writers */ +int +put(Io **iop, char c) +{ + int nb, ne, nc; + Io *io = *iop; + char *e = io->b + io->cap; + + if(io->e == e){ + nb = io->b - io->buf; + ne = io->e - io->buf; + nc = 2*io->cap; + + if(!(io = erealloc(io, sizeof(*io)+nc))) + return 0; + + io->b = io->buf + nb; + io->e = io->buf + ne; + io->cap = nc; + + *iop = io; + } + + *io->e++ = c; + return 1; +} + +/* printers */ +static int pfmtnest; + +void +print(Io *io, char *fmt, ...) +{ + va_list args; + char err[ERRMAX]; + + va_start(args, fmt); + pfmtnest++; + + for(;*fmt;fmt++) + if(*fmt!='%') + printchar(io, *fmt); + else + switch(*++fmt){ + case '\0': + va_end(args); + return; + case 'c': + printchar(io, va_arg(args, int)); + break; + case 'd': + printint(io, va_arg(args, int)); + break; + case 'o': + printoct(io, va_arg(args, unsigned)); + break; + case 'p': + printptr(io, va_arg(args, void*)); + break; + case 'Q': + printquote(io, va_arg(args, char *)); + break; + case 'q': + printword(io, va_arg(args, char *)); + break; + case 's': + printstr(io, va_arg(args, char *)); + break; + case 't': + printtree(io, va_arg(args, struct Tree *)); + break; + case 'v': + printval(io, va_arg(args, struct Word *)); + break; + default: + printchar(io, *fmt); + break; + } + + va_end(args); + + if(--pfmtnest==0) + flush(io); +} diff --git a/src/cmd/rc/job.c b/src/cmd/rc/job.c new file mode 100644 index 0000000..1587951 --- /dev/null +++ b/src/cmd/rc/job.c @@ -0,0 +1,91 @@ +#include "rc.h" + +#include +#include + +// ----------------------------------------------------------------------- +// exports + +Thread * +getjob(int pid, int *index) +{ + int i; + Thread *job; + for(i=0,job=shell.jobs; job && job->pid != pid; i++, job=job->link) + ; + + return job; +} + +void +report(Thread *job, int index) +{ + switch(job->wait.status){ + case Pdone: + print(shell.err, "job %d [%d]: done\n", index, job->pid); + break; + case Pstop: + print(shell.err, "job %d [%d]: suspended\n", index, job->pid); + break; + case Pagain: + print(shell.err, "job %d [%d]: continued\n", index, job->pid); + break; + case Prun: + print(shell.err, "job %d [%d]: running\n", index, job->pid); + break; + default: + fatal("bad wait status: %d\n", job->wait.status); + } +} + +void +wakeup(Thread *job) +{ + int i; + job->wait.status = Prun; + for(i=0; i < job->wait.len; i++){ + if(job->wait.on[i].status == Pstop) + job->wait.on[i].status = Prun; + } + + tcsetpgrp(0, job->pgid); +} + +void +foreground(Thread *job, int now) +{ + Thread *caller = job->caller; + if(now){ + if(kill(-job->pgid, SIGCONT) < 0) + perror("kill[SIGCONT]"); + } + + waitall(job); + /* + * reset state if we have a caller + * otherwise we will exit anyways + */ + if(caller && caller->flag.user){ + tcsetpgrp(0, caller->pid); + job->flag.user = 1; + } +} + +void +addjob(Thread *job) +{ + job->link = shell.jobs; + shell.jobs = job; + job->wait.status = Prun; +} + +void +deljob(Thread *job) +{ + Thread **jp; + + for(jp = &shell.jobs; *jp && *jp != job; jp = &(*jp)->link) + ; + + *jp = job->link; +} diff --git a/src/cmd/rc/lex.c b/src/cmd/rc/lex.c new file mode 100644 index 0000000..9ca2453 --- /dev/null +++ b/src/cmd/rc/lex.c @@ -0,0 +1,394 @@ +#include "rc.h" +#include "parse.h" + +static int advance(void); + +// ----------------------------------------------------------------------- +// lexer + +struct Lexer +{ + int c[2]; + ushort doprompt; + ushort hadword; + ushort haddollar; + ushort inquote; + char buf[BUFSIZ]; +}; + +static struct Lexer lexer = { .c={0, EOF}, .doprompt=1 }; + +#define put1(b) lexer.buf[0] = (b), lexer.buf[1] = 0; +#define put2(b0,b1) lexer.buf[0] = (b0), lexer.buf[1] = (b1), lexer.buf[2] = 0; +#define put3(b0,b1,b2) lexer.buf[0] = (b0), lexer.buf[1] = (b1), lexer.buf[2] = b2, lexer.buf[3] = 0; + +void +yyerror(const char *msg) +{ + print(shell.err, "rc:%d: ", runner->line); + + if(lexer.buf[0] && lexer.buf[0]!='\n') + print(shell.err, "%q: ", lexer.buf); + + print(shell.err, "%s\n", msg); + flush(shell.err); + + lexer.hadword = 0; + lexer.haddollar = 0; + + /* consume remaining tokens */ + while(lexer.c[0] !='\n' && lexer.c[0] != EOF) + advance(); +} + +int +readc(void) +{ + int c; + static int peek = EOF; + + if(peek!=EOF){ + c = peek; + peek = EOF; + return c; + } + + if(runner->flag.eof) + return EOF; + + if(!prompt(&lexer.doprompt)) + exit(1); // XXX: hack for signal handling right now... + + c = get(runner->cmd.io); + lexer.doprompt = lexer.doprompt || c=='\n' || c==EOF; + + if(c==EOF) + runner->flag.eof = 1; + + return c; +} + +static +int +peekc(void) +{ + if(lexer.c[1] == EOF) + lexer.c[1] = readc(); + + return lexer.c[1]; +} + +static +int +advance(void) +{ + int c = peekc(); + lexer.c[0] = lexer.c[1], lexer.c[1] = EOF; + + return c; +} + +static +void +skipws(void) +{ + int c; + for(;;){ + c = peekc(); + if(c== ' ' || c == '\t') + advance(); + else + return; + } +} + +static +void +skipnl(void) +{ + int c; + for(;;){ + c = peekc(); + if(c== ' ' || c == '\t' || c == '\n') + advance(); + else + return; + } +} + +static +int +nextis(int c) +{ + if(peekc()==c){ + advance(); + return 1; + } + return 0; +} + +static +char * +putbyte(char *buf, int c) +{ + if(!buf) + return buf; + + if(buf == arrend(lexer.buf)){ + fatal("lexer: out of buffer space"); + return nil; + } + *buf++ = c; + return buf; +} + +static +char * +putrune(char *buf, int c) +{ + buf = putbyte(buf, c); + if(utf8·onebyte(c)) + return buf; + if(utf8·twobyte(c)) + return putbyte(buf,advance()); + if(utf8·threebyte(c)){ + buf = putbyte(buf,advance()); + return putbyte(buf,advance()); + } + if(utf8·fourbyte(c)){ + buf = putbyte(buf,advance()); + buf = putbyte(buf,advance()); + return putbyte(buf,advance()); + } + fatal("malformed utf8 stream"); + + return nil; +} + +// ----------------------------------------------------------------------- +// exported functions + +// TODO: turn into static tables +int +iswordchar(int c) +{ + return !strchr("\n \t#;&|^$=`'{}()<>", c) && c!=EOF; +} + +int +isidentchar(int c) +{ + return c>' ' && !strchr("!\"#$%&'()+,-./:;<=>?@[\\]^`{|}~", c); +} + +int +yylex(void) +{ + int c, d = peekc(); + Tree *node; + char *w = lexer.buf; + + yylval.tree = nil; + + /* inject tokens */ + if(lexer.hadword){ + lexer.hadword = 0; + if(d=='('){ + advance(); + strcpy(lexer.buf, "( [Tindex]"); + return Tindex; + } + if(iswordchar(d) || d=='\'' || d=='`' || d=='$' || d=='"'){ + strcpy(lexer.buf, "^"); + return '^'; + } + } + + lexer.inquote = 0; + + skipws(); + switch(c=advance()){ + case EOF: + lexer.haddollar = 0; + put3('E','O','F'); + return EOF; + + case '$': + lexer.haddollar = 1; + if(nextis('#')){ + put2('$','#'); + return Tcount; + } + if(nextis('^')){ + put2('$','^'); + return Tjoin; + } + put1('$'); + return '$'; + + case '@': + lexer.haddollar = 0; + put1('@'); + return Tsubshell; + + case '!': + lexer.haddollar = 0; + put1('!'); + return Tbang; + + case '&': + lexer.haddollar = 0; + if(nextis('&')){ + put2('&','&'); + return Tandand; + } + put1('&'); + return '&'; + + case '|': + lexer.haddollar = 0; + if(nextis('|')){ + put2('|','|'); + return Toror; + } + node = maketree(); + *w++ = '|'; + + node->type = Tpipe; + node->redir.fd[0] = 1; + node->redir.fd[1] = 0; + goto redir; + + case '>': + lexer.haddollar = 0; + node = maketree(); + *w++ = '>'; + node->type = Tredir; + + if(nextis('>')){ + node->redir.type = Rappend; + *w++ = '>'; + }else + node->redir.type = Rwrite; + node->redir.fd[0] = 1; + goto redir; + + case '<': + lexer.haddollar = 0; + node = maketree(); + *w++ = '<'; + node->type = Tredir; + + if(nextis('<')){ + node->redir.type = Rhere; + *w++ = '<'; + }else if(nextis('>')){ + node->redir.type = Rrdwr; + *w++ = '>'; + }else{ + node->redir.type = Rread; + } + node->redir.fd[0] = 0; + /* fallthrough */ + redir: + if(nextis('[')){ + *w++='['; + c = advance(); + *w++ = c; + if(c < '0' || '9' < c){ + badredir: + *w = 0; + yyerror(node->type == Tpipe ? "pipe syntax" : "redirection syntax"); + return EOF; + } + node->redir.fd[0] = 0; + do{ + node->redir.fd[0] = 10*node->redir.fd[0]+(c-'0'); + *w++ = c; + c = advance(); + }while('0'<=c && c<='9'); + + if(c == '='){ + *w++ = '='; + if(node->type==Tredir) + node->type = Tdup; + c = advance(); + } + if(c < '0' || '9' < c){ + if(node->type == Tpipe) + goto badredir; + node->redir.type = Rclose; + }else{ + node->redir.type = Rdupfd; + node->redir.fd[1] = node->redir.fd[0]; + node->redir.fd[0] = 0; + do{ + node->redir.fd[0] = 10*node->redir.fd[0]+(c-'0'); + *w++ = c; + c = advance(); + }while('0'<=c && c<='9'); + } + if(c != ']' || (node->type == Tdup && (node->redir.type = Rhere || node->redir.type == Rappend))) + goto badredir; + *w++ = ']'; + } + *w++ = 0; + yylval.tree = node; + + return node->type; + + case '\'': + lexer.hadword = 1; + lexer.inquote = 1; + lexer.haddollar = 0; + for(;;){ + c = advance(); + if(c==EOF) + break; + + if(c=='\''){ + if(peekc()!='\'') + break; + advance(); + } + w = putrune(w, c); + } + if(w) + *w = 0; + node = token(Tword, lexer.buf); + node->quoted = 1; + return node->type; + + default: + ; + } + if(!iswordchar(c)){ + put1(c); + lexer.haddollar = 0; + return c; + } + + for(;;){ + w = putrune(w, c); + c = peekc(); + if(lexer.haddollar ? !isidentchar(c) : !iswordchar(c)) + break; + advance(); + } + + lexer.hadword = 1; + lexer.haddollar = 0; + if(w) + *w = 0; + + node = token(Tword, lexer.buf); + if((c=iskeyword(lexer.buf))){ + node->type = c; + lexer.hadword = 0; + } + + node->quoted = 0; + + yylval.tree = node; + return node->type; +} diff --git a/src/cmd/rc/main.c b/src/cmd/rc/main.c new file mode 100644 index 0000000..2c0aa42 --- /dev/null +++ b/src/cmd/rc/main.c @@ -0,0 +1,66 @@ +#include "rc.h" +#include "parse.h" +#include "exec.h" + +#include +#include + +// ----------------------------------------------------------------------- +// globals + +Thread *runner = nil; +Shell shell = { 0 }; + +// ----------------------------------------------------------------------- +// functions + +void +initshell(void) +{ + if((shell.interactive=isatty(0))){ + while(tcgetpgrp(0) != (shell.pid = getpgrp())) + kill(-shell.pid, SIGTTIN); + + /* ignore job control signals */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + /* + * NOTE: if SIGCHLD is set to SIG_IGN then + * 1. children that terminate do not become zombies + * 2. call a to wait() will block until all children have terminated + * 3. the call to wait will fail with errno == ECHILD + * see for discussion: + * https://stackoverflow.com/questions/1608017/no-child-process-error-from-waitpid-when-waiting-for-process-group + */ + // signal(SIGCHLD, SIG_IGN); + + /* take control */ + shell.pid = getpid(); + if(setpgid(shell.pid, shell.pid)<0) + fatal("could not put shell in its own process group"); + + tcsetpgrp(shell.pid, shell.pid); + } +} + +// ----------------------------------------------------------------------- +// main point of entry + +int +main(int argc, char *argv[]) +{ + shell.err = openfd(2); + + initenv(); + initpath(); + initkeywords(); + initshell(); + inithistory(); + + enablevi(); + xboot(argc, argv); + /* unreachable */ +} diff --git a/src/cmd/rc/parse.c b/src/cmd/rc/parse.c new file mode 100644 index 0000000..1b29d41 --- /dev/null +++ b/src/cmd/rc/parse.c @@ -0,0 +1,2059 @@ +/* A Bison parser, made by GNU Bison 3.8.2. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output, and Bison version. */ +#define YYBISON 30802 + +/* Bison version string. */ +#define YYBISON_VERSION "3.8.2" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + + + +/* First part of user prologue. */ +#line 7 "sys/cmd/rc/syntax.y" + + #include "rc.h" + + int yylex(void); + void yyerror(const char *); + +#line 78 "sys/cmd/rc/parse.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +#include "parse.h" +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_Tfor = 3, /* Tfor */ + YYSYMBOL_Tin = 4, /* Tin */ + YYSYMBOL_Twhile = 5, /* Twhile */ + YYSYMBOL_Tif = 6, /* Tif */ + YYSYMBOL_Telse = 7, /* Telse */ + YYSYMBOL_Tswitch = 8, /* Tswitch */ + YYSYMBOL_Tcase = 9, /* Tcase */ + YYSYMBOL_Tcasebody = 10, /* Tcasebody */ + YYSYMBOL_Ttwiddle = 11, /* Ttwiddle */ + YYSYMBOL_Tbang = 12, /* Tbang */ + YYSYMBOL_Tsubshell = 13, /* Tsubshell */ + YYSYMBOL_Tfunc = 14, /* Tfunc */ + YYSYMBOL_Tredir = 15, /* Tredir */ + YYSYMBOL_Tdup = 16, /* Tdup */ + YYSYMBOL_Tpipe = 17, /* Tpipe */ + YYSYMBOL_Tindex = 18, /* Tindex */ + YYSYMBOL_Tbasic = 19, /* Tbasic */ + YYSYMBOL_Targs = 20, /* Targs */ + YYSYMBOL_Tword = 21, /* Tword */ + YYSYMBOL_Twords = 22, /* Twords */ + YYSYMBOL_Tparen = 23, /* Tparen */ + YYSYMBOL_Tblock = 24, /* Tblock */ + YYSYMBOL_25_ = 25, /* ')' */ + YYSYMBOL_Tandand = 26, /* Tandand */ + YYSYMBOL_Toror = 27, /* Toror */ + YYSYMBOL_28_n_ = 28, /* '\n' */ + YYSYMBOL_29_ = 29, /* '^' */ + YYSYMBOL_30_ = 30, /* '$' */ + YYSYMBOL_Tcount = 31, /* Tcount */ + YYSYMBOL_Tjoin = 32, /* Tjoin */ + YYSYMBOL_33_ = 33, /* '(' */ + YYSYMBOL_34_ = 34, /* '{' */ + YYSYMBOL_35_ = 35, /* '}' */ + YYSYMBOL_36_ = 36, /* ';' */ + YYSYMBOL_37_ = 37, /* '&' */ + YYSYMBOL_38_ = 38, /* '=' */ + YYSYMBOL_39_ = 39, /* '`' */ + YYSYMBOL_YYACCEPT = 40, /* $accept */ + YYSYMBOL_rc = 41, /* rc */ + YYSYMBOL_line = 42, /* line */ + YYSYMBOL_body = 43, /* body */ + YYSYMBOL_paren = 44, /* paren */ + YYSYMBOL_block = 45, /* block */ + YYSYMBOL_cmds = 46, /* cmds */ + YYSYMBOL_cmdsln = 47, /* cmdsln */ + YYSYMBOL_ifbody = 48, /* ifbody */ + YYSYMBOL_case = 49, /* case */ + YYSYMBOL_casebody = 50, /* casebody */ + YYSYMBOL_assign = 51, /* assign */ + YYSYMBOL_redir = 52, /* redir */ + YYSYMBOL_epilog = 53, /* epilog */ + YYSYMBOL_cmd = 54, /* cmd */ + YYSYMBOL_basic = 55, /* basic */ + YYSYMBOL_atom = 56, /* atom */ + YYSYMBOL_word = 57, /* word */ + YYSYMBOL_executable = 58, /* executable */ + YYSYMBOL_nonkeyword = 59, /* nonkeyword */ + YYSYMBOL_keyword = 60, /* keyword */ + YYSYMBOL_words = 61, /* words */ + YYSYMBOL_wordsnl = 62, /* wordsnl */ + YYSYMBOL_nl = 63 /* nl */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif +#endif + +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; +#else +typedef short yytype_int16; +#endif + +/* Work around bug in HP-UX 11.23, which defines these macros + incorrectly for preprocessor constants. This workaround can likely + be removed in 2023, as HPE has promised support for HP-UX 11.23 + (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of + . */ +#ifdef __hpux +# undef UINT_LEAST8_MAX +# undef UINT_LEAST16_MAX +# define UINT_LEAST8_MAX 255 +# define UINT_LEAST16_MAX 65535 +#endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif +#endif + +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_uint8 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YY_USE(E) ((void) (E)) +#else +# define YY_USE(E) /* empty */ +#endif + +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ +# if __GNUC__ * 100 + __GNUC_MINOR__ < 407 +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") +# else +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# endif +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) + +#if 1 + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* 1 */ + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 56 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 478 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 40 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 24 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 73 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 129 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 283 + + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex. */ +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 30, 2, 37, 2, + 33, 25, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 36, + 2, 38, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 29, 2, 39, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 34, 2, 35, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 26, 27, 31, 32 +}; + +#if YYDEBUG +/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint8 yyrline[] = +{ + 0, 38, 38, 39, 42, 43, 46, 47, 50, 53, + 56, 57, 60, 61, 64, 65, 68, 69, 72, 73, + 74, 77, 80, 81, 84, 85, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 105, 106, 107, 110, 111, 114, 115, 118, 119, + 122, 123, 124, 125, 126, 127, 128, 132, 132, 132, + 132, 132, 132, 132, 132, 132, 132, 135, 136, 139, + 140, 141, 143, 145 +}; +#endif + +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if 1 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "\"invalid token\"", "Tfor", "Tin", + "Twhile", "Tif", "Telse", "Tswitch", "Tcase", "Tcasebody", "Ttwiddle", + "Tbang", "Tsubshell", "Tfunc", "Tredir", "Tdup", "Tpipe", "Tindex", + "Tbasic", "Targs", "Tword", "Twords", "Tparen", "Tblock", "')'", + "Tandand", "Toror", "'\\n'", "'^'", "'$'", "Tcount", "Tjoin", "'('", + "'{'", "'}'", "';'", "'&'", "'='", "'`'", "$accept", "rc", "line", + "body", "paren", "block", "cmds", "cmdsln", "ifbody", "case", "casebody", + "assign", "redir", "epilog", "cmd", "basic", "atom", "word", + "executable", "nonkeyword", "keyword", "words", "wordsnl", "nl", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#define YYPACT_NINF (-82) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-3) + +#define yytable_value_is_error(Yyn) \ + 0 + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + 121, -17, -2, -2, 5, 439, 439, 343, -82, -82, + 343, 343, 343, -82, 439, -23, 45, 32, 11, 439, + 439, 439, 13, 158, -14, -82, 343, 439, -82, -82, + 343, 30, 30, -82, -82, -82, -82, -82, -82, -82, + -82, -82, -82, -82, 34, -82, -82, 47, -82, -82, + 195, 41, -82, 439, 54, -82, -82, -82, 11, -82, + -82, 30, 30, -82, -82, -82, -82, -82, -82, 34, + 343, 343, 19, 44, 375, 375, 4, 343, -82, -82, + -82, 34, -82, -82, -82, -82, 375, 375, 375, -82, + 34, -82, -82, -82, -82, 29, 77, -82, 29, -82, + -82, 269, -82, 30, 30, 306, 375, -82, 25, -82, + 34, -82, 29, 375, 407, 375, 29, -82, 407, 407, + 48, 54, 29, 232, -82, -82, -82, -82, -82 +}; + +/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_int8 yydefact[] = +{ + 26, 0, 0, 0, 0, 26, 26, 0, 22, 50, + 0, 0, 0, 69, 26, 0, 0, 0, 24, 26, + 26, 26, 4, 27, 41, 48, 0, 26, 72, 72, + 0, 34, 35, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 46, 23, 44, 45, 51, 54, 55, + 0, 0, 12, 26, 6, 56, 1, 3, 24, 28, + 5, 33, 32, 72, 72, 72, 10, 11, 43, 42, + 0, 0, 0, 0, 26, 26, 0, 0, 67, 53, + 70, 71, 9, 7, 13, 25, 26, 26, 26, 49, + 21, 67, 72, 8, 73, 38, 24, 39, 14, 72, + 47, 0, 29, 30, 31, 0, 26, 72, 0, 52, + 68, 72, 36, 26, 26, 26, 15, 67, 26, 26, + 0, 18, 37, 0, 20, 19, 40, 17, 16 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int8 yypgoto[] = +{ + -82, -82, 75, -19, 93, -11, 18, -52, -82, -82, + -21, -82, -1, 42, 0, -82, -9, 28, -82, 2, + -82, -81, -82, -22 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int8 yydefgoto[] = +{ + 0, 16, 17, 51, 28, 18, 52, 53, 97, 119, + 120, 20, 21, 59, 54, 23, 43, 110, 24, 25, + 46, 101, 50, 74 +}; + +/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int16 yytable[] = +{ + 22, 47, 48, 49, 55, 31, 32, 75, 73, 45, + 105, 14, 45, 45, 45, 70, 26, 58, 19, 22, + 61, 62, 68, 91, 71, 45, 7, 8, 45, 99, + 63, 27, 45, 77, 83, 44, 123, 19, 30, 64, + 65, 86, 87, 88, 92, 56, 63, 63, 77, 66, + 67, 69, 45, 94, 72, 64, 65, 58, 76, 114, + 57, 89, 118, 77, 96, 78, 118, 118, 100, 93, + 106, 63, 45, 45, 95, 98, 82, 108, 81, 45, + 64, 65, 84, 126, 107, 113, 102, 103, 104, 115, + 66, 67, 7, 8, 60, 58, 29, 124, 125, 90, + 85, 0, 0, 45, 0, 0, 112, 45, 0, 0, + 0, 0, 0, 116, 121, 122, 0, 0, 121, 121, + 0, -2, 0, 0, 1, 45, 2, 3, 0, 4, + 0, 0, 0, 5, 6, 0, 7, 8, 0, 0, + 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, + 0, 10, 11, 12, 13, 14, 0, 0, 0, 0, + 15, 33, 34, 35, 36, 37, 38, 39, 0, 0, + 40, 41, 42, 7, 8, 0, 0, 0, 0, 9, + 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, + 12, 13, 0, 0, 0, 0, 0, 15, 33, 34, + 35, 36, 37, 38, 39, 0, 0, 40, 41, 42, + 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, + 79, 0, 0, 80, 0, 10, 11, 12, 13, 0, + 0, 0, 0, 0, 15, 33, 34, 35, 36, 37, + 38, 39, 0, 0, 40, 41, 42, 0, 0, 0, + 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, + 127, 0, 10, 11, 12, 13, 0, 0, 128, 0, + 0, 15, 33, 34, 35, 36, 37, 38, 39, 0, + 0, 40, 41, 42, 0, 0, 0, 0, 0, 0, + 9, 0, 0, 0, 109, 0, 0, 0, 0, 10, + 11, 12, 13, 0, 0, 0, 0, 0, 15, 33, + 34, 35, 36, 37, 38, 39, 0, 0, 40, 41, + 42, 0, 0, 0, 0, 0, 0, 9, 0, 0, + 0, 111, 0, 0, 0, 0, 10, 11, 12, 13, + 0, 0, 0, 0, 0, 15, 33, 34, 35, 36, + 37, 38, 39, 0, 0, 40, 41, 42, 0, 0, + 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 10, 11, 12, 13, 0, 1, 0, + 2, 3, 15, 4, 0, 0, 0, 5, 6, 0, + 7, 8, 0, 0, 0, 0, 9, 0, 0, 0, + 0, 0, 0, 94, 0, 10, 11, 12, 13, 14, + 1, 0, 2, 3, 15, 4, 117, 0, 0, 5, + 6, 0, 7, 8, 0, 0, 0, 0, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, + 13, 14, 1, 0, 2, 3, 15, 4, 0, 0, + 0, 5, 6, 0, 7, 8, 0, 0, 0, 0, + 9, 0, 0, 0, 0, 0, 0, 0, 0, 10, + 11, 12, 13, 14, 0, 0, 0, 0, 15 +}; + +static const yytype_int8 yycheck[] = +{ + 0, 10, 11, 12, 15, 5, 6, 29, 27, 7, + 91, 34, 10, 11, 12, 29, 33, 18, 0, 19, + 20, 21, 23, 4, 38, 23, 15, 16, 26, 25, + 17, 33, 30, 29, 53, 7, 117, 19, 33, 26, + 27, 63, 64, 65, 25, 0, 17, 17, 29, 36, + 37, 23, 50, 28, 26, 26, 27, 58, 30, 34, + 28, 70, 114, 29, 75, 18, 118, 119, 77, 25, + 92, 17, 70, 71, 74, 75, 35, 99, 50, 77, + 26, 27, 28, 35, 7, 107, 86, 87, 88, 111, + 36, 37, 15, 16, 19, 96, 3, 118, 119, 71, + 58, -1, -1, 101, -1, -1, 106, 105, -1, -1, + -1, -1, -1, 113, 114, 115, -1, -1, 118, 119, + -1, 0, -1, -1, 3, 123, 5, 6, -1, 8, + -1, -1, -1, 12, 13, -1, 15, 16, -1, -1, + -1, -1, 21, -1, -1, -1, -1, -1, -1, -1, + -1, 30, 31, 32, 33, 34, -1, -1, -1, -1, + 39, 3, 4, 5, 6, 7, 8, 9, -1, -1, + 12, 13, 14, 15, 16, -1, -1, -1, -1, 21, + -1, -1, -1, -1, -1, -1, -1, -1, 30, 31, + 32, 33, -1, -1, -1, -1, -1, 39, 3, 4, + 5, 6, 7, 8, 9, -1, -1, 12, 13, 14, + -1, -1, -1, -1, -1, -1, 21, -1, -1, -1, + 25, -1, -1, 28, -1, 30, 31, 32, 33, -1, + -1, -1, -1, -1, 39, 3, 4, 5, 6, 7, + 8, 9, -1, -1, 12, 13, 14, -1, -1, -1, + -1, -1, -1, 21, -1, -1, -1, -1, -1, -1, + 28, -1, 30, 31, 32, 33, -1, -1, 36, -1, + -1, 39, 3, 4, 5, 6, 7, 8, 9, -1, + -1, 12, 13, 14, -1, -1, -1, -1, -1, -1, + 21, -1, -1, -1, 25, -1, -1, -1, -1, 30, + 31, 32, 33, -1, -1, -1, -1, -1, 39, 3, + 4, 5, 6, 7, 8, 9, -1, -1, 12, 13, + 14, -1, -1, -1, -1, -1, -1, 21, -1, -1, + -1, 25, -1, -1, -1, -1, 30, 31, 32, 33, + -1, -1, -1, -1, -1, 39, 3, 4, 5, 6, + 7, 8, 9, -1, -1, 12, 13, 14, -1, -1, + -1, -1, -1, -1, 21, -1, -1, -1, -1, -1, + -1, -1, -1, 30, 31, 32, 33, -1, 3, -1, + 5, 6, 39, 8, -1, -1, -1, 12, 13, -1, + 15, 16, -1, -1, -1, -1, 21, -1, -1, -1, + -1, -1, -1, 28, -1, 30, 31, 32, 33, 34, + 3, -1, 5, 6, 39, 8, 9, -1, -1, 12, + 13, -1, 15, 16, -1, -1, -1, -1, 21, -1, + -1, -1, -1, -1, -1, -1, -1, 30, 31, 32, + 33, 34, 3, -1, 5, 6, 39, 8, -1, -1, + -1, 12, 13, -1, 15, 16, -1, -1, -1, -1, + 21, -1, -1, -1, -1, -1, -1, -1, -1, 30, + 31, 32, 33, 34, -1, -1, -1, -1, 39 +}; + +/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of + state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 3, 5, 6, 8, 12, 13, 15, 16, 21, + 30, 31, 32, 33, 34, 39, 41, 42, 45, 46, + 51, 52, 54, 55, 58, 59, 33, 33, 44, 44, + 33, 54, 54, 3, 4, 5, 6, 7, 8, 9, + 12, 13, 14, 56, 57, 59, 60, 56, 56, 56, + 62, 43, 46, 47, 54, 45, 0, 28, 52, 53, + 42, 54, 54, 17, 26, 27, 36, 37, 52, 57, + 29, 38, 57, 43, 63, 63, 57, 29, 18, 25, + 28, 57, 35, 43, 28, 53, 63, 63, 63, 56, + 57, 4, 25, 25, 28, 54, 45, 48, 54, 25, + 56, 61, 54, 54, 54, 61, 63, 7, 63, 25, + 57, 25, 54, 63, 34, 63, 54, 9, 47, 49, + 50, 54, 54, 61, 50, 50, 35, 28, 36 +}; + +/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr1[] = +{ + 0, 40, 41, 41, 42, 42, 43, 43, 44, 45, + 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, + 50, 51, 52, 52, 53, 53, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, + 54, 55, 55, 55, 56, 56, 57, 57, 58, 58, + 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, + 60, 60, 60, 60, 60, 60, 60, 61, 61, 62, + 62, 62, 63, 63 +}; + +/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 0, 2, 1, 2, 1, 2, 3, 3, + 2, 2, 1, 2, 1, 4, 3, 3, 1, 2, + 2, 3, 1, 2, 0, 2, 0, 1, 2, 4, + 4, 4, 2, 2, 2, 2, 6, 8, 4, 4, + 8, 1, 2, 2, 1, 1, 1, 3, 1, 3, + 1, 2, 5, 3, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, + 2, 2, 0, 2 +}; + + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab +#define YYNOMEM goto yyexhaustedlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use YYerror or YYUNDEF. */ +#define YYERRCODE YYUNDEF + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + + + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep) +{ + FILE *yyoutput = yyo; + YY_USE (yyoutput); + if (!yyvaluep) + return; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ + +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep); + YYFPRINTF (yyo, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule) +{ + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)]); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +/* Context of a parse error. */ +typedef struct +{ + yy_state_t *yyssp; + yysymbol_kind_t yytoken; +} yypcontext_t; + +/* Put in YYARG at most YYARGN of the expected tokens given the + current YYCTX, and return the number of tokens stored in YYARG. If + YYARG is null, return the number of expected tokens (guaranteed to + be less than YYNTOKENS). Return YYENOMEM on memory exhaustion. + Return 0 if there are more than YYARGN expected tokens, yet fill + YYARG up to YYARGN. */ +static int +yypcontext_expected_tokens (const yypcontext_t *yyctx, + yysymbol_kind_t yyarg[], int yyargn) +{ + /* Actual size of YYARG. */ + int yycount = 0; + int yyn = yypact[+*yyctx->yyssp]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYSYMBOL_YYerror + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (!yyarg) + ++yycount; + else if (yycount == yyargn) + return 0; + else + yyarg[yycount++] = YY_CAST (yysymbol_kind_t, yyx); + } + } + if (yyarg && yycount == 0 && 0 < yyargn) + yyarg[0] = YYSYMBOL_YYEMPTY; + return yycount; +} + + + + +#ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen(S) (YY_CAST (YYPTRDIFF_T, strlen (S))) +# else +/* Return the length of YYSTR. */ +static YYPTRDIFF_T +yystrlen (const char *yystr) +{ + YYPTRDIFF_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +#endif + +#ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +yystpcpy (char *yydest, const char *yysrc) +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +#endif + +#ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYPTRDIFF_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYPTRDIFF_T yyn = 0; + char const *yyp = yystr; + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + else + goto append; + + append: + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (yyres) + return yystpcpy (yyres, yystr) - yyres; + else + return yystrlen (yystr); +} +#endif + + +static int +yy_syntax_error_arguments (const yypcontext_t *yyctx, + yysymbol_kind_t yyarg[], int yyargn) +{ + /* Actual size of YYARG. */ + int yycount = 0; + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yyctx->yytoken != YYSYMBOL_YYEMPTY) + { + int yyn; + if (yyarg) + yyarg[yycount] = yyctx->yytoken; + ++yycount; + yyn = yypcontext_expected_tokens (yyctx, + yyarg ? yyarg + 1 : yyarg, yyargn - 1); + if (yyn == YYENOMEM) + return YYENOMEM; + else + yycount += yyn; + } + return yycount; +} + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return -1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return YYENOMEM if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYPTRDIFF_T *yymsg_alloc, char **yymsg, + const yypcontext_t *yyctx) +{ + enum { YYARGS_MAX = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat: reported tokens (one for the "unexpected", + one per "expected"). */ + yysymbol_kind_t yyarg[YYARGS_MAX]; + /* Cumulated lengths of YYARG. */ + YYPTRDIFF_T yysize = 0; + + /* Actual size of YYARG. */ + int yycount = yy_syntax_error_arguments (yyctx, yyarg, YYARGS_MAX); + if (yycount == YYENOMEM) + return YYENOMEM; + + switch (yycount) + { +#define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + default: /* Avoid compiler warnings. */ + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +#undef YYCASE_ + } + + /* Compute error message size. Don't count the "%s"s, but reserve + room for the terminator. */ + yysize = yystrlen (yyformat) - 2 * yycount + 1; + { + int yyi; + for (yyi = 0; yyi < yycount; ++yyi) + { + YYPTRDIFF_T yysize1 + = yysize + yytnamerr (YY_NULLPTR, yytname[yyarg[yyi]]); + if (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM) + yysize = yysize1; + else + return YYENOMEM; + } + } + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return -1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yytname[yyarg[yyi++]]); + yyformat += 2; + } + else + { + ++yyp; + ++yyformat; + } + } + return 0; +} + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep) +{ + YY_USE (yyvaluep); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/* Lookahead token kind. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; +/* Number of syntax errors so far. */ +int yynerrs; + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (void) +{ + yy_state_fast_t yystate = 0; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus = 0; + + /* Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* Their size. */ + YYPTRDIFF_T yystacksize = YYINITDEPTH; + + /* The state stack: array, bottom, top. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss = yyssa; + yy_state_t *yyssp = yyss; + + /* The semantic value stack: array, bottom, top. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp = yyvs; + + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead symbol kind. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYPTRDIFF_T yymsg_alloc = sizeof yymsgbuf; + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yychar = YYEMPTY; /* Cause a token to be read. */ + + goto yysetstate; + + +/*------------------------------------------------------------. +| yynewstate -- push a new state, which is found in yystate. | +`------------------------------------------------------------*/ +yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + YYNOMEM; +#else + { + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; + +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + YYNOMEM; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + YYNOMEM; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ + + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (); + } + + if (yychar <= YYEOF) + { + yychar = YYEOF; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else if (yychar == YYerror) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = YYUNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + /* Discard the shifted token. */ + yychar = YYEMPTY; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: /* rc: %empty */ +#line 38 "sys/cmd/rc/syntax.y" + { return 0; } +#line 1549 "sys/cmd/rc/parse.c" + break; + + case 3: /* rc: line '\n' */ +#line 39 "sys/cmd/rc/syntax.y" + { return compile((yyvsp[-1].tree)); } +#line 1555 "sys/cmd/rc/parse.c" + break; + + case 5: /* line: cmds line */ +#line 43 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(';', (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1561 "sys/cmd/rc/parse.c" + break; + + case 7: /* body: cmdsln body */ +#line 47 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(';', (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1567 "sys/cmd/rc/parse.c" + break; + + case 8: /* paren: '(' body ')' */ +#line 50 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tparen, (yyvsp[-1].tree)); } +#line 1573 "sys/cmd/rc/parse.c" + break; + + case 9: /* block: '{' body '}' */ +#line 53 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tblock, (yyvsp[-1].tree)); } +#line 1579 "sys/cmd/rc/parse.c" + break; + + case 11: /* cmds: cmd '&' */ +#line 57 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1('&', (yyvsp[-1].tree)); } +#line 1585 "sys/cmd/rc/parse.c" + break; + + case 14: /* ifbody: cmd */ +#line 64 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Tif, nil, (yyvsp[0].tree)); } +#line 1591 "sys/cmd/rc/parse.c" + break; + + case 15: /* ifbody: block Telse nl cmd */ +#line 65 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree3(Tif, nil, (yyvsp[-3].tree), (yyvsp[-2].tree)); } +#line 1597 "sys/cmd/rc/parse.c" + break; + + case 16: /* case: Tcase words ';' */ +#line 68 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-2].tree), (yyvsp[-1].tree), 0); } +#line 1603 "sys/cmd/rc/parse.c" + break; + + case 17: /* case: Tcase words '\n' */ +#line 69 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-2].tree), (yyvsp[-1].tree), 0); } +#line 1609 "sys/cmd/rc/parse.c" + break; + + case 18: /* casebody: cmd */ +#line 72 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Tcasebody, (yyvsp[0].tree), nil); } +#line 1615 "sys/cmd/rc/parse.c" + break; + + case 19: /* casebody: case casebody */ +#line 73 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Tcasebody, (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1621 "sys/cmd/rc/parse.c" + break; + + case 20: /* casebody: cmdsln casebody */ +#line 74 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Tcasebody, (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1627 "sys/cmd/rc/parse.c" + break; + + case 21: /* assign: executable '=' word */ +#line 77 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2('=', (yyvsp[-2].tree), (yyvsp[0].tree)); } +#line 1633 "sys/cmd/rc/parse.c" + break; + + case 23: /* redir: Tredir word */ +#line 81 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 0); } +#line 1639 "sys/cmd/rc/parse.c" + break; + + case 24: /* epilog: %empty */ +#line 84 "sys/cmd/rc/syntax.y" + { (yyval.tree) = nil; } +#line 1645 "sys/cmd/rc/parse.c" + break; + + case 25: /* epilog: redir epilog */ +#line 85 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 1); } +#line 1651 "sys/cmd/rc/parse.c" + break; + + case 26: /* cmd: %empty */ +#line 88 "sys/cmd/rc/syntax.y" + { (yyval.tree) = nil; } +#line 1657 "sys/cmd/rc/parse.c" + break; + + case 27: /* cmd: basic */ +#line 89 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tbasic, (yyvsp[0].tree)); } +#line 1663 "sys/cmd/rc/parse.c" + break; + + case 28: /* cmd: block epilog */ +#line 90 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangepilog((yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1669 "sys/cmd/rc/parse.c" + break; + + case 29: /* cmd: cmd Tpipe nl cmd */ +#line 91 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild2((yyvsp[-2].tree), (yyvsp[-3].tree), 0, (yyvsp[0].tree), 1); } +#line 1675 "sys/cmd/rc/parse.c" + break; + + case 30: /* cmd: cmd Tandand nl cmd */ +#line 92 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Tandand, (yyvsp[-3].tree), (yyvsp[0].tree)); } +#line 1681 "sys/cmd/rc/parse.c" + break; + + case 31: /* cmd: cmd Toror nl cmd */ +#line 93 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Toror, (yyvsp[-3].tree), (yyvsp[0].tree)); } +#line 1687 "sys/cmd/rc/parse.c" + break; + + case 32: /* cmd: redir cmd */ +#line 94 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 1); } +#line 1693 "sys/cmd/rc/parse.c" + break; + + case 33: /* cmd: assign cmd */ +#line 95 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-1].tree), (yyvsp[0].tree), 2); } +#line 1699 "sys/cmd/rc/parse.c" + break; + + case 34: /* cmd: Tbang cmd */ +#line 96 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tbang, (yyvsp[0].tree)); } +#line 1705 "sys/cmd/rc/parse.c" + break; + + case 35: /* cmd: Tsubshell cmd */ +#line 97 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tsubshell, (yyvsp[0].tree)); } +#line 1711 "sys/cmd/rc/parse.c" + break; + + case 36: /* cmd: Tfor '(' word ')' nl cmd */ +#line 98 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild3((yyvsp[-5].tree), (yyvsp[-3].tree), nil, (yyvsp[0].tree)); } +#line 1717 "sys/cmd/rc/parse.c" + break; + + case 37: /* cmd: Tfor '(' word Tin words ')' nl cmd */ +#line 99 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild3((yyvsp[-7].tree), (yyvsp[-5].tree), (yyvsp[-3].tree), (yyvsp[0].tree)); } +#line 1723 "sys/cmd/rc/parse.c" + break; + + case 38: /* cmd: Twhile paren nl cmd */ +#line 100 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild2((yyvsp[-3].tree), (yyvsp[-2].tree), 0, (yyvsp[0].tree), 1); } +#line 1729 "sys/cmd/rc/parse.c" + break; + + case 39: /* cmd: Tif paren nl ifbody */ +#line 101 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild1((yyvsp[-2].tree), (yyvsp[-3].tree), 0); } +#line 1735 "sys/cmd/rc/parse.c" + break; + + case 40: /* cmd: Tswitch '(' word ')' nl '{' casebody '}' */ +#line 102 "sys/cmd/rc/syntax.y" + { (yyval.tree) = hangchild2((yyvsp[-7].tree), (yyvsp[-5].tree), 0, (yyvsp[-1].tree), 1); } +#line 1741 "sys/cmd/rc/parse.c" + break; + + case 42: /* basic: basic word */ +#line 106 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Targs, (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1747 "sys/cmd/rc/parse.c" + break; + + case 43: /* basic: basic redir */ +#line 107 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Targs, (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1753 "sys/cmd/rc/parse.c" + break; + + case 45: /* atom: keyword */ +#line 111 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tword, (yyvsp[0].tree)); } +#line 1759 "sys/cmd/rc/parse.c" + break; + + case 47: /* word: word '^' atom */ +#line 115 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2('^', (yyvsp[-2].tree), (yyvsp[0].tree)); } +#line 1765 "sys/cmd/rc/parse.c" + break; + + case 49: /* executable: executable '^' atom */ +#line 119 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2('^', (yyvsp[-2].tree), (yyvsp[0].tree)); } +#line 1771 "sys/cmd/rc/parse.c" + break; + + case 51: /* nonkeyword: '$' atom */ +#line 123 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1('$', (yyvsp[0].tree)); } +#line 1777 "sys/cmd/rc/parse.c" + break; + + case 52: /* nonkeyword: '$' atom Tindex words ')' */ +#line 124 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Tindex, (yyvsp[-3].tree), (yyvsp[-1].tree)); } +#line 1783 "sys/cmd/rc/parse.c" + break; + + case 53: /* nonkeyword: '(' wordsnl ')' */ +#line 125 "sys/cmd/rc/syntax.y" + { (yyval.tree) = (yyvsp[-1].tree); } +#line 1789 "sys/cmd/rc/parse.c" + break; + + case 54: /* nonkeyword: Tcount atom */ +#line 126 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tcount, (yyvsp[0].tree)); } +#line 1795 "sys/cmd/rc/parse.c" + break; + + case 55: /* nonkeyword: Tjoin atom */ +#line 127 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1(Tjoin, (yyvsp[0].tree)); } +#line 1801 "sys/cmd/rc/parse.c" + break; + + case 56: /* nonkeyword: '`' block */ +#line 128 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree1('`', (yyvsp[0].tree)); } +#line 1807 "sys/cmd/rc/parse.c" + break; + + case 67: /* words: %empty */ +#line 135 "sys/cmd/rc/syntax.y" + { (yyval.tree) = nil; } +#line 1813 "sys/cmd/rc/parse.c" + break; + + case 68: /* words: words word */ +#line 136 "sys/cmd/rc/syntax.y" + { (yyval.tree) = maketree2(Twords, (yyvsp[-1].tree), (yyvsp[0].tree)); } +#line 1819 "sys/cmd/rc/parse.c" + break; + + case 69: /* wordsnl: %empty */ +#line 139 "sys/cmd/rc/syntax.y" + { (yyval.tree) = nil; } +#line 1825 "sys/cmd/rc/parse.c" + break; + + case 71: /* wordsnl: wordsnl word */ +#line 141 "sys/cmd/rc/syntax.y" + {(yyval.tree) = (!(yyvsp[-1].tree)) ? ((!(yyvsp[0].tree)) ? nil : (yyvsp[0].tree)) : ((!(yyvsp[0].tree)) ? (yyvsp[-1].tree) : maketree2(Twords, (yyvsp[-1].tree), (yyvsp[0].tree))); } +#line 1831 "sys/cmd/rc/parse.c" + break; + + +#line 1835 "sys/cmd/rc/parse.c" + + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + { + yypcontext_t yyctx + = {yyssp, yytoken}; + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == -1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = YY_CAST (char *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, yymsg_alloc))); + if (yymsg) + { + yysyntax_error_status + = yysyntax_error (&yymsg_alloc, &yymsg, &yyctx); + yymsgp = yymsg; + } + else + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = YYENOMEM; + } + } + yyerror (yymsgp); + if (yysyntax_error_status == YYENOMEM) + YYNOMEM; + } + } + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + ++yynerrs; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + /* Pop stack until we find a state that shifts the error token. */ + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturnlab; + + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturnlab; + + +/*-----------------------------------------------------------. +| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. | +`-----------------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + goto yyreturnlab; + + +/*----------------------------------------------------------. +| yyreturnlab -- parsing is finished, clean up and return. | +`----------------------------------------------------------*/ +yyreturnlab: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + return yyresult; +} + +#line 147 "sys/cmd/rc/syntax.y" + diff --git a/src/cmd/rc/parse.h b/src/cmd/rc/parse.h new file mode 100644 index 0000000..64ee07b --- /dev/null +++ b/src/cmd/rc/parse.h @@ -0,0 +1,141 @@ +/* A Bison parser, made by GNU Bison 3.8.2. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +#ifndef YY_YY_SYS_CMD_RC_PARSE_H_INCLUDED +# define YY_YY_SYS_CMD_RC_PARSE_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + Tfor = 258, /* Tfor */ + Tin = 259, /* Tin */ + Twhile = 260, /* Twhile */ + Tif = 261, /* Tif */ + Telse = 262, /* Telse */ + Tswitch = 263, /* Tswitch */ + Tcase = 264, /* Tcase */ + Tcasebody = 265, /* Tcasebody */ + Ttwiddle = 266, /* Ttwiddle */ + Tbang = 267, /* Tbang */ + Tsubshell = 268, /* Tsubshell */ + Tfunc = 269, /* Tfunc */ + Tredir = 270, /* Tredir */ + Tdup = 271, /* Tdup */ + Tpipe = 272, /* Tpipe */ + Tindex = 273, /* Tindex */ + Tbasic = 274, /* Tbasic */ + Targs = 275, /* Targs */ + Tword = 276, /* Tword */ + Twords = 277, /* Twords */ + Tparen = 278, /* Tparen */ + Tblock = 279, /* Tblock */ + Tandand = 280, /* Tandand */ + Toror = 281, /* Toror */ + Tcount = 282, /* Tcount */ + Tjoin = 283 /* Tjoin */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define Tfor 258 +#define Tin 259 +#define Twhile 260 +#define Tif 261 +#define Telse 262 +#define Tswitch 263 +#define Tcase 264 +#define Tcasebody 265 +#define Ttwiddle 266 +#define Tbang 267 +#define Tsubshell 268 +#define Tfunc 269 +#define Tredir 270 +#define Tdup 271 +#define Tpipe 272 +#define Tindex 273 +#define Tbasic 274 +#define Targs 275 +#define Tword 276 +#define Twords 277 +#define Tparen 278 +#define Tblock 279 +#define Tandand 280 +#define Toror 281 +#define Tcount 282 +#define Tjoin 283 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 24 "sys/cmd/rc/syntax.y" + + struct Tree *tree; + +#line 127 "sys/cmd/rc/parse.h" + +}; +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE yylval; + + +int yyparse (void); + + +#endif /* !YY_YY_SYS_CMD_RC_PARSE_H_INCLUDED */ diff --git a/src/cmd/rc/prompt.c b/src/cmd/rc/prompt.c new file mode 100644 index 0000000..1122d54 --- /dev/null +++ b/src/cmd/rc/prompt.c @@ -0,0 +1,36 @@ +#include "rc.h" + +/* static char promptbuf[7] = {'>', ' ', 0, ' ' , ' ', ' ', 0}; */ +static char *base= "\x1b[1;31m" ">" "\x1b[0;0m" " ", *promptstr; + +void +resetprompt(void) +{ + promptstr = base; +} + +int +prompt(ushort *flag) +{ + int f = *flag; + + if(f){ + if(!readline(promptstr)){ + runner->flag.eof = 1; + return 0; + } + if(runner->cmd.io->e[-1] == '\n'){ + runner->cmd.io->e[-1] = 0; + addhistory(runner->cmd.io->b); + runner->cmd.io->e[-1] = '\n'; + } + + write(mapfd(0), "\n\r", 2); + promptstr = " "; + + runner->line++; + *flag = 0; + } + + return 1; +} diff --git a/src/cmd/rc/rc.h b/src/cmd/rc/rc.h new file mode 100644 index 0000000..9b415fc --- /dev/null +++ b/src/cmd/rc/rc.h @@ -0,0 +1,263 @@ + +#include +#include +#include + +// ----------------------------------------------------------------------- +// types + +typedef struct Io Io; +typedef struct Var Var; +typedef struct Word Word; +typedef struct List List; +typedef struct Tree Tree; +typedef struct Redir Redir; +typedef union Code Code; +typedef struct Thread Thread; +typedef struct Shell Shell; + +struct Io +{ + int fd, cap; + char *s; + char *b, *e, buf[]; +}; + +enum +{ + Rappend, + Rwrite, + Rread, + Rhere, + Rdupfd, + Ropen, + Rclose, + Rrdwr +}; + +struct Tree +{ + int type; + union{ + struct { + ushort quoted; + char *str; // Tword + }; + struct { + ushort type; // Tpipe, Tredir, Tdup + int fd[2]; + } redir; + }; + Tree *child[3]; + Tree *link; +}; + +struct Word +{ + char *str; + Word *link; +}; + +struct List +{ + Word *word; + List *link; +}; + +/* + * the first word of any code vector is a reference count + * always create a new reference to a code vector by calling copycode() + * always call freecode() when deleting a reference + */ +union Code +{ + int i; + void (*f)(void); + char *s; +}; + +struct Var +{ + char *name; + Word *val; + short new : 1; + short newfunc : 1; + Code *func; + void (*update)(Var *); + Var *link; +}; + +struct Redir +{ + char type; /* what to do */ + short from, to; /* what to do it to */ + struct Redir *link; /* what else to do (reverse order) */ +}; + +enum +{ + Pnil, + Prun, + Pstop, + Psig, + Pagain, + Pdone, +}; + +struct WaitItem +{ + int pid; + ushort status; +}; + +struct Thread +{ + struct { + int i; + Code *exe; + } code; // execution stack + struct { + Io *io; + char *path; + } cmd; // command input + + List *args; // argument stack + Var *local; // local variables + struct { + Redir *start; + Redir *end; + } redir; // list of redirections + + struct { + ushort user : 1; + ushort eof : 1; + } flag; + + struct { + ushort status; + int len, cap; + struct WaitItem *on; + } wait; + + int pid, pgid, status; + long line; + + Thread *caller; // process we return to + Thread *link; // next job +}; + +struct Shell +{ + int pid; + Io *err; + int status; + int interactive; + Thread *jobs; +}; + +// ----------------------------------------------------------------------- +// globals + +extern Shell shell; +extern Thread *runner; +extern Code *compiled; + +// ----------------------------------------------------------------------- +// functions + +/* util.c */ +void itoa(char*, long i); +void fatal(char *, ...); +void *emalloc(uintptr); +void *erealloc(void*, uintptr); +void efree(void*); + +/* input.c */ +int readline(char *); +void enablevi(void); + +void inithistory(void); +int addhistory(char *); + +/* prompt.c */ +void resetprompt(void); +int prompt(ushort *); + +/* io.c */ +Io *openfd(int fd); +Io *openstr(void); +void terminate(Io *io); + +int get(Io *); +int put(Io **, char); + +void flush(Io *io); +void print(Io *, char *, ...); + +/* lex.c */ +int iswordchar(int c); +int yylex(void); + +/* tree.c */ +Tree *maketree(void); +Tree *maketree1(int, Tree*); +Tree *maketree2(int, Tree*, Tree*); +Tree *maketree3(int, Tree*, Tree*, Tree*); + +Tree *token(int, char *); +Tree *hangchild1(Tree *, Tree *, int); +Tree *hangchild2(Tree *, Tree *, int, Tree *, int); +Tree *hangchild3(Tree *, Tree *, Tree *, Tree *); +Tree *hangepilog(Tree *, Tree*); + +void freeparsetree(void); + +/* sys.c */ +void initenv(void); +void redirect(struct Redir *); +void execute(Word *, Word*); +int mapfd(int fd); + +/* wait.c */ +void addwait(Thread *, int); +void delwait(Thread *, int); +void clearwait(Thread*); + +int waitall(Thread *); +int waitfor(Thread *, int); + +void killzombies(void); + +/* job.c */ +Thread *getjob(int, int*); +void addjob(Thread *); +void deljob(Thread *); +void wakeup(Thread *); +void report(Thread *, int); + +void foreground(Thread *, int); +void background(Thread *, int); + +/* exec.c */ +// XXX: odd place for this +int count(Word *); +Word *makeword(char *str, Word *link); +void freeword(Word *w); + +/* var.c */ +Var *var(char*); +Var *definevar(char*, Var *); +Var *globalvar(char*); +Var *makevar(char *name, Var *link); +void setvar(char *, Word *); +int iskeyword(char *); + +void initpath(void); +void initkeywords(void); + +char **mkenv(void); + +/* code.c */ +int compile(Tree *); +Code *copycode(Code *c); +void freecode(Code *c); diff --git a/src/cmd/rc/rules.mk b/src/cmd/rc/rules.mk new file mode 100644 index 0000000..76837fc --- /dev/null +++ b/src/cmd/rc/rules.mk @@ -0,0 +1,32 @@ +include share/push.mk +# Iterate through subdirectory tree + +# local sources +SRCS_$(d):=\ + $(d)/util.c\ + $(d)/input.c\ + $(d)/prompt.c\ + $(d)/io.c\ + $(d)/lex.c\ + $(d)/parse.c\ + $(d)/tree.c\ + $(d)/code.c\ + $(d)/var.c\ + $(d)/sys.c\ + $(d)/wait.c\ + $(d)/job.c\ + $(d)/exec.c\ + $(d)/main.c + +# local targets +BINS_$(d) := $(d)/rc + +include share/paths.mk +$(d)/parse.h $(d)/parse.c: $(d)/syntax.y + yacc --header=$( line cmds cmdsln body paren block ifbody casebody case assign epilog redir; +%type cmd basic executable nonkeyword keyword word words wordsnl atom; +%type Tfor Tin Twhile Tif Telse Tswitch Tcase Ttwiddle Tbang Tsubshell Tfunc; +%type Tword Tredir Tpipe Tdup; + +/* grammar */ + +%start rc + +%% +rc: +/*empty*/ { return 0; } +| line '\n' { return compile($1); } + +line: + cmd +| cmds line { $$ = maketree2(';', $1, $2); } + +body: + cmd +| cmdsln body { $$ = maketree2(';', $1, $2); } + +paren: + '(' body ')' { $$ = maketree1(Tparen, $2); } + +block: + '{' body '}' { $$ = maketree1(Tblock, $2); } + +cmds: + cmd ';' +| cmd '&' { $$ = maketree1('&', $1); } + +cmdsln: + cmds +| cmd '\n' + +ifbody: + cmd %prec Telse { $$ = maketree2(Tif, nil, $1); } +| block Telse nl cmd { $$ = maketree3(Tif, nil, $1, $2); } + +case: + Tcase words ';' { $$ = hangchild1($1, $2, 0); } +| Tcase words '\n' { $$ = hangchild1($1, $2, 0); } + +casebody: + cmd { $$ = maketree2(Tcasebody, $1, nil); } +| case casebody { $$ = maketree2(Tcasebody, $1, $2); } +| cmdsln casebody { $$ = maketree2(Tcasebody, $1, $2); } + +assign: + executable '=' word { $$ = maketree2('=', $1, $3); } + +redir: + Tdup +| Tredir word { $$ = hangchild1($1, $2, 0); } + +epilog: +/* empty */ { $$ = nil; } +| redir epilog { $$ = hangchild1($1, $2, 1); } + +cmd: +/* empty */ %prec Twhile { $$ = nil; } +| basic { $$ = maketree1(Tbasic, $1); } +| block epilog { $$ = hangepilog($1, $2); } +| cmd Tpipe nl cmd { $$ = hangchild2($2, $1, 0, $4, 1); } +| cmd Tandand nl cmd { $$ = maketree2(Tandand, $1, $4); } +| cmd Toror nl cmd { $$ = maketree2(Toror, $1, $4); } +| redir cmd %prec Tbang { $$ = hangchild1($1, $2, 1); } +| assign cmd %prec Tbang { $$ = hangchild1($1, $2, 2); } +| Tbang cmd { $$ = maketree1(Tbang, $2); } +| Tsubshell cmd { $$ = maketree1(Tsubshell, $2); } +| Tfor '(' word ')' nl cmd { $$ = hangchild3($1, $3, nil, $6); } +| Tfor '(' word Tin words ')' nl cmd { $$ = hangchild3($1, $3, $5, $8); } +| Twhile paren nl cmd { $$ = hangchild2($1, $2, 0, $4, 1); } +| Tif paren nl ifbody { $$ = hangchild1($2, $1, 0); } +| Tswitch '(' word ')' nl '{' casebody '}' { $$ = hangchild2($1, $3, 0, $7, 1); } + +basic: + executable +| basic word { $$ = maketree2(Targs, $1, $2); } +| basic redir { $$ = maketree2(Targs, $1, $2); } + +atom: + nonkeyword +| keyword { $$ = maketree1(Tword, $1); } + +word: + atom +| word '^' atom { $$ = maketree2('^', $1, $3); } + +executable: + nonkeyword +| executable '^' atom { $$ = maketree2('^', $1, $3); } + +nonkeyword: + Tword +| '$' atom { $$ = maketree1('$', $2); } +| '$' atom Tindex words ')' { $$ = maketree2(Tindex, $2, $4); } +| '(' wordsnl ')' { $$ = $2; } +| Tcount atom { $$ = maketree1(Tcount, $2); } +| Tjoin atom { $$ = maketree1(Tjoin, $2); } +| '`' block { $$ = maketree1('`', $2); } +//| Tredir block { $$ = hangchild1($1, $2, 0); $$->type = Tpipefd; } + +keyword: + Tfor|Tin|Twhile|Tif|Telse|Tswitch|Tcase|Tbang|Tsubshell|Tfunc + +words: +/* empty */ { $$ = nil; } +| words word { $$ = maketree2(Twords, $1, $2); } + +wordsnl: +/* empty */ { $$ = nil; } +| wordsnl '\n' /* empty */ +| wordsnl word {$$ = (!$1) ? ((!$2) ? nil : $2) : ((!$2) ? $1 : maketree2(Twords, $1, $2)); } + +nl: +/*empty*/ +| nl '\n' + +%% diff --git a/src/cmd/rc/sys.c b/src/cmd/rc/sys.c new file mode 100644 index 0000000..807359d --- /dev/null +++ b/src/cmd/rc/sys.c @@ -0,0 +1,137 @@ +#include "rc.h" + +// ----------------------------------------------------------------------- +// internal + +static +char** +mkargv(Word *args) +{ + char **argv=emalloc((count(args)+2)*sizeof(char *)); + char **argp=argv+1; /* leave one at front for runcoms */ + + for(;args;args=args->link) + *argp++=args->str; + *argp=nil; + + return argv; +} + +static +Word* +envval(char *s) +{ + Word *v; + char *t, c; + + for(t=s; *t && *t!='\1'; t++) + ; + + c = *t; + *t = '\0'; + + v = makeword(s, (c=='\0') ? nil : envval(t+1)); + *t=c; + + return v; +} + +// ----------------------------------------------------------------------- +// exported + +void +initenv(void) +{ + extern char **environ; + + char *s; + char **env; + + for(env=environ; *env; env++) { + for(s=*env; *s && *s != '(' && *s != '='; s++) + ; + switch(*s){ + case '\0': + break; + case '(': /* ignore functions */ + break; + case '=': + *s = '\0'; + setvar(*env, envval(s+1)); + *s = '='; + break; + } + } +} + +void +execute(Word *cmd, Word *path) +{ + int nc; + char **argv = mkargv(cmd); + char **env = mkenv(); + char file[1024]; + + for(; path; path=path->link){ + nc = strlen(path->str); + if(nc < arrlen(file)){ + strcpy(file, path->str); + if(file[0]){ + strcat(file, "/"); + nc++; + } + if(nc+strlen(argv[1]) < arrlen(file)){ + strcat(file, argv[1]); + execve(file, argv+1, env); + }else + fatal("command name too long"); + } + } + print(shell.err, "could not execute command: %s\n", argv[1]); + efree(argv); +} + +void +redirect(Redir *r) +{ + if(r){ + redirect(r->link); + switch(r->type){ + case Ropen: + if(r->from != r->to){ + dup2(r->from, r->to); + close(r->from); + } + break; + case Rdupfd: + dup2(r->from, r->to); // TODO: error checking + break; + case Rclose: + close(r->from); + break; + default: + fatal("unrecognized redirection type %d\n", r->type); + } + } +} + +int +mapfd(int fd) +{ + Redir *r; + for(r = runner->redir.end; r; r = r->link){ + switch(r->type){ + case Rclose: + if(r->from == fd) + fd = -1; + break; + case Rdupfd: + case Ropen: + if(r->to == fd) + fd = r->from; + break; + } + } + + return fd; +} diff --git a/src/cmd/rc/tree.c b/src/cmd/rc/tree.c new file mode 100644 index 0000000..2c65041 --- /dev/null +++ b/src/cmd/rc/tree.c @@ -0,0 +1,111 @@ +#include "rc.h" +#include "parse.h" + +static Tree *nodes; + +Tree* +maketree(void) +{ + Tree *node = emalloc(sizeof(*node)); + + node->link = nodes; + nodes = node; + return node; +} + +void +freeparsetree(void) +{ + Tree *t, *nt; + for(t = nodes; t; t = nt) { + nt = t->link; + if(t->type == Tword && t->str) + efree(t->str); + efree(t); + } + nodes = nil; +} + +Tree* +maketree1(int type, Tree *c0) +{ + return maketree2(type, c0, nil); +} + +Tree* +maketree2(int type, Tree *c0, Tree *c1) +{ + return maketree3(type, c0, c1, nil); +} + +Tree* +maketree3(int type, Tree *c0, Tree *c1, Tree *c2) +{ + Tree *node = maketree(); + + node->type = type; + node->child[0] = c0; + node->child[1] = c1; + node->child[2] = c2; + + return node; +} + +Tree* +hangchild1(Tree *node, Tree *c, int i) +{ + node->child[i] = c; + + return node; +} + +Tree* +hangchild2(Tree *node, Tree *c1, int i1, Tree *c2, int i2) +{ + node->child[i1] = c1; + node->child[i2] = c2; + + return node; +} + +Tree* +hangchild3(Tree *node, Tree *c0, Tree *c1, Tree *c2) +{ + node->child[0] = c0; + node->child[1] = c1; + node->child[2] = c2; + + return node; +} + +Tree* +hangepilog(Tree *cmd, Tree *epi) +{ + Tree *p; + if(!epi) + return cmd; + for(p = epi; p->child[1]; p = p->child[1]) + ; + + p->child[1] = cmd; + return epi; +} + +Tree* +token(int type, char *s) +{ + Tree *node = maketree(); + + node->type = type; + node->str = strdup(s); + + return node; +} + +/* +Tree* +basic(Tree *node) +{ + return maketree1(Tbasic, node); +} +*/ diff --git a/src/cmd/rc/util.c b/src/cmd/rc/util.c new file mode 100644 index 0000000..b0be788 --- /dev/null +++ b/src/cmd/rc/util.c @@ -0,0 +1,65 @@ +#include "rc.h" + +void +fatal(char *msg, ...) +{ + va_list args; + vfprintf(stderr, msg, args); + va_end(args); + + abort(); +} + +void* +emalloc(uintptr n) +{ + void *p; + if(!(p = malloc(n))) + fatal("out of memory: can't allocate %d bytes", n); + + memset(p, 0, n); + return p; +} + +void* +erealloc(void *p, uintptr n) +{ + void *r; + if(!(r = realloc(p,n))) + fatal("out of memory: can't reallocate %d bytes", n); + + return r; +} + +void +efree(void *p) +{ + if(p) + free(p); + // TODO: log the double free +} + + +char *bp; + +static +void +iacvt(int n) +{ + if(n<0){ + *bp++='-'; + n=-n; /* doesn't work for n==-inf */ + } + if(n/10) + iacvt(n/10); + + *bp++=n%10+'0'; +} + +void +itoa(char *s, long n) +{ + bp = s; + iacvt(n); + *bp='\0'; +} diff --git a/src/cmd/rc/var.c b/src/cmd/rc/var.c new file mode 100644 index 0000000..3e9635f --- /dev/null +++ b/src/cmd/rc/var.c @@ -0,0 +1,336 @@ +#include "rc.h" +#include "parse.h" + +// TODO: string interning + +// ----------------------------------------------------------------------- +// globals + +struct Keyword +{ + char *name; + int type; +}; + +static Var *globals[512]; +static struct Keyword keywords[100]; // sparse map means less hits + +// ----------------------------------------------------------------------- +// internals + +static +int +hash(char *s, int len) +{ + int h =0, i = 1; + while(*s) + h += *s++*i++; + + h %= len; + return h < 0 ? h+len : h; +} + +static +void +·setvar(char *name, Word *val, int call) +{ + Var *v = var(name); + freeword(v->val); + + v->val = val; + v->new = 1; // this never turns off? + + if(call && v->update) + v->update(v); +} + +static +char* +list2strcolon(Word *words) +{ + char *value, *s, *t; + int len = 0; + Word *ap; + for(ap = words;ap;ap = ap->link) + len+=1+strlen(ap->str); + + value = emalloc(len+1); + + s = value; + for(ap = words; ap; ap = ap->link){ + for(t = ap->str;*t;) *s++=*t++; + *s++=':'; + } + + if(s==value) + *s='\0'; + else + s[-1]='\0'; + + return value; +} + +static +void +littlepath(Var *v) +{ + /* convert $path to $PATH */ + char *p; + Word *w; + + p = list2strcolon(v->val); + w = emalloc(sizeof(*w)); + w->str = p; + w->link = nil; + + ·setvar("PATH", w, 1); +} + +static +void +bigpath(Var *v) +{ + /* convert $PATH to $path */ + char *p, *q; + Word **l, *w; + + if(v->val == nil){ + ·setvar("path", nil, 0); + return; + } + + p = v->val->str; + w = nil; + l = &w; + + /* Doesn't handle escaped colon nonsense. */ + if(p[0] == 0) + p = nil; + + while(p){ + q = strchr(p, ':'); + if(q) + *q = 0; + + *l = makeword(p[0] ? p : ".", nil); + l = &(*l)->link; + + if(q){ + *q = ':'; + p = q+1; + }else + p = nil; + } + ·setvar("path", w, 0); +} + +// ----------------------------------------------------------------------- +// exports + +Var* +makevar(char *name, Var *link) +{ + Var *v = emalloc(sizeof(*v)); + + v->name = name; + v->val = 0; + v->new = 0; + v->newfunc = 0; + v->link = link; + v->func = nil; + v->update = nil; + + return v; +} + +void +setvar(char *name, Word *val) +{ + ·setvar(name, val, 1); +} + +Var* +definevar(char *name, Var *link) +{ + Var *v = emalloc(sizeof(*v)); + + v->name = name; + v->val = 0; + v->link = link; + + return v; +} + +Var* +globalvar(char *name) +{ + int h; + Var *v; + + h = hash(name, arrlen(globals)); + + if(strcmp(name,"PATH")==0){ + flush(shell.err); + } + + for(v = globals[h]; v; v = v->link){ + if(strcmp(v->name, name) == 0){ + return v; + } + } + + return globals[h] = definevar(strdup(name), globals[h]); +} + +Var* +var(char *name) +{ + Var *v; + if(runner){ + for(v = runner->local; v; v=v->link) + if(strcmp(v->name, name) == 0) + return v; + } + return globalvar(name); +} + +static +int +cmpenv(const void *a, const void *b) +{ + return strcmp(*(char**)a, *(char**)b); +} + +char** +mkenv(void) +{ + char **env, **ep, *p, *q; + Var **h, *v; + Word *a; + int nvar=0, nchr=0, sep; + +#define BODY \ + if((v==var(v->name)) && v->val){ \ + nvar++; \ + nchr+=strlen(v->name)+1; \ + for(a=v->val;a;a=a->link) \ + nchr+=strlen(a->str)+1; \ + } + + for(v= runner->local; v; v=v->link){ + BODY + } + for(h=globals; h!=arrend(globals); h++){ + for(v = *h; v; v=v->link){ + BODY + } + } + +#undef BODY + + env=emalloc((nvar+1)*sizeof(*env)+nchr); + ep=env; + p=(char *)&env[nvar+1]; + +#define BODY \ + if((v==var(v->name)) && v->val){ \ + *ep++=p; \ + q=v->name; \ + while(*q) \ + *p++=*q++; \ + sep='='; \ + for(a=v->val;a;a=a->link){ \ + *p++=sep; \ + sep='\1'; \ + q=a->str; \ + while(*q) \ + *p++=*q++; \ + } \ + *p++='\0'; \ + } + + for(v=runner->local; v; v=v->link){ + BODY + } + for(h=globals; h!=arrend(globals); h++){ + for(v = *h; v; v=v->link){ + BODY + } + } +#undef BODY + + *ep=0; + + qsort((char *)env, nvar, sizeof ep[0], cmpenv); + return env; +} + +void +initpath(void) +{ + Var *v; + + v = globalvar("path"); + v->update = littlepath; + + v = globalvar("PATH"); + v->update = bigpath; + + flush(shell.err); + bigpath(v); +} + +#define KEYWORDS \ + KEYWORD("for", Tfor) \ + KEYWORD("in", Tin) \ + KEYWORD("while", Twhile) \ + KEYWORD("if", Tif) \ + KEYWORD("else", Telse) \ + KEYWORD("switch", Tswitch) \ + KEYWORD("case", Tcase) \ + KEYWORD("!", Tbang) \ + KEYWORD("@", Tsubshell) \ + KEYWORD("func", Tfunc) + +void +initkeywords(void) +{ + int i, s, j, h; +#define KEYWORD(a, b) a, + static char *name[] = { KEYWORDS }; +#undef KEYWORD +#define KEYWORD(a, b) b, + static int type[] = { KEYWORDS }; +#undef KEYWORD + + for(i = 0; i < arrlen(type); i++){ + h = hash(name[i], arrlen(keywords)); + for(s=0; s < arrlen(keywords); s++){ + j = (h + s) % arrlen(keywords); + if(!keywords[j].type || strcmp(keywords[j].name, name[i]) == 0){ + keywords[j].name = name[i]; + keywords[j].type = type[i]; + goto nextkeyword; + } + } + nextkeyword:; + } +} + +int +iskeyword(char *word) +{ + int i, s, h; + + h = hash(word, arrlen(keywords)); + for(s = 0; s < arrlen(keywords); s++){ + i = (h + s) % arrlen(keywords); + if(!keywords[i].type) + return 0; + if(strcmp(keywords[i].name, word) == 0) + return keywords[i].type; + } + return 0; +} + +#undef KEYWORDS diff --git a/src/cmd/rc/wait.c b/src/cmd/rc/wait.c new file mode 100644 index 0000000..911601c --- /dev/null +++ b/src/cmd/rc/wait.c @@ -0,0 +1,247 @@ +#include "rc.h" + +#include + +// ----------------------------------------------------------------------- +// globals + +struct WaitMsg +{ + int pid; + int type; + ulong time[3]; + int status; +}; + +// ----------------------------------------------------------------------- +// internal + +static +int +await(int pid4, int opt, struct WaitMsg *msg) +{ + int pid, status, core; + struct rusage ru; + ulong u, s; + + /* event loop */ + for(;;){ + if((pid = wait4(pid4, &status, opt, &ru)) <= 0){ + if(errno == ECHILD){ + msg->pid = -1; + return 1; + } + msg->pid = 0; + perror("failed wait4"); + return 0; + } + + u = ru.ru_utime.tv_sec*1000+((ru.ru_utime.tv_usec+500)/1000); + s = ru.ru_stime.tv_sec*1000+((ru.ru_stime.tv_usec+500)/1000); + + if(WIFEXITED(status)){ + msg->pid = pid; + msg->time[0] = u; + msg->time[1] = s; + msg->time[2] = u+s; + msg->status = WEXITSTATUS(status); + msg->type = Pdone; + + return 1; + } + + if(WIFSIGNALED(status)){ + msg->pid = pid; + msg->time[0] = u; + msg->time[1] = s; + msg->time[2] = u+s; + msg->status = WTERMSIG(status); + msg->type = Psig; + + return 1; + } + + if(WIFSTOPPED(status)){ + msg->pid = pid; + msg->time[0] = u; + msg->time[1] = s; + msg->time[2] = u+s; + msg->status = WSTOPSIG(status); + msg->type = Pstop; + + return 1; + } + } +} + +static +int +shouldwait(Thread *job) +{ + int i; + + for(i=0; iwait.len; i++){ + if(job->wait.on[i].status == Prun) + return 1; + } + + return 0; +} + +static inline +void +notify(Thread *job, struct WaitMsg msg) +{ + int i; + for(i=0; i < job->wait.len; i++){ + if(job->wait.on[i].pid == msg.pid){ + job->status = msg.status; + switch(msg.type){ + case Pstop: + print(shell.err, "%d: suspended\n", msg.pid); + job->wait.status = Pstop; + job->wait.on[i].status = Pstop; + break; + + case Psig: + print(shell.err, "%d: terminated by signal %d\n", msg.pid, msg.status); + /* fallthrough */ + case Pdone: + job->wait.on[i].status = Pdone; + delwait(job, msg.pid); + if(!job->wait.len) + job->wait.status = Pdone; + break; + + default: + fatal("%d: unrecognized message type %d\n", msg.pid, msg.type); + } + break; + } + } +} + +// ----------------------------------------------------------------------- +// exported + +void +clearwait(Thread *job) +{ + job->wait.len = 0; +} + +int +havewait(Thread *job, int pid) +{ + int i; + + for(i=0; iwait.len; i++) + if(job->wait.on[i].pid == pid) + return 1; + return 0; +} + +void +addwait(Thread *job, int pid) +{ + if(job->wait.len == job->wait.cap){ + job->wait.cap = job->wait.cap + 2; + job->wait.on = erealloc(job->wait.on, job->wait.cap*sizeof(*job->wait.on)); + } + + job->wait.on[job->wait.len++] = (struct WaitItem){.pid=pid, .status=Prun}; +} + +void +delwait(Thread *job, int pid) +{ + int r, w; + + for(r=w=0; r < job->wait.len; r++){ + if(job->wait.on[r].pid != pid) + job->wait.on[w++].pid = job->wait.on[r].pid; + } + job->wait.len = w; +} + +int +waitall(Thread *job) +{ + int i; + Thread *t; + struct WaitMsg msg; + + while(shouldwait(job) && await(-job->pgid, WUNTRACED, &msg)){ + switch(msg.pid){ + case 0: // error + perror("wait job"); + return 0; + case -1: // no children: assume they have exited + job->wait.status = Pdone; + clearwait(job); + return 1; + default: + ; + } + + notify(job, msg); + } + return 1; +} + +int +waitfor(Thread *job, int pid) +{ + int i; + Thread *t; + struct WaitMsg msg; + + while(shouldwait(job) && await(-job->pgid, WUNTRACED, &msg)){ + switch(msg.pid){ + case 0: // error + perror("wait for"); + return 0; + case -1: // no children: assume they have exited + job->wait.status = Pdone; + clearwait(job); + return 1; + default: + ; + } + + notify(job, msg); + /* allow for an early exit */ + if(msg.pid == pid) + return 1; + } + return 1; + +} + +void +killzombies(void) +{ + Thread *job; + int index, status, pid; + + while((pid=waitpid(-1, &status, WNOHANG))>0){ + print(shell.err, "found zombie pid %d\n", pid); + flush(shell.err); + + job = getjob(pid, &index); + if(!job) + perror("invalid pid"); + + if(WIFEXITED(status)) + job->wait.status = Pdone; + if(WIFSTOPPED(status)) + job->wait.status = Pstop; + if(WIFCONTINUED(status)) + job->wait.status = Pagain; + + if(job->wait.status == Pdone){ + report(job,index); + deljob(job); + } + } +} diff --git a/src/cmd/rules.mk b/src/cmd/rules.mk new file mode 100644 index 0000000..72cd0ce --- /dev/null +++ b/src/cmd/rules.mk @@ -0,0 +1,32 @@ +include share/push.mk + +# Iterate through subdirectory tree + +# DIR := $(d)/cc +# include $(DIR)/rules.mk + +DIR := $(d)/rc +include $(DIR)/rules.mk + +DIR := $(d)/walk +include $(DIR)/rules.mk + +DIR := $(d)/filter +include $(DIR)/rules.mk + +DIR := $(d)/ic +include $(DIR)/rules.mk + +DIR := $(d)/dwm +include $(DIR)/rules.mk + +DIR := $(d)/menu +include $(DIR)/rules.mk + +DIR := $(d)/term +include $(DIR)/rules.mk + +# DIR := $(d)/wm +# include $(DIR)/rules.mk + +include share/pop.mk diff --git a/src/cmd/term/LICENSE b/src/cmd/term/LICENSE new file mode 100644 index 0000000..c356c39 --- /dev/null +++ b/src/cmd/term/LICENSE @@ -0,0 +1,34 @@ +MIT/X Consortium License + +© 2014-2018 Hiltjo Posthuma +© 2018 Devin J. Pohly +© 2014-2017 Quentin Rameau +© 2009-2012 Aurélien APTEL +© 2008-2017 Anselm R Garbe +© 2012-2017 Roberto E. Vargas Caballero +© 2012-2016 Christoph Lohmann <20h at r-36 dot net> +© 2013 Eon S. Jeon +© 2013 Alexander Sedov +© 2013 Mark Edgar +© 2013-2014 Eric Pruitt +© 2013 Michael Forney +© 2013-2014 Markus Teich +© 2014-2015 Laslo Hunhold + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/cmd/term/config.h b/src/cmd/term/config.h new file mode 100644 index 0000000..a740ecf --- /dev/null +++ b/src/cmd/term/config.h @@ -0,0 +1,474 @@ +/* See LICENSE file for copyright and license details. */ +#define VERSION "1" + +/* + * appearance + * + * font: see http://freedesktop.org/software/fontconfig/fontconfig-user.html + */ +static char *font = "consolas:size=16"; +static int borderpx = 2; + +/* + * What program is execed by st depends of these precedence rules: + * 1: program passed with -e + * 2: scroll and/or utmp + * 3: SHELL environment variable + * 4: value of shell in /etc/passwd + * 5: value of shell in config.h + */ +static char *shell = "/bin/mksh"; +char *utmp = nil; +/* scroll program: to enable use a string like "scroll" */ +char *scroll = nil; +char *stty_args = "stty raw pass8 nl -echo -iexten -cstopb 38400"; + +/* identification sequence returned in DA and DECID */ +char *vtiden = "\033[?6c"; + +/* Kerning / character bounding-box multipliers */ +static float cwscale = 1.0; +static float chscale = 1.0; + +/* + * word delimiter string + * + * More advanced example: L" `'\"()[]{}" + */ +wchar_t *worddelimiters = L" "; + +/* selection timeouts (in milliseconds) */ +static uint doubleclicktimeout = 300; +static uint tripleclicktimeout = 600; + +/* alt screens */ +int allowaltscreen = 1; + +/* allow certain non-interactive (insecure) window operations such as: + setting the clipboard text */ +int allowwindowops = 0; + +/* + * draw latency range in ms - from new content/keypress/etc until drawing. + * within this range, st draws when content stops arriving (idle). mostly it's + * near minlatency, but it waits longer for slow updates to avoid partial draw. + * low minlatency will tear/flicker more, as it can "detect" idle too early. + */ +static double minlatency = 8; +static double maxlatency = 33; + +/* + * blinking timeout (set to 0 to disable blinking) for the terminal blinking + * attribute. + */ +static uint blinktimeout = 800; + +/* + * thickness of underline and bar cursors + */ +static uint cursorthickness = 2; + +/* + * bell volume. It must be a value between -100 and 100. Use 0 for disabling + * it + */ +static int bellvolume = 0; + +/* default TERM value */ +char *termname = "term-256color"; + +/* + * spaces per tab + * + * When you are changing this value, don't forget to adapt the »it« value in + * the st.info and appropriately install the st.info in the environment where + * you use this st version. + * + * it#$tabspaces, + * + * Secondly make sure your kernel is not expanding tabs. When running `stty + * -a` »tab0« should appear. You can tell the terminal to not expand tabs by + * running following command: + * + * stty tabs + */ +uint tabspaces = 4; + +/* bg opacity */ +float alpha = 0.98; + +/* Terminal colors (16 first used in escape sequence) */ +static char *colorname[] = { + "#282828", /* hard contrast: #1d2021 / soft contrast: #32302f */ + "#ea6962", /* red */ + "#a9b665", /* green */ + "#d8a657", /* yellow */ + "#7daea3", /* blue */ + "#d3869b", /* magenta */ + "#89b482", /* cyan */ + "#d4be98", /* white */ + + "#928374", /* black */ + "#ef938e", /* red */ + "#bbc585", /* green */ + "#e1bb7e", /* yellow */ + "#9dc2ba", /* blue */ + "#e1acbb", /* magenta */ + "#a7c7a2", /* cyan */ + "#e2d3ba", /* white */ + + [255] = 0, + + /* more colors can be added after 255 to use with DefaultXX */ + "#fbf1c7", + "#3c3836", + "#555555", +}; + +/* + * Default colors (colorname index) + * foreground, background, cursor, reverse cursor + */ +uint defaultfg = 256; +uint defaultbg = 257; +static uint defaultcs = 15; +static uint defaultrcs = 258; + +/* + * Default shape of cursor + * 2: Block ("█") + * 4: Underline ("_") + * 6: Bar ("|") + * 7: Snowman ("☃") + */ +static uint cursorshape = 2; + +/* + * Default columns and rows numbers + */ + +static uint cols = 80; +static uint rows = 24; + +/* + * Default colour and shape of the mouse cursor + */ +static uint mouseshape = XC_left_ptr; +static uint mousefg = 0; +static uint mousebg = 7; + +/* + * Color used to display font attributes when fontconfig selected a font which + * doesn't match the ones requested. + */ +static uint defaultattr = 11; + +/* + * Force mouse select/shortcuts while mask is active (when MODE_MOUSE is set). + * Note that if you want to use ShiftMask with selmasks, set this to an other + * modifier, set to 0 to not use it. + */ +static uint forcemousemod = ShiftMask; + +/* + * Internal mouse shortcuts. + * Beware that overloading Button1 will disable the selection. + */ +static MouseShortcut mshortcuts[] = { + /* mask button function argument release */ + { XK_ANY_MOD, Button2, selpaste, {.i = 0}, 1 }, + { ShiftMask, Button4, ttysend, {.s = "\033[5;2~"} }, + { XK_ANY_MOD, Button4, ttysend, {.s = "\031"} }, + { ShiftMask, Button5, ttysend, {.s = "\033[6;2~"} }, + { XK_ANY_MOD, Button5, ttysend, {.s = "\005"} }, +}; + +/* Internal keyboard shortcuts. */ +#define MODKEY Mod1Mask +#define TERMMOD (ControlMask|ShiftMask) + +static Shortcut shortcuts[] = { + /* mask keysym function argument */ + { XK_ANY_MOD, XK_Break, sendbreak, {.i = 0} }, + { ControlMask, XK_Print, toggleprinter, {.i = 0} }, + { ShiftMask, XK_Print, printscreen, {.i = 0} }, + { XK_ANY_MOD, XK_Print, printsel, {.i = 0} }, + { TERMMOD, XK_plus, zoom, {.f = +1} }, + { ControlMask, XK_minus, zoom, {.f = -1} }, + { TERMMOD, XK_Home, zoomreset, {.f = 0} }, + { TERMMOD, XK_C, clipcopy, {.i = 0} }, + { TERMMOD, XK_V, clippaste, {.i = 0} }, + { TERMMOD, XK_Y, selpaste, {.i = 0} }, + { ShiftMask, XK_Insert, selpaste, {.i = 0} }, + { TERMMOD, XK_Num_Lock, numlock, {.i = 0} }, +}; + +/* + * Special keys (change & recompile st.info accordingly) + * + * Mask value: + * * Use XK_ANY_MOD to match the key no matter modifiers state + * * Use XK_NO_MOD to match the key alone (no modifiers) + * appkey value: + * * 0: no value + * * > 0: keypad application mode enabled + * * = 2: term.numlock = 1 + * * < 0: keypad application mode disabled + * appcursor value: + * * 0: no value + * * > 0: cursor application mode enabled + * * < 0: cursor application mode disabled + * + * Be careful with the order of the definitions because st searches in + * this table sequentially, so any XK_ANY_MOD must be in the last + * position for a key. + */ + +/* + * If you want keys other than the X11 function keys (0xFD00 - 0xFFFF) + * to be mapped below, add them to this array. + */ +static KeySym mappedkeys[] = { -1 }; + +/* + * State bits to ignore when matching key or button events. By default, + * numlock (Mod2Mask) and keyboard layout (XK_SWITCH_MOD) are ignored. + */ +static uint ignoremod = Mod2Mask|XK_SWITCH_MOD; + +/* + * This is the huge key array which defines all compatibility to the Linux + * world. Please decide about changes wisely. + */ +static Key key[] = { + /* keysym mask string appkey appcursor */ + { XK_KP_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_KP_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_KP_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_KP_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_KP_Up, XK_ANY_MOD, "\033Ox", +1, 0}, + { XK_KP_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_KP_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_KP_Down, XK_ANY_MOD, "\033Or", +1, 0}, + { XK_KP_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_KP_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_KP_Left, XK_ANY_MOD, "\033Ot", +1, 0}, + { XK_KP_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_KP_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_KP_Right, XK_ANY_MOD, "\033Ov", +1, 0}, + { XK_KP_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_KP_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_KP_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_KP_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_KP_Begin, XK_ANY_MOD, "\033[E", 0, 0}, + { XK_KP_End, ControlMask, "\033[J", -1, 0}, + { XK_KP_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_KP_End, ShiftMask, "\033[K", -1, 0}, + { XK_KP_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_KP_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_KP_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_KP_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_KP_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_KP_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[L", -1, 0}, + { XK_KP_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_KP_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_KP_Delete, ControlMask, "\033[M", -1, 0}, + { XK_KP_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_KP_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_KP_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_KP_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_KP_Multiply, XK_ANY_MOD, "\033Oj", +2, 0}, + { XK_KP_Add, XK_ANY_MOD, "\033Ok", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\033OM", +2, 0}, + { XK_KP_Enter, XK_ANY_MOD, "\r", -1, 0}, + { XK_KP_Subtract, XK_ANY_MOD, "\033Om", +2, 0}, + { XK_KP_Decimal, XK_ANY_MOD, "\033On", +2, 0}, + { XK_KP_Divide, XK_ANY_MOD, "\033Oo", +2, 0}, + { XK_KP_0, XK_ANY_MOD, "\033Op", +2, 0}, + { XK_KP_1, XK_ANY_MOD, "\033Oq", +2, 0}, + { XK_KP_2, XK_ANY_MOD, "\033Or", +2, 0}, + { XK_KP_3, XK_ANY_MOD, "\033Os", +2, 0}, + { XK_KP_4, XK_ANY_MOD, "\033Ot", +2, 0}, + { XK_KP_5, XK_ANY_MOD, "\033Ou", +2, 0}, + { XK_KP_6, XK_ANY_MOD, "\033Ov", +2, 0}, + { XK_KP_7, XK_ANY_MOD, "\033Ow", +2, 0}, + { XK_KP_8, XK_ANY_MOD, "\033Ox", +2, 0}, + { XK_KP_9, XK_ANY_MOD, "\033Oy", +2, 0}, + { XK_Up, ShiftMask, "\033[1;2A", 0, 0}, + { XK_Up, Mod1Mask, "\033[1;3A", 0, 0}, + { XK_Up, ShiftMask|Mod1Mask,"\033[1;4A", 0, 0}, + { XK_Up, ControlMask, "\033[1;5A", 0, 0}, + { XK_Up, ShiftMask|ControlMask,"\033[1;6A", 0, 0}, + { XK_Up, ControlMask|Mod1Mask,"\033[1;7A", 0, 0}, + { XK_Up,ShiftMask|ControlMask|Mod1Mask,"\033[1;8A", 0, 0}, + { XK_Up, XK_ANY_MOD, "\033[A", 0, -1}, + { XK_Up, XK_ANY_MOD, "\033OA", 0, +1}, + { XK_Down, ShiftMask, "\033[1;2B", 0, 0}, + { XK_Down, Mod1Mask, "\033[1;3B", 0, 0}, + { XK_Down, ShiftMask|Mod1Mask,"\033[1;4B", 0, 0}, + { XK_Down, ControlMask, "\033[1;5B", 0, 0}, + { XK_Down, ShiftMask|ControlMask,"\033[1;6B", 0, 0}, + { XK_Down, ControlMask|Mod1Mask,"\033[1;7B", 0, 0}, + { XK_Down,ShiftMask|ControlMask|Mod1Mask,"\033[1;8B",0, 0}, + { XK_Down, XK_ANY_MOD, "\033[B", 0, -1}, + { XK_Down, XK_ANY_MOD, "\033OB", 0, +1}, + { XK_Left, ShiftMask, "\033[1;2D", 0, 0}, + { XK_Left, Mod1Mask, "\033[1;3D", 0, 0}, + { XK_Left, ShiftMask|Mod1Mask,"\033[1;4D", 0, 0}, + { XK_Left, ControlMask, "\033[1;5D", 0, 0}, + { XK_Left, ShiftMask|ControlMask,"\033[1;6D", 0, 0}, + { XK_Left, ControlMask|Mod1Mask,"\033[1;7D", 0, 0}, + { XK_Left,ShiftMask|ControlMask|Mod1Mask,"\033[1;8D",0, 0}, + { XK_Left, XK_ANY_MOD, "\033[D", 0, -1}, + { XK_Left, XK_ANY_MOD, "\033OD", 0, +1}, + { XK_Right, ShiftMask, "\033[1;2C", 0, 0}, + { XK_Right, Mod1Mask, "\033[1;3C", 0, 0}, + { XK_Right, ShiftMask|Mod1Mask,"\033[1;4C", 0, 0}, + { XK_Right, ControlMask, "\033[1;5C", 0, 0}, + { XK_Right, ShiftMask|ControlMask,"\033[1;6C", 0, 0}, + { XK_Right, ControlMask|Mod1Mask,"\033[1;7C", 0, 0}, + { XK_Right,ShiftMask|ControlMask|Mod1Mask,"\033[1;8C",0, 0}, + { XK_Right, XK_ANY_MOD, "\033[C", 0, -1}, + { XK_Right, XK_ANY_MOD, "\033OC", 0, +1}, + { XK_ISO_Left_Tab, ShiftMask, "\033[Z", 0, 0}, + { XK_Return, Mod1Mask, "\033\r", 0, 0}, + { XK_Return, XK_ANY_MOD, "\r", 0, 0}, + { XK_Insert, ShiftMask, "\033[4l", -1, 0}, + { XK_Insert, ShiftMask, "\033[2;2~", +1, 0}, + { XK_Insert, ControlMask, "\033[L", -1, 0}, + { XK_Insert, ControlMask, "\033[2;5~", +1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[4h", -1, 0}, + { XK_Insert, XK_ANY_MOD, "\033[2~", +1, 0}, + { XK_Delete, ControlMask, "\033[M", -1, 0}, + { XK_Delete, ControlMask, "\033[3;5~", +1, 0}, + { XK_Delete, ShiftMask, "\033[2K", -1, 0}, + { XK_Delete, ShiftMask, "\033[3;2~", +1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[P", -1, 0}, + { XK_Delete, XK_ANY_MOD, "\033[3~", +1, 0}, + { XK_BackSpace, XK_NO_MOD, "\177", 0, 0}, + { XK_BackSpace, Mod1Mask, "\033\177", 0, 0}, + { XK_Home, ShiftMask, "\033[2J", 0, -1}, + { XK_Home, ShiftMask, "\033[1;2H", 0, +1}, + { XK_Home, XK_ANY_MOD, "\033[H", 0, -1}, + { XK_Home, XK_ANY_MOD, "\033[1~", 0, +1}, + { XK_End, ControlMask, "\033[J", -1, 0}, + { XK_End, ControlMask, "\033[1;5F", +1, 0}, + { XK_End, ShiftMask, "\033[K", -1, 0}, + { XK_End, ShiftMask, "\033[1;2F", +1, 0}, + { XK_End, XK_ANY_MOD, "\033[4~", 0, 0}, + { XK_Prior, ControlMask, "\033[5;5~", 0, 0}, + { XK_Prior, ShiftMask, "\033[5;2~", 0, 0}, + { XK_Prior, XK_ANY_MOD, "\033[5~", 0, 0}, + { XK_Next, ControlMask, "\033[6;5~", 0, 0}, + { XK_Next, ShiftMask, "\033[6;2~", 0, 0}, + { XK_Next, XK_ANY_MOD, "\033[6~", 0, 0}, + { XK_F1, XK_NO_MOD, "\033OP" , 0, 0}, + { XK_F1, /* F13 */ ShiftMask, "\033[1;2P", 0, 0}, + { XK_F1, /* F25 */ ControlMask, "\033[1;5P", 0, 0}, + { XK_F1, /* F37 */ Mod4Mask, "\033[1;6P", 0, 0}, + { XK_F1, /* F49 */ Mod1Mask, "\033[1;3P", 0, 0}, + { XK_F1, /* F61 */ Mod3Mask, "\033[1;4P", 0, 0}, + { XK_F2, XK_NO_MOD, "\033OQ" , 0, 0}, + { XK_F2, /* F14 */ ShiftMask, "\033[1;2Q", 0, 0}, + { XK_F2, /* F26 */ ControlMask, "\033[1;5Q", 0, 0}, + { XK_F2, /* F38 */ Mod4Mask, "\033[1;6Q", 0, 0}, + { XK_F2, /* F50 */ Mod1Mask, "\033[1;3Q", 0, 0}, + { XK_F2, /* F62 */ Mod3Mask, "\033[1;4Q", 0, 0}, + { XK_F3, XK_NO_MOD, "\033OR" , 0, 0}, + { XK_F3, /* F15 */ ShiftMask, "\033[1;2R", 0, 0}, + { XK_F3, /* F27 */ ControlMask, "\033[1;5R", 0, 0}, + { XK_F3, /* F39 */ Mod4Mask, "\033[1;6R", 0, 0}, + { XK_F3, /* F51 */ Mod1Mask, "\033[1;3R", 0, 0}, + { XK_F3, /* F63 */ Mod3Mask, "\033[1;4R", 0, 0}, + { XK_F4, XK_NO_MOD, "\033OS" , 0, 0}, + { XK_F4, /* F16 */ ShiftMask, "\033[1;2S", 0, 0}, + { XK_F4, /* F28 */ ControlMask, "\033[1;5S", 0, 0}, + { XK_F4, /* F40 */ Mod4Mask, "\033[1;6S", 0, 0}, + { XK_F4, /* F52 */ Mod1Mask, "\033[1;3S", 0, 0}, + { XK_F5, XK_NO_MOD, "\033[15~", 0, 0}, + { XK_F5, /* F17 */ ShiftMask, "\033[15;2~", 0, 0}, + { XK_F5, /* F29 */ ControlMask, "\033[15;5~", 0, 0}, + { XK_F5, /* F41 */ Mod4Mask, "\033[15;6~", 0, 0}, + { XK_F5, /* F53 */ Mod1Mask, "\033[15;3~", 0, 0}, + { XK_F6, XK_NO_MOD, "\033[17~", 0, 0}, + { XK_F6, /* F18 */ ShiftMask, "\033[17;2~", 0, 0}, + { XK_F6, /* F30 */ ControlMask, "\033[17;5~", 0, 0}, + { XK_F6, /* F42 */ Mod4Mask, "\033[17;6~", 0, 0}, + { XK_F6, /* F54 */ Mod1Mask, "\033[17;3~", 0, 0}, + { XK_F7, XK_NO_MOD, "\033[18~", 0, 0}, + { XK_F7, /* F19 */ ShiftMask, "\033[18;2~", 0, 0}, + { XK_F7, /* F31 */ ControlMask, "\033[18;5~", 0, 0}, + { XK_F7, /* F43 */ Mod4Mask, "\033[18;6~", 0, 0}, + { XK_F7, /* F55 */ Mod1Mask, "\033[18;3~", 0, 0}, + { XK_F8, XK_NO_MOD, "\033[19~", 0, 0}, + { XK_F8, /* F20 */ ShiftMask, "\033[19;2~", 0, 0}, + { XK_F8, /* F32 */ ControlMask, "\033[19;5~", 0, 0}, + { XK_F8, /* F44 */ Mod4Mask, "\033[19;6~", 0, 0}, + { XK_F8, /* F56 */ Mod1Mask, "\033[19;3~", 0, 0}, + { XK_F9, XK_NO_MOD, "\033[20~", 0, 0}, + { XK_F9, /* F21 */ ShiftMask, "\033[20;2~", 0, 0}, + { XK_F9, /* F33 */ ControlMask, "\033[20;5~", 0, 0}, + { XK_F9, /* F45 */ Mod4Mask, "\033[20;6~", 0, 0}, + { XK_F9, /* F57 */ Mod1Mask, "\033[20;3~", 0, 0}, + { XK_F10, XK_NO_MOD, "\033[21~", 0, 0}, + { XK_F10, /* F22 */ ShiftMask, "\033[21;2~", 0, 0}, + { XK_F10, /* F34 */ ControlMask, "\033[21;5~", 0, 0}, + { XK_F10, /* F46 */ Mod4Mask, "\033[21;6~", 0, 0}, + { XK_F10, /* F58 */ Mod1Mask, "\033[21;3~", 0, 0}, + { XK_F11, XK_NO_MOD, "\033[23~", 0, 0}, + { XK_F11, /* F23 */ ShiftMask, "\033[23;2~", 0, 0}, + { XK_F11, /* F35 */ ControlMask, "\033[23;5~", 0, 0}, + { XK_F11, /* F47 */ Mod4Mask, "\033[23;6~", 0, 0}, + { XK_F11, /* F59 */ Mod1Mask, "\033[23;3~", 0, 0}, + { XK_F12, XK_NO_MOD, "\033[24~", 0, 0}, + { XK_F12, /* F24 */ ShiftMask, "\033[24;2~", 0, 0}, + { XK_F12, /* F36 */ ControlMask, "\033[24;5~", 0, 0}, + { XK_F12, /* F48 */ Mod4Mask, "\033[24;6~", 0, 0}, + { XK_F12, /* F60 */ Mod1Mask, "\033[24;3~", 0, 0}, + { XK_F13, XK_NO_MOD, "\033[1;2P", 0, 0}, + { XK_F14, XK_NO_MOD, "\033[1;2Q", 0, 0}, + { XK_F15, XK_NO_MOD, "\033[1;2R", 0, 0}, + { XK_F16, XK_NO_MOD, "\033[1;2S", 0, 0}, + { XK_F17, XK_NO_MOD, "\033[15;2~", 0, 0}, + { XK_F18, XK_NO_MOD, "\033[17;2~", 0, 0}, + { XK_F19, XK_NO_MOD, "\033[18;2~", 0, 0}, + { XK_F20, XK_NO_MOD, "\033[19;2~", 0, 0}, + { XK_F21, XK_NO_MOD, "\033[20;2~", 0, 0}, + { XK_F22, XK_NO_MOD, "\033[21;2~", 0, 0}, + { XK_F23, XK_NO_MOD, "\033[23;2~", 0, 0}, + { XK_F24, XK_NO_MOD, "\033[24;2~", 0, 0}, + { XK_F25, XK_NO_MOD, "\033[1;5P", 0, 0}, + { XK_F26, XK_NO_MOD, "\033[1;5Q", 0, 0}, + { XK_F27, XK_NO_MOD, "\033[1;5R", 0, 0}, + { XK_F28, XK_NO_MOD, "\033[1;5S", 0, 0}, + { XK_F29, XK_NO_MOD, "\033[15;5~", 0, 0}, + { XK_F30, XK_NO_MOD, "\033[17;5~", 0, 0}, + { XK_F31, XK_NO_MOD, "\033[18;5~", 0, 0}, + { XK_F32, XK_NO_MOD, "\033[19;5~", 0, 0}, + { XK_F33, XK_NO_MOD, "\033[20;5~", 0, 0}, + { XK_F34, XK_NO_MOD, "\033[21;5~", 0, 0}, + { XK_F35, XK_NO_MOD, "\033[23;5~", 0, 0}, +}; + +/* + * Selection types' masks. + * Use the same masks as usual. + * Button1Mask is always unset, to make masks match between ButtonPress. + * ButtonRelease and MotionNotify. + * If no match is found, regular selection is used. + */ +static uint selmasks[] = { + [SelRectangular] = Mod1Mask, +}; + +/* + * Printable characters in ASCII, used to estimate the advance width + * of single wide characters. + */ +static char ascii_printable[] = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; diff --git a/src/cmd/term/hb.c b/src/cmd/term/hb.c new file mode 100644 index 0000000..4b6b42d --- /dev/null +++ b/src/cmd/term/hb.c @@ -0,0 +1,147 @@ +#include "term.h" + +#include +#include + +#define FEATURE(c1,c2,c3,c4) { .tag = HB_TAG(c1,c2,c3,c4), .value = 1, .start = HB_FEATURE_GLOBAL_START, .end = HB_FEATURE_GLOBAL_END } + +hb_font_t *hbfindfont(XftFont *match); +void hbtransformsegment(XftFont *xfont, const Letter *glyph, rune *codepoints, int start, int end); + +typedef struct +{ + XftFont *match; + hb_font_t *font; +} HbFontMatch; + +static int hbfontslen = 0; +static HbFontMatch *hbfontcache = nil; + +/* + * Replace 0 with a list of font features, wrapped in FEATURE macro, e.g. + * FEATURE('c', 'a', 'l', 't'), FEATURE('d', 'l', 'i', 'g') + */ +hb_feature_t features[] = { 0 }; + +void +hbunloadfonts() +{ + int i; + for(i = 0; i < hbfontslen; i++) { + hb_font_destroy(hbfontcache[i].font); + XftUnlockFace(hbfontcache[i].match); + } + + if(hbfontcache != nil) { + free(hbfontcache); + hbfontcache = nil; + } + + hbfontslen = 0; +} + +hb_font_t * +hbfindfont(XftFont *match) +{ + int i; + for (i = 0; i < hbfontslen; i++) { + if (hbfontcache[i].match == match) + return hbfontcache[i].font; + } + + /* Font not found in cache, caching it now. */ + hbfontcache = realloc(hbfontcache, sizeof(HbFontMatch) * (hbfontslen + 1)); + FT_Face face = XftLockFace(match); + hb_font_t *font = hb_ft_font_create(face, NULL); + if(!font) + fatal("failed to load Harfbuzz font."); + + hbfontcache[hbfontslen].match = match; + hbfontcache[hbfontslen].font = font; + hbfontslen += 1; + + return font; +} + +void +hbtransform(XftGlyphFontSpec *specs, const Letter *glyphs, size_t len, int x, int y) +{ + int idx, specidx, start = 0, length = 1, gstart = 0; + rune *runes = calloc((unsigned int)len, sizeof(hb_codepoint_t)); + + for(idx = 1, specidx = 1; idx < len; idx++) { + if(glyphs[idx].mode & Gwdummy) { + length += 1; + continue; + } + + if(specs[specidx].font != specs[start].font + || GLYPHCMP(glyphs[gstart], glyphs[idx]) + || selected(x + idx, y) != selected(x + gstart, y) + ) { + hbtransformsegment(specs[start].font, glyphs, runes, gstart, length); + /* reset the sequence. */ + length = 1; + start = specidx; + gstart = idx; + } else { + length += 1; + } + + specidx++; + } + + /* eol */ + hbtransformsegment(specs[start].font, glyphs, runes, gstart, length); + + /* apply the transformation to glyph specs. */ + for(idx = 0, specidx = 0; idx < len; idx++) { + if(glyphs[idx].mode & Gwdummy) + continue; + + if(runes[idx] != specs[specidx].glyph) + ((Letter *)glyphs)[idx].mode |= Gliga; + + specs[specidx++].glyph = runes[idx]; + } + + free(runes); +} + +void +hbtransformsegment(XftFont *xfont, const Letter *glyph, rune *codepoints, int start, int len) +{ + hb_font_t *font = hbfindfont(xfont); + if(!font) + return; + + int i; + rune r; + ushort mode = USHRT_MAX; + hb_buffer_t *buffer = hb_buffer_create(); + hb_buffer_set_direction(buffer, HB_DIRECTION_LTR); + + /* Fill buffer with codepoints. */ + for(i=start; i < (start+len); i++) { + r = glyph[i].u; + mode = glyph[i].mode; + if(mode & Gwdummy) + r = 0x0020; + hb_buffer_add_codepoints(buffer, &r, 1, 0, 1); + } + + /* Shape the segment. */ + hb_shape(font, buffer, features, sizeof(features)); + + /* Get new glyph info. */ + hb_glyph_info_t *info = hb_buffer_get_glyph_infos(buffer, NULL); + + /* Write new codepoints. */ + for(i = 0; i < len; i++) { + r = info[i].codepoint; + codepoints[start+i] = r; + } + + /* Cleanup. */ + hb_buffer_destroy(buffer); +} diff --git a/src/cmd/term/nonspacing.h b/src/cmd/term/nonspacing.h new file mode 100644 index 0000000..5d05a3d --- /dev/null +++ b/src/cmd/term/nonspacing.h @@ -0,0 +1,89 @@ +16,16,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,16,16,32,16,16,16,33,34,35, +36,37,38,39,16,16,40,16,16,16,16,16,16,16,16,16,16,16,41,42,16,16,43,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,44,16,45,46,47,48,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,49,16,16,50, +51,16,52,53,54,16,16,16,16,16,16,55,16,16,56,16,57,58,59,60,61,62,63,64,65,66, +67,68,16,69,70,71,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,72,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,73,74,16,16,16,75,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,76,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,77,78,16,16,16,16,16,16,16,79,16,16,16,16,16,80,81,82,16,16,16,16,16,83, +84,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,248,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,254,255,255,255,255,191,182,0,0,0,0,0,0,0,63,0,255,23,0,0,0,0,0,248,255, +255,0,0,1,0,0,0,0,0,0,0,0,0,0,0,192,191,159,61,0,0,0,128,2,0,0,0,255,255,255, +7,0,0,0,0,0,0,0,0,0,0,192,255,1,0,0,0,0,0,0,248,15,32,0,0,192,251,239,62,0,0, +0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,255,255,255,255, +255,7,0,0,0,0,0,0,20,254,33,254,0,12,0,0,0,2,0,0,0,0,0,0,16,30,32,0,0,12,0,0, +64,6,0,0,0,0,0,0,16,134,57,2,0,0,0,35,0,6,0,0,0,0,0,0,16,190,33,0,0,12,0,0, +252,2,0,0,0,0,0,0,144,30,32,64,0,12,0,0,0,4,0,0,0,0,0,0,0,1,32,0,0,0,0,0,0,17, +0,0,0,0,0,0,192,193,61,96,0,12,0,0,0,2,0,0,0,0,0,0,144,64,48,0,0,12,0,0,0,3,0, +0,0,0,0,0,24,30,32,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,4,92,0,0,0,0,0,0,0,0,0,0,0, +242,7,128,127,0,0,0,0,0,0,0,0,0,0,0,0,242,31,0,63,0,0,0,0,0,0,0,0,0,3,0,0,160, +2,0,0,0,0,0,0,254,127,223,224,255,254,255,255,255,31,64,0,0,0,0,0,0,0,0,0,0,0, +0,224,253,102,0,0,0,195,1,0,30,0,100,32,0,32,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,0, +0,0,28,0,0,0,12,0,0,0,12,0,0,0,0,0,0,0,176,63,64,254,15,32,0,0,0,0,0,120,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,1,4,14,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,9,0,0,0,0,0,0,64,127, +229,31,248,159,0,0,0,0,0,0,255,127,0,0,0,0,0,0,0,0,15,0,0,0,0,0,208,23,4,0,0, +0,0,248,15,0,3,0,0,0,60,59,0,0,0,0,0,0,64,163,3,0,0,0,0,0,0,240,207,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,247,255,253,33,16,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255, +251,0,248,0,0,0,124,0,0,0,0,0,0,223,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255, +255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0, +0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,128,247,63,0,0,0,192,0,0,0,0,0,0,0,0,0,0,3,0,68,8,0,0,96,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,255,255,3,128,0,0,0,0,192,63,0,0,128,255,3,0, +0,0,0,0,7,0,0,0,0,0,200,51,0,0,0,0,32,0,0,0,0,0,0,0,0,126,102,0,8,16,0,0,0,0, +0,16,0,0,0,0,0,0,157,193,2,0,0,0,0,48,64, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,0,0,0,0,0,64, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,0,0,255,255,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,110,240,0,0,0,0,0,135,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0, +0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,192,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,255, +127,0,0,0,0,0,0,128,3,0,0,0,0,0,120,38,0,32,0,0,0,0,0,0,7,0,0,0,128,239,31,0, +0,0,0,0,0,0,8,0,3,0,0,0,0,0,192,127,0,30,0,0,0,0,0,0,0,0,0,0,0,128,211,64,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,248,7,0,0,3,0,0,0,0,0,0,24,1,0,0,0,192, +31,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,92,0,0,64,0,0,0,0,0, +0,0,0,0,0,248,133,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,60,176,1,0,0,48,0,0,0, +0,0,0,0,0,0,0,248,167,1,0,0,0,0,0,0,0,0,0,0,0,0,40,191,0,0,0,0,0,0,0,0,0,0,0, +0,224,188,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +128,255,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,12,1,0,0,0,254,7,0,0,0,0,248,121,128,0, +126,14,0,0,0,0,0,252,127,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,191,0,0,0, +0,0,0,0,0,0,0,252,255,255,252,109,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,180,191,0, +0,0,0,0,0,0,0,0,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,24, +0,0,0,0,0,0,0,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,127,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0, +0,128,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,15, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,248,255,231,15,0,0,0,60,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,255,255,255,255,255,255,127,248,255,255,255,255,255,31,32,0,16,0,0,248, +254,255,0,0,0,0,0,0,0,0,0, +0,127,255,255,249,219,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,240,7,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0, diff --git a/src/cmd/term/rules.mk b/src/cmd/term/rules.mk new file mode 100644 index 0000000..76701ec --- /dev/null +++ b/src/cmd/term/rules.mk @@ -0,0 +1,26 @@ +include share/push.mk + +# local sources +SRCS_$(d) := $(d)/term.c $(d)/x.c #$(d)/hb.c + +# local outputs +BINS_$(d) := $(d)/term + +include share/paths.mk + +# Local rules +include share/dynamic.mk + +$(BINS_$(d)): TCFLAGS=\ + `$(PKG) --cflags fontconfig`\ + `$(PKG) --cflags freetype2` + +$(BINS_$(d)): TCLIBS=\ + `$(PKG) --libs fontconfig`\ + `$(PKG) --libs freetype2`\ + -lm -lrt -lX11 -lutil -lXft -lXrender #-lharfbuzz + +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libutf/libutf.a $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/term/term.c b/src/cmd/term/term.c new file mode 100644 index 0000000..50ab29c --- /dev/null +++ b/src/cmd/term/term.c @@ -0,0 +1,2417 @@ +/* See LICENSE for license details. */ +#include "term.h" + +#include +#include +#if defined(__linux) + #include +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) + #include +#elif defined(__FreeBSD__) || defined(__DragonFly__) + #include +#endif + +/* macros */ +#define IS_SET(flag) ((term.mode & (flag)) != 0) +#define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == 0x7f) +#define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) +#define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) +#define ISDELIM(u) (u && wcschr(worddelimiters, u)) + +/* forward declare functions */ +static void execsh(char *, char **); +static void stty(char **); +static void sigchld(int); +static void ttywriteraw(char *, size_t); + +static void csidump(void); +static void csihandle(void); +static void csiparse(void); +static void csireset(void); +static int eschandle(uchar); +static void strdump(void); +static void strhandle(void); +static void strparse(void); +static void strreset(void); + +static void tprinter(char *, size_t); +static void tdumpsel(void); +static void tdumpline(int); +static void tdump(void); +static void tclearregion(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 int tlinelen(int); +static void tmoveto(int, int); +static void tmoveato(int, int); +static void tnewline(int); +static void tputtab(int); +static void tputc(rune); +static void treset(void); +static void tscrollup(int, int); +static void tscrolldown(int, int); +static void tsetattr(int *, int); +static void tsetchar(rune, Letter *, int, int); +static void tsetdirt(int, int); +static void tsetscroll(int, int); +static void tswapscreen(void); +static void tsetmode(int, int, int *, int); +static int twrite(char *, int, int); +static void tfulldirt(void); +static void tcontrolcode(uchar ); +static void tdectest(char ); +static void tdefutf8(char); +static int32 tdefcolor(int *, int *, int); +static void tdeftran(char); +static void tstrsequence(uchar); + +static void drawregion(int, int, int, int); + +static void selnormalize(void); +static void selscroll(int, int); +static void selsnap(int *, int *, int); + +static char *base64dec(char *); +static char base64dec_getc(char **); + +static uintptr xwrite(int, char *, size_t); +extern int wcwidth(wchar_t wc); + +/* globals */ +static Terminal term; +static Selection sel; +static CSIEscape csiescseq; +static STREscape strescseq; +static int iofd = 1; +static int cmdfd; +static pid_t pid; + +/* functions */ +uintptr +xwrite(int fd, char *s, size_t len) +{ + size_t aux = len; + ssize_t r; + + while (len > 0) { + r = write(fd, s, len); + if (r < 0) + return r; + len -= r; + s += r; + } + + return aux; +} + +void * +xmalloc(size_t len) +{ + void *p; + + if (!(p = malloc(len))) + fatal("malloc: %s\n", strerror(errno)); + + return p; +} + +void * +xrealloc(void *p, size_t len) +{ + if ((p = realloc(p, len)) == nil) + fatal("realloc: %s\n", strerror(errno)); + + return p; +} + +char * +xstrdup(char *s) +{ + if ((s = strdup(s)) == nil) + fatal("strdup: %s\n", strerror(errno)); + + return s; +} + +static char base64_digits[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, + 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +char +base64dec_getc(char **src) +{ + while (**src && !isprint(**src)) + (*src)++; + return **src ? *((*src)++) : '='; /* emulate padding if string ends */ +} + +char * +base64dec(char *src) +{ + size_t in_len = strlen(src); + char *result, *dst; + + if (in_len % 4) + in_len += 4 - (in_len % 4); + result = dst = xmalloc(in_len / 4 * 3 + 1); + while (*src) { + int a = base64_digits[(unsigned char) base64dec_getc(&src)]; + int b = base64_digits[(unsigned char) base64dec_getc(&src)]; + int c = base64_digits[(unsigned char) base64dec_getc(&src)]; + int d = base64_digits[(unsigned char) base64dec_getc(&src)]; + + /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */ + if (a == -1 || b == -1) + break; + + *dst++ = (a << 2) | ((b & 0x30) >> 4); + if (c == -1) + break; + *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2); + if (d == -1) + break; + *dst++ = ((c & 0x03) << 6) | d; + } + *dst = '\0'; + return result; +} + +void +selinit(void) +{ + sel.mode = SelIdle; + sel.snap = 0; + sel.ob.x = -1; +} + +int +tlinelen(int y) +{ + int i = term.col; + + if (term.line[y][i - 1].mode & Gwrap) + return i; + + while (i > 0 && term.line[y][i - 1].u == ' ') + --i; + + return i; +} + +void +selstart(int col, int row, int snap) +{ + selclear(); + sel.mode = SelEmpty; + sel.type = SelRegular; + sel.alt = IS_SET(Taltscreen); + sel.snap = snap; + sel.oe.x = sel.ob.x = col; + sel.oe.y = sel.ob.y = row; + selnormalize(); + + if (sel.snap != 0) + sel.mode = SelReady; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +selextend(int col, int row, int type, int done) +{ + int oldey, oldex, oldsby, oldsey, oldtype; + + if (sel.mode == SelIdle) + return; + if (done && sel.mode == SelEmpty) { + selclear(); + return; + } + + oldey = sel.oe.y; + oldex = sel.oe.x; + oldsby = sel.nb.y; + oldsey = sel.ne.y; + oldtype = sel.type; + + sel.oe.x = col; + sel.oe.y = row; + selnormalize(); + sel.type = type; + + if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SelEmpty) + tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); + + sel.mode = done ? SelIdle : SelReady; +} + +void +selnormalize(void) +{ + int i; + + if (sel.type == SelRegular && sel.ob.y != sel.oe.y) { + sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x; + sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x; + } else { + sel.nb.x = MIN(sel.ob.x, sel.oe.x); + sel.ne.x = MAX(sel.ob.x, sel.oe.x); + } + sel.nb.y = MIN(sel.ob.y, sel.oe.y); + sel.ne.y = MAX(sel.ob.y, sel.oe.y); + + selsnap(&sel.nb.x, &sel.nb.y, -1); + selsnap(&sel.ne.x, &sel.ne.y, +1); + + /* expand selection over line breaks */ + if (sel.type == SelRectangular) + return; + i = tlinelen(sel.nb.y); + if (i < sel.nb.x) + sel.nb.x = i; + if (tlinelen(sel.ne.y) <= sel.ne.x) + sel.ne.x = term.col - 1; +} + +int +selected(int x, int y) +{ + if(sel.mode == SelEmpty || sel.ob.x == -1 || + sel.alt != IS_SET(Taltscreen)) + return 0; + + if(sel.type == SelRectangular) + return BETWEEN(y, sel.nb.y, sel.ne.y) + && BETWEEN(x, sel.nb.x, sel.ne.x); + + return BETWEEN(y, sel.nb.y, sel.ne.y) + && (y != sel.nb.y || x >= sel.nb.x) + && (y != sel.ne.y || x <= sel.ne.x); +} + +void +selsnap(int *x, int *y, int direction) +{ + int newx, newy, xt, yt; + int delim, prevdelim; + Letter *gp, *prevgp; + + switch (sel.snap) { + case SnapWord: + /* + * Snap around if the word wraps around at the end or + * beginning of a line. + */ + prevgp = &term.line[*y][*x]; + prevdelim = ISDELIM(prevgp->u); + for (;;) { + newx = *x + direction; + newy = *y; + if (!BETWEEN(newx, 0, term.col - 1)) { + newy += direction; + newx = (newx + term.col) % term.col; + if (!BETWEEN(newy, 0, term.row - 1)) + break; + + if (direction > 0) + yt = *y, xt = *x; + else + yt = newy, xt = newx; + if (!(term.line[yt][xt].mode & Gwrap)) + break; + } + + if (newx >= tlinelen(newy)) + break; + + gp = &term.line[newy][newx]; + delim = ISDELIM(gp->u); + if (!(gp->mode & Gwdummy) && (delim != prevdelim + || (delim && gp->u != prevgp->u))) + break; + + *x = newx; + *y = newy; + prevgp = gp; + prevdelim = delim; + } + break; + case SnapLine: + /* + * Snap around if the the previous line or the current one + * has set ATTR_WRAP at its end. Then the whole next or + * previous line will be selected. + */ + *x = (direction < 0) ? 0 : term.col - 1; + if (direction < 0) { + for (; *y > 0; *y += direction) { + if (!(term.line[*y-1][term.col-1].mode + & Gwrap)) { + break; + } + } + } else if (direction > 0) { + for (; *y < term.row-1; *y += direction) { + if (!(term.line[*y][term.col-1].mode + & Gwrap)) { + break; + } + } + } + break; + } +} + +char * +getsel(void) +{ + char *str, *ptr; + int y, bufsize, lastx, linelen; + Letter *gp, *last; + + if (sel.ob.x == -1) + return nil; + + bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTFmax; + ptr = str = xmalloc(bufsize); + + /* append every set & selected glyph to the selection */ + for(y = sel.nb.y; y <= sel.ne.y; y++) { + if((linelen = tlinelen(y)) == 0) { + *ptr++ = '\n'; + continue; + } + + if(sel.type == SelRectangular) { + gp = &term.line[y][sel.nb.x]; + lastx = sel.ne.x; + }else{ + gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0]; + lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1; + } + last = &term.line[y][MIN(lastx, linelen-1)]; + while (last >= gp && last->u == ' ') + --last; + + for ( ; gp <= last; ++gp) { + if (gp->mode & Gwdummy) + continue; + + ptr += utf8·encode(&gp->u, ptr); + } + + /* + * Copy and pasting of line endings is inconsistent + * in the inconsistent terminal and GUI world. + * The best solution seems like to produce '\n' when + * something is copied from st and convert '\n' to + * '\r', when something to be pasted is received by + * st. + * FIXME: Fix the computer world. + */ + if ((y < sel.ne.y || lastx >= linelen) && + (!(last->mode & Gwrap) || sel.type == SelRectangular)) + *ptr++ = '\n'; + } + *ptr = 0; + return str; +} + +void +selclear(void) +{ + if (sel.ob.x == -1) + return; + sel.mode = SelIdle; + sel.ob.x = -1; + tsetdirt(sel.nb.y, sel.ne.y); +} + +void +fatal(char *errstr, ...) +{ + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(1); +} + +void +execsh(char *cmd, char **args) +{ + char *sh, *prog, *arg; + struct passwd *pw; + + errno = 0; + if ((pw = getpwuid(getuid())) == nil) { + if (errno) + fatal("getpwuid: %s\n", strerror(errno)); + else + fatal("who are you?\n"); + } + + if ((sh = getenv("SHELL")) == nil) + sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd; + + if (args) { + prog = args[0]; + arg = nil; + } else if (scroll) { + prog = scroll; + arg = utmp ? utmp : sh; + } else if (utmp) { + prog = utmp; + arg = nil; + } else { + prog = sh; + arg = nil; + } + DEFAULT(args, ((char *[]) {prog, arg, nil})); + + unsetenv("COLUMNS"); + unsetenv("LINES"); + unsetenv("TERMCAP"); + setenv("LOGNAME", pw->pw_name, 1); + setenv("USER", pw->pw_name, 1); + setenv("SHELL", sh, 1); + setenv("HOME", pw->pw_dir, 1); + setenv("TERM", termname, 1); + + signal(SIGCHLD, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGALRM, SIG_DFL); + + execvp(prog, args); + _exit(1); +} + +void +sigchld(int a) +{ + int stat; + pid_t p; + + if ((p = waitpid(pid, &stat, WNOHANG)) < 0) + fatal("waiting for pid %hd failed: %s\n", pid, strerror(errno)); + + if (pid != p) + return; + + if (WIFEXITED(stat) && WEXITSTATUS(stat)) + fatal("child exited with status %d\n", WEXITSTATUS(stat)); + else if (WIFSIGNALED(stat)) + fatal("child terminated due to signal %d\n", WTERMSIG(stat)); + _exit(0); +} + +void +stty(char **args) +{ + char cmd[_POSIX_ARG_MAX], **p, *q, *s; + size_t n, siz; + + if ((n = strlen(stty_args)) > sizeof(cmd)-1) + fatal("incorrect stty parameters\n"); + memcpy(cmd, stty_args, n); + q = cmd + n; + siz = sizeof(cmd) - n; + for (p = args; p && (s = *p); ++p) { + if ((n = strlen(s)) > siz-1) + fatal("stty parameter length too long\n"); + *q++ = ' '; + memcpy(q, s, n); + q += n; + siz -= n + 1; + } + *q = '\0'; + if (system(cmd) != 0) + perror("Couldn't call stty"); +} + +int +ttynew(char *line, char *cmd, char *out, char **args) +{ + int m, s; + + if (out) { + term.mode |= Tprint; + iofd = (!strcmp(out, "-")) ? + 1 : open(out, O_WRONLY | O_CREAT, 0666); + if (iofd < 0) { + fprintf(stderr, "Error opening %s:%s\n", + out, strerror(errno)); + } + } + + if (line) { + if ((cmdfd = open(line, O_RDWR)) < 0) + fatal("open line '%s' failed: %s\n", + line, strerror(errno)); + dup2(cmdfd, 0); + stty(args); + return cmdfd; + } + + /* seems to work fine on linux, openbsd and freebsd */ + if (openpty(&m, &s, nil, nil, nil) < 0) + fatal("openpty failed: %s\n", strerror(errno)); + + switch (pid = fork()) { + case -1: + fatal("fork failed: %s\n", strerror(errno)); + break; + case 0: + close(iofd); + setsid(); /* create a new process group */ + dup2(s, 0); + dup2(s, 1); + dup2(s, 2); + if (ioctl(s, TIOCSCTTY, nil) < 0) + fatal("ioctl TIOCSCTTY failed: %s\n", strerror(errno)); + close(s); + close(m); +#ifdef __OpenBSD__ + if (pledge("stdio getpw proc exec", nil) == -1) + fatal("pledge\n"); +#endif + execsh(cmd, args); + break; + default: +#ifdef __OpenBSD__ + if (pledge("stdio rpath tty proc", nil) == -1) + fatal("pledge\n"); +#endif + close(s); + cmdfd = m; + signal(SIGCHLD, sigchld); + break; + } + return cmdfd; +} + +size_t +ttyread(void) +{ + static char buf[BUFSIZ]; + static int buflen = 0; + int ret, written; + + /* append read bytes to unprocessed bytes */ + ret = read(cmdfd, buf+buflen, arrlen(buf)-buflen); + + switch (ret) { + case 0: + exit(0); + case -1: + fatal("couldn't read from shell: %s\n", strerror(errno)); + default: + buflen += ret; + written = twrite(buf, buflen, 0); + buflen -= written; + /* keep any incomplete UTF-8 byte sequence for the next call */ + if(buflen > 0) + memmove(buf, buf + written, buflen); + return ret; + } +} + +void +ttywrite(char *s, size_t n, int may_echo) +{ + char *next; + + if (may_echo && IS_SET(Techo)) + twrite(s, n, 1); + + if (!IS_SET(Tcrlf)) { + ttywriteraw(s, n); + return; + } + + /* This is similar to how the kernel handles ONLCR for ttys */ + while (n > 0) { + if (*s == '\r') { + next = s + 1; + ttywriteraw("\r\n", 2); + } else { + next = memchr(s, '\r', n); + DEFAULT(next, s + n); + ttywriteraw(s, next - s); + } + n -= next - s; + s = next; + } +} + +void +ttywriteraw(char *s, size_t n) +{ + fd_set wfd, rfd; + ssize_t r; + size_t lim = 256; + + /* + * Remember that we are using a pty, which might be a modem line. + * Writing too much will clog the line. That's why we are doing this + * dance. + * FIXME: Migrate the world to Plan 9. + */ + while (n > 0) { + FD_ZERO(&wfd); + FD_ZERO(&rfd); + FD_SET(cmdfd, &wfd); + FD_SET(cmdfd, &rfd); + + /* Check if we can write. */ + if (pselect(cmdfd+1, &rfd, &wfd, nil, nil, nil) < 0) { + if (errno == EINTR) + continue; + fatal("select failed: %s\n", strerror(errno)); + } + if (FD_ISSET(cmdfd, &wfd)) { + /* + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. + */ + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; + if (r < n) { + /* + * We weren't able to write out everything. + * This means the buffer is getting full + * again. Empty it. + */ + if (n < lim) + lim = ttyread(); + n -= r; + s += r; + } else { + /* All bytes have been written. */ + break; + } + } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); + } + return; + +write_error: + fatal("write error on tty: %s\n", strerror(errno)); +} + +void +ttyresize(int tw, int th) +{ + struct winsize w; + + w.ws_row = term.row; + w.ws_col = term.col; + w.ws_xpixel = tw; + w.ws_ypixel = th; + if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0) + fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno)); +} + +void +ttyhangup() +{ + /* Send SIGHUP to shell */ + kill(pid, SIGHUP); +} + +int +tattrset(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) + return 1; + } + } + + return 0; +} + +void +tsetdirt(int top, int bot) +{ + int i; + + LIMIT(top, 0, term.row-1); + LIMIT(bot, 0, term.row-1); + + for (i = top; i <= bot; i++) + term.dirty[i] = 1; +} + +void +tsetdirtattr(int attr) +{ + int i, j; + + for (i = 0; i < term.row-1; i++) { + for (j = 0; j < term.col-1; j++) { + if (term.line[i][j].mode & attr) { + tsetdirt(i, i); + break; + } + } + } +} + +void +tfulldirt(void) +{ + tsetdirt(0, term.row-1); +} + +void +tcursor(int mode) +{ + static Dot c[2]; + int alt = IS_SET(Taltscreen); + + if (mode == CursorSave) { + c[alt] = term.c; + } else if (mode == CursorLoad) { + term.c = c[alt]; + tmoveto(c[alt].x, c[alt].y); + } +} + +void +treset(void) +{ + uint i; + + term.c = (Dot){{ + .mode = Gnil, + .fg = defaultfg, + .bg = defaultbg + }, .x = 0, .y = 0, .state = CursorDefault}; + + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + for (i = tabspaces; i < term.col; i += tabspaces) + term.tabs[i] = 1; + term.top = 0; + term.bot = term.row - 1; + term.mode = Twrap|Tutf8; + memset(term.trantbl, CSusa, sizeof(term.trantbl)); + term.charset = 0; + + for (i = 0; i < 2; i++) { + tmoveto(0, 0); + tcursor(CursorSave); + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } +} + +void +tnew(int col, int row) +{ + term = (Terminal){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } }; + tresize(col, row); + treset(); +} + +void +tswapscreen(void) +{ + Letter **tmp = term.line; + + term.line = term.alt; + term.alt = tmp; + term.mode ^= Taltscreen; + tfulldirt(); +} + +void +tscrolldown(int orig, int n) +{ + int i; + Letter *temp; + + LIMIT(n, 0, term.bot-orig+1); + + tsetdirt(orig, term.bot-n); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); + + for (i = term.bot; i >= orig+n; i--) { + temp = term.line[i]; + term.line[i] = term.line[i-n]; + term.line[i-n] = temp; + } + + selscroll(orig, n); +} + +void +tscrollup(int orig, int n) +{ + int i; + Letter *temp; + + LIMIT(n, 0, term.bot-orig+1); + + tclearregion(0, orig, term.col-1, orig+n-1); + tsetdirt(orig+n, term.bot); + + for (i = orig; i <= term.bot-n; i++) { + temp = term.line[i]; + term.line[i] = term.line[i+n]; + term.line[i+n] = temp; + } + + selscroll(orig, -n); +} + +void +selscroll(int orig, int n) +{ + if (sel.ob.x == -1) + return; + + if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) { + selclear(); + } else if (BETWEEN(sel.nb.y, orig, term.bot)) { + sel.ob.y += n; + sel.oe.y += n; + if (sel.ob.y < term.top || sel.ob.y > term.bot || + sel.oe.y < term.top || sel.oe.y > term.bot) { + selclear(); + } else { + selnormalize(); + } + } +} + +void +tnewline(int first_col) +{ + int y = term.c.y; + + if (y == term.bot) { + tscrollup(term.top, 1); + } else { + y++; + } + tmoveto(first_col ? 0 : term.c.x, y); +} + +void +csiparse(void) +{ + char *p = csiescseq.buf, *np; + long int v; + + csiescseq.narg = 0; + if (*p == '?') { + csiescseq.priv = 1; + p++; + } + + csiescseq.buf[csiescseq.len] = '\0'; + while (p < csiescseq.buf+csiescseq.len) { + np = nil; + v = strtol(p, &np, 10); + if (np == p) + v = 0; + if (v == LONG_MAX || v == LONG_MIN) + v = -1; + csiescseq.arg[csiescseq.narg++] = v; + p = np; + if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) + break; + p++; + } + csiescseq.mode[0] = *p++; + csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0'; +} + +/* for absolute user moves, when decom is set */ +void +tmoveato(int x, int y) +{ + tmoveto(x, y + ((term.c.state & CursorOrigin) ? term.top: 0)); +} + +void +tmoveto(int x, int y) +{ + int miny, maxy; + + if (term.c.state & CursorOrigin) { + miny = term.top; + maxy = term.bot; + } else { + miny = 0; + maxy = term.row - 1; + } + term.c.state &= ~CursorWrap; + term.c.x = LIMIT(x, 0, term.col-1); + term.c.y = LIMIT(y, miny, maxy); +} + +void +tsetchar(rune u, Letter *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 */ + 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */ + "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */ + "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */ + "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */ + "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */ + }; + + /* + * table is proudly stolen from rxvt. + */ + if (term.trantbl[term.charset] == CSgfx0 && + BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41]) + utf8·decode(vt100_0[u - 0x41], &u); + + if (term.line[y][x].mode & Gwide) { + if (x+1 < term.col) { + term.line[y][x+1].u = ' '; + term.line[y][x+1].mode &= ~Gwdummy; + } + } else if (term.line[y][x].mode & Gwdummy) { + term.line[y][x-1].u = ' '; + term.line[y][x-1].mode &= ~Gwide; + } + + term.dirty[y] = 1; + term.line[y][x] = *attr; + term.line[y][x].u = u; +} + +void +tclearregion(int x1, int y1, int x2, int y2) +{ + int x, y, temp; + Letter *gp; + + if(x1 > x2) + temp = x1, x1 = x2, x2 = temp; + if(y1 > y2) + temp = y1, y1 = y2, y2 = temp; + + LIMIT(x1, 0, term.col-1); + LIMIT(x2, 0, term.col-1); + LIMIT(y1, 0, term.row-1); + LIMIT(y2, 0, term.row-1); + + for(y = y1; y <= y2; y++) { + term.dirty[y] = 1; + for(x = x1; x <= x2; x++) { + gp = &term.line[y][x]; + if(selected(x, y)) + selclear(); + gp->fg = term.c.attr.fg; + gp->bg = term.c.attr.bg; + gp->mode = 0; + gp->u = ' '; + } + } +} + +void +tdeletechar(int n) +{ + int dst, src, size; + Letter *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x; + src = term.c.x + n; + size = term.col - src; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Letter)); + tclearregion(term.col-n, term.c.y, term.col-1, term.c.y); +} + +void +tinsertblank(int n) +{ + int dst, src, size; + Letter *line; + + LIMIT(n, 0, term.col - term.c.x); + + dst = term.c.x + n; + src = term.c.x; + size = term.col - dst; + line = term.line[term.c.y]; + + memmove(&line[dst], &line[src], size * sizeof(Letter)); + tclearregion(src, term.c.y, dst - 1, term.c.y); +} + +void +tinsertblankline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrolldown(term.c.y, n); +} + +void +tdeleteline(int n) +{ + if (BETWEEN(term.c.y, term.top, term.bot)) + tscrollup(term.c.y, n); +} + +int32_t +tdefcolor(int *attr, int *npar, int l) +{ + int32_t idx = -1; + uint r, g, b; + + switch (attr[*npar + 1]) { + case 2: /* direct color in RGB space */ + if (*npar + 4 >= l) { + fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar); + break; + } + r = attr[*npar + 2]; + g = attr[*npar + 3]; + b = attr[*npar + 4]; + *npar += 4; + if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255)) + fprintf(stderr, "erresc(38): bad rgb color (%u,%u,%u)\n", r, g, b); + else + idx = TRUECOLOR(r, g, b); + break; + case 5: /* indexed color */ + if (*npar + 2 >= l) { + fprintf(stderr, "erresc(38): Incorrect number of parameters (%d)\n", *npar); + break; + } + *npar += 2; + if (!BETWEEN(attr[*npar], 0, 255)) + fprintf(stderr, "erresc: bad color %d\n", attr[*npar]); + else + idx = attr[*npar]; + break; + case 0: /* implemented defined (only foreground) */ + case 1: /* transparent */ + case 3: /* direct color in CMY space */ + case 4: /* direct color in CMYK space */ + default: + fprintf(stderr, "erresc(38): gfx attr %d unknown\n", attr[*npar]); + break; + } + + return idx; +} + +void +tsetattr(int *attr, int l) +{ + int i; + int32_t idx; + + for (i = 0; i < l; i++) { + switch (attr[i]) { + case 0: + term.c.attr.mode &= ~( + Gbold | + Gfaint | + Gitalic | + Gunline | + Gblink | + Greverse | + Ginvisible | + Gstruck ); + term.c.attr.fg = defaultfg; + term.c.attr.bg = defaultbg; + break; + case 1: + term.c.attr.mode |= Gbold; + break; + case 2: + term.c.attr.mode |= Gfaint; + break; + case 3: + term.c.attr.mode |= Gitalic; + break; + case 4: + term.c.attr.mode |= Gunline; + break; + case 5: /* slow blink */ + /* FALLTHROUGH */ + case 6: /* rapid blink */ + term.c.attr.mode |= Gblink; + break; + case 7: + term.c.attr.mode |= Greverse; + break; + case 8: + term.c.attr.mode |= Ginvisible; + break; + case 9: + term.c.attr.mode |= Gstruck; + break; + case 22: + term.c.attr.mode &= ~(Gbold | Gfaint); + break; + case 23: + term.c.attr.mode &= ~Gitalic; + break; + case 24: + term.c.attr.mode &= ~Gunline; + break; + case 25: + term.c.attr.mode &= ~Gblink; + break; + case 27: + term.c.attr.mode &= ~Greverse; + break; + case 28: + term.c.attr.mode &= ~Ginvisible; + break; + case 29: + term.c.attr.mode &= ~Gstruck; + break; + case 38: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.fg = idx; + break; + case 39: + term.c.attr.fg = defaultfg; + break; + case 48: + if ((idx = tdefcolor(attr, &i, l)) >= 0) + term.c.attr.bg = idx; + break; + case 49: + term.c.attr.bg = defaultbg; + break; + default: + if (BETWEEN(attr[i], 30, 37)) { + term.c.attr.fg = attr[i] - 30; + } else if (BETWEEN(attr[i], 40, 47)) { + term.c.attr.bg = attr[i] - 40; + } else if (BETWEEN(attr[i], 90, 97)) { + term.c.attr.fg = attr[i] - 90 + 8; + } else if (BETWEEN(attr[i], 100, 107)) { + term.c.attr.bg = attr[i] - 100 + 8; + } else { + fprintf(stderr, + "erresc(default): gfx attr %d unknown\n", + attr[i]); + csidump(); + } + break; + } + } +} + +void +tsetscroll(int t, int b) +{ + int temp; + + LIMIT(t, 0, term.row-1); + LIMIT(b, 0, term.row-1); + if (t > b) { + temp = t; + t = b; + b = temp; + } + term.top = t; + term.bot = b; +} + +void +tsetmode(int priv, int set, int *args, int narg) +{ + int alt, *lim; + + for (lim = args + narg; args < lim; ++args) { + if (priv) { + switch (*args) { + case 1: /* DECCKM -- Cursor key */ + xsetmode(set, Wappcursor); + break; + case 5: /* DECSCNM -- Reverse video */ + xsetmode(set, Wreverse); + break; + case 6: /* DECOM -- Origin */ + MODBIT(term.c.state, set, CursorOrigin); + tmoveato(0, 0); + break; + case 7: /* DECAWM -- Auto wrap */ + MODBIT(term.mode, set, Twrap); + 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: /* DECTCEM -- Text Cursor Enable Mode */ + xsetmode(!set, Whide); + break; + case 9: /* X10 mouse compatibility mode */ + xsetpointermotion(0); + xsetmode(0, Wmouse); + xsetmode(set, Wmousex10); + break; + case 1000: /* 1000: report button press */ + xsetpointermotion(0); + xsetmode(0, Wmouse); + xsetmode(set, Wmousebtn); + break; + case 1002: /* 1002: report motion on button press */ + xsetpointermotion(0); + xsetmode(0, Wmouse); + xsetmode(set, Wmousemotion); + break; + case 1003: /* 1003: enable all mouse motions */ + xsetpointermotion(set); + xsetmode(0, Wmouse); + xsetmode(set, Wmousemany); + break; + case 1004: /* 1004: send focus events to tty */ + xsetmode(set, Wfocus); + break; + case 1006: /* 1006: extended reporting mode */ + xsetmode(set, Wmousesgr); + break; + case 1034: + xsetmode(set, W8bit); + break; + case 1049: /* swap screen & set/restore cursor as xterm */ + if (!allowaltscreen) + break; + tcursor((set) ? CursorSave : CursorLoad); + /* FALLTHROUGH */ + case 47: /* swap screen */ + case 1047: + if (!allowaltscreen) + break; + alt = IS_SET(Taltscreen); + if (alt) { + tclearregion(0, 0, term.col-1, + term.row-1); + } + if (set ^ alt) /* set is always 1 or 0 */ + tswapscreen(); + if (*args != 1049) + break; + /* FALLTHROUGH */ + case 1048: + tcursor((set) ? CursorSave : CursorLoad); + break; + case 2004: /* 2004: bracketed paste mode */ + xsetmode(set, Wbrcktpaste); + break; + /* Not implemented mouse modes. See comments there. */ + case 1001: /* mouse highlight mode; can hang the + terminal by design when implemented. */ + case 1005: /* UTF-8 mouse mode; will confuse + applications not supporting UTF-8 + and luit. */ + case 1015: /* urxvt mangled mouse mode; incompatible + and can be mistaken for other control + codes. */ + break; + default: + fprintf(stderr, + "erresc: unknown private set/reset mode %d\n", + *args); + break; + } + } else { + switch (*args) { + case 0: /* Error (IGNORED) */ + break; + case 2: + xsetmode(set, Wkbdblock); + break; + case 4: /* IRM -- Insertion-replacement */ + MODBIT(term.mode, set, Tinsert); + break; + case 12: /* SRM -- Send/Receive */ + MODBIT(term.mode, !set, Techo); + break; + case 20: /* LNM -- Linefeed/new line */ + MODBIT(term.mode, set, Tcrlf); + break; + default: + fprintf(stderr, + "erresc: unknown set/reset mode %d\n", + *args); + break; + } + } + } +} + +void +csihandle(void) +{ + char buf[40]; + int len; + + switch (csiescseq.mode[0]) { + default: + unknown: + fprintf(stderr, "erresc: unknown csi "); + csidump(); + /* fatal(""); */ + break; + case '@': /* ICH -- Insert blank char */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblank(csiescseq.arg[0]); + break; + case 'A': /* CUU -- Cursor Up */ + 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; + case 'i': /* MC -- Media Copy */ + switch (csiescseq.arg[0]) { + case 0: + tdump(); + break; + case 1: + tdumpline(term.c.y); + break; + case 2: + tdumpsel(); + break; + case 4: + term.mode &= ~Tprint; + break; + case 5: + term.mode |= Tprint; + break; + } + break; + case 'c': /* DA -- Device Attributes */ + if (csiescseq.arg[0] == 0) + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'b': /* REP -- if last char is printable print it more times */ + DEFAULT(csiescseq.arg[0], 1); + if (term.lastc) + while (csiescseq.arg[0]-- > 0) + tputc(term.lastc); + break; + case 'C': /* CUF -- Cursor Forward */ + case 'a': /* HPR -- Cursor Forward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x+csiescseq.arg[0], term.c.y); + break; + case 'D': /* CUB -- Cursor Backward */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(term.c.x-csiescseq.arg[0], term.c.y); + break; + case 'E': /* CNL -- Cursor Down and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y+csiescseq.arg[0]); + break; + case 'F': /* CPL -- Cursor Up and first col */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(0, term.c.y-csiescseq.arg[0]); + break; + case 'g': /* TBC -- Tabulation clear */ + switch (csiescseq.arg[0]) { + case 0: /* clear current tab stop */ + term.tabs[term.c.x] = 0; + break; + case 3: /* clear all the tabs */ + memset(term.tabs, 0, term.col * sizeof(*term.tabs)); + break; + default: + goto unknown; + } + break; + case 'G': /* CHA -- Move to */ + case '`': /* HPA */ + DEFAULT(csiescseq.arg[0], 1); + tmoveto(csiescseq.arg[0]-1, term.c.y); + break; + case 'H': /* CUP -- Move to */ + case 'f': /* HVP */ + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], 1); + tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1); + break; + case 'I': /* CHT -- Cursor Forward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(csiescseq.arg[0]); + break; + case 'J': /* ED -- Clear screen */ + 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); + } + 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); + break; + case 2: /* all */ + tclearregion(0, 0, term.col-1, term.row-1); + break; + default: + goto unknown; + } + break; + 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); + break; + case 1: /* left */ + tclearregion(0, term.c.y, term.c.x, term.c.y); + break; + case 2: /* all */ + tclearregion(0, term.c.y, term.col-1, term.c.y); + break; + } + break; + case 'S': /* SU -- Scroll line up */ + DEFAULT(csiescseq.arg[0], 1); + tscrollup(term.top, csiescseq.arg[0]); + break; + case 'T': /* SD -- Scroll line down */ + DEFAULT(csiescseq.arg[0], 1); + tscrolldown(term.top, csiescseq.arg[0]); + break; + case 'L': /* IL -- Insert blank lines */ + DEFAULT(csiescseq.arg[0], 1); + tinsertblankline(csiescseq.arg[0]); + break; + case 'l': /* RM -- Reset Mode */ + tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg); + break; + case 'M': /* DL -- Delete lines */ + DEFAULT(csiescseq.arg[0], 1); + tdeleteline(csiescseq.arg[0]); + break; + case 'X': /* ECH -- Erase char */ + DEFAULT(csiescseq.arg[0], 1); + tclearregion(term.c.x, term.c.y, + term.c.x + csiescseq.arg[0] - 1, term.c.y); + break; + case 'P': /* DCH -- Delete char */ + DEFAULT(csiescseq.arg[0], 1); + tdeletechar(csiescseq.arg[0]); + break; + case 'Z': /* CBT -- Cursor Backward Tabulation tab stops */ + DEFAULT(csiescseq.arg[0], 1); + tputtab(-csiescseq.arg[0]); + break; + case 'd': /* VPA -- Move to */ + DEFAULT(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); + break; + case 'm': /* SGR -- Terminal attribute (color) */ + tsetattr(csiescseq.arg, csiescseq.narg); + break; + case 'n': /* DSR – Device Status Report (cursor position) */ + if (csiescseq.arg[0] == 6) { + len = snprintf(buf, sizeof(buf), "\033[%i;%iR", term.c.y+1, term.c.x+1); + ttywrite(buf, len, 0); + } + break; + case 'r': /* DECSTBM -- Set Scrolling Region */ + if (csiescseq.priv) { + goto unknown; + } else { + DEFAULT(csiescseq.arg[0], 1); + DEFAULT(csiescseq.arg[1], term.row); + tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1); + tmoveato(0, 0); + } + break; + case 's': /* DECSC -- Save cursor position (ANSI.SYS) */ + tcursor(CursorSave); + break; + case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */ + tcursor(CursorLoad); + break; + case ' ': + switch (csiescseq.mode[1]) { + case 'q': /* DECSCUSR -- Set Cursor Style */ + if (xsetcursor(csiescseq.arg[0])) + goto unknown; + break; + default: + goto unknown; + } + break; + } +} + +void +csidump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC["); + for (i = 0; i < csiescseq.len; i++) { + c = csiescseq.buf[i] & 0xff; + if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + putc('\n', stderr); +} + +void +csireset(void) +{ + memset(&csiescseq, 0, sizeof(csiescseq)); +} + +void +strhandle(void) +{ + char *p = nil, *dec; + int j, narg, par; + + term.esc &= ~(Xstrend|Xstr); + strparse(); + par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0; + + switch (strescseq.type) { + case ']': /* OSC -- Operating System Command */ + switch (par) { + case 0: + case 1: + case 2: + if (narg > 1) + xsettitle(strescseq.args[1]); + return; + case 52: + if (narg > 2 && allowwindowops) { + dec = base64dec(strescseq.args[2]); + if (dec) { + xsetsel(dec); + xclipcopy(); + } else { + fprintf(stderr, "erresc: invalid base64\n"); + } + } + return; + case 4: /* color set */ + if (narg < 3) + break; + p = strescseq.args[2]; + /* FALLTHROUGH */ + case 104: /* color reset, here p = nil */ + j = (narg > 1) ? atoi(strescseq.args[1]) : -1; + if (xsetcolorname(j, p)) { + if (par == 104 && narg <= 1) + return; /* color reset without parameter */ + fprintf(stderr, "erresc: invalid color j=%d, p=%s\n", + j, p ? p : "(null)"); + } else { + /* + * TODO if defaultbg color is changed, borders + * are dirty + */ + redraw(); + } + return; + } + break; + case 'k': /* old title set compatibility */ + xsettitle(strescseq.args[0]); + return; + case 'P': /* DCS -- Device Control String */ + term.mode |= Xdcs; + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + return; + } + + fprintf(stderr, "erresc: unknown str "); + strdump(); +} + +void +strparse(void) +{ + int c; + char *p = strescseq.buf; + + strescseq.narg = 0; + strescseq.buf[strescseq.len] = '\0'; + + if (*p == '\0') + return; + + while (strescseq.narg < STR_ARG_SIZ) { + strescseq.args[strescseq.narg++] = p; + while ((c = *p) != ';' && c != '\0') + ++p; + if (c == '\0') + return; + *p++ = '\0'; + } +} + +void +strdump(void) +{ + size_t i; + uint c; + + fprintf(stderr, "ESC%c", strescseq.type); + for (i = 0; i < strescseq.len; i++) { + c = strescseq.buf[i] & 0xff; + if (c == '\0') { + putc('\n', stderr); + return; + } else if (isprint(c)) { + putc(c, stderr); + } else if (c == '\n') { + fprintf(stderr, "(\\n)"); + } else if (c == '\r') { + fprintf(stderr, "(\\r)"); + } else if (c == 0x1b) { + fprintf(stderr, "(\\e)"); + } else { + fprintf(stderr, "(%02x)", c); + } + } + fprintf(stderr, "ESC\\\n"); +} + +void +strreset(void) +{ + strescseq = (STREscape){ + .buf = xrealloc(strescseq.buf, STR_BUF_SIZ), + .siz = STR_BUF_SIZ, + }; +} + +void +sendbreak(Arg *arg) +{ + if (tcsendbreak(cmdfd, 0)) + perror("Error sending break"); +} + +void +tprinter(char *s, size_t len) +{ + if (iofd != -1 && xwrite(iofd, s, len) < 0) { + perror("Error writing to output file"); + close(iofd); + iofd = -1; + } +} + +void +toggleprinter(Arg *arg) +{ + term.mode ^= Tprint; +} + +void +printscreen(Arg *arg) +{ + tdump(); +} + +void +printsel(Arg *arg) +{ + tdumpsel(); +} + +void +tdumpsel(void) +{ + char *ptr; + + if ((ptr = getsel())) { + tprinter(ptr, strlen(ptr)); + free(ptr); + } +} + +void +tdumpline(int n) +{ + char buf[UTFmax]; + Letter *bp, *end; + + bp = &term.line[n][0]; + end = &bp[MIN(tlinelen(n), term.col) - 1]; + if (bp != end || bp->u != ' ') { + for ( ; bp <= end; ++bp) + tprinter(buf, utf8·encode(&bp->u, buf)); + } + tprinter("\n", 1); +} + +void +tdump(void) +{ + int i; + + for (i = 0; i < term.row; ++i) + tdumpline(i); +} + +void +tputtab(int n) +{ + uint x = term.c.x; + + if (n > 0) { + while (x < term.col && n--) + for (++x; x < term.col && !term.tabs[x]; ++x) + /* nothing */ ; + } else if (n < 0) { + while (x > 0 && n++) + for (--x; x > 0 && !term.tabs[x]; --x) + /* nothing */ ; + } + term.c.x = LIMIT(x, 0, term.col-1); +} + +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= Tutf8; + else if (ascii == '@') + term.mode &= ~Tutf8; +} + +void +tdeftran(char ascii) +{ + static char cs[] = "0B"; + static int vcs[] = {CSgfx0, CSusa}; + char *p; + + if ((p = strchr(cs, ascii)) == nil) { + fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii); + } else { + term.trantbl[term.icharset] = vcs[p - cs]; + } +} + +void +tdectest(char c) +{ + int x, y; + + if (c == '8') { /* DEC screen alignment test. */ + for (x = 0; x < term.col; ++x) { + for (y = 0; y < term.row; ++y) + tsetchar('E', &term.c.attr, x, y); + } + } +} + +void +tstrsequence(uchar c) +{ + strreset(); + + switch (c) { + case 0x90: /* DCS -- Device Control String */ + c = 'P'; + term.esc |= Xdcs; + break; + case 0x9f: /* APC -- Application Program Command */ + c = '_'; + break; + case 0x9e: /* PM -- Privacy Message */ + c = '^'; + break; + case 0x9d: /* OSC -- Operating System Command */ + c = ']'; + break; + } + strescseq.type = c; + term.esc |= Xstr; +} + +void +tcontrolcode(uchar ascii) +{ + switch (ascii) { + case '\t': /* HT */ + tputtab(1); + return; + case '\b': /* BS */ + tmoveto(term.c.x-1, term.c.y); + return; + case '\r': /* CR */ + tmoveto(0, term.c.y); + return; + case '\f': /* LF */ + case '\v': /* VT */ + case '\n': /* LF */ + /* go to first col if the mode is set */ + tnewline(IS_SET(Tcrlf)); + return; + case '\a': /* BEL */ + if (term.esc & Xstrend) { + /* backwards compatibility to xterm */ + strhandle(); + } else { + xbell(); + } + break; + case '\033': /* ESC */ + csireset(); + term.esc &= ~(Xcsi|Xaltcs|Xtest); + term.esc |= Xstart; + return; + case '\016': /* SO (LS1 -- Locking shift 1) */ + case '\017': /* SI (LS0 -- Locking shift 0) */ + term.charset = 1 - (ascii - '\016'); + return; + case '\032': /* SUB */ + tsetchar('?', &term.c.attr, term.c.x, term.c.y); + /* FALLTHROUGH */ + case '\030': /* CAN */ + csireset(); + break; + case '\005': /* ENQ (IGNORED) */ + case '\000': /* NUL (IGNORED) */ + case '\021': /* XON (IGNORED) */ + case '\023': /* XOFF (IGNORED) */ + case 0177: /* DEL (IGNORED) */ + return; + case 0x80: /* TODO: PAD */ + case 0x81: /* TODO: HOP */ + case 0x82: /* TODO: BPH */ + case 0x83: /* TODO: NBH */ + case 0x84: /* TODO: IND */ + break; + case 0x85: /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 0x86: /* TODO: SSA */ + case 0x87: /* TODO: ESA */ + break; + case 0x88: /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 0x89: /* TODO: HTJ */ + case 0x8a: /* TODO: VTS */ + case 0x8b: /* TODO: PLD */ + case 0x8c: /* TODO: PLU */ + case 0x8d: /* TODO: RI */ + case 0x8e: /* TODO: SS2 */ + case 0x8f: /* TODO: SS3 */ + case 0x91: /* TODO: PU1 */ + case 0x92: /* TODO: PU2 */ + case 0x93: /* TODO: STS */ + case 0x94: /* TODO: CCH */ + case 0x95: /* TODO: MW */ + case 0x96: /* TODO: SPA */ + case 0x97: /* TODO: EPA */ + case 0x98: /* TODO: SOS */ + case 0x99: /* TODO: SGCI */ + break; + case 0x9a: /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 0x9b: /* TODO: CSI */ + case 0x9c: /* TODO: ST */ + break; + case 0x90: /* DCS -- Device Control String */ + case 0x9d: /* OSC -- Operating System Command */ + case 0x9e: /* PM -- Privacy Message */ + case 0x9f: /* APC -- Application Program Command */ + tstrsequence(ascii); + return; + } + /* only CAN, SUB, \a and C1 chars interrupt a sequence */ + term.esc &= ~(Xstrend|Xstr); +} + +/* + * returns 1 when the sequence is finished and it hasn't to read + * more characters for this sequence, otherwise 0 + */ +int +eschandle(uchar ascii) +{ + switch (ascii) { + case '[': + term.esc |= Xcsi; + return 0; + case '#': + term.esc |= Xtest; + return 0; + case '%': + term.esc |= Xutf8; + return 0; + case 'P': /* DCS -- Device Control String */ + case '_': /* APC -- Application Program Command */ + case '^': /* PM -- Privacy Message */ + case ']': /* OSC -- Operating System Command */ + case 'k': /* old title set compatibility */ + tstrsequence(ascii); + return 0; + case 'n': /* LS2 -- Locking shift 2 */ + case 'o': /* LS3 -- Locking shift 3 */ + term.charset = 2 + (ascii - 'n'); + break; + case '(': /* GZD4 -- set primary charset G0 */ + case ')': /* G1D4 -- set secondary charset G1 */ + case '*': /* G2D4 -- set tertiary charset G2 */ + case '+': /* G3D4 -- set quaternary charset G3 */ + term.icharset = ascii - '('; + term.esc |= Xaltcs; + return 0; + case 'D': /* IND -- Linefeed */ + if (term.c.y == term.bot) { + tscrollup(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y+1); + } + break; + case 'E': /* NEL -- Next line */ + tnewline(1); /* always go to first col */ + break; + case 'H': /* HTS -- Horizontal tab stop */ + term.tabs[term.c.x] = 1; + break; + case 'M': /* RI -- Reverse index */ + if (term.c.y == term.top) { + tscrolldown(term.top, 1); + } else { + tmoveto(term.c.x, term.c.y-1); + } + break; + case 'Z': /* DECID -- Identify Terminal */ + ttywrite(vtiden, strlen(vtiden), 0); + break; + case 'c': /* RIS -- Reset to initial state */ + treset(); + resettitle(); + xloadcols(); + break; + case '=': /* DECPAM -- Application keypad */ + xsetmode(1, Wappkeypad); + break; + case '>': /* DECPNM -- Normal keypad */ + xsetmode(0, Wappkeypad); + break; + case '7': /* DECSC -- Save Cursor */ + tcursor(CursorSave); + break; + case '8': /* DECRC -- Restore Cursor */ + tcursor(CursorLoad); + break; + case '\\': /* ST -- String Terminator */ + if (term.esc & Xstrend) + strhandle(); + break; + default: + fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n", + (uchar) ascii, isprint(ascii)? ascii:'.'); + break; + } + return 1; +} + +void +tputc(rune u) +{ + char c[UTFmax]; + int control; + int width, len; + rune nu; + Letter *gp; + + control = ISCONTROL(u); + if (u < 127 || !IS_SET(Tutf8 | Tsixel)) { + c[0] = u; + width = len = 1; + } else { + len = utf8·encode(&u, c); + if(!control && (width = wcwidth(u)) == -1) + width = 1; + } + + /* combining characters */ + if(!width){ + if(term.c.x > 0) + gp = &term.line[term.c.y][term.c.x-1]; + else if(term.c.y > 0) + gp = &term.line[term.c.y-1][term.col-1]; + else + return; + +#if 0 + if(!hb_unicode_compose(hb_unicode_funcs_get_default(),gp->u, u, &nu)) { + return; + } +#endif + + gp->u = nu; + return; + } + + if (IS_SET(Tprint)) + tprinter(c, len); + + /* + * STR sequence must be checked before anything else + * because it uses all following characters until it + * receives a ESC, a SUB, a ST or any other C1 control + * character. + */ + if(term.esc & Xstr) { + if (u == '\a' || u == 030 || u == 032 || u == 033 || + ISCONTROLC1(u)) { + term.esc &= ~(Xstart|Xstr|Xdcs); + if (IS_SET(Tsixel)) { + /* TODO: render sixel */; + term.mode &= ~Tsixel; + return; + } + term.esc |= Xstrend; + goto check_control_code; + } + + if(IS_SET(Tsixel)) { + /* TODO: implement sixel mode */ + return; + } + if (term.esc&Xdcs && strescseq.len == 0 && u == 'q') + term.mode |= Tsixel; + + if (strescseq.len+len >= strescseq.siz) { + /* + * 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(); + */ + if(strescseq.siz > (SIZE_MAX - UTFmax) / 2) + return; + strescseq.siz *= 2; + strescseq.buf = xrealloc(strescseq.buf, strescseq.siz); + } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; + } + +check_control_code: + /* + * Actions of control codes must be performed as soon they arrive + * because they can be embedded inside a control sequence, and + * they must not cause conflicts with sequences. + */ + if(control) { + tcontrolcode(u); + /* + * control codes are not shown ever + */ + if (!term.esc) + term.lastc = 0; + return; + } else if(term.esc & Xstart) { + if (term.esc & Xcsi) { + csiescseq.buf[csiescseq.len++] = u; + if (BETWEEN(u, 0x40, 0x7E) + || csiescseq.len >= \ + sizeof(csiescseq.buf)-1) { + term.esc = 0; + csiparse(); + csihandle(); + } + return; + } else if (term.esc & Xutf8) { + tdefutf8(u); + } else if (term.esc & Xaltcs) { + tdeftran(u); + } else if (term.esc & Xtest) { + tdectest(u); + } else { + if (!eschandle(u)) + return; + /* sequence already finished */ + } + term.esc = 0; + /* + * All characters which form part of a sequence are not + * printed + */ + return; + } + + if(selected(term.c.x, term.c.y)) + selclear(); + + gp = &term.line[term.c.y][term.c.x]; + if(IS_SET(Twrap) && (term.c.state & CursorWrap)) { + gp->mode |= Gwrap; + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + if(IS_SET(Tinsert) && term.c.x+width < term.col) + memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Letter)); + + if(term.c.x+width > term.col) { + tnewline(1); + gp = &term.line[term.c.y][term.c.x]; + } + + tsetchar(u, &term.c.attr, term.c.x, term.c.y); + term.lastc = u; + + if(width == 2) { + gp->mode |= Gwrap; + if (term.c.x+1 < term.col) { + gp[1].u = '\0'; + gp[1].mode = Gwdummy; + } + } + if(term.c.x+width < term.col) { + tmoveto(term.c.x+width, term.c.y); + }else{ + term.c.state |= CursorWrap; + } +} + +int +twrite(char *buf, int buflen, int show_ctrl) +{ + int charsize; + rune u; + int n; + + for (n = 0; n < buflen; n += charsize) { + if(IS_SET(Tutf8) && !IS_SET(Tsixel)) { + /* process a complete utf8 char */ + charsize = utf8·decode(buf + n, &u); + if(charsize == 0) + break; + } else { + u = buf[n] & 0xFF; + charsize = 1; + } + if(show_ctrl && ISCONTROL(u)) { + if (u & 0x80) { + u &= 0x7f; + tputc('^'); + tputc('['); + } else if (u != '\n' && u != '\r' && u != '\t') { + u ^= 0x40; + tputc('^'); + } + } + tputc(u); + } + return n; +} + +void +tresize(int col, int row) +{ + int i; + int minrow = MIN(row, term.row); + int mincol = MIN(col, term.col); + int *bp; + Dot c; + + if (col < 1 || row < 1) { + fprintf(stderr, + "tresize: error resizing to %dx%d\n", col, row); + return; + } + + /* + * 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 + */ + for (i = 0; i <= term.c.y - row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + /* ensure that both src and dst are not nil */ + if (i > 0) { + memmove(term.line, term.line + i, row * sizeof(Letter*)); + memmove(term.alt, term.alt + i, row * sizeof(Letter*)); + } + for (i += row; i < term.row; i++) { + free(term.line[i]); + free(term.alt[i]); + } + + /* resize to new height */ + term.line = xrealloc(term.line, row * sizeof(Letter*)); + term.alt = xrealloc(term.alt, row * sizeof(Letter*)); + term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty)); + term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs)); + + /* resize each row to new width, zero-pad if needed */ + for (i = 0; i < minrow; i++) { + term.line[i] = xrealloc(term.line[i], col * sizeof(Letter)); + term.alt[i] = xrealloc(term.alt[i], col * sizeof(Letter)); + } + + /* allocate any new rows */ + for (/* i = minrow */; i < row; i++) { + term.line[i] = xmalloc(col * sizeof(Letter)); + term.alt[i] = xmalloc(col * sizeof(Letter)); + } + if (col > term.col) { + bp = term.tabs + term.col; + + memset(bp, 0, sizeof(*term.tabs) * (col - term.col)); + while (--bp > term.tabs && !*bp) + /* nothing */ ; + for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces) + *bp = 1; + } + /* update terminal size */ + 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); + /* Clearing both screens (it makes dirty all lines) */ + c = term.c; + for (i = 0; i < 2; i++) { + if (mincol < col && 0 < minrow) { + tclearregion(mincol, 0, col - 1, minrow - 1); + } + if (0 < col && minrow < row) { + tclearregion(0, minrow, col - 1, row - 1); + } + tswapscreen(); + tcursor(CursorLoad); + } + term.c = c; +} + +void +resettitle(void) +{ + xsettitle(nil); +} + +void +drawregion(int x1, int y1, int x2, int y2) +{ + int y; + + for (y = y1; y < y2; y++) { + if (!term.dirty[y]) + continue; + + term.dirty[y] = 0; + xdrawline(term.line[y], x1, y, x2); + } +} + +void +draw(void) +{ + int cx = term.c.x, ocx = term.ocx, ocy = term.ocy; + + if (!xstartdraw()) + return; + + /* adjust cursor position */ + LIMIT(term.ocx, 0, term.col-1); + LIMIT(term.ocy, 0, term.row-1); + if (term.line[term.ocy][term.ocx].mode & Gwdummy) + term.ocx--; + if (term.line[term.c.y][cx].mode & Gwdummy) + cx--; + + drawregion(0, 0, term.col, term.row); + xdrawcursor(cx, term.c.y, term.line[term.c.y][cx], + term.ocx, term.ocy, term.line[term.ocy][term.ocx], + term.line[term.ocy], term.col + ); + term.ocx = cx; + term.ocy = term.c.y; + xfinishdraw(); + if (ocx != term.ocx || ocy != term.ocy) + xximspot(term.ocx, term.ocy); +} + +void +redraw(void) +{ + tfulldirt(); + draw(); +} diff --git a/src/cmd/term/term.h b/src/cmd/term/term.h new file mode 100644 index 0000000..6784974 --- /dev/null +++ b/src/cmd/term/term.h @@ -0,0 +1,316 @@ +/* See LICENSE for license details. */ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +// ----------------------------------------------------------------------- +// macros + +#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b)) +#define DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) +#define DEFAULT(a, b) (a) = (a) ? (a) : (b) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define GLYPHCMP(a, b) (((a).mode & (~Gwrap) & (~Gliga)) != ((b).mode & (~Gwrap) & (~Gliga)) || \ + (a).fg != (b).fg || (a).bg != (b).bg) +#define TIMEDIFF(t1, t2) ((t1.tv_sec-t2.tv_sec)*1000 + (t1.tv_nsec-t2.tv_nsec)/1E6) +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) +#define TRUECOLOR(r,g,b) (1 << 24 | (r) << 16 | (g) << 8 | (b)) +#define IS_TRUECOL(x) (1 << 24 & (x)) + +#define iota(x) 1 << (x) + +/* arbitrary sizes */ +#define ESC_BUF_SIZ (128*UTFmax) +#define ESC_ARG_SIZ 16 +#define STR_BUF_SIZ ESC_BUF_SIZ +#define STR_ARG_SIZ ESC_ARG_SIZ + +// ----------------------------------------------------------------------- +// constants + +enum { + Gnil, + Gbold = iota(0), + Gfaint = iota(1), + Gitalic = iota(2), + Gunline = iota(3), + Gblink = iota(4), + Greverse = iota(5), + Ginvisible = iota(6), + Gstruck = iota(7), + Gwrap = iota(8), + Gwide = iota(9), + Gwdummy = iota(10), + Gliga = iota(11), + Gboldfaint = Gbold | Gfaint, +}; + +enum { + SelIdle = 0, + SelEmpty = 1, + SelReady = 2 +}; + +enum { + SelRegular = 1, + SelRectangular = 2 +}; + +enum { + SnapWord = 1, + SnapLine = 2 +}; + +/* cursor state */ +enum { + CursorSave, + CursorLoad +}; + +/* cursor mode */ +enum { + CursorDefault = 0, + CursorWrap = 1, + CursorOrigin = 2 +}; + +/* character set */ +enum { + CSgfx0, + CSgfx1, + CSuk, + CSusa, + CSmulti, + CSger, + CSfin, +}; + +/* escape sequences */ +enum { + Xstart = 1, + Xcsi = 2, + Xstr = 4, /* OSC, PM, APC */ + Xaltcs = 8, + Xstrend = 16, /* a final string was encountered */ + Xtest = 32, /* Enter in test mode */ + Xutf8 = 64, + Xdcs =128, +}; + +/* terminal mode */ +enum { + Twrap = iota(0), + Tinsert = iota(1), + Taltscreen = iota(2), + Tcrlf = iota(3), + Techo = iota(4), + Tprint = iota(5), + Tutf8 = iota(6), + Tsixel = iota(7), +}; + +/* window mode */ +enum { + Wvisible = iota(0), + Wfocused = iota(1), + Wappkeypad = iota(2), + Wmousebtn = iota(3), + Wmousemotion = iota(4), + Wreverse = iota(5), + Wkbdblock = iota(6), + Whide = iota(7), + Wappcursor = iota(8), + Wmousesgr = iota(9), + W8bit = iota(10), + Wblink = iota(11), + Wbflink = iota(12), + Wfocus = iota(13), + Wmousex10 = iota(14), + Wmousemany = iota(15), + Wbrcktpaste = iota(16), + Wnumlock = iota(17), + Wmouse = Wmousebtn|Wmousemotion|Wmousex10|Wmousemany, +}; + + +// ----------------------------------------------------------------------- +// types + +/* term.c */ +typedef struct Letter Letter; +typedef struct Dot Dot; +typedef struct Selection Selection; +typedef struct Terminal Terminal; + +typedef union Arg Arg; + +struct Letter { + rune u; /* character code */ + ushort mode; /* attribute flags */ + uint32 fg; /* foreground */ + uint32 bg; /* background */ +}; + +struct Dot { + Letter attr; /* current char attributes */ + int x; + int y; + char state; +}; + +struct Selection { + int mode; + int type; + int snap; + /* + * Selection variables: + * nb – normalized coordinates of the beginning of the selection + * ne – normalized coordinates of the end of the selection + * ob – original coordinates of the beginning of the selection + * oe – original coordinates of the end of the selection + */ + struct { + int x, y; + } nb, ne, ob, oe; + + int alt; +}; + +/* Internal representation of the screen */ +struct Terminal { + int row; /* nb row */ + int col; /* nb col */ + Letter **line; /* screen */ + Letter **alt; /* alternate screen */ + int *dirty; /* dirtyness of lines */ + Dot c; /* cursor */ + int ocx; /* old cursor col */ + int ocy; /* old cursor row */ + int top; /* top scroll limit */ + int bot; /* bottom scroll limit */ + int mode; /* terminal mode flags */ + int esc; /* escape state flags */ + char trantbl[4];/* charset table translation */ + int charset; /* current charset */ + int icharset; /* selected charset for sequence */ + int *tabs; + rune lastc; /* last printed char outside of sequence, 0 if control */ +}; + +/* CSI Escape sequence structs */ +/* ESC '[' [[ [] [;]] []] */ +typedef struct { + char buf[ESC_BUF_SIZ]; /* raw string */ + ulong len; /* raw string length */ + char priv; + int arg[ESC_ARG_SIZ]; + int narg; /* nb of args */ + char mode[2]; +} CSIEscape; + +/* STR Escape sequence structs */ +/* ESC type [[ [] [;]] ] ESC '\' */ +typedef struct { + char type; /* ESC type ... */ + char *buf; /* allocated raw string */ + size_t siz; /* allocation size */ + size_t len; /* raw string length */ + char *args[STR_ARG_SIZ]; + int narg; /* nb of args */ +} STREscape; + +/* x.c */ +typedef struct TermWindow TermWindow; + +struct TermWindow { + int tw, th; /* tty width and height */ + int w, h; /* window width and height */ + int hb, vb; /* horizontal and vertical border (in pix) */ + int ch; /* char height */ + int cw; /* char width */ + int mode; /* window state/mode flags */ + int cursor; /* cursor style */ +}; + +/* used for user hooks */ +union Arg { + int i; + uint ui; + float f; + void *v; + char *s; +}; + +// ----------------------------------------------------------------------- +// x.c (backend functions) + +void xbell(void); +void xclipcopy(void); +void xdrawcursor(int, int, Letter, int, int, Letter, Letter*, int); +void xdrawline(Letter*, int, int, int); +void xfinishdraw(void); +void xloadcols(void); +int xsetcolorname(int, char *); +void xsettitle(char *); +int xsetcursor(int); +void xsetmode(int, uint); +void xsetpointermotion(int); +void xsetsel(char *); +int xstartdraw(void); +void xximspot(int, int); + +void fatal( char *, ...); +void redraw(void); +void draw(void); + +void printscreen(Arg *); +void printsel(Arg *); +void sendbreak(Arg *); +void toggleprinter(Arg *); + +int tattrset(int); +void tnew(int, int); +void tresize(int, int); +void tsetdirtattr(int); +void ttyhangup(void); +int ttynew(char *, char *, char *, char **); +ulong ttyread(void); +void ttyresize(int, int); +void ttywrite( char *, size_t, int); + +void resettitle(void); + +void selclear(void); +void selinit(void); +void selstart(int, int, int); +void selextend(int, int, int, int); +int selected(int, int); +char *getsel(void); + +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +char *xstrdup(char *); + +/* config.h globals */ +extern char *utmp; +extern char *scroll; +extern char *stty_args; +extern char *vtiden; +extern wchar *worddelimiters; +extern int allowaltscreen; +extern int allowwindowops; +extern char *termname; +extern uint tabspaces; +extern uint defaultfg; +extern uint defaultbg; +extern float alpha; diff --git a/src/cmd/term/term.info b/src/cmd/term/term.info new file mode 100644 index 0000000..7b90344 --- /dev/null +++ b/src/cmd/term/term.info @@ -0,0 +1,250 @@ +term+mono| simpleterm monocolor, + acsc=+C\,D-A.B0E``aaffgghFiGjjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~, + am, + bce, + bel=^G, + blink=\E[5m, + bold=\E[1m, + cbt=\E[Z, + cvvis=\E[?25h, + civis=\E[?25l, + clear=\E[H\E[2J, + cnorm=\E[?12l\E[?25h, + colors#2, + cols#80, + cr=^M, + csr=\E[%i%p1%d;%p2%dr, + cub=\E[%p1%dD, + cub1=^H, + cud1=^J, + cud=\E[%p1%dB, + cuf1=\E[C, + cuf=\E[%p1%dC, + cup=\E[%i%p1%d;%p2%dH, + cuu1=\E[A, + cuu=\E[%p1%dA, + dch=\E[%p1%dP, + dch1=\E[P, + dim=\E[2m, + dl=\E[%p1%dM, + dl1=\E[M, + ech=\E[%p1%dX, + ed=\E[J, + el=\E[K, + el1=\E[1K, + enacs=\E)0, + flash=\E[?5h$<80/>\E[?5l, + fsl=^G, + home=\E[H, + hpa=\E[%i%p1%dG, + hs, + ht=^I, + hts=\EH, + ich=\E[%p1%d@, + il1=\E[L, + il=\E[%p1%dL, + ind=^J, + indn=\E[%p1%dS, + invis=\E[8m, + is2=\E[4l\E>\E[?1034l, + it#4, + kel=\E[1;2F, + ked=\E[1;5F, + ka1=\E[1~, + ka3=\E[5~, + kc1=\E[4~, + kc3=\E[6~, + kbs=\177, + kcbt=\E[Z, + kb2=\EOu, + kcub1=\EOD, + kcud1=\EOB, + kcuf1=\EOC, + kcuu1=\EOA, + kDC=\E[3;2~, + kent=\EOM, + kEND=\E[1;2F, + kIC=\E[2;2~, + kNXT=\E[6;2~, + kPRV=\E[5;2~, + kHOM=\E[1;2H, + kLFT=\E[1;2D, + kRIT=\E[1;2C, + kind=\E[1;2B, + kri=\E[1;2A, + kclr=\E[3;5~, + kdl1=\E[3;2~, + kdch1=\E[3~, + kich1=\E[2~, + kend=\E[4~, + kf1=\EOP, + kf2=\EOQ, + kf3=\EOR, + kf4=\EOS, + kf5=\E[15~, + kf6=\E[17~, + kf7=\E[18~, + kf8=\E[19~, + kf9=\E[20~, + kf10=\E[21~, + kf11=\E[23~, + kf12=\E[24~, + kf13=\E[1;2P, + kf14=\E[1;2Q, + kf15=\E[1;2R, + kf16=\E[1;2S, + kf17=\E[15;2~, + kf18=\E[17;2~, + kf19=\E[18;2~, + kf20=\E[19;2~, + kf21=\E[20;2~, + kf22=\E[21;2~, + kf23=\E[23;2~, + kf24=\E[24;2~, + kf25=\E[1;5P, + kf26=\E[1;5Q, + kf27=\E[1;5R, + kf28=\E[1;5S, + kf29=\E[15;5~, + kf30=\E[17;5~, + kf31=\E[18;5~, + kf32=\E[19;5~, + kf33=\E[20;5~, + kf34=\E[21;5~, + kf35=\E[23;5~, + kf36=\E[24;5~, + kf37=\E[1;6P, + kf38=\E[1;6Q, + kf39=\E[1;6R, + kf40=\E[1;6S, + kf41=\E[15;6~, + kf42=\E[17;6~, + kf43=\E[18;6~, + kf44=\E[19;6~, + kf45=\E[20;6~, + kf46=\E[21;6~, + kf47=\E[23;6~, + kf48=\E[24;6~, + kf49=\E[1;3P, + kf50=\E[1;3Q, + kf51=\E[1;3R, + kf52=\E[1;3S, + kf53=\E[15;3~, + kf54=\E[17;3~, + kf55=\E[18;3~, + kf56=\E[19;3~, + kf57=\E[20;3~, + kf58=\E[21;3~, + kf59=\E[23;3~, + kf60=\E[24;3~, + kf61=\E[1;4P, + kf62=\E[1;4Q, + kf63=\E[1;4R, + khome=\E[1~, + kil1=\E[2;5~, + krmir=\E[2;2~, + knp=\E[6~, + kmous=\E[M, + kpp=\E[5~, + lines#24, + mir, + msgr, + npc, + op=\E[39;49m, + pairs#64, + mc0=\E[i, + mc4=\E[4i, + mc5=\E[5i, + rc=\E8, + rev=\E[7m, + ri=\EM, + rin=\E[%p1%dT, + ritm=\E[23m, + rmacs=\E(B, + rmcup=\E[?1049l, + rmir=\E[4l, + rmkx=\E[?1l\E>, + rmso=\E[27m, + rmul=\E[24m, + rs1=\Ec, + rs2=\E[4l\E>\E[?1034l, + sc=\E7, + sitm=\E[3m, + sgr0=\E[0m, + smacs=\E(0, + smcup=\E[?1049h, + smir=\E[4h, + smkx=\E[?1h\E=, + smso=\E[7m, + smul=\E[4m, + tbc=\E[3g, + tsl=\E]0;, + xenl, + vpa=\E[%i%p1%dd, +# XTerm extensions + rmxx=\E[29m, + smxx=\E[9m, +# disabled rep for now: causes some issues with older ncurses versions. +# rep=%p1%c\E[%p2%{1}%-%db, +# tmux extensions, see TERMINFO EXTENSIONS in tmux(1) + Tc, + Ms=\E]52;%p1%s;%p2%s\007, + Se=\E[2 q, + Ss=\E[%p1%d q, + +term| simpleterm, + use=term+mono, + colors#8, pairs#64, + setab=\E[4%p1%dm, + setaf=\E[3%p1%dm, + setb=\E[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + setf=\E[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m, + sgr=%?%p9%t\E(0%e\E(B%;\E[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m, + +term-256color| simpleterm with 256 colors, + use=term, + ccc, + colors#256, pairs#32767, + oc=\E]104\007, +# Nicked from xterm-256color + initc=\E]4;%p1%d;rgb\:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\E\\, + setab=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m, + +term-direct| simpleterm with true color, + use=term, + RGB, +# Nicked from xterm-direct + colors#0x1000000, pairs#0x7FFFF, + initc@, op=\E[39;49m, + setab=\E[%?%p1%{8}%<%t4%p1%d%e48;2;%p1%{65536}%/%d;%p1%{256} + %/%{255}%&%d;%p1%{255}%&%d%;m, + setaf=\E[%?%p1%{8}%<%t3%p1%d%e38;2;%p1%{65536}%/%d;%p1%{256} + %/%{255}%&%d;%p1%{255}%&%d%;m, + setb@, setf@, + +term-meta| simpleterm with meta key, + use=term, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +term-meta-256color| simpleterm with meta key and 256 colors, + use=term-256color, + km, + rmm=\E[?1034l, + smm=\E[?1034h, + rs2=\E[4l\E>\E[?1034h, + is2=\E[4l\E>\E[?1034h, + +term-bs| simpleterm with backspace as backspace, + use=term, + kbs=\010, + kdch1=\177, + +term-bs-256color| simpleterm with backspace as backspace and 256colors, + use=term-256color, + kbs=\010, + kdch1=\177, diff --git a/src/cmd/term/util.c b/src/cmd/term/util.c new file mode 100644 index 0000000..3e7d81b --- /dev/null +++ b/src/cmd/term/util.c @@ -0,0 +1,30 @@ +#include + +static const uchar table[] = { +#include "nonspacing.h" +}; + +static const uchar wtable[] = { +#include "wide.h" +}; + +int +wcwidth(wchar_t wc) +{ + if (wc < 0xffU) + return (wc+1 & 0x7f) >= 0x21 ? 1 : wc ? -1 : 0; + if ((wc & 0xfffeffffU) < 0xfffe) { + if ((table[table[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1) + return 0; + if ((wtable[wtable[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1) + return 2; + return 1; + } + if ((wc & 0xfffe) == 0xfffe) + return -1; + if (wc-0x20000U < 0x20000) + return 2; + if (wc == 0xe0001 || wc-0xe0020U < 0x5f || wc-0xe0100U < 0xef) + return 0; + return 1; +} diff --git a/src/cmd/term/wide.h b/src/cmd/term/wide.h new file mode 100644 index 0000000..e403c9a --- /dev/null +++ b/src/cmd/term/wide.h @@ -0,0 +1,65 @@ +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,18,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,19,16,20,21,22,16,16,16,23,16,16,24,25,26,27,28,17, +17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,29, +17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +17,17,17,17,17,17,17,17,30,16,16,16,16,31,16,16,17,17,17,17,17,17,17,17,17,17, +17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +17,17,17,17,17,17,17,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,16,16,16,33, +34,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,35,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, +17,17,17,17,17,17,36,17,17,37,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,38,39,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,40,41,42,43,44,45,46,47,16,48,49,16,16,16,16, +16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,6,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,48,0,0,0,0,0,0,255,15,0,0,0,0,128,0,0,8, +0,2,12,0,96,48,64,16,0,0,4,44,36,32,12,0,0,0,1,0,0,0,80,184,0,0,0,0,0,0,0,224, +0,0,0,1,128,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,251,255,255,255,255,255,255,255, +255,255,255,15,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,63,0,0,0,255,15,255,255,255,255, +255,255,255,127,254,255,255,255,255,255,255,255,255,255,127,254,255,255,255, +255,255,255,255,255,255,255,255,255,224,255,255,255,255,255,254,255,255,255, +255,255,255,255,255,255,255,127,255,255,255,255,255,7,255,255,255,255,15,0, +255,255,255,255,255,127,255,255,255,255,255,0,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0, +0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,31,255,255,255,255,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255, +255,255,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,15,0,0,0,0,0,0,0,0,0,0,0,0,0,255,3,0,0,255,255,255,255,247,255,127,15,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,255,255,255,255,255,255,255,255,255,255, +255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,7,0,255,255,255,127,0,0,0,0,0, +0,7,0,240,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255, +15,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,64,254,7,0,0,0,0,0,0,0,0,0,0,0,0,7,0,255,255,255, +255,255,15,255,1,3,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255, +1,224,191,255,255,255,255,255,255,255,255,223,255,255,15,0,255,255,255,255, +255,135,15,0,255,255,17,255,255,255,255,255,255,255,255,127,253,255,255,255, +255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +159,255,255,255,255,255,255,255,63,0,120,255,255,255,0,0,4,0,0,96,0,16,0,0,0, +0,0,0,0,0,0,0,248,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255, +255,255,255,255,255,255,63,16,39,0,0,24,240,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,255,15,0, +0,0,224,255,255,255,255,255,255,255,255,255,255,255,255,123,252,255,255,255, +255,231,199,255,255,255,231,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,15,7,7,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0, diff --git a/src/cmd/term/x.c b/src/cmd/term/x.c new file mode 100644 index 0000000..27a2e7a --- /dev/null +++ b/src/cmd/term/x.c @@ -0,0 +1,2070 @@ +/* See LICENSE for license details. */ +#include "term.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* harfbuzz additions */ +#if 0 +void hbunloadfonts(); +void hbtransform(XftGlyphFontSpec *, const Letter*, size_t, int, int); +#endif + +/* types used in config.h */ +typedef struct Shortcut Shortcut; +typedef struct MouseShortcut MouseShortcut; +typedef struct Key Key; + +struct Shortcut{ + uint mod; + KeySym keysym; + void (*func)(Arg *); + Arg arg; +}; + +struct MouseShortcut { + uint mod; + uint button; + void (*func)(Arg *); + Arg arg; + uint release; +}; + +struct Key { + KeySym k; + uint mask; + char *s; + /* three-valued logic variables: 0 indifferent, 1 on, -1 off */ + schar appkey; /* application keypad */ + schar appcursor; /* application cursor */ +}; + +/* X modifiers */ +#define XK_ANY_MOD UINT_MAX +#define XK_NO_MOD 0 +#define XK_SWITCH_MOD (1<<13) + +/* function definitions used in config.h */ +static void clipcopy(Arg *); +static void clippaste(Arg *); +static void numlock(Arg *); +static void selpaste(Arg *); +static void zoom(Arg *); +static void zoomabs(Arg *); +static void zoomreset(Arg *); +static void ttysend(Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* XEMBED messages */ +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 + +/* macros */ +#define IS_SET(flag) ((win.mode & (flag)) != 0) +#define TRUERED(x) (((x) & 0xff0000) >> 8) +#define TRUEGREEN(x) (((x) & 0xff00)) +#define TRUEBLUE(x) (((x) & 0xff) << 8) + +typedef XftDraw *Draw; +typedef XftColor Color; +typedef XftGlyphFontSpec GlyphFontSpec; + +/* Purely graphic info */ +typedef struct { + Display *dpy; + Colormap cmap; + Window win; + Drawable buf; + GlyphFontSpec *specbuf; /* font spec buffer used for rendering */ + Atom xembed, wmdeletewin, netwmname, netwmpid; + struct { + XIM xim; + XIC xic; + XPoint spot; + XVaNestedList spotlist; + } ime; + Draw draw; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int isfixed; /* is fixed geometry? */ + int depth; /* color depth */ + int l, t; /* left and top offset */ + int gm; /* geometry mask */ +} XWindow; + +typedef struct { + Atom xtarget; + char *primary, *clipboard; + struct timespec tclick1; + struct timespec tclick2; +} XSelection; + +/* Font structure */ +#define Font Font_ +typedef struct { + int height; + int width; + int ascent; + int descent; + int badslant; + int badweight; + short lbearing; + short rbearing; + XftFont *match; + FcFontSet *set; + FcPattern *pattern; +} Font; + +/* Drawing Context */ +typedef struct { + Color *col; + size_t collen; + Font font, bfont, ifont, ibfont; + GC gc; +} DC; + +static inline ushort sixd_to_16bit(int); +static int xmakeglyphfontspecs(XftGlyphFontSpec *, Letter *, int, int, int); +static void xdrawglyphfontspecs(XftGlyphFontSpec *, Letter, int, int, int); +static void xdrawglyph(Letter, int, int); +static void xclear(int, int, int, int); +static int xgeommasktogravity(int); +static int ximopen(Display *); +static void ximinstantiate(Display *, XPointer, XPointer); +static void ximdestroy(XIM, XPointer, XPointer); +static int xicdestroy(XIC, XPointer, XPointer); +static void xinit(int, int); +static void cresize(int, int); +static void xresize(int, int); +static void xhints(void); +static int xloadcolor(int, char *, Color *); +static int xloadfont(Font *, FcPattern *); +static void xloadfonts(char *, double); +static void xunloadfont(Font *); +static void xunloadfonts(void); +static void xsetenv(void); +static void xseturgency(int); +static int evcol(XEvent *); +static int evrow(XEvent *); + +static void expose(XEvent *); +static void visibility(XEvent *); +static void unmap(XEvent *); +static void kpress(XEvent *); +static void cmessage(XEvent *); +static void resize(XEvent *); +static void focus(XEvent *); +static uint buttonmask(uint); +static int mouseaction(XEvent *, uint); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); +static void propnotify(XEvent *); +static void selnotify(XEvent *); +static void selclear_(XEvent *); +static void selrequest(XEvent *); +static void setsel(char *, Time); +static void mousesel(XEvent *, int); +static void mousereport(XEvent *); +static char *kmap(KeySym, uint); +static int match(uint, uint); + +static void run(void); +static void usage(void); + +static void (*handler[LASTEvent])(XEvent *) = { + [KeyPress] = kpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [VisibilityNotify] = visibility, + [UnmapNotify] = unmap, + [Expose] = expose, + [FocusIn] = focus, + [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, + [SelectionClear] = selclear_, + [SelectionNotify] = selnotify, +/* + * PropertyNotify is only turned on when there is some INCR transfer happening + * for the selection retrieval. + */ + [PropertyNotify] = propnotify, + [SelectionRequest] = selrequest, +}; + +/* Globals */ +static DC dc; +static XWindow xw; +static XSelection xsel; +static TermWindow win; + +/* Font Ring Cache */ +enum { + FRC_NORMAL, + FRC_ITALIC, + FRC_BOLD, + FRC_ITALICBOLD +}; + +typedef struct { + XftFont *font; + int flags; + rune unicodep; +} Fontcache; + +/* Fontcache is an array now. A new font will be appended to the array. */ +static Fontcache *frc = nil; +static int frclen = 0; +static int frccap = 0; +static char *usedfont = nil; +static double usedfontsize = 0; +static double defaultfontsize = 0; + +static char *opt_alpha = nil; +static char *opt_class = nil; +static char **opt_cmd = nil; +static char *opt_embed = nil; +static char *opt_font = nil; +static char *opt_io = nil; +static char *opt_line = nil; +static char *opt_name = nil; +static char *opt_title = nil; + +static int oldbutton = 3; /* button event on startup: 3 = release */ + +void +clipcopy(Arg *_) +{ + Atom clipboard; + + free(xsel.clipboard); + xsel.clipboard = nil; + + if (xsel.primary != nil) { + xsel.clipboard = xstrdup(xsel.primary); + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); + } +} + +void +clippaste(Arg *_) +{ + Atom clipboard; + + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, + xw.win, CurrentTime); +} + +void +selpaste(Arg *_) +{ + XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, + xw.win, CurrentTime); +} + +void +numlock(Arg *_) +{ + win.mode ^= Wnumlock; +} + +void +zoom(Arg *arg) +{ + Arg larg; + + larg.f = usedfontsize + arg->f; + zoomabs(&larg); +} + +void +zoomabs(Arg *arg) +{ + xunloadfonts(); + xloadfonts(usedfont, arg->f); + cresize(0, 0); + redraw(); + xhints(); +} + +void +zoomreset(Arg *arg) +{ + Arg larg; + + if (defaultfontsize > 0) { + larg.f = defaultfontsize; + zoomabs(&larg); + } +} + +void +ttysend(Arg *arg) +{ + ttywrite(arg->s, strlen(arg->s), 1); +} + +int +evcol(XEvent *e) +{ + int x = e->xbutton.x - win.hb; + LIMIT(x, 0, win.tw - 1); + return x / win.cw; +} + +int +evrow(XEvent *e) +{ + int y = e->xbutton.y - win.vb; + LIMIT(y, 0, win.th - 1); + return y / win.ch; +} + +void +mousesel(XEvent *e, int done) +{ + int type, seltype = SelRegular; + uint state = e->xbutton.state & ~(Button1Mask | forcemousemod); + + for (type = 1; type < arrlen(selmasks); ++type) { + if (match(selmasks[type], state)) { + seltype = type; + break; + } + } + selextend(evcol(e), evrow(e), seltype, done); + if (done) + setsel(getsel(), e->xbutton.time); +} + +void +mousereport(XEvent *e) +{ + int len, x = evcol(e), y = evrow(e), + button = e->xbutton.button, state = e->xbutton.state; + char buf[40]; + static int ox, oy; + + /* from urxvt */ + if (e->xbutton.type == MotionNotify) { + if (x == ox && y == oy) + return; + if (!IS_SET(Wmousemotion) && !IS_SET(Wmousemany)) + return; + /* MOUSE_MOTION: no reporting if no button is pressed */ + if (IS_SET(Wmousemotion) && oldbutton == 3) + return; + + button = oldbutton + 32; + ox = x; + oy = y; + } else { + if (!IS_SET(Wmousesgr) && e->xbutton.type == ButtonRelease) { + button = 3; + } else { + button -= Button1; + if (button >= 3) + button += 64 - 3; + } + if (e->xbutton.type == ButtonPress) { + oldbutton = button; + ox = x; + oy = y; + } else if (e->xbutton.type == ButtonRelease) { + oldbutton = 3; + /* Wmousex10: no button release reporting */ + if (IS_SET(Wmousex10)) + return; + if (button == 64 || button == 65) + return; + } + } + + if (!IS_SET(Wmousex10)) { + button += ((state & ShiftMask ) ? 4 : 0) + + ((state & Mod4Mask ) ? 8 : 0) + + ((state & ControlMask) ? 16 : 0); + } + + if (IS_SET(Wmousesgr)) { + 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, 0); +} + +uint +buttonmask(uint button) +{ + return button == Button1 ? Button1Mask + : button == Button2 ? Button2Mask + : button == Button3 ? Button3Mask + : button == Button4 ? Button4Mask + : button == Button5 ? Button5Mask + : 0; +} + +int +mouseaction(XEvent *e, uint release) +{ + MouseShortcut *ms; + + /* ignore Buttonmask for Button - it's set on release */ + uint state = e->xbutton.state & ~buttonmask(e->xbutton.button); + + for (ms = mshortcuts; ms < mshortcuts + arrlen(mshortcuts); ms++) { + if (ms->release == release && + ms->button == e->xbutton.button && + (match(ms->mod, state) || /* exact or forced */ + match(ms->mod, state & ~forcemousemod))) { + ms->func(&(ms->arg)); + return 1; + } + } + + return 0; +} + +void +bpress(XEvent *e) +{ + struct timespec now; + int snap; + + if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 0)) + return; + + if (e->xbutton.button == 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 = SnapLine; + } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) { + snap = SnapWord; + } else { + snap = 0; + } + xsel.tclick2 = xsel.tclick1; + xsel.tclick1 = now; + + selstart(evcol(e), evrow(e), snap); + } +} + +void +propnotify(XEvent *e) +{ + XPropertyEvent *xpev; + Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + + xpev = &e->xproperty; + if (xpev->state == PropertyNewValue && + (xpev->atom == XA_PRIMARY || + xpev->atom == clipboard)) { + selnotify(e); + } +} + +void +selnotify(XEvent *e) +{ + ulong nitems, ofs, rem; + int format; + uchar *data, *last, *repl; + Atom type, incratom, property = None; + + incratom = XInternAtom(xw.dpy, "INCR", 0); + + 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, + 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. + */ + MODBIT(xw.attrs.event_mask, 0, 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. + */ + MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, + &xw.attrs); + + /* + * Deleting the property is the transfer start signal. + */ + XDeleteProperty(xw.dpy, xw.win, (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 = memchr(repl, '\n', last - repl))) { + *repl++ = '\r'; + } + + if (IS_SET(Wbrcktpaste) && ofs == 0) + ttywrite("\033[200~", 6, 0); + ttywrite((char *)data, nitems * format / 8, 1); + if (IS_SET(Wbrcktpaste) && 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, (int)property); +} + +void +xclipcopy(void) +{ + clipcopy(nil); +} + +void +selclear_(XEvent *e) +{ + selclear(); +} + +void +selrequest(XEvent *e) +{ + XSelectionRequestEvent *xsre; + XSelectionEvent xev; + Atom xa_targets, string, clipboard; + char *seltext; + + xsre = (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", 0); + if (xsre->target == xa_targets) { + /* respond with the supported type */ + string = xsel.xtarget; + XChangeProperty(xsre->display, xsre->requestor, xsre->property, + XA_ATOM, 32, PropModeReplace, + (uchar *) &string, 1); + xev.property = xsre->property; + } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { + /* + * xith XA_STRING non ascii characters may be incorrect in the + * requestor. It is not our problem, use utf8. + */ + clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); + 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 != nil) { + XChangeProperty(xsre->display, xsre->requestor, + xsre->property, xsre->target, + 8, PropModeReplace, + (uchar *)seltext, strlen(seltext)); + xev.property = xsre->property; + } + } + + /* all done, send a notification to the listener */ + if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) + fprintf(stderr, "Error sending SelectionNotify event\n"); +} + +void +setsel(char *str, Time t) +{ + if (!str) + return; + + free(xsel.primary); + xsel.primary = str; + + XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); + if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) + selclear(); +} + +void +xsetsel(char *str) +{ + setsel(str, CurrentTime); +} + +void +brelease(XEvent *e) +{ + if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + if (mouseaction(e, 1)) + return; + if (e->xbutton.button == Button1) + mousesel(e, 1); +} + +void +bmotion(XEvent *e) +{ + if (IS_SET(Wmouse) && !(e->xbutton.state & forcemousemod)) { + mousereport(e); + return; + } + + mousesel(e, 0); +} + +void +cresize(int width, int height) +{ + int col, row; + + if (width != 0) + win.w = width; + if (height != 0) + win.h = height; + + col = (win.w - 2 * borderpx) / win.cw; + row = (win.h - 2 * borderpx) / win.ch; + col = MAX(1, col); + row = MAX(1, row); + + win.hb = (win.w - col*win.cw)/2; + win.vb = (win.h - row*win.ch)/2; + + tresize(col, row); + xresize(col, row); + ttyresize(win.tw, win.th); +} + +void +xresize(int col, int row) +{ + win.tw = col * win.cw; + win.th = row * win.ch; + + XFreePixmap(xw.dpy, xw.buf); + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + + XftDrawChange(xw.draw, xw.buf); + xclear(0, 0, win.w, win.h); + + /* resize to new width */ + xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec)); +} + +ushort +sixd_to_16bit(int x) +{ + return x == 0 ? 0 : 0x3737 + 0x2828 * x; +} + +int +xloadcolor(int i, char *name, Color *ncolor) +{ + XRenderColor color = { .alpha = 0xffff }; + + if (!name) { + if (BETWEEN(i, 16, 255)) { /* 256 color */ + if (i < 6*6*6+16) { /* same colors as xterm */ + 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 = 0x0808 + 0x0a0a * (i - (6*6*6+16)); + color.green = color.blue = color.red; + } + return XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &color, ncolor); + } else + name = colorname[i]; + } + + return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); +} + +void +xloadcols(void) +{ + 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(arrlen(colorname), 256); + dc.col = xmalloc(dc.collen * sizeof(Color)); + } + + for (i = 0; i < dc.collen; i++) + if (!xloadcolor(i, nil, &dc.col[i])) { + if (colorname[i]) + fatal("could not allocate color '%s'\n", colorname[i]); + else + fatal("could not allocate color %d\n", i); + } + + if (opt_alpha) + alpha = strtof(opt_alpha, nil); + + dc.col[defaultbg].color.alpha = (ushort)(0xffff * alpha); + dc.col[defaultbg].pixel &= 0x00ffffff; + dc.col[defaultbg].pixel |= (uchar)(0xff*alpha) << 24; + loaded = 1; +} + +int +xsetcolorname(int x, char *name) +{ + Color ncolor; + + if (!BETWEEN(x, 0, dc.collen)) + return 1; + + if (!xloadcolor(x, name, &ncolor)) + return 1; + + XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); + dc.col[x] = ncolor; + + return 0; +} + +/* + * Absolute coordinates. + */ +void +xclear(int x1, int y1, int x2, int y2) +{ + XftDrawRect(xw.draw, &dc.col[IS_SET(Wreverse)? defaultfg : defaultbg], x1, y1, x2-x1, y2-y1); +} + +void +xhints(void) +{ + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; + XWMHints wm = {.flags = InputHint, .input = 1}; + XSizeHints *sizeh; + + sizeh = XAllocSizeHints(); + + sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize; + sizeh->height = win.h, sizeh->width = win.w; + sizeh->height_inc = 1; + sizeh->width_inc = 1; + sizeh->base_height = 2 * borderpx; + sizeh->base_width = 2 * borderpx; + sizeh->min_height = win.ch + 2 * borderpx; + sizeh->min_width = win.cw + 2 * borderpx; + if (xw.isfixed) { + sizeh->flags |= PMaxSize; + sizeh->min_width = sizeh->max_width = win.w; + sizeh->min_height = sizeh->max_height = 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, nil, nil, nil, 0, sizeh, &wm, + &class); + XFree(sizeh); +} + +int +xgeommasktogravity(int mask) +{ + switch (mask & (XNegative|YNegative)) { + case 0: + return NorthWestGravity; + case XNegative: + return NorthEastGravity; + case YNegative: + return SouthWestGravity; + } + + return SouthEastGravity; +} + +int +xloadfont(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(nil, configured, FcMatchPattern); + XftDefaultSubstitute(xw.dpy, xw.scr, configured); + + match = FcFontMatch(nil, configured, &result); + if (!match) { + FcPatternDestroy(configured); + return 1; + } + + if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { + FcPatternDestroy(configured); + FcPatternDestroy(match); + return 1; + } + + if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == + XftResultMatch)) { + /* + * Check if xft was unable to find a font with the appropriate + * slant but gave us one anyway. Try to mitigate. + */ + if ((XftPatternGetInteger(f->match->pattern, "slant", 0, + &haveattr) != XftResultMatch) || haveattr < wantattr) { + f->badslant = 1; + fputs("font slant does not match\n", stderr); + } + } + + if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == + XftResultMatch)) { + if ((XftPatternGetInteger(f->match->pattern, "weight", 0, + &haveattr) != XftResultMatch) || haveattr != wantattr) { + f->badweight = 1; + fputs("font weight does not match\n", stderr); + } + } + + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + + f->set = nil; + f->pattern = configured; + + f->ascent = f->match->ascent; + f->descent = f->match->descent; + f->lbearing = 0; + f->rbearing = f->match->max_advance_width; + + f->height = f->ascent + f->descent; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); + + return 0; +} + +void +xloadfonts(char *fontstr, double fontsize) +{ + FcPattern *pattern; + double fontval; + + if (fontstr[0] == '-') + pattern = XftXlfdParse(fontstr, False, False); + else + pattern = FcNameParse((FcChar8 *)fontstr); + + if (!pattern) + fatal("can't open font %s\n", fontstr); + + if (fontsize > 1) { + FcPatternDel(pattern, FC_PIXEL_SIZE); + FcPatternDel(pattern, FC_SIZE); + FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); + usedfontsize = fontsize; + } else { + if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == + FcResultMatch) { + usedfontsize = fontval; + } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == + 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)) + fatal("can't open font %s\n", fontstr); + + if (usedfontsize < 0) { + FcPatternGetDouble(dc.font.match->pattern, + FC_PIXEL_SIZE, 0, &fontval); + usedfontsize = fontval; + if (fontsize == 0) + defaultfontsize = fontval; + } + + /* Setting character width and height. */ + win.cw = ceilf(dc.font.width * cwscale); + win.ch = ceilf(dc.font.height * chscale); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); + if (xloadfont(&dc.ifont, pattern)) + fatal("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_WEIGHT); + FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); + if (xloadfont(&dc.ibfont, pattern)) + fatal("can't open font %s\n", fontstr); + + FcPatternDel(pattern, FC_SLANT); + FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); + if (xloadfont(&dc.bfont, pattern)) + fatal("can't open font %s\n", fontstr); + + FcPatternDestroy(pattern); +} + +void +xunloadfont(Font *f) +{ + XftFontClose(xw.dpy, f->match); + FcPatternDestroy(f->pattern); + if (f->set) + FcFontSetDestroy(f->set); +} + +void +xunloadfonts(void) +{ +#if 0 + hbunloadfonts(); +#endif + + /* Free the loaded fonts in the font cache. */ + while (frclen > 0) + XftFontClose(xw.dpy, frc[--frclen].font); + + xunloadfont(&dc.font); + xunloadfont(&dc.bfont); + xunloadfont(&dc.ifont); + xunloadfont(&dc.ibfont); +} + +int +ximopen(Display *dpy) +{ + XIMCallback imdestroy = { .client_data = nil, .callback = ximdestroy }; + XICCallback icdestroy = { .client_data = nil, .callback = xicdestroy }; + + xw.ime.xim = XOpenIM(xw.dpy, nil, nil, nil); + if (xw.ime.xim == nil) + return 0; + + if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, nil)) + fprintf(stderr, "XSetIMValues: " + "Could not set XNDestroyCallback.\n"); + + xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot, + nil); + + if (xw.ime.xic == nil) { + xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle, + XIMPreeditNothing | XIMStatusNothing, + XNClientWindow, xw.win, + XNDestroyCallback, &icdestroy, + nil); + } + if (xw.ime.xic == nil) + fprintf(stderr, "XCreateIC: Could not create input context.\n"); + + return 1; +} + +void +ximinstantiate(Display *dpy, XPointer client, XPointer call) +{ + if (ximopen(dpy)) + XUnregisterIMInstantiateCallback(xw.dpy, nil, nil, nil, + ximinstantiate, nil); +} + +void +ximdestroy(XIM xim, XPointer client, XPointer call) +{ + xw.ime.xim = nil; + XRegisterIMInstantiateCallback(xw.dpy, nil, nil, nil, + ximinstantiate, nil); + XFree(xw.ime.spotlist); +} + +int +xicdestroy(XIC xim, XPointer client, XPointer call) +{ + xw.ime.xic = nil; + return 1; +} + +void +xinit(int cols, int rows) +{ + XGCValues gcvalues; + Cursor cursor; + Window parent; + XColor xmousefg, xmousebg; + XWindowAttributes attr; + XVisualInfo vis; + pid_t thispid = getpid(); + + if (!(xw.dpy = XOpenDisplay(nil))) + fatal("can't open display\n"); + + if (!(opt_embed && (parent == strtol(opt_embed, nil, 0)))) { + parent = XRootWindow(xw.dpy, xw.scr); + xw.depth = 32; + } else { + XGetWindowAttributes(xw.dpy, parent, &attr); + xw.depth = attr.depth; + } + + XMatchVisualInfo(xw.dpy, xw.scr, xw.depth, TrueColor, &vis); + xw.vis = vis.visual; + xw.scr = XDefaultScreen(xw.dpy); + + /* font */ + if (!FcInit()) + fatal("could not init fontconfig.\n"); + + usedfont = (opt_font == nil)? font : opt_font; + xloadfonts(usedfont, 0); + + /* colors */ + xw.cmap = XCreateColormap(xw.dpy, parent, xw.vis, None); + xloadcols(); + + /* adjust fixed window geometry */ + win.w = 2 * win.hb + cols*win.cw; + win.h = 2 * win.vb + rows*win.ch; + + if (xw.gm & XNegative) + xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; + if (xw.gm & YNegative) + xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; + + /* Events */ + xw.attrs.background_pixel = dc.col[defaultbg].pixel; + xw.attrs.border_pixel = dc.col[defaultbg].pixel; + xw.attrs.bit_gravity = NorthWestGravity; + xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask + | ExposureMask | VisibilityChangeMask | StructureNotifyMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + xw.attrs.colormap = xw.cmap; + + xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, + win.w, win.h, 0, xw.depth, InputOutput, + xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity + | CWEventMask | CWColormap, &xw.attrs); + + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + + xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, xw.depth); + dc.gc = XCreateGC(xw.dpy, xw.buf, GCGraphicsExposures, &gcvalues); + + XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); + XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); + + /* font spec buffer */ + xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec)); + + /* Xft rendering context */ + xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); + + /* input methods */ + if (!ximopen(xw.dpy)) + XRegisterIMInstantiateCallback(xw.dpy, nil, nil, nil, ximinstantiate, nil); + + /* white cursor, black outline */ + cursor = XCreateFontCursor(xw.dpy, mouseshape); + XDefineCursor(xw.dpy, xw.win, cursor); + + if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { + xmousefg.red = 0xffff; + xmousefg.green = 0xffff; + xmousefg.blue = 0xffff; + } + + if (XParseColor(xw.dpy, xw.cmap, colorname[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); + 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, (uchar *)&thispid, 1); + + win.mode = Wnumlock; + 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 = nil; + xsel.clipboard = nil; + xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); + if (xsel.xtarget == None) + xsel.xtarget = XA_STRING; +} + +int +xmakeglyphfontspecs(XftGlyphFontSpec *specs, Letter *glyphs, int len, int x, int y) +{ + float winx = win.hb + x * win.cw, winy = win.vb + y * win.ch, xp, yp; + ushort mode, prevmode = USHRT_MAX; + Font *font = &dc.font; + int frcflags = FRC_NORMAL; + float runewidth = win.cw; + rune r; + FT_UInt glyphidx; + FcResult fcres; + FcPattern *fcpattern, *fontpattern; + FcFontSet *fcsets[] = { nil }; + FcCharSet *fccharset; + int i, f, numspecs = 0; + + for(i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { + /* Fetch rune and mode for current glyph. */ + r = glyphs[i].u; + mode = glyphs[i].mode; + + /* Skip dummy wide-character spacing. */ +#if 0 + if(mode & Gwdummy) +#endif + if(mode == Gwdummy) + continue; + + /* Determine font for glyph if different from previous glyph. */ + if(prevmode != mode){ + prevmode = mode; + font = &dc.font; + frcflags = FRC_NORMAL; + runewidth = win.cw * ((mode & Gwide) ? 2.0f : 1.0f); + if ((mode & Gitalic) && (mode & Gbold)) { + font = &dc.ibfont; + frcflags = FRC_ITALICBOLD; + } else if (mode & Gitalic) { + font = &dc.ifont; + frcflags = FRC_ITALIC; + } else if (mode & Gbold) { + font = &dc.bfont; + frcflags = FRC_BOLD; + } + yp = winy + font->ascent; + } + + /* lookup character index with default font. */ + glyphidx = XftCharIndex(xw.dpy, font->match, r); + if(glyphidx){ + specs[numspecs].font = font->match; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + continue; + } + + /* Fallback on font cache, search the font cache for match. */ + for(f = 0; f < frclen; f++) { + glyphidx = XftCharIndex(xw.dpy, frc[f].font, r); + /* 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 == r) { + break; + } + } + + /* Nothing was found. Use fontconfig to find matching font. */ + if(f >= frclen) { + if (!font->set) + font->set = FcFontSort(0, font->pattern, + 1, 0, &fcres); + fcsets[0] = font->set; + + /* + * 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 = FcPatternDuplicate(font->pattern); + fccharset = FcCharSetCreate(); + + FcCharSetAddChar(fccharset, r); + FcPatternAddCharSet(fcpattern, FC_CHARSET, + fccharset); + FcPatternAddBool(fcpattern, FC_SCALABLE, 1); + + FcConfigSubstitute(0, fcpattern, + FcMatchPattern); + FcDefaultSubstitute(fcpattern); + + fontpattern = FcFontSetMatch(0, fcsets, 1, + fcpattern, &fcres); + + /* Allocate memory for the new cache entry. */ + if (frclen >= frccap) { + frccap += 16; + frc = xrealloc(frc, frccap * sizeof(Fontcache)); + } + + frc[frclen].font = XftFontOpenPattern(xw.dpy, + fontpattern); + if (!frc[frclen].font) + fatal("XftFontOpenPattern failed seeking fallback font: %s\n", + strerror(errno)); + frc[frclen].flags = frcflags; + frc[frclen].unicodep = r; + + glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, r); + + f = frclen; + frclen++; + + FcPatternDestroy(fcpattern); + FcCharSetDestroy(fccharset); + } + + specs[numspecs].font = frc[f].font; + specs[numspecs].glyph = glyphidx; + specs[numspecs].x = (short)xp; + specs[numspecs].y = (short)yp; + xp += runewidth; + numspecs++; + } + +#if 0 + hbtransform(specs, glyphs, len, x, y); +#endif + + return numspecs; +} + +void +xdrawglyphfontspecs(XftGlyphFontSpec *specs, Letter base, int len, int x, int y) +{ + int charlen = len * ((base.mode & Gwide) ? 2 : 1); + int winx = win.hb + x * win.cw, winy = win.vb + y * win.ch, width = charlen * win.cw; + Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; + XRenderColor colfg, colbg; + XRectangle r; + + /* Fallback on color display for attributes not supported by the font */ + if (base.mode & Gitalic && base.mode & Gbold) { + if (dc.ibfont.badslant || dc.ibfont.badweight) + base.fg = defaultattr; + } else if ((base.mode & Gitalic && dc.ifont.badslant) || + (base.mode & Gbold && dc.bfont.badweight)) { + base.fg = defaultattr; + } + + if (IS_TRUECOL(base.fg)) { + colfg.alpha = 0xffff; + colfg.red = TRUERED(base.fg); + colfg.green = TRUEGREEN(base.fg); + colfg.blue = TRUEBLUE(base.fg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); + fg = &truefg; + } else { + fg = &dc.col[base.fg]; + } + + if (IS_TRUECOL(base.bg)) { + colbg.alpha = 0xffff; + colbg.green = TRUEGREEN(base.bg); + colbg.red = TRUERED(base.bg); + colbg.blue = TRUEBLUE(base.bg); + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); + bg = &truebg; + } else { + bg = &dc.col[base.bg]; + } + + /* Change basic system colors [0-7] to bright system colors [8-15] */ + if ((base.mode & Gfaint) == Gbold && BETWEEN(base.fg, 0, 7)) + fg = &dc.col[base.fg + 8]; + + if (IS_SET(Wreverse)) { + if (fg == &dc.col[defaultfg]) { + fg = &dc.col[defaultbg]; + } else { + colfg.red = ~fg->color.red; + colfg.green = ~fg->color.green; + colfg.blue = ~fg->color.blue; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (bg == &dc.col[defaultbg]) { + bg = &dc.col[defaultfg]; + } else { + colbg.red = ~bg->color.red; + colbg.green = ~bg->color.green; + colbg.blue = ~bg->color.blue; + colbg.alpha = bg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &revbg); + bg = &revbg; + } + } + + if ((base.mode & Gboldfaint) == Gfaint) { + colfg.red = fg->color.red / 2; + colfg.green = fg->color.green / 2; + colfg.blue = fg->color.blue / 2; + colfg.alpha = fg->color.alpha; + XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); + fg = &revfg; + } + + if (base.mode & Greverse) + temp = fg, fg = bg, bg = temp; + + if (base.mode & Gblink && win.mode & Wblink) + fg = bg; + + if (base.mode & Ginvisible) + fg = bg; + + /* Intelligent cleaning up of the borders. */ + if (x == 0) { + xclear(0, (y == 0)? 0 : winy, win.vb, + winy + win.ch + + ((winy + win.ch >= win.vb + win.th)? win.h : 0)); + } + if (winx + width >= win.hb + win.tw) { + xclear(winx + width, (y == 0)? 0 : winy, win.w, + ((winy + win.ch >= win.vb + win.th)? win.h : (winy + win.ch))); + } + if (y == 0) + xclear(winx, 0, winx + width, win.hb); + if (winy + win.ch >= win.hb + win.th) + xclear(winx, winy + win.ch, winx + width, win.h); + + /* Clean up the region we want to draw to. */ + XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); + + /* Set the clip region because Xft is sometimes dirty. */ + r.x = 0, r.y = 0; + r.height = win.ch; + r.width = width; + XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); + + /* Render the glyphs. */ + XftDrawGlyphFontSpec(xw.draw, fg, specs, len); + + /* Render underline and strikethrough. */ + if (base.mode & Gunline) + XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, width, 1); + + if (base.mode & Gstruck) + XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, width, 1); + + /* Reset clip to none. */ + XftDrawSetClip(xw.draw, 0); +} + +void +xdrawglyph(Letter g, int x, int y) +{ + int numspecs; + XftGlyphFontSpec spec; + + numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); + xdrawglyphfontspecs(&spec, g, numspecs, x, y); +} + +void +xdrawcursor(int cx, int cy, Letter g, int ox, int oy, Letter og, Letter *line, int len) +{ + Color drawcol; + + /* remove the old cursor */ + if(selected(ox, oy)) + og.mode ^= Greverse; + xdrawglyph(og, ox, oy); +#if 0 + xdrawline(line, 0, oy, len); +#endif + + if(IS_SET(Whide)) + return; + + /* + * Select the right color for the right mode. + */ + g.mode &= Gbold|Gitalic|Gunline|Gstruck|Gwide; + + if(IS_SET(Wreverse)) { + g.mode |= Greverse; + g.bg = defaultfg; + if (selected(cx, cy)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } + } else { + if(selected(cx, cy)) { + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + g.fg = og.bg; //defaultbg; + g.bg = og.fg; //defaultcs; + if (IS_TRUECOL(og.fg)) { + drawcol.color.alpha = 0xffff; + drawcol.color.red = TRUERED(og.fg); + drawcol.color.green = TRUEGREEN(og.fg); + drawcol.color.blue = TRUEBLUE(og.fg); + goto drawnew; + } + } + drawcol = dc.col[g.bg]; + } + +drawnew: + if(IS_SET(Wfocused)) { + switch (win.cursor) { + case 7: /* st extension */ + g.u = 0x2603; /* snowman (U+2603) */ + /* fallthrough */ + case 0: /* Blinking Block */ + case 1: /* Blinking Block (Default) */ + case 2: /* Steady Block */ + xdrawglyph(g, cx, cy); + break; + case 3: /* Blinking Underline */ + case 4: /* Steady Underline */ + XftDrawRect(xw.draw, &drawcol, + win.hb + cx * win.cw, + win.vb + (cy + 1) * win.ch - cursorthickness, + win.cw, cursorthickness); + break; + case 5: /* Blinking bar */ + case 6: /* Steady bar */ + XftDrawRect(xw.draw, &drawcol, + win.hb + cx * win.cw, + win.vb + cy * win.ch, + cursorthickness, win.ch); + break; + } + } else { + XftDrawRect(xw.draw, &drawcol, + win.hb + cx * win.cw, + win.vb + cy * win.ch, + win.cw - 1, 1); + XftDrawRect(xw.draw, &drawcol, + win.hb + cx * win.cw, + win.vb + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + win.hb + (cx + 1) * win.cw - 1, + win.vb + cy * win.ch, + 1, win.ch - 1); + XftDrawRect(xw.draw, &drawcol, + win.hb + cx * win.cw, + win.vb + (cy + 1) * win.ch - 1, + win.cw, 1); + } +} + +void +xsetenv(void) +{ + char buf[sizeof(long) * 8 + 1]; + + snprintf(buf, sizeof(buf), "%lu", xw.win); + setenv("WINDOWID", buf, 1); +} + +void +xsettitle(char *p) +{ + XTextProperty prop; + DEFAULT(p, opt_title); + + Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, + &prop); + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); +} + +int +xstartdraw(void) +{ + return IS_SET(Wvisible); +} + +void +xdrawline(Letter *line, int x1, int y1, int x2) +{ + int i, x, ox, numspecs; + Letter base, new; + XftGlyphFontSpec *specs = xw.specbuf; + + numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1); + i = ox = 0; + for (x = x1; x < x2 && i < numspecs; x++) { + new = line[x]; + if (new.mode == Gwdummy) + continue; + if (selected(x, y1)) + new.mode ^= Greverse; + if (i > 0 && GLYPHCMP(base, new)) { + xdrawglyphfontspecs(specs, base, i, ox, y1); + specs += i; + numspecs -= i; + i = 0; + } + if (i == 0) { + ox = x; + base = new; + } + i++; + } + if (i > 0) + xdrawglyphfontspecs(specs, base, i, ox, y1); +} + +void +xfinishdraw(void) +{ + XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, + win.h, 0, 0); + XSetForeground(xw.dpy, dc.gc, + dc.col[IS_SET(Wreverse)? + defaultfg : defaultbg].pixel); +} + +void +xximspot(int x, int y) +{ + if (xw.ime.xic == nil) + return; + + xw.ime.spot.x = borderpx + x * win.cw; + xw.ime.spot.y = borderpx + (y + 1) * win.ch; + + XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, nil); +} + +void +expose(XEvent *ev) +{ + redraw(); +} + +void +visibility(XEvent *ev) +{ + XVisibilityEvent *e = &ev->xvisibility; + + MODBIT(win.mode, e->state != VisibilityFullyObscured, Wvisible); +} + +void +unmap(XEvent *ev) +{ + win.mode &= ~Wvisible; +} + +void +xsetpointermotion(int set) +{ + MODBIT(xw.attrs.event_mask, set, PointerMotionMask); + XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); +} + +void +xsetmode(int set, uint flags) +{ + int mode = win.mode; + MODBIT(win.mode, set, flags); + if ((win.mode & Wreverse) != (mode & Wreverse)) + redraw(); +} + +int +xsetcursor(int cursor) +{ + if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */ + return 1; + win.cursor = cursor; + return 0; +} + +void +xseturgency(int add) +{ + XWMHints *h = XGetWMHints(xw.dpy, xw.win); + + MODBIT(h->flags, add, XUrgencyHint); + XSetWMHints(xw.dpy, xw.win, h); + XFree(h); +} + +void +xbell(void) +{ + if (!(IS_SET(Wfocused))) + xseturgency(1); + if (bellvolume) + XkbBell(xw.dpy, xw.win, bellvolume, (Atom)nil); +} + +void +focus(XEvent *ev) +{ + XFocusChangeEvent *e = &ev->xfocus; + + if (e->mode == NotifyGrab) + return; + + if (ev->type == FocusIn) { + if (xw.ime.xic) + XSetICFocus(xw.ime.xic); + win.mode |= Wfocused; + xseturgency(0); + if (IS_SET(Wfocus)) + ttywrite("\033[I", 3, 0); + } else { + if (xw.ime.xic) + XUnsetICFocus(xw.ime.xic); + win.mode &= ~Wfocused; + if (IS_SET(Wfocus)) + ttywrite("\033[O", 3, 0); + } +} + +int +match(uint mask, uint state) +{ + return mask == XK_ANY_MOD || mask == (state & ~ignoremod); +} + +char* +kmap(KeySym k, uint state) +{ + Key *kp; + int i; + + /* Check for mapped keys out of X11 function keys. */ + for (i = 0; i < arrlen(mappedkeys); i++) { + if (mappedkeys[i] == k) + break; + } + if (i == arrlen(mappedkeys)) { + if ((k & 0xFFFF) < 0xFD00) + return nil; + } + + for (kp = key; kp < key + arrlen(key); kp++) { + if (kp->k != k) + continue; + + if (!match(kp->mask, state)) + continue; + + if (IS_SET(Wappkeypad) ? kp->appkey < 0 : kp->appkey > 0) + continue; + if (IS_SET(Wnumlock) && kp->appkey == 2) + continue; + + if (IS_SET(Wappcursor) ? kp->appcursor < 0 : kp->appcursor > 0) + continue; + + return kp->s; + } + + return nil; +} + +void +kpress(XEvent *ev) +{ + XKeyEvent *e = &ev->xkey; + KeySym ksym; + char buf[64], *customkey; + int len; + rune c; + Status status; + Shortcut *bp; + + if (IS_SET(Wkbdblock)) + return; + + if (xw.ime.xic) + len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); + else + len = XLookupString(e, buf, sizeof buf, &ksym, nil); + /* 1. shortcuts */ + for (bp = shortcuts; bp < shortcuts + arrlen(shortcuts); bp++) { + if (ksym == bp->keysym && match(bp->mod, e->state)) { + bp->func(&(bp->arg)); + return; + } + } + + /* 2. custom keys from config.h */ + if ((customkey = kmap(ksym, e->state))) { + ttywrite(customkey, strlen(customkey), 1); + return; + } + + /* 3. composed string from input method */ + if (len == 0) + return; + if (len == 1 && e->state & Mod1Mask) { + if (IS_SET(W8bit)) { + if (*buf < 0177) { + c = *buf | 0x80; + len = utf8·encode(&c, buf); + } + } else { + buf[1] = buf[0]; + buf[0] = '\033'; + len = 2; + } + } + ttywrite(buf, len, 1); +} + +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) { + win.mode |= Wfocused; + xseturgency(0); + } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { + win.mode &= ~Wfocused; + } + } else if (e->xclient.data.l[0] == xw.wmdeletewin) { + ttyhangup(); + exit(0); + } +} + +void +resize(XEvent *e) +{ + if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) + return; + + cresize(e->xconfigure.width, e->xconfigure.height); +} + +void +run(void) +{ + XEvent ev; + int w = win.w, h = win.h; + fd_set rfd; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; + + /* Waiting for window mapping */ + do { + 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)) + continue; + if (ev.type == ConfigureNotify) { + w = ev.xconfigure.width; + h = ev.xconfigure.height; + } + } while (ev.type != MapNotify); + + ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); + cresize(w, h); + + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { + FD_ZERO(&rfd); + FD_SET(ttyfd, &rfd); + FD_SET(xfd, &rfd); + + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : nil; + + if (pselect(MAX(xfd, ttyfd)+1, &rfd, nil, nil, tv, nil) < 0) { + if (errno == EINTR) + continue; + fatal("select failed: %s\n", strerror(errno)); + } + clock_gettime(CLOCK_MONOTONIC, &now); + + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); + + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); + } + + /* + * 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 minlatency ms. 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(Gblink)) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= Wblink; + win.mode ^= Wblink; + tsetdirtattr(Gblink); + lastblink = now; + timeout = blinktimeout; + } + } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +void +usage(void) +{ + fatal("usage: %s [-aiv] [-c class] [-f font] [-A alpha] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); +} + +int +main(int argc, char *argv[]) +{ + xw.l = xw.t = 0; + xw.isfixed = False; + xsetcursor(cursorshape); + + ARGBEGIN { + case 'a': + allowaltscreen = 0; + break; + case 'A': + opt_alpha = EARGF(usage()); + break; + case 'c': + opt_class = EARGF(usage()); + break; + case 'e': + if (argc > 0) + --argc, ++argv; + goto run; + case 'f': + opt_font = EARGF(usage()); + break; + case 'g': + xw.gm = XParseGeometry(EARGF(usage()), + &xw.l, &xw.t, &cols, &rows); + break; + case 'i': + xw.isfixed = 1; + break; + case 'o': + opt_io = EARGF(usage()); + break; + case 'l': + opt_line = EARGF(usage()); + break; + case 'n': + opt_name = EARGF(usage()); + break; + case 't': + case 'T': + opt_title = EARGF(usage()); + break; + case 'w': + opt_embed = EARGF(usage()); + break; + case 'v': + fatal("%s " VERSION "\n", argv0); + break; + default: + usage(); + } ARGEND; + +run: + if (argc > 0) /* eat all remaining arguments */ + opt_cmd = argv; + + if (!opt_title) + opt_title = (opt_line || !opt_cmd) ? "term" : opt_cmd[0]; + + setlocale(LC_CTYPE, ""); + XSetLocaleModifiers(""); + + cols = MAX(cols, 1); + rows = MAX(rows, 1); + + tnew(cols, rows); + xinit(cols, rows); + xsetenv(); + selinit(); + + run(); + + return 0; +} diff --git a/src/cmd/walk/rules.mk b/src/cmd/walk/rules.mk new file mode 100644 index 0000000..cb9768c --- /dev/null +++ b/src/cmd/walk/rules.mk @@ -0,0 +1,15 @@ +include share/push.mk + +# local sources +SRCS_$(d):=$(d)/walk.c + +# local outputs +BINS_$(d):=$(d)/walk + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/walk/walk.c b/src/cmd/walk/walk.c new file mode 100644 index 0000000..c68d6e0 --- /dev/null +++ b/src/cmd/walk/walk.c @@ -0,0 +1,84 @@ +#include +#include + +static char buf[4*1024], *c = buf; /* should be greater or equal to PATH_MAX */ + +static +void +flush(void) +{ + *c = 0; + puts(buf); + c = buf; +} + +static +int +print(void *data, char *rel, char *abs, io·Stat *info) +{ +copy: + while (*abs && c < (arrend(buf)-2)) + *c++ = *abs++; + + if (*abs) { + flush(); + goto copy; + } + *c++ = '\n'; + + return 0; +} + +static +void +usage(void) +{ + fprintf(stderr, "usage: walk [-dlpv] file ...\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int i, f = fs·nolinks, err, max = 0; + char *p; + static fs·Walker walker; + + ARGBEGIN{ + case 'd': + max = atoi(ARGF()); + break; + case 'l': + f ^= fs·nolinks; + break; + case 'p': + f |= fs·preorder; + break; + case 'v': + f |= fs·verbose; + break; + default: + usage(); + }ARGEND; + + walker.flags = f; + walker.func = print; + walker.data = nil; + walker.max = max; + + if (argc == 0) { + fs·init(&walker, ""); + fs·walk(&walker); + return(err = walker.err); + } else { + err = 0; + for (i=0; ix, server.cursor.dot->y))) + return; + + floating(server.grab.client, 1); +} + +void +move_client(Arg *arg) +{ + grab_client(); + server.cursor.mode = CursorMove; + + server.grab.x = server.cursor.dot->x - server.grab.client->geometry.x; + server.grab.y = server.cursor.dot->y - server.grab.client->geometry.y; + wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "fleur", server.cursor.dot); +} + +void +float_client(Arg *arg) +{ + Client *client = selected_client(); + wlr_log(WLR_DEBUG, "client selected = %lx", (uintptr)client); + if(!client) + return; + + floating(client, client->isfloating ? 0 : 1); +} + +void +resize_client(Arg *arg) +{ + double x, y; + struct wlr_box geometry; + + grab_client(); + server.cursor.mode = CursorResize; + + wlr_xdg_surface_get_geometry(server.grab.client->xdg, &geometry); + + x = server.grab.client->geometry.x + geometry.x + geometry.width; + y = server.grab.client->geometry.y + geometry.y + geometry.height; + + server.grab.x = server.cursor.dot->x - x; + server.grab.y = server.cursor.dot->y - y; + + server.grab.box = geometry; + server.grab.box.x += server.grab.client->geometry.x; + server.grab.box.y += server.grab.client->geometry.y; +} + +// ----------------------------------------------------------------------- +// core + +static inline +void +activate(struct wlr_surface *surface, int state) +{ +} + +void +focus(Client *client, int lift) +{ + struct wlr_xdg_surface *xdg; + struct wlr_surface *old, *new; + struct wlr_keyboard *keyboard; + + if(!client) { + wlr_seat_keyboard_notify_clear_focus(server.input.seat); + return; + } + + old = server.input.seat->keyboard_state.focused_surface; + + if(lift) { + wl_list_remove(&client->stack); + wl_list_insert(&server.client.stack, &client->stack); + } + + new = client->xdg->surface; + if(old==new) + return; + + wl_list_remove(&client->focus); + wl_list_insert(&server.client.focus, &client->focus); + server.monitor.selected = client->monitor; + client->isurgent = 0; + + if(old) { + if(wlr_surface_is_xdg_surface(old)) { + xdg = wlr_xdg_surface_from_wlr_surface(old); + wlr_xdg_toplevel_set_activated(xdg, false); + } + } + + keyboard = wlr_seat_get_keyboard(server.input.seat); + + wlr_seat_keyboard_notify_enter(server.input.seat, new, + keyboard->keycodes, + keyboard->num_keycodes, + &keyboard->modifiers + ); + + wlr_xdg_toplevel_set_activated(client->xdg, true); +} + +Client* +client_at(double x, double y) +{ + Client *client; + wl_list_for_each(client, &server.client.stack, stack) + if(VISIBLE_ON(client, client->monitor) && wlr_box_contains_point(&client->geometry, x, y)) + return client; + return nil; +} + +static +int +has(Client *client, double lx, double ly, struct wlr_surface **surface, double *sx, double *sy) +{ + double x, y, vsx = lx - client->geometry.x, vsy = ly - client->geometry.y; + struct wlr_surface *find = nil; + + find = wlr_xdg_surface_surface_at(client->xdg, vsx, vsy, &x, &y); + if(find) { + *sx = x; + *sy = y; + *surface = find; + return true; + } + + return false; +} + +struct wlr_surface * +client_surface_at(Client *client, double cx, double cy, double *sx, double *sy) +{ + return wlr_xdg_surface_surface_at(client->xdg, cx, cy, sx, sy); +} + + +static +void +constrain(Client *client, struct wlr_box *box) +{ + client->geometry.width = MAX(1, client->geometry.width); + client->geometry.height = MAX(1, client->geometry.height); + + if(client->geometry.x >= box->x + box->width) + client->geometry.x = box->x + box->width - client->geometry.width; + if(client->geometry.y >= box->y + box->height) + client->geometry.y = box->y + box->height - client->geometry.height; + if(client->geometry.x + client->geometry.width + 2*client->border <= box->x) + client->geometry.x = box->x; + if(client->geometry.y + client->geometry.height + 2*client->border <= box->y) + client->geometry.y = box->y; +} + +void +resize(Client *client, int x, int y, int w, int h, int interact) +{ + struct wlr_box *box = interact ? &server.monitor.geometry : &client->monitor->window; + + client->geometry.x = x; + client->geometry.y = y; + client->geometry.width = w; + client->geometry.height = h; + + constrain(client, box); + + client->resize = wlr_xdg_toplevel_set_size(client->xdg, + client->geometry.width - 2*client->border, + client->geometry.height - 2*client->border + ); +} + +void +attach(Client *client, Monitor *monitor, uint tags) +{ + Monitor *old = client->monitor; + if(old == monitor) + return; + + client->monitor = monitor; + + if(old) { + wlr_surface_send_leave(client->xdg->surface, old->output); + arrange(old); + } + + if(monitor) { + /* make sure window actually overlaps with the monitor */ + constrain(client, &monitor->geometry); + wlr_surface_send_enter(client->xdg->surface, monitor->output); + client->tags = tags ? tags : monitor->tag.set[monitor->tag.selected]; + arrange(monitor); + } + + focus(focused_client(server.monitor.selected), 1); +} + +void +rules(Client *client) +{ + /* rule matching */ + Rule *rule; + uint i, tags; + char *id, *title; + Monitor *monitor, *it; + + monitor = server.monitor.selected; + + if (!(id=client->xdg->toplevel->app_id)) + id = broken; + if (!(title=client->xdg->toplevel->title)) + title = broken; + + for(tags=0, rule=cfg·rule; rule != cfg·endrule; ++rule) { + if ((!rule->title || strstr(title, rule->title)) + && (!rule->id || strstr(id, rule->id))) { + client->isfloating = rule->isfloating; + tags |= rule->tags; + i = 0; + wl_list_for_each(it, &server.monitor.list, link) + if(rule->monitor == i++) + monitor = it; + } + } + + attach(client, monitor, tags); +} + +void +floating(Client *client, int state) +{ + wlr_log(WLR_DEBUG, "client %lx, floating = %d", (uintptr)client, state); + client->isfloating = state; + arrange(client->monitor); +} + +Client * +selected_client(void) +{ + Client *client = wl_container_of(server.client.focus.next, client, focus); + if(wl_list_empty(&server.client.focus) || !VISIBLE_ON(client, server.monitor.selected)) + return nil; + return client; +} + +void +request_activate(struct wl_listener *l, void *data) +{ + struct wlr_xdg_activation_v1_request_activate_event *event = data; + Client *client; + + if (!wlr_surface_is_xdg_surface(event->surface)) + return; + + client = wlr_xdg_surface_from_wlr_surface(event->surface)->data; + if(client != selected_client()) + client->isurgent = 1; +} diff --git a/src/cmd/wm/config.h b/src/cmd/wm/config.h new file mode 100644 index 0000000..1f5ba85 --- /dev/null +++ b/src/cmd/wm/config.h @@ -0,0 +1,70 @@ +/* appearance */ +CONFIG(int, sloppyfocus, 1); +CONFIG(int, borderpixel, 1); +CONFIG(float, rootcolor[], {0.3, 0.3, 0.3, 1.0}); +CONFIG(float, bordercolor[], {0.5, 0.5, 0.5, 1.0}); +CONFIG(float, focuscolor[], {1.0, 0.0, 0.0, 1.0}); + +/* sampling */ +CONFIG(int, repeat_rate, 25); +CONFIG(int, repeat_delay, 600); + +/* tags */ +CONFIG(char*, tags[], { "1", "2", "3", "4", "5", "6", "7", "8", "9" }); + +/* application specific rules */ +CONFIG(Rule, rule[], { + /* app_id title tags mask isfloating monitor */ + /* examples: + { "Gimp", nil, 0, 1, -1 }, + { "firefox", nil, 1 << 8, 0, -1 }, + */ +}); +CONFIG(Rule*, endrule, arrend(cfg·rule)); + +/* commands */ +CONFIG(char*, termcommand[], { "alacritty", nil }); +CONFIG(char*, menucommand[], { "dmenu-wl_run", nil }); + +/* layouts */ +CONFIG(Layout, layouts[], { + /* symbol arrange */ + { "[]=", tile }, + { "><>", nil }, /* no layout function means floating behavior */ +}); +CONFIG(Layout*, endlayout, arrend(cfg·layouts)); + +/* monitors + * The order in which monitors are defined determines their position. + * non-configured monitors are always added to the left. */ +CONFIG(MonitorRule, monitorrule[], { + /* name layout, x, y, scale, transform master */ + { nil, &cfg·layouts[0], 0, 0, 1, WL_OUTPUT_TRANSFORM_NORMAL, {0.55, 1} }, +}); +CONFIG(MonitorRule*, endmonitorrule, arrend(cfg·monitorrule)); + +/* keybindings */ +#define MODKEY WLR_MODIFIER_ALT +#define MOD(a) WLR_MODIFIER_##a +#define KEY(a) XKB_KEY_##a + +CONFIG(Key, binding[], { + /* modifier key function argument */ + { MODKEY, KEY(Return), spawn, {.v = cfg·termcommand} }, + { MODKEY, KEY(d), spawn, {.v = cfg·menucommand} }, + { MODKEY|MOD(SHIFT), KEY(Q), quit, {.v = nil} }, +}); +CONFIG(Key*, endbinding, arrend(cfg·binding)); + +#undef MOD +#undef KEY + +/* mouse buttons */ +CONFIG(Button, button[], { + { MODKEY, BTN_LEFT, move_client, {0} }, + { MODKEY, BTN_MIDDLE, float_client, {0} }, + { MODKEY, BTN_RIGHT, resize_client, {0} }, +}); +CONFIG(Button*, endbutton, arrend(cfg·button)); + +#undef MODKEY diff --git a/src/cmd/wm/input.c b/src/cmd/wm/input.c new file mode 100644 index 0000000..4c6bfd4 --- /dev/null +++ b/src/cmd/wm/input.c @@ -0,0 +1,316 @@ +#include "wm.h" + +// ----------------------------------------------------------------------- +// keyboard + +static +void +keymodifier(struct wl_listener *l, void *data) +{ + Keyboard *keyboard = wl_container_of(l, keyboard, event.modify); + + wlr_seat_set_keyboard(server.input.seat, keyboard->device); + wlr_seat_keyboard_notify_modifiers(server.input.seat, &keyboard->device->keyboard->modifiers); +} + +static +int +keybinding(uint32 modifier, xkb_keysym_t sym) +{ + Key *key; + + for(key=cfg·binding; key!=cfg·endbinding; ++key) { + if(modifier == key->modifier && sym == key->sym && key->action){ + key->action(&key->arg); + return 1; + } + } + return 0; +} + +static +void +keypress(struct wl_listener *l, void *data) +{ + int i,h,n; + uint32 keycode, modifier; + const xkb_keysym_t *syms; + struct Keyboard *keyboard = wl_container_of(l, keyboard, event.press); + struct wlr_event_keyboard_key *event = data; + + keycode = event->keycode + 8; + + h = 0; + n = xkb_state_key_get_syms(keyboard->device->keyboard->xkb_state, keycode, &syms); + + modifier = wlr_keyboard_get_modifiers(keyboard->device->keyboard); + if(event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { + for(i=0; idevice); + wlr_seat_keyboard_notify_key(server.input.seat, event->time_msec, event->keycode, event->state); + } +} + +static +void +free_keyboard(struct wl_listener *l, void *data) +{ + struct wlr_input_device *device = data; + Keyboard *keyboard = device->data; + + /* XXX: debug + wl_list_remove(&keyboard->link); + wl_list_remove(&keyboard->event.modify.link); + wl_list_remove(&keyboard->event.press.link); + wl_list_remove(&keyboard->event.destroy.link); + + free(keyboard); + */ +} + +static +void +make_keyboard(struct wlr_input_device *device) +{ + Keyboard *keyboard; + struct xkb_context *context; + struct xkb_keymap *keymap; + + keyboard = device->data = calloc(1, sizeof(*keyboard)); + keyboard->device = device; + + context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + keymap = xkb_keymap_new_from_names(context, nil, XKB_KEYMAP_COMPILE_NO_FLAGS); + + wlr_keyboard_set_keymap(device->keyboard, keymap); + + xkb_keymap_unref(keymap); + xkb_context_unref(context); + + wlr_keyboard_set_repeat_info(device->keyboard, cfg·repeat_rate, cfg·repeat_delay); + + keyboard->event.modify.notify = keymodifier; + wl_signal_add(&device->keyboard->events.modifiers, &keyboard->event.modify); + + keyboard->event.press.notify = keypress; + wl_signal_add(&device->keyboard->events.key, &keyboard->event.press); + + keyboard->event.destroy.notify = free_keyboard; + wl_signal_add(&device->keyboard->events.destroy, &keyboard->event.destroy); + + wlr_seat_set_keyboard(server.input.seat, device); + + wl_list_insert(&server.input.keyboards, &keyboard->link); +} + +// ----------------------------------------------------------------------- +// cursor + +static +void +focus_surface(Client *client, struct wlr_surface *surface, double sx, double sy, uint32 time) +{ + struct timespec now; + int lift = time; + + if(client && !surface) + surface = client->xdg->surface; + + if(!surface){ + wlr_seat_pointer_notify_clear_focus(server.input.seat); + return; + } + + if(!time) { + clock_gettime(CLOCK_MONOTONIC, &now); + time = now.tv_sec * 1000 + now.tv_nsec / 1000000; + } + + if(surface == server.input.seat->pointer_state.focused_surface) { + wlr_seat_pointer_notify_motion(server.input.seat, time, sx, sy); + return; + } + + wlr_seat_pointer_notify_enter(server.input.seat, surface, sx, sy); + + if(cfg·sloppyfocus && lift) + focus(client, 0); +} + +void +notify_move(uint32 time) +{ + double sx, sy; + Client *client; + struct wlr_box box; + struct wlr_surface *surface; + + if(time) { + wlr_idle_notify_activity(server.input.idle, server.input.seat); + if(cfg·sloppyfocus) + server.monitor.selected = monitor_at(server.cursor.dot->x, server.cursor.dot->y); + } + + if(server.cursor.mode == CursorMove) { + resize(server.grab.client, + server.cursor.dot->x - server.grab.x, + server.cursor.dot->y - server.grab.y, + server.grab.client->geometry.width, + server.grab.client->geometry.height, + 1 + ); + return; + } + + if(server.cursor.mode == CursorResize) { + wlr_xdg_surface_get_geometry(server.grab.client->xdg, &box); + resize(server.grab.client, + server.grab.box.x - box.x, + server.grab.box.y - box.y, + server.cursor.dot->x - server.grab.x - server.grab.box.x, + server.cursor.dot->y - server.grab.y - server.grab.box.y, + 1 + ); + return; + } + + /* otherwise, find the client under the pointer and send the event along. */ + client = client_at(server.cursor.dot->x, server.cursor.dot->y); + if(!client) { + wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot); + return; + } + + surface = client_surface_at( + client, + server.cursor.dot->x - client->geometry.x - client->border, + server.cursor.dot->y - client->geometry.y - client->border, + &sx, &sy + ); + + focus_surface(client, surface, sx, sy, time); +} + +void +cursor_move(struct wl_listener *l, void *data) +{ + struct wlr_event_pointer_motion *event = data; + wlr_cursor_move(server.cursor.dot, event->device, event->delta_x, event->delta_y); + notify_move(event->time_msec); +} + +void +cursor_move_abs(struct wl_listener *l, void *data) +{ + struct wlr_event_pointer_motion_absolute *event = data; + wlr_cursor_warp_absolute(server.cursor.dot, event->device, event->x, event->y); + notify_move(event->time_msec); +} + +void +cursor_button(struct wl_listener *l, void *data) +{ + Client *client; + uint32 modifier; + Button *button; + struct wlr_keyboard *keyboard; + struct wlr_event_pointer_button *event = data; + + wlr_idle_notify_activity(server.input.idle, server.input.seat); + + switch(event->state) { + case WLR_BUTTON_PRESSED: + if((client=client_at(server.cursor.dot->x, server.cursor.dot->y))) + focus(client,1); + + keyboard = wlr_seat_get_keyboard(server.input.seat); + modifier = wlr_keyboard_get_modifiers(keyboard); + for(button=cfg·button; button != cfg·endbutton; ++button) { + if(modifier == button->modifier && event->button == button->code && button->function) { + button->function(&button->arg); + return; + } + } + break; + case WLR_BUTTON_RELEASED: + if(server.cursor.mode != CursorNormal) { + wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot); + server.cursor.mode = CursorNormal; + /* Drop the window off on its new monitor */ + server.monitor.selected = monitor_at(server.cursor.dot->x, server.cursor.dot->y); + attach(server.grab.client, server.monitor.selected, 0); + return; + } + } + + wlr_seat_pointer_notify_button(server.input.seat, event->time_msec, event->button, event->state); +} + +void +cursor_axis(struct wl_listener *l, void *data) +{ + struct wlr_event_pointer_axis *event = data; + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(server.input.seat, + event->time_msec, event->orientation, event->delta, + event->delta_discrete, event->source); +} + +void +cursor_frame(struct wl_listener *l, void *data) +{ + wlr_seat_pointer_notify_frame(server.input.seat); +} + +void +request_cursor(struct wl_listener *l, void *data) +{ + struct wlr_seat_pointer_request_set_cursor_event *event = data; + struct wlr_seat_client *focused = server.input.seat->pointer_state.focused_client; + if(focused == event->seat_client) + wlr_cursor_set_surface(server.cursor.dot, event->surface, event->hotspot_x, event->hotspot_y); +} + +void +request_set_selection(struct wl_listener *l, void *data) +{ + struct wlr_seat_request_set_selection_event *event = data; + wlr_seat_set_selection(server.input.seat, event->source, event->serial); +} + +static +void +make_pointer(struct wlr_input_device *device) +{ + wlr_cursor_attach_input_device(server.cursor.dot, device); +} + +// ----------------------------------------------------------------------- +// generic input + +void +make_input(struct wl_listener *l, void *data) +{ + uint32 capability; + struct wlr_input_device *device = data; + + switch(device->type) { + case WLR_INPUT_DEVICE_KEYBOARD: + make_keyboard(device); + break; + case WLR_INPUT_DEVICE_POINTER: + make_pointer(device); + /* fallthrough */ + default: + break; + } + + capability = WL_SEAT_CAPABILITY_POINTER; + if(!wl_list_empty(&server.input.keyboards)) + capability |= WL_SEAT_CAPABILITY_KEYBOARD; + wlr_seat_set_capabilities(server.input.seat, capability); +} diff --git a/src/cmd/wm/layer.c b/src/cmd/wm/layer.c new file mode 100644 index 0000000..bfac744 --- /dev/null +++ b/src/cmd/wm/layer.c @@ -0,0 +1,107 @@ +#include "wm.h" + +static +void +map(struct wl_listener *l, void *data) +{ + Layer *layer = wl_container_of(l, layer, event.map); + wlr_surface_send_enter(layer->surface->surface, layer->surface->output); + notify_move(0); +} + +static +void +finalize(Layer *layer) +{ + layer->surface->mapped = 0; + if (layer->surface->surface == server.input.seat->keyboard_state.focused_surface) + focus(selected_client(), 1); + notify_move(0); +} + +static +void +unmap(struct wl_listener *l, void *data) +{ + Layer *layer = wl_container_of(l, layer, event.unmap); + finalize(layer); +} + +static +void +destroy(struct wl_listener *l, void *data) +{ + Monitor *monitor; + Layer *layer = wl_container_of(l, layer, event.destroy); + + if (layer->surface->mapped) + finalize(layer); + + wl_list_remove(&layer->link); + wl_list_remove(&layer->event.destroy.link); + wl_list_remove(&layer->event.map.link); + wl_list_remove(&layer->event.unmap.link); + wl_list_remove(&layer->event.commit.link); + + if(layer->surface->output) { + monitor = layer->surface->output->data; + if(monitor) + stratify(monitor); + layer->surface->output = nil; + } + free(layer); +} + +static +void +commit(struct wl_listener *l, void *data) +{ + Monitor *monitor; + Layer *layer = wl_container_of(l, layer, event.commit); + struct wlr_layer_surface_v1 *surface = layer->surface; + struct wlr_output *output = surface->output; + + if(!output) + return; + + monitor = output->data; + stratify(monitor); + + if (layer->type != surface->current.layer) { + wl_list_remove(&layer->link); + wl_list_insert(&monitor->layer[surface->current.layer], &layer->link); + layer->type = surface->current.layer; + } +} + +void +make_layer_surface(struct wl_listener *l, void *data) +{ + Layer *layer; + Monitor *monitor; + struct wlr_layer_surface_v1_state state; + struct wlr_layer_surface_v1 *surface = data; + + if(!surface->output) + surface->output = server.monitor.selected->output; + + layer = surface->data = calloc(1, sizeof(*layer)); + layer->surface = surface; + + layer->event.map.notify = map; + wl_signal_add(&surface->events.map, &layer->event.map); + layer->event.unmap.notify = unmap; + wl_signal_add(&surface->events.unmap, &layer->event.unmap); + layer->event.destroy.notify = destroy; + wl_signal_add(&surface->events.destroy, &layer->event.destroy); + layer->event.commit.notify = commit; + wl_signal_add(&surface->surface->events.commit, &layer->event.commit); + + monitor = surface->output->data; + wl_list_insert(&monitor->layer[surface->client_pending.layer], &layer->link); + + state = surface->current; + surface->current = surface->client_pending; + stratify(monitor); + surface->current = state; +} diff --git a/src/cmd/wm/main.c b/src/cmd/wm/main.c new file mode 100644 index 0000000..2607801 --- /dev/null +++ b/src/cmd/wm/main.c @@ -0,0 +1,177 @@ +#include "wm.h" + +Server server = { + .event = { + .make_input = { .notify = make_input }, + .make_monitor = { .notify = make_monitor }, + .make_xdg_surface = { .notify = make_xdg_surface }, + .make_layer_surface = { .notify = make_layer_surface }, + + .monitor_change = { .notify = monitor_change }, + .monitor_test = { .notify = monitor_test }, + .monitor_apply = { .notify = monitor_apply }, + + .cursor_move = { .notify = cursor_move }, + .cursor_move_abs = { .notify = cursor_move_abs }, + .cursor_button = { .notify = cursor_button }, + .cursor_axis = { .notify = cursor_axis }, + .cursor_frame = { .notify = cursor_frame }, + + .request_activate = { .notify = request_activate }, + .request_cursor = { .notify = request_cursor }, + .request_set_selection = { .notify = request_set_selection }, + }, +}; + +// ----------------------------------------------------------------------- +// helper functions + +static inline +void +init(void) +{ + /* compositor initialization */ + server.display = wl_display_create(); + server.backend = wlr_backend_autocreate(server.display); + server.renderer = wlr_backend_get_renderer(server.backend); + server.present = wlr_presentation_create(server.display, server.backend); + + wlr_renderer_init_wl_display(server.renderer, server.display); + + wlr_compositor_create(server.display, server.renderer); + wlr_export_dmabuf_manager_v1_create(server.display); + wlr_screencopy_manager_v1_create(server.display); + wlr_data_control_manager_v1_create(server.display); + wlr_data_device_manager_create(server.display); + wlr_gamma_control_manager_v1_create(server.display); + wlr_primary_selection_v1_device_manager_create(server.display); + wlr_viewporter_create(server.display); + + server.activate = wlr_xdg_activation_v1_create(server.display); + wl_signal_add(&server.activate->events.request_activate, &server.event.request_activate); + + wlr_data_device_manager_create(server.display); + + server.monitor.layout = wlr_output_layout_create(); + wl_signal_add(&server.monitor.layout->events.change, &server.event.monitor_change); + wlr_xdg_output_manager_v1_create(server.display, server.monitor.layout); + + wl_list_init(&server.monitor.list); + wl_signal_add(&server.backend->events.new_output, &server.event.make_monitor); + + server.monitor.manager = wlr_output_manager_v1_create(server.display); + wl_signal_add(&server.monitor.manager->events.test, &server.event.monitor_test); + wl_signal_add(&server.monitor.manager->events.apply, &server.event.monitor_apply); + + /* shell initialization */ + wl_list_init(&server.client.list); + wl_list_init(&server.client.stack); + wl_list_init(&server.client.focus); + + server.shell.xdg = wlr_xdg_shell_create(server.display); + wl_signal_add(&server.shell.xdg->events.new_surface, &server.event.make_xdg_surface); + + server.shell.layer = wlr_layer_shell_v1_create(server.display); + wl_signal_add(&server.shell.layer->events.new_surface, &server.event.make_layer_surface); + + wlr_server_decoration_manager_set_default_mode( + wlr_server_decoration_manager_create(server.display), + WLR_SERVER_DECORATION_MANAGER_MODE_SERVER + ); + wlr_xdg_decoration_manager_v1_create(server.display); + + /* input initialization */ + server.cursor.dot = wlr_cursor_create(); + wlr_cursor_attach_output_layout(server.cursor.dot, server.monitor.layout); + + server.cursor.manager = wlr_xcursor_manager_create(nil, 24); + wlr_xcursor_manager_load(server.cursor.manager, 1); + + wl_signal_add(&server.cursor.dot->events.motion, &server.event.cursor_move); + wl_signal_add(&server.cursor.dot->events.motion_absolute, &server.event.cursor_move_abs); + wl_signal_add(&server.cursor.dot->events.button, &server.event.cursor_button); + wl_signal_add(&server.cursor.dot->events.axis, &server.event.cursor_axis); + wl_signal_add(&server.cursor.dot->events.frame, &server.event.cursor_frame); + + wl_list_init(&server.input.keyboards); + wl_signal_add(&server.backend->events.new_input, &server.event.make_input); + + server.input.idle = wlr_idle_create(server.display); + server.input.seat = wlr_seat_create(server.display, "seat0"); + + wl_signal_add(&server.input.seat->events.request_set_cursor, &server.event.request_cursor); + wl_signal_add(&server.input.seat->events.request_set_selection, &server.event.request_set_selection); +} + +static inline +void +fini(void) +{ + wl_display_destroy_clients(server.display); + + wlr_backend_destroy(server.backend); + wlr_xcursor_manager_destroy(server.cursor.manager); + wlr_output_layout_destroy(server.monitor.layout); + wlr_seat_destroy(server.input.seat); + + wl_display_destroy(server.display); +} + +// ----------------------------------------------------------------------- +// main point of entry + +int +usage(void) +{ + fprintf(stderr, "usage: %s [-s startup command]\n", argv0); + return 1; +} + + +int +main(int argc, char *argv[]) +{ + char *socket, *cmd=nil; + + ARGBEGIN{ + case 's': + cmd = ARGF(); + break; + default: + return usage(); + } ARGEND + + if(argc != 0) + return usage(); + + wlr_log_init(WLR_DEBUG, nil); + + init(); + + if(!(socket=(char*)wl_display_add_socket_auto(server.display))) { + wlr_backend_destroy(server.backend); + return 1; + } + + if(!(wlr_backend_start(server.backend))) { + wlr_backend_destroy(server.backend); + wl_display_destroy(server.display); + return 1; + } + + setenv("WAYLAND_DISPLAY", socket, true); + if(cmd) { + if(fork()==0) + execl("/bin/sh", "/bin/sh", "-c", cmd, nil); + } + wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s", socket); + + server.monitor.selected = monitor_at(server.cursor.dot->x, server.cursor.dot->y); + wlr_cursor_warp_closest(server.cursor.dot, nil, server.cursor.dot->x, server.cursor.dot->y); + wlr_xcursor_manager_set_cursor_image(server.cursor.manager, "left_ptr", server.cursor.dot); + + wl_display_run(server.display); /* event loop */ + + fini(); + return 0; +} diff --git a/src/cmd/wm/monitor.c b/src/cmd/wm/monitor.c new file mode 100644 index 0000000..93073f3 --- /dev/null +++ b/src/cmd/wm/monitor.c @@ -0,0 +1,386 @@ +#include "wm.h" + +/* callbacks */ +void +monitor_change(struct wl_listener *l, void *data) +{ + Monitor *monitor; + struct wlr_output_configuration_v1 *config; + + config = wlr_output_configuration_v1_create(); + server.monitor.geometry = *wlr_output_layout_get_box(server.monitor.layout, nil); + + wl_list_for_each(monitor, &server.monitor.list, link) { + struct wlr_output_configuration_head_v1 *head = + wlr_output_configuration_head_v1_create(config, monitor->output); + + monitor->geometry = monitor->window = *wlr_output_layout_get_box(server.monitor.layout, monitor->output); + + stratify(monitor); + arrange(monitor); + + head->state.enabled = monitor->output->enabled; + head->state.mode = monitor->output->current_mode; + head->state.x = monitor->geometry.x; + head->state.y = monitor->geometry.y; + } + + wlr_output_manager_v1_set_configuration(server.monitor.manager, config); +} + +static +void +trylayout(struct wlr_output_configuration_v1 *config, int force) +{ + int ok; + struct wlr_output_configuration_head_v1 *head; + + ok = 1; + wl_list_for_each(head, &config->heads, link) { + struct wlr_output *output= head->state.output; + wlr_output_enable(output, head->state.enabled); + if (head->state.enabled) { + if (head->state.mode) + wlr_output_set_mode(output, head->state.mode); + else + wlr_output_set_custom_mode( + output, + head->state.custom_mode.width, + head->state.custom_mode.height, + head->state.custom_mode.refresh + ); + + wlr_output_layout_move(server.monitor.layout, output, + head->state.x, head->state.y); + wlr_output_set_transform(output, head->state.transform); + } + + if(!(ok=wlr_output_test(output))) + break; + } + + wl_list_for_each(head, &config->heads, link) { + if(ok && force) + wlr_output_commit(head->state.output); + else + wlr_output_rollback(head->state.output); + } + + if(ok) + wlr_output_configuration_v1_send_succeeded(config); + else + wlr_output_configuration_v1_send_failed(config); + + wlr_output_configuration_v1_destroy(config); +} + +void +monitor_apply(struct wl_listener *l, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + trylayout(config, 1); +} + +void +monitor_test(struct wl_listener *l, void *data) +{ + struct wlr_output_configuration_v1 *config = data; + trylayout(config, 0); +} + +void +make_monitor(struct wl_listener *l, void *data) +{ + int i; + Client *client; + Monitor *monitor; + MonitorRule *rule; + struct wlr_output_mode *mode; + struct wlr_output *output = data; + + /* + * XXX: needed? + if (wl_list_empty(&output->modes)) + return; + */ + + monitor = output->data = calloc(1, sizeof(*monitor)); + monitor->output = output; + + for(i=0; i < arrlen(monitor->layer); i++) + wl_list_init(&monitor->layer[i]); + monitor->tag.set[0] = monitor->tag.set[1] = 1; + + for(rule=cfg·monitorrule; rule != cfg·endmonitorrule; ++rule) { + if(!rule->name || strstr(output->name, rule->name)) { + monitor->master.len = rule->master.len; + monitor->master.frac = rule->master.frac; + + wlr_output_set_scale(output, rule->scale); + wlr_xcursor_manager_load(server.cursor.manager, rule->scale); + monitor->layouts[0] = monitor->layouts[1] = monitor->layout = rule->layout; + + wlr_output_set_transform(output, rule->transform); + break; + } + } + + mode = wlr_output_preferred_mode(output); + wlr_output_set_mode(output, mode); + wlr_output_enable_adaptive_sync(output, true); + + monitor->event.render.notify = render_monitor; + wl_signal_add(&output->events.frame, &monitor->event.render); + monitor->event.destroy.notify = free_monitor; + wl_signal_add(&output->events.destroy, &monitor->event.destroy); + + wlr_output_enable(output, true); + if(!wlr_output_commit(output)) + return; + + wl_list_insert(&server.monitor.list, &monitor->link); + + wlr_output_layout_add(server.monitor.layout, output, rule->x, rule->y); + server.monitor.geometry = *wlr_output_layout_get_box(server.monitor.layout, nil); + + /* update the geometries of all monitors */ + wl_list_for_each(monitor, &server.monitor.list, link) { + /* first monitor in the list = most recently added */ + wl_list_for_each(client, &server.client.list, link) { + if(client->isfloating) + resize(client, client->geometry.x+monitor->window.width, client->geometry.y, + client->geometry.width, client->geometry.height, 0); + } + return; + } +} + +void +free_monitor(struct wl_listener *l, void *data) +{ + int i, len; + Client *client; + struct wlr_output *output = data; + Monitor *monitor = output->data; + + wl_list_remove(&monitor->event.destroy.link); + wl_list_remove(&monitor->event.render.link); + wl_list_remove(&monitor->link); + + wlr_output_layout_remove(server.monitor.layout, monitor->output); + + for(i=0, len=wl_list_length(&server.monitor.list); i < len; i++) { + server.monitor.selected = wl_container_of(server.monitor.list.prev, server.monitor.selected, link); + if(server.monitor.selected->output->enabled) + break; + } + + focus(focused_client(server.monitor.selected), 1); + + /* move closed monitor's clients to newly selected one */ + wl_list_for_each(client, &server.client.list, link) { + if(client->isfloating && client->geometry.x > monitor->geometry.width) + resize(client, + client->geometry.x - monitor->window.width, + client->geometry.y, + client->geometry.width, + client->geometry.height, + 0 + ); + if(client->monitor == monitor) + attach(client, monitor, client->tags); + } + + free(monitor); +} + +/* methods */ +void +arrange(Monitor *monitor) +{ + if(monitor->layout->arrange) + monitor->layout->arrange(monitor); +} + +void +stratum(Monitor *monitor, struct wl_list *list, struct wlr_box *area, int exclusive) +{ + Layer *layer; + struct wlr_box full = monitor->geometry; + + wl_list_for_each(layer, list, link) { + struct wlr_layer_surface_v1 *surface = layer->surface; + struct wlr_layer_surface_v1_state *state = &surface->current; + struct wlr_box bounds; + struct wlr_box box = { + .width = state->desired_width, + .height = state->desired_height + }; + const uint32 horizontal = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT + | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + const uint32 vertical = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP + | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + + if (exclusive != (state->exclusive_zone > 0)) + continue; + + bounds = state->exclusive_zone == -1 ? full : *area; + + // horizontal axis + if((state->anchor & horizontal) && box.width == 0) { + box.x = bounds.x; + box.width = bounds.width; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x = bounds.x; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x = bounds.x + (bounds.width - box.width); + } else { + box.x = bounds.x + ((bounds.width / 2) - (box.width / 2)); + } + + // vertical axis + if((state->anchor & vertical) && box.height == 0) { + box.y = bounds.y; + box.height = bounds.height; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y = bounds.y; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y = bounds.y + (bounds.height - box.height); + } else { + box.y = bounds.y + ((bounds.height / 2) - (box.height / 2)); + } + + // margin + if((state->anchor & horizontal) == horizontal) { + box.x += state->margin.left; + box.width -= state->margin.left + state->margin.right; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT)) { + box.x += state->margin.left; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT)) { + box.x -= state->margin.right; + } + + if((state->anchor & vertical) == vertical) { + box.y += state->margin.top; + box.height -= state->margin.top + state->margin.bottom; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP)) { + box.y += state->margin.top; + } else if((state->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM)) { + box.y -= state->margin.bottom; + } + if(box.width < 0 || box.height < 0) { + wlr_layer_surface_v1_close(surface); + continue; + } + layer->geometry = box; + + if (state->exclusive_zone > 0) + exclude(area, + state->anchor, state->exclusive_zone, + state->margin.top, state->margin.right, + state->margin.bottom, state->margin.left); + wlr_layer_surface_v1_configure(surface, box.width, box.height); + } +} + +void +stratify(Monitor *monitor) +{ + int i; + Layer *layer; + struct wlr_box area = monitor->geometry; + uint32_t overlays[] = { + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + ZWLR_LAYER_SHELL_V1_LAYER_TOP, + }; + struct wlr_keyboard *keyboard = wlr_seat_get_keyboard(server.input.seat); + + // arrange exclusive surfaces from top->bottom + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &area, 1); + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &area, 1); + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &area, 1); + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &area, 1); + + if(memcmp(&area, &monitor->window, sizeof(area))) { + monitor->window = area; + arrange(monitor); + } + + // arrange non-exlusive surfaces from top->bottom + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &area, 0); + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &area, 0); + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &area, 0); + stratum(monitor, &monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &area, 0); + + // find topmost keyboard interactive layer, if such a layer exists + for(i = 0; i < arrlen(overlays); i++) { + wl_list_for_each_reverse(layer, &monitor->layer[overlays[i]], link) { + if (layer->surface->current.keyboard_interactive && layer->surface->mapped) { + // Deactivate the focused client. + focus(nil, 0); + wlr_seat_keyboard_notify_enter( + server.input.seat, + layer->surface->surface, + keyboard->keycodes, + keyboard->num_keycodes, + &keyboard->modifiers + ); + return; + } + } + } +} + +Client * +focused_client(Monitor *monitor) +{ + Client *client; + wl_list_for_each(client, &server.client.focus, focus) { + if(VISIBLE_ON(client, monitor)) + return client; + } + + return nil; +} + +void +tile(Monitor *monitor) +{ + Client *client; + uint i, n, h, mw, my, ty; + + n = 0; + wl_list_for_each(client, &server.client.list, link) { + if(VISIBLE_ON(client, monitor) && !client->isfloating) + n++; + } + if(!n) return; + + if(n > monitor->master.len) + mw = monitor->master.len ? monitor->window.width * monitor->master.frac : 0; + else + mw = monitor->window.width; + + i = my = ty = 0; + wl_list_for_each(client, &server.client.list, link) { + if(!VISIBLE_ON(client,monitor) || client->isfloating || client->isfullscreen) + continue; + if(i < monitor->master.len) { + h = (monitor->window.height - my) / (MIN(n, monitor->master.len) - i); + resize(client, monitor->window.x, monitor->window.y + my, mw, h, 0); + my += client->geometry.height; + } else { + h = (monitor->window.height - ty) / (n - i); + resize(client, monitor->window.x + mw, monitor->window.y + ty, monitor->window.width - mw, h, 0); + ty += client->geometry.height; + } + i++; + } +} + +Monitor * +monitor_at(double x, double y) +{ + struct wlr_output *output = wlr_output_layout_output_at(server.monitor.layout, x, y); + return output ? output->data : nil; +} diff --git a/src/cmd/wm/protocol/sync b/src/cmd/wm/protocol/sync new file mode 100755 index 0000000..19a728a --- /dev/null +++ b/src/cmd/wm/protocol/sync @@ -0,0 +1,6 @@ +#!/bin/sh + +for base in wlr-layer-shell-unstable-v1.xml +do + curl https://raw.githubusercontent.com/swaywm/wlroots/master/protocol/$base --output $base +done diff --git a/src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml b/src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..d62fd51 --- /dev/null +++ b/src/cmd/wm/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/src/cmd/wm/render.c b/src/cmd/wm/render.c new file mode 100644 index 0000000..1f51804 --- /dev/null +++ b/src/cmd/wm/render.c @@ -0,0 +1,160 @@ +#include "wm.h" + +struct Payload +{ + Client *client; + struct wlr_output *output; + struct timespec *when; + int x, y; +}; + +static +void +render(struct wlr_surface *surface, int sx, int sy, void *data) +{ + float matrix[9]; + double x, y; + struct Payload *payload; + + struct wlr_box box; + struct wlr_output *output; + struct wlr_texture *texture; + + enum wl_output_transform transform; + + payload = data; + output = payload->output; + + texture = wlr_surface_get_texture(surface); + if(!texture) + return; + + x = 0, y = 0; + wlr_output_layout_output_coords(server.monitor.layout, output, &x, &y); + + box = (struct wlr_box) { + .x = x + payload->x + sx, + .y = y + payload->y + sy, + .width = surface->current.width, + .height = surface->current.height, + }; + scale_box(&box, output->scale); + + transform = wlr_output_transform_invert(surface->current.transform); + wlr_matrix_project_box(matrix, &box, transform, 0, output->transform_matrix); + + wlr_render_texture_with_matrix(server.renderer, texture, matrix, 1); + wlr_surface_send_frame_done(surface, payload->when); + wlr_presentation_surface_sampled_on_output(server.present, surface, output); +} + +static +void +render_layer(struct wl_list *list, struct timespec *now) +{ + Layer *layer; + wl_list_for_each(layer, list, link) { + struct Payload payload= { + .output = layer->surface->output, + .x = layer->geometry.x, + .y = layer->geometry.y, + .when = now, + }; + + wlr_surface_for_each_surface(layer->surface->surface, render, &payload); + } +} + +static +void +render_clients(Monitor *monitor, struct timespec *now) +{ + double x, y; + int i, w, h, bw; + float *color; + + Client *client; + struct wlr_output *output; + struct wlr_box *borders; + struct wlr_surface *surface; + + output = monitor->output; + wl_list_for_each_reverse(client, &server.client.stack, stack) { + if(!VISIBLE_ON(client, client->monitor)) + continue; + if(!wlr_output_layout_intersects(server.monitor.layout, monitor->output, &client->geometry)) + continue; + + surface = client->xdg->surface; + + x = client->geometry.x, y = client->geometry.y; + wlr_output_layout_output_coords(server.monitor.layout, output, &x, &y); + + if((bw=client->border)) { + w = surface->current.width; + h = surface->current.height; + borders = (struct wlr_box[4]) { + {x, y, w+2*bw, bw}, /* top */ + {x, y+bw, bw, h}, /* left */ + {x+bw+w, y+bw, bw, h}, /* right */ + {x, y+bw+h, w+2*bw, bw}, /* bottom */ + }; + + color = (client == server.selected) ? cfg·focuscolor : cfg·bordercolor; + for(i=0; i<4; i++) { + scale_box(&borders[i], output->scale); + wlr_render_rect(server.renderer, &borders[i], color, output->transform_matrix); + } + } + + struct Payload payload = { + .output = output, + .when = now, + + .x = client->geometry.x + client->border, + .y = client->geometry.y + client->border, + }; + + wlr_xdg_surface_for_each_surface(client->xdg, render, &payload); + } +} + +void +render_monitor(struct wl_listener *l, void *data) +{ + int w, h; + Client *client; + Monitor *monitor; + struct timespec now; + + clock_gettime(CLOCK_MONOTONIC, &now); + monitor = wl_container_of(l, monitor, event.render); + + wl_list_for_each(client, &server.client.list, link) { + if(client->resize) { + wlr_surface_send_frame_done(client->xdg->surface, &now); + } + } + + if(!wlr_output_attach_render(monitor->output, nil)) + return; + + wlr_output_effective_resolution(monitor->output, &w, &h); + + /* start of rendering kernel */ + wlr_renderer_begin(server.renderer, w, h); + wlr_renderer_clear(server.renderer, cfg·rootcolor); + + render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], &now); + render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], &now); + + render_clients(monitor, &now); + + render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_TOP], &now); + render_layer(&monitor->layer[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], &now); + + wlr_output_render_software_cursors(monitor->output, nil); + + wlr_renderer_end(server.renderer); + wlr_output_commit(monitor->output); +} diff --git a/src/cmd/wm/rules.mk b/src/cmd/wm/rules.mk new file mode 100644 index 0000000..30d786d --- /dev/null +++ b/src/cmd/wm/rules.mk @@ -0,0 +1,62 @@ +include share/push.mk +# Iterate through subdirectory tree + +# local sources +SRCS_$(d):=\ + $(d)/xdg-shell-protocol.c\ + $(d)/wlr-layer-shell-unstable-v1-protocol.c\ + $(d)/util.c\ + $(d)/input.c\ + $(d)/render.c\ + $(d)/layer.c\ + $(d)/xdg.c\ + $(d)/client.c\ + $(d)/monitor.c\ + $(d)/main.c + +# local outputs +BINS_$(d) := $(d)/wm + +include share/paths.mk + +# Local rules +include share/dynamic.mk + +$(d)/xdg-shell-protocol.h: + @echo "MK $(notdir $@)";\ + $(WL_SCAN) server-header $(WL_PROTO)/stable/xdg-shell/xdg-shell.xml $@ + +$(d)/xdg-shell-protocol.c: $(d)/xdg-shell-protocol.h + @echo "MK $(notdir $@)";\ + $(WL_SCAN) private-code $(WL_PROTO)/stable/xdg-shell/xdg-shell.xml $@ + +$(d)/wlr-layer-shell-unstable-v1-protocol.h: + @echo "MK $(notdir $@)";\ + $(WL_SCAN) server-header $(dir $@)protocol/wlr-layer-shell-unstable-v1.xml $@ + +$(d)/wlr-layer-shell-unstable-v1-protocol.c: $(d)/wlr-layer-shell-unstable-v1-protocol.h + @echo "MK $(notdir $@)";\ + $(WL_SCAN) private-code $(dir $@)protocol/wlr-layer-shell-unstable-v1.xml $@ + +GENS+=\ + $(d)/xdg-shell-protocol.h\ + $(d)/xdg-shell-protocol.c\ + $(d)/wlr-layer-shell-unstable-v1-protocol.h\ + $(d)/wlr-layer-shell-unstable-v1-protocol.c + +$(BINS_$(d)): TCINCS=-I cmd/wm + +$(BINS_$(d)): TCFLAGS=\ + `$(PKG) --cflags wlroots`\ + `$(PKG) --cflags wayland-server`\ + `$(PKG) --cflags xkbcommon` + +$(BINS_$(d)): TCLIBS=\ + `$(PKG) --libs wlroots`\ + `$(PKG) --libs wayland-server`\ + `$(PKG) --libs xkbcommon`\ + +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/base/base.a + $(COMPLINK) + +include share/pop.mk diff --git a/src/cmd/wm/util.c b/src/cmd/wm/util.c new file mode 100644 index 0000000..7871d15 --- /dev/null +++ b/src/cmd/wm/util.c @@ -0,0 +1,99 @@ +#include "wm.h" + +typedef struct { + uint32 singular_anchor; + uint32 anchor_triplet; + int *positive_axis; + int *negative_axis; + int margin; +} Edge; + +// ----------------------------------------------------------------------- +// general purpose function on rectangles + +void +scale_box(struct wlr_box *box, float scale) +{ + box->width = ROUND((box->x + box->width) * scale) - ROUND(box->x * scale); + box->height = ROUND((box->y + box->height) * scale) - ROUND(box->y * scale); + box->x = ROUND(box->x * scale); + box->y = ROUND(box->y * scale); +} + +void +exclude(struct wlr_box *usable_area, uint32 anchor, int32 exclusive, + int32 margin_top, int32 margin_right, int32 margin_bottom, int32 margin_left) +{ + Edge edges[] = { + { // Top + .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP, + .positive_axis = &usable_area->y, + .negative_axis = &usable_area->height, + .margin = margin_top, + }, + { // Bottom + .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->height, + .margin = margin_bottom, + }, + { // Left + .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT, + .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = &usable_area->x, + .negative_axis = &usable_area->width, + .margin = margin_left, + }, + { // Right + .singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT, + .anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM, + .positive_axis = NULL, + .negative_axis = &usable_area->width, + .margin = margin_right, + } + }; + for(size_t i = 0; i < arrlen(edges); i++) { + if((anchor == edges[i].singular_anchor || anchor == edges[i].anchor_triplet) + && exclusive + edges[i].margin > 0) { + if(edges[i].positive_axis) + *edges[i].positive_axis += exclusive + edges[i].margin; + if(edges[i].negative_axis) + *edges[i].negative_axis -= exclusive + edges[i].margin; + break; + } + } +} + +// ----------------------------------------------------------------------- +// user facing functions + +void +spawn(Arg *arg) +{ + wlr_log(WLR_DEBUG, "spawning %s", ((char **)arg->v)[0]); + if(!fork()) { + dup2(2, 1); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); + } +} + +void +quit(Arg *arg) +{ + wl_display_terminate(server.display); +} + +#define CONFIG(a,b,...) a cfg·##b = __VA_ARGS__ +#include "config.h" +#undef CONFIG diff --git a/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c new file mode 100644 index 0000000..95ff317 --- /dev/null +++ b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.c @@ -0,0 +1,93 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright © 2017 Drew DeVault + * + * Permission to use, copy, modify, distribute, and sell this + * software and its documentation for any purpose is hereby granted + * without fee, provided that the above copyright notice appear in + * all copies and that both that copyright notice and this permission + * notice appear in supporting documentation, and that the name of + * the copyright holders not be used in advertising or publicity + * pertaining to distribution of the software without specific, + * written prior permission. The copyright holders make no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface zwlr_layer_surface_v1_interface; + +static const struct wl_interface *wlr_layer_shell_unstable_v1_types[] = { + NULL, + NULL, + NULL, + NULL, + &zwlr_layer_surface_v1_interface, + &wl_surface_interface, + &wl_output_interface, + NULL, + NULL, + &xdg_popup_interface, +}; + +static const struct wl_message zwlr_layer_shell_v1_requests[] = { + { "get_layer_surface", "no?ous", wlr_layer_shell_unstable_v1_types + 4 }, + { "destroy", "3", wlr_layer_shell_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_layer_shell_v1_interface = { + "zwlr_layer_shell_v1", 4, + 2, zwlr_layer_shell_v1_requests, + 0, NULL, +}; + +static const struct wl_message zwlr_layer_surface_v1_requests[] = { + { "set_size", "uu", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_anchor", "u", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_exclusive_zone", "i", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_margin", "iiii", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_keyboard_interactivity", "u", wlr_layer_shell_unstable_v1_types + 0 }, + { "get_popup", "o", wlr_layer_shell_unstable_v1_types + 9 }, + { "ack_configure", "u", wlr_layer_shell_unstable_v1_types + 0 }, + { "destroy", "", wlr_layer_shell_unstable_v1_types + 0 }, + { "set_layer", "2u", wlr_layer_shell_unstable_v1_types + 0 }, +}; + +static const struct wl_message zwlr_layer_surface_v1_events[] = { + { "configure", "uuu", wlr_layer_shell_unstable_v1_types + 0 }, + { "closed", "", wlr_layer_shell_unstable_v1_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface zwlr_layer_surface_v1_interface = { + "zwlr_layer_surface_v1", 4, + 9, zwlr_layer_surface_v1_requests, + 2, zwlr_layer_surface_v1_events, +}; + diff --git a/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h new file mode 100644 index 0000000..ea2fa9b --- /dev/null +++ b/src/cmd/wm/wlr-layer-shell-unstable-v1-protocol.h @@ -0,0 +1,564 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef WLR_LAYER_SHELL_UNSTABLE_V1_SERVER_PROTOCOL_H +#define WLR_LAYER_SHELL_UNSTABLE_V1_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_wlr_layer_shell_unstable_v1 The wlr_layer_shell_unstable_v1 protocol + * @section page_ifaces_wlr_layer_shell_unstable_v1 Interfaces + * - @subpage page_iface_zwlr_layer_shell_v1 - create surfaces that are layers of the desktop + * - @subpage page_iface_zwlr_layer_surface_v1 - layer metadata interface + * @section page_copyright_wlr_layer_shell_unstable_v1 Copyright + *
+ *
+ * Copyright © 2017 Drew DeVault
+ *
+ * Permission to use, copy, modify, distribute, and sell this
+ * software and its documentation for any purpose is hereby granted
+ * without fee, provided that the above copyright notice appear in
+ * all copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of
+ * the copyright holders not be used in advertising or publicity
+ * pertaining to distribution of the software without specific,
+ * written prior permission.  The copyright holders make no
+ * representations about the suitability of this software for any
+ * purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
+ * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
+ * THIS SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_surface; +struct xdg_popup; +struct zwlr_layer_shell_v1; +struct zwlr_layer_surface_v1; + +#ifndef ZWLR_LAYER_SHELL_V1_INTERFACE +#define ZWLR_LAYER_SHELL_V1_INTERFACE +/** + * @page page_iface_zwlr_layer_shell_v1 zwlr_layer_shell_v1 + * @section page_iface_zwlr_layer_shell_v1_desc Description + * + * Clients can use this interface to assign the surface_layer role to + * wl_surfaces. Such surfaces are assigned to a "layer" of the output and + * rendered with a defined z-depth respective to each other. They may also be + * anchored to the edges and corners of a screen and specify input handling + * semantics. This interface should be suitable for the implementation of + * many desktop shell components, and a broad number of other applications + * that interact with the desktop. + * @section page_iface_zwlr_layer_shell_v1_api API + * See @ref iface_zwlr_layer_shell_v1. + */ +/** + * @defgroup iface_zwlr_layer_shell_v1 The zwlr_layer_shell_v1 interface + * + * Clients can use this interface to assign the surface_layer role to + * wl_surfaces. Such surfaces are assigned to a "layer" of the output and + * rendered with a defined z-depth respective to each other. They may also be + * anchored to the edges and corners of a screen and specify input handling + * semantics. This interface should be suitable for the implementation of + * many desktop shell components, and a broad number of other applications + * that interact with the desktop. + */ +extern const struct wl_interface zwlr_layer_shell_v1_interface; +#endif +#ifndef ZWLR_LAYER_SURFACE_V1_INTERFACE +#define ZWLR_LAYER_SURFACE_V1_INTERFACE +/** + * @page page_iface_zwlr_layer_surface_v1 zwlr_layer_surface_v1 + * @section page_iface_zwlr_layer_surface_v1_desc Description + * + * An interface that may be implemented by a wl_surface, for surfaces that + * are designed to be rendered as a layer of a stacked desktop-like + * environment. + * + * Layer surface state (layer, size, anchor, exclusive zone, + * margin, interactivity) is double-buffered, and will be applied at the + * time wl_surface.commit of the corresponding wl_surface is called. + * + * Attaching a null buffer to a layer surface unmaps it. + * + * Unmapping a layer_surface means that the surface cannot be shown by the + * compositor until it is explicitly mapped again. The layer_surface + * returns to the state it had right after layer_shell.get_layer_surface. + * The client can re-map the surface by performing a commit without any + * buffer attached, waiting for a configure event and handling it as usual. + * @section page_iface_zwlr_layer_surface_v1_api API + * See @ref iface_zwlr_layer_surface_v1. + */ +/** + * @defgroup iface_zwlr_layer_surface_v1 The zwlr_layer_surface_v1 interface + * + * An interface that may be implemented by a wl_surface, for surfaces that + * are designed to be rendered as a layer of a stacked desktop-like + * environment. + * + * Layer surface state (layer, size, anchor, exclusive zone, + * margin, interactivity) is double-buffered, and will be applied at the + * time wl_surface.commit of the corresponding wl_surface is called. + * + * Attaching a null buffer to a layer surface unmaps it. + * + * Unmapping a layer_surface means that the surface cannot be shown by the + * compositor until it is explicitly mapped again. The layer_surface + * returns to the state it had right after layer_shell.get_layer_surface. + * The client can re-map the surface by performing a commit without any + * buffer attached, waiting for a configure event and handling it as usual. + */ +extern const struct wl_interface zwlr_layer_surface_v1_interface; +#endif + +#ifndef ZWLR_LAYER_SHELL_V1_ERROR_ENUM +#define ZWLR_LAYER_SHELL_V1_ERROR_ENUM +enum zwlr_layer_shell_v1_error { + /** + * wl_surface has another role + */ + ZWLR_LAYER_SHELL_V1_ERROR_ROLE = 0, + /** + * layer value is invalid + */ + ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER = 1, + /** + * wl_surface has a buffer attached or committed + */ + ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED = 2, +}; +#endif /* ZWLR_LAYER_SHELL_V1_ERROR_ENUM */ + +#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM +#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM +/** + * @ingroup iface_zwlr_layer_shell_v1 + * available layers for surfaces + * + * These values indicate which layers a surface can be rendered in. They + * are ordered by z depth, bottom-most first. Traditional shell surfaces + * will typically be rendered between the bottom and top layers. + * Fullscreen shell surfaces are typically rendered at the top layer. + * Multiple surfaces can share a single layer, and ordering within a + * single layer is undefined. + */ +enum zwlr_layer_shell_v1_layer { + ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0, + ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1, + ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3, +}; +#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */ + +/** + * @ingroup iface_zwlr_layer_shell_v1 + * @struct zwlr_layer_shell_v1_interface + */ +struct zwlr_layer_shell_v1_interface { + /** + * create a layer_surface from a surface + * + * Create a layer surface for an existing surface. This assigns + * the role of layer_surface, or raises a protocol error if another + * role is already assigned. + * + * Creating a layer surface from a wl_surface which has a buffer + * attached or committed is a client error, and any attempts by a + * client to attach or manipulate a buffer prior to the first + * layer_surface.configure call must also be treated as errors. + * + * After creating a layer_surface object and setting it up, the + * client must perform an initial commit without any buffer + * attached. The compositor will reply with a + * layer_surface.configure event. The client must acknowledge it + * and is then allowed to attach a buffer to map the surface. + * + * You may pass NULL for output to allow the compositor to decide + * which output to use. Generally this will be the one that the + * user most recently interacted with. + * + * Clients can specify a namespace that defines the purpose of the + * layer surface. + * @param layer layer to add this surface to + * @param namespace namespace for the layer surface + */ + void (*get_layer_surface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface, + struct wl_resource *output, + uint32_t layer, + const char *namespace); + /** + * destroy the layer_shell object + * + * This request indicates that the client will not use the + * layer_shell object any more. Objects that have been created + * through this instance are not affected. + * @since 3 + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); +}; + + +/** + * @ingroup iface_zwlr_layer_shell_v1 + */ +#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_shell_v1 + */ +#define ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION 3 + +#ifndef ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ENUM +#define ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ENUM +/** + * @ingroup iface_zwlr_layer_surface_v1 + * request regular keyboard focus semantics + * + * This requests the compositor to allow this surface to be focused and + * unfocused by the user in an implementation-defined manner. The user + * should be able to unfocus this surface even regardless of the layer + * it is on. + * + * Typically, the compositor will want to use its normal mechanism to + * manage keyboard focus between layer shell surfaces with this setting + * and regular toplevels on the desktop layer (e.g. click to focus). + * Nevertheless, it is possible for a compositor to require a special + * interaction to focus or unfocus layer shell surfaces (e.g. requiring + * a click even if focus follows the mouse normally, or providing a + * keybinding to switch focus between layers). + * + * This setting is mainly intended for desktop shell components (e.g. + * panels) that allow keyboard interaction. Using this option can allow + * implementing a desktop shell that can be fully usable without the + * mouse. + */ +enum zwlr_layer_surface_v1_keyboard_interactivity { + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE = 0, + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE = 1, + /** + * @since 4 + */ + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND = 2, +}; +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ON_DEMAND_SINCE_VERSION 4 +#endif /* ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_ENUM */ + +#ifndef ZWLR_LAYER_SURFACE_V1_ERROR_ENUM +#define ZWLR_LAYER_SURFACE_V1_ERROR_ENUM +enum zwlr_layer_surface_v1_error { + /** + * provided surface state is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE = 0, + /** + * size is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE = 1, + /** + * anchor bitfield is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR = 2, + /** + * keyboard interactivity is invalid + */ + ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_KEYBOARD_INTERACTIVITY = 3, +}; +#endif /* ZWLR_LAYER_SURFACE_V1_ERROR_ENUM */ + +#ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM +#define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM +enum zwlr_layer_surface_v1_anchor { + /** + * the top edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1, + /** + * the bottom edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2, + /** + * the left edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4, + /** + * the right edge of the anchor rectangle + */ + ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8, +}; +#endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */ + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * @struct zwlr_layer_surface_v1_interface + */ +struct zwlr_layer_surface_v1_interface { + /** + * sets the size of the surface + * + * Sets the size of the surface in surface-local coordinates. The + * compositor will display the surface centered with respect to its + * anchors. + * + * If you pass 0 for either value, the compositor will assign it + * and inform you of the assignment in the configure event. You + * must set your anchor to opposite edges in the dimensions you + * omit; not doing so is a protocol error. Both values are 0 by + * default. + * + * Size is double-buffered, see wl_surface.commit. + */ + void (*set_size)(struct wl_client *client, + struct wl_resource *resource, + uint32_t width, + uint32_t height); + /** + * configures the anchor point of the surface + * + * Requests that the compositor anchor the surface to the + * specified edges and corners. If two orthogonal edges are + * specified (e.g. 'top' and 'left'), then the anchor point will be + * the intersection of the edges (e.g. the top left corner of the + * output); otherwise the anchor point will be centered on that + * edge, or in the center if none is specified. + * + * Anchor is double-buffered, see wl_surface.commit. + */ + void (*set_anchor)(struct wl_client *client, + struct wl_resource *resource, + uint32_t anchor); + /** + * configures the exclusive geometry of this surface + * + * Requests that the compositor avoids occluding an area with + * other surfaces. The compositor's use of this information is + * implementation-dependent - do not assume that this region will + * not actually be occluded. + * + * A positive value is only meaningful if the surface is anchored + * to one edge or an edge and both perpendicular edges. If the + * surface is not anchored, anchored to only two perpendicular + * edges (a corner), anchored to only two parallel edges or + * anchored to all edges, a positive value will be treated the same + * as zero. + * + * A positive zone is the distance from the edge in surface-local + * coordinates to consider exclusive. + * + * Surfaces that do not wish to have an exclusive zone may instead + * specify how they should interact with surfaces that do. If set + * to zero, the surface indicates that it would like to be moved to + * avoid occluding surfaces with a positive exclusive zone. If set + * to -1, the surface indicates that it would not like to be moved + * to accommodate for other surfaces, and the compositor should + * extend it all the way to the edges it is anchored to. + * + * For example, a panel might set its exclusive zone to 10, so that + * maximized shell surfaces are not shown on top of it. A + * notification might set its exclusive zone to 0, so that it is + * moved to avoid occluding the panel, but shell surfaces are shown + * underneath it. A wallpaper or lock screen might set their + * exclusive zone to -1, so that they stretch below or over the + * panel. + * + * The default value is 0. + * + * Exclusive zone is double-buffered, see wl_surface.commit. + */ + void (*set_exclusive_zone)(struct wl_client *client, + struct wl_resource *resource, + int32_t zone); + /** + * sets a margin from the anchor point + * + * Requests that the surface be placed some distance away from + * the anchor point on the output, in surface-local coordinates. + * Setting this value for edges you are not anchored to has no + * effect. + * + * The exclusive zone includes the margin. + * + * Margin is double-buffered, see wl_surface.commit. + */ + void (*set_margin)(struct wl_client *client, + struct wl_resource *resource, + int32_t top, + int32_t right, + int32_t bottom, + int32_t left); + /** + * requests keyboard events + * + * Set how keyboard events are delivered to this surface. By + * default, layer shell surfaces do not receive keyboard events; + * this request can be used to change this. + * + * This setting is inherited by child surfaces set by the get_popup + * request. + * + * Layer surfaces receive pointer, touch, and tablet events + * normally. If you do not want to receive them, set the input + * region on your surface to an empty region. + * + * Keyboard interactivity is double-buffered, see + * wl_surface.commit. + */ + void (*set_keyboard_interactivity)(struct wl_client *client, + struct wl_resource *resource, + uint32_t keyboard_interactivity); + /** + * assign this layer_surface as an xdg_popup parent + * + * This assigns an xdg_popup's parent to this layer_surface. This + * popup should have been created via xdg_surface::get_popup with + * the parent set to NULL, and this request must be invoked before + * committing the popup's initial state. + * + * See the documentation of xdg_popup for more details about what + * an xdg_popup is and how it is used. + */ + void (*get_popup)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *popup); + /** + * ack a configure event + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client must + * make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * If the client receives multiple configure events before it can + * respond to one, it only has to ack the last configure event. + * + * A client is not required to commit immediately after sending an + * ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before + * committing, but only the last request sent before a commit + * indicates which configure event the client really is responding + * to. + * @param serial the serial from the configure event + */ + void (*ack_configure)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); + /** + * destroy the layer_surface + * + * This request destroys the layer surface. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * change the layer of the surface + * + * Change the layer that the surface is rendered on. + * + * Layer is double-buffered, see wl_surface.commit. + * @param layer layer to move this surface to + * @since 2 + */ + void (*set_layer)(struct wl_client *client, + struct wl_resource *resource, + uint32_t layer); +}; + +#define ZWLR_LAYER_SURFACE_V1_CONFIGURE 0 +#define ZWLR_LAYER_SURFACE_V1_CLOSED 1 + +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_CLOSED_SINCE_VERSION 1 + +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_zwlr_layer_surface_v1 + */ +#define ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION 2 + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_layer_surface_v1_send_configure(struct wl_resource *resource_, uint32_t serial, uint32_t width, uint32_t height) +{ + wl_resource_post_event(resource_, ZWLR_LAYER_SURFACE_V1_CONFIGURE, serial, width, height); +} + +/** + * @ingroup iface_zwlr_layer_surface_v1 + * Sends an closed event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +zwlr_layer_surface_v1_send_closed(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, ZWLR_LAYER_SURFACE_V1_CLOSED); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/cmd/wm/wm.h b/src/cmd/wm/wm.h new file mode 100644 index 0000000..a263804 --- /dev/null +++ b/src/cmd/wm/wm.h @@ -0,0 +1,350 @@ +#pragma once + +#include +#include +#include +#include + +#define WLR_USE_UNSTABLE +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +// ----------------------------------------------------------------------- +// macros + +#define ROUND(x) ((int)((x)+0.5)) +#define VISIBLE_ON(C,M) ((C)->monitor == (M) && ((C)->tags & (M)->tag.set[(M)->tag.selected])) + +// ----------------------------------------------------------------------- +// types + +enum +{ + CursorNormal, + CursorMove, + CursorResize, +}; + +typedef union Arg Arg; +typedef struct Button Button; +typedef struct Key Key; +typedef struct Keyboard Keyboard; +typedef struct Layer Layer; +typedef struct Client Client; +typedef struct Layout Layout; +typedef struct Monitor Monitor; +typedef struct Server Server; + +typedef struct Rule Rule; +typedef struct MonitorRule MonitorRule; + +struct Rectangle +{ + int x, y; + int w, h; +}; + +union Arg +{ + int i; + uint ui; + float f; + void *v; +}; + +struct Key +{ + uint modifier; + xkb_keysym_t sym; + void (*action)(Arg *); + Arg arg; +}; + +struct Button +{ + uint modifier; + uint code; + void (*function)(Arg *); + Arg arg; +}; + +struct Keyboard +{ + struct wl_list link; + struct wlr_input_device *device; + struct { + struct wl_listener press; + struct wl_listener modify; + struct wl_listener destroy; + } event; +}; + +struct Layer +{ + struct wl_list link; + struct wlr_layer_surface_v1 *surface; + enum zwlr_layer_shell_v1_layer type; + + struct wlr_box geometry; + + struct { + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener commit; + struct wl_listener destroy; + } event; +}; + +struct Client +{ + struct wl_list link; + struct wl_list stack; + struct wl_list focus; + + struct wlr_xdg_surface *xdg; + + struct { + struct wl_listener map; + struct wl_listener unmap; + struct wl_listener commit; + struct wl_listener destroy; + struct wl_listener request_move; + struct wl_listener request_title; + struct wl_listener request_resize; + struct wl_listener request_fullscreen; + } event; + + struct wlr_box geometry, oldgeometry; + + Monitor *monitor; + + uint tags; + int border : 4; + int ismapped : 1; + int isfloating : 1; + int isurgent : 1; + int isfullscreen : 1; + + uint32 resize; +}; + +struct Layout +{ + char *symbol; + void (*arrange)(Monitor *); +}; + +struct Monitor +{ + struct wl_list link; + struct wlr_output *output; + struct { + struct wl_listener render; + struct wl_listener destroy; + } event; + + struct wlr_box geometry; + struct wlr_box window; + struct wl_list layer[4]; + + Layout *layout, *layouts[2]; + struct { + uint set[2]; + uint selected; + } tag; + struct { + double frac; + int len; + } master; +}; + +struct MonitorRule +{ + char *name; + Layout *layout; + int x, y; + float scale; + enum wl_output_transform transform; + struct { + double frac; + int len; + } master; +}; + +struct Rule +{ + char *id; + char *title; + uint tags; + int isfloating; + int monitor; +}; + +struct Server +{ + struct wl_display *display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + struct wlr_presentation *present; + struct wlr_xdg_activation_v1 *activate; + + struct { + struct wlr_xdg_shell *xdg; + struct wlr_layer_shell_v1 *layer; + } shell; + + struct { + struct wl_list list; + struct wl_list stack; + struct wl_list focus; + } client; + Client *selected; + + struct { + Client *client; + double x, y; + struct wlr_box box; + } grab; + uint32 resize; + + struct { + struct wlr_output_layout *layout; + struct wl_list list; + struct wlr_box geometry; + struct wlr_output_manager_v1 *manager; + Monitor *selected; + } monitor; + + struct { + struct wlr_cursor *dot; + struct wlr_xcursor_manager *manager; + int mode; + } cursor; + + struct { + struct wlr_seat *seat; + struct wl_list keyboards; + struct wlr_idle *idle; + } input; + + struct { + struct wl_listener make_input; + struct wl_listener make_monitor; + struct wl_listener make_xdg_surface; + struct wl_listener make_layer_surface; + + struct wl_listener monitor_test; + struct wl_listener monitor_apply; + struct wl_listener monitor_change; + + struct wl_listener cursor_move; + struct wl_listener cursor_move_abs; + struct wl_listener cursor_button; + struct wl_listener cursor_axis; + struct wl_listener cursor_frame; + + struct wl_listener request_cursor; + struct wl_listener request_activate; + struct wl_listener request_set_selection; + } event; +}; + +extern struct Server server; + +// ----------------------------------------------------------------------- +// functions + +/* util.c */ +void scale_box(struct wlr_box *, float); +void exclude(struct wlr_box *, uint32, int32, int32, int32, int32, int32 ); + +/* render.c */ +void render_monitor(struct wl_listener *, void *); + +/* xdg.c */ +void make_xdg_surface(struct wl_listener *, void *); + +/* layer.c */ +void make_layer_surface(struct wl_listener *, void *); + +/* input.c */ +void make_input(struct wl_listener *, void *); +void notify_move(uint32 time); + +void cursor_axis(struct wl_listener *, void *); +void cursor_frame(struct wl_listener *, void *); +void cursor_button(struct wl_listener *, void *); +void cursor_move(struct wl_listener *, void *); +void cursor_move_abs(struct wl_listener *, void *); + +void request_cursor(struct wl_listener *, void *); +void request_set_selection(struct wl_listener *, void *); + +/* client.c */ +void rules(Client *); +void focus(Client *, int lift); +void resize(Client *, int x, int y, int w, int h, int interact); +void attach(Client *, Monitor *, uint tags); +void floating(Client *, int); + +void move_client(Arg *arg); +void float_client(Arg *arg); +void resize_client(Arg *arg); + +void request_activate(struct wl_listener *, void *); + +Client *selected_client(void); +Client *client_at(double x, double y); +struct wlr_surface *client_surface_at(Client *, double cx, double cy, double *sx, double *sy); +struct wlr_surface *top_surface(Client *); + +/* monitor.c */ +void tile(Monitor *); +void arrange(Monitor *); +void stratify(Monitor *); +Client *focused_client(Monitor *); +Monitor *monitor_at(double x, double y); + +void monitor_test(struct wl_listener *, void *); +void monitor_apply(struct wl_listener *, void *); +void monitor_change(struct wl_listener *, void *); + +void free_monitor(struct wl_listener *, void *); +void make_monitor(struct wl_listener *, void *); + +#define CONFIG(a,b,...) extern a cfg·##b +#include "config.h" +#undef CONFIG diff --git a/src/cmd/wm/xdg-shell-protocol.c b/src/cmd/wm/xdg-shell-protocol.c new file mode 100644 index 0000000..62a2612 --- /dev/null +++ b/src/cmd/wm/xdg-shell-protocol.c @@ -0,0 +1,181 @@ +/* Generated by wayland-scanner 1.19.0 */ + +/* + * Copyright © 2008-2013 Kristian Høgsberg + * Copyright © 2013 Rafael Antognolli + * Copyright © 2013 Jasper St. Pierre + * Copyright © 2010-2013 Intel Corporation + * Copyright © 2015-2017 Samsung Electronics Co., Ltd + * Copyright © 2015-2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include "wayland-util.h" + +#ifndef __has_attribute +# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ +#endif + +#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) +#define WL_PRIVATE __attribute__ ((visibility("hidden"))) +#else +#define WL_PRIVATE +#endif + +extern const struct wl_interface wl_output_interface; +extern const struct wl_interface wl_seat_interface; +extern const struct wl_interface wl_surface_interface; +extern const struct wl_interface xdg_popup_interface; +extern const struct wl_interface xdg_positioner_interface; +extern const struct wl_interface xdg_surface_interface; +extern const struct wl_interface xdg_toplevel_interface; + +static const struct wl_interface *xdg_shell_types[] = { + NULL, + NULL, + NULL, + NULL, + &xdg_positioner_interface, + &xdg_surface_interface, + &wl_surface_interface, + &xdg_toplevel_interface, + &xdg_popup_interface, + &xdg_surface_interface, + &xdg_positioner_interface, + &xdg_toplevel_interface, + &wl_seat_interface, + NULL, + NULL, + NULL, + &wl_seat_interface, + NULL, + &wl_seat_interface, + NULL, + NULL, + &wl_output_interface, + &wl_seat_interface, + NULL, + &xdg_positioner_interface, + NULL, +}; + +static const struct wl_message xdg_wm_base_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "create_positioner", "n", xdg_shell_types + 4 }, + { "get_xdg_surface", "no", xdg_shell_types + 5 }, + { "pong", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_wm_base_events[] = { + { "ping", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { + "xdg_wm_base", 3, + 4, xdg_wm_base_requests, + 1, xdg_wm_base_events, +}; + +static const struct wl_message xdg_positioner_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_size", "ii", xdg_shell_types + 0 }, + { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, + { "set_anchor", "u", xdg_shell_types + 0 }, + { "set_gravity", "u", xdg_shell_types + 0 }, + { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, + { "set_offset", "ii", xdg_shell_types + 0 }, + { "set_reactive", "3", xdg_shell_types + 0 }, + { "set_parent_size", "3ii", xdg_shell_types + 0 }, + { "set_parent_configure", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_positioner_interface = { + "xdg_positioner", 3, + 10, xdg_positioner_requests, + 0, NULL, +}; + +static const struct wl_message xdg_surface_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "get_toplevel", "n", xdg_shell_types + 7 }, + { "get_popup", "n?oo", xdg_shell_types + 8 }, + { "set_window_geometry", "iiii", xdg_shell_types + 0 }, + { "ack_configure", "u", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_surface_events[] = { + { "configure", "u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_surface_interface = { + "xdg_surface", 3, + 5, xdg_surface_requests, + 1, xdg_surface_events, +}; + +static const struct wl_message xdg_toplevel_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "set_parent", "?o", xdg_shell_types + 11 }, + { "set_title", "s", xdg_shell_types + 0 }, + { "set_app_id", "s", xdg_shell_types + 0 }, + { "show_window_menu", "ouii", xdg_shell_types + 12 }, + { "move", "ou", xdg_shell_types + 16 }, + { "resize", "ouu", xdg_shell_types + 18 }, + { "set_max_size", "ii", xdg_shell_types + 0 }, + { "set_min_size", "ii", xdg_shell_types + 0 }, + { "set_maximized", "", xdg_shell_types + 0 }, + { "unset_maximized", "", xdg_shell_types + 0 }, + { "set_fullscreen", "?o", xdg_shell_types + 21 }, + { "unset_fullscreen", "", xdg_shell_types + 0 }, + { "set_minimized", "", xdg_shell_types + 0 }, +}; + +static const struct wl_message xdg_toplevel_events[] = { + { "configure", "iia", xdg_shell_types + 0 }, + { "close", "", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { + "xdg_toplevel", 3, + 14, xdg_toplevel_requests, + 2, xdg_toplevel_events, +}; + +static const struct wl_message xdg_popup_requests[] = { + { "destroy", "", xdg_shell_types + 0 }, + { "grab", "ou", xdg_shell_types + 22 }, + { "reposition", "3ou", xdg_shell_types + 24 }, +}; + +static const struct wl_message xdg_popup_events[] = { + { "configure", "iiii", xdg_shell_types + 0 }, + { "popup_done", "", xdg_shell_types + 0 }, + { "repositioned", "3u", xdg_shell_types + 0 }, +}; + +WL_PRIVATE const struct wl_interface xdg_popup_interface = { + "xdg_popup", 3, + 3, xdg_popup_requests, + 3, xdg_popup_events, +}; + diff --git a/src/cmd/wm/xdg-shell-protocol.h b/src/cmd/wm/xdg-shell-protocol.h new file mode 100644 index 0000000..312e5b9 --- /dev/null +++ b/src/cmd/wm/xdg-shell-protocol.h @@ -0,0 +1,1676 @@ +/* Generated by wayland-scanner 1.19.0 */ + +#ifndef XDG_SHELL_SERVER_PROTOCOL_H +#define XDG_SHELL_SERVER_PROTOCOL_H + +#include +#include +#include "wayland-server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct wl_client; +struct wl_resource; + +/** + * @page page_xdg_shell The xdg_shell protocol + * @section page_ifaces_xdg_shell Interfaces + * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces + * - @subpage page_iface_xdg_positioner - child surface positioner + * - @subpage page_iface_xdg_surface - desktop user interface surface base interface + * - @subpage page_iface_xdg_toplevel - toplevel surface + * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus + * @section page_copyright_xdg_shell Copyright + *
+ *
+ * Copyright © 2008-2013 Kristian Høgsberg
+ * Copyright © 2013      Rafael Antognolli
+ * Copyright © 2013      Jasper St. Pierre
+ * Copyright © 2010-2013 Intel Corporation
+ * Copyright © 2015-2017 Samsung Electronics Co., Ltd
+ * Copyright © 2015-2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * 
+ */ +struct wl_output; +struct wl_seat; +struct wl_surface; +struct xdg_popup; +struct xdg_positioner; +struct xdg_surface; +struct xdg_toplevel; +struct xdg_wm_base; + +#ifndef XDG_WM_BASE_INTERFACE +#define XDG_WM_BASE_INTERFACE +/** + * @page page_iface_xdg_wm_base xdg_wm_base + * @section page_iface_xdg_wm_base_desc Description + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + * @section page_iface_xdg_wm_base_api API + * See @ref iface_xdg_wm_base. + */ +/** + * @defgroup iface_xdg_wm_base The xdg_wm_base interface + * + * The xdg_wm_base interface is exposed as a global object enabling clients + * to turn their wl_surfaces into windows in a desktop environment. It + * defines the basic functionality needed for clients and the compositor to + * create windows that can be dragged, resized, maximized, etc, as well as + * creating transient windows such as popup menus. + */ +extern const struct wl_interface xdg_wm_base_interface; +#endif +#ifndef XDG_POSITIONER_INTERFACE +#define XDG_POSITIONER_INTERFACE +/** + * @page page_iface_xdg_positioner xdg_positioner + * @section page_iface_xdg_positioner_desc Description + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an error. + * @section page_iface_xdg_positioner_api API + * See @ref iface_xdg_positioner. + */ +/** + * @defgroup iface_xdg_positioner The xdg_positioner interface + * + * The xdg_positioner provides a collection of rules for the placement of a + * child surface relative to a parent surface. Rules can be defined to ensure + * the child surface remains within the visible area's borders, and to + * specify how the child surface changes its position, such as sliding along + * an axis, or flipping around a rectangle. These positioner-created rules are + * constrained by the requirement that a child surface must intersect with or + * be at least partially adjacent to its parent surface. + * + * See the various requests for details about possible rules. + * + * At the time of the request, the compositor makes a copy of the rules + * specified by the xdg_positioner. Thus, after the request is complete the + * xdg_positioner object can be destroyed or reused; further changes to the + * object will have no effect on previous usages. + * + * For an xdg_positioner object to be considered complete, it must have a + * non-zero size set by set_size, and a non-zero anchor rectangle set by + * set_anchor_rect. Passing an incomplete xdg_positioner object when + * positioning a surface raises an error. + */ +extern const struct wl_interface xdg_positioner_interface; +#endif +#ifndef XDG_SURFACE_INTERFACE +#define XDG_SURFACE_INTERFACE +/** + * @page page_iface_xdg_surface xdg_surface + * @section page_iface_xdg_surface_desc Description + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed. + * @section page_iface_xdg_surface_api API + * See @ref iface_xdg_surface. + */ +/** + * @defgroup iface_xdg_surface The xdg_surface interface + * + * An interface that may be implemented by a wl_surface, for + * implementations that provide a desktop-style user interface. + * + * It provides a base set of functionality required to construct user + * interface elements requiring management by the compositor, such as + * toplevel windows, menus, etc. The types of functionality are split into + * xdg_surface roles. + * + * Creating an xdg_surface does not set the role for a wl_surface. In order + * to map an xdg_surface, the client must create a role-specific object + * using, e.g., get_toplevel, get_popup. The wl_surface for any given + * xdg_surface can have at most one role, and may not be assigned any role + * not based on xdg_surface. + * + * A role must be assigned before any other requests are made to the + * xdg_surface object. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_surface state to take effect. + * + * Creating an xdg_surface from a wl_surface which has a buffer attached or + * committed is a client error, and any attempts by a client to attach or + * manipulate a buffer prior to the first xdg_surface.configure call must + * also be treated as errors. + * + * After creating a role-specific object and setting it up, the client must + * perform an initial commit without any buffer attached. The compositor + * will reply with an xdg_surface.configure event. The client must + * acknowledge it and is then allowed to attach a buffer to map the surface. + * + * Mapping an xdg_surface-based role surface is defined as making it + * possible for the surface to be shown by the compositor. Note that + * a mapped surface is not guaranteed to be visible once it is mapped. + * + * For an xdg_surface to be mapped by the compositor, the following + * conditions must be met: + * (1) the client has assigned an xdg_surface-based role to the surface + * (2) the client has set and committed the xdg_surface state and the + * role-dependent state to the surface + * (3) the client has committed a buffer to the surface + * + * A newly-unmapped surface is considered to have met condition (1) out + * of the 3 required conditions for mapping a surface if its role surface + * has not been destroyed. + */ +extern const struct wl_interface xdg_surface_interface; +#endif +#ifndef XDG_TOPLEVEL_INTERFACE +#define XDG_TOPLEVEL_INTERFACE +/** + * @page page_iface_xdg_toplevel xdg_toplevel + * @section page_iface_xdg_toplevel_desc Description + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by perfoming a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + * @section page_iface_xdg_toplevel_api API + * See @ref iface_xdg_toplevel. + */ +/** + * @defgroup iface_xdg_toplevel The xdg_toplevel interface + * + * This interface defines an xdg_surface role which allows a surface to, + * among other things, set window-like properties such as maximize, + * fullscreen, and minimize, set application-specific metadata like title and + * id, and well as trigger user interactive operations such as interactive + * resize and move. + * + * Unmapping an xdg_toplevel means that the surface cannot be shown + * by the compositor until it is explicitly mapped again. + * All active operations (e.g., move, resize) are canceled and all + * attributes (e.g. title, state, stacking, ...) are discarded for + * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to + * the state it had right after xdg_surface.get_toplevel. The client + * can re-map the toplevel by perfoming a commit without any buffer + * attached, waiting for a configure event and handling it as usual (see + * xdg_surface description). + * + * Attaching a null buffer to a toplevel unmaps the surface. + */ +extern const struct wl_interface xdg_toplevel_interface; +#endif +#ifndef XDG_POPUP_INTERFACE +#define XDG_POPUP_INTERFACE +/** + * @page page_iface_xdg_popup xdg_popup + * @section page_iface_xdg_popup_desc Description + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + * @section page_iface_xdg_popup_api API + * See @ref iface_xdg_popup. + */ +/** + * @defgroup iface_xdg_popup The xdg_popup interface + * + * A popup surface is a short-lived, temporary surface. It can be used to + * implement for example menus, popovers, tooltips and other similar user + * interface concepts. + * + * A popup can be made to take an explicit grab. See xdg_popup.grab for + * details. + * + * When the popup is dismissed, a popup_done event will be sent out, and at + * the same time the surface will be unmapped. See the xdg_popup.popup_done + * event for details. + * + * Explicitly destroying the xdg_popup object will also dismiss the popup and + * unmap the surface. Clients that want to dismiss the popup when another + * surface of their own is clicked should dismiss the popup using the destroy + * request. + * + * A newly created xdg_popup will be stacked on top of all previously created + * xdg_popup surfaces associated with the same xdg_toplevel. + * + * The parent of an xdg_popup must be mapped (see the xdg_surface + * description) before the xdg_popup itself. + * + * The client must call wl_surface.commit on the corresponding wl_surface + * for the xdg_popup state to take effect. + */ +extern const struct wl_interface xdg_popup_interface; +#endif + +#ifndef XDG_WM_BASE_ERROR_ENUM +#define XDG_WM_BASE_ERROR_ENUM +enum xdg_wm_base_error { + /** + * given wl_surface has another role + */ + XDG_WM_BASE_ERROR_ROLE = 0, + /** + * xdg_wm_base was destroyed before children + */ + XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, + /** + * the client tried to map or destroy a non-topmost popup + */ + XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, + /** + * the client specified an invalid popup parent surface + */ + XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, + /** + * the client provided an invalid surface state + */ + XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, + /** + * the client provided an invalid positioner + */ + XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, +}; +#endif /* XDG_WM_BASE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_wm_base + * @struct xdg_wm_base_interface + */ +struct xdg_wm_base_interface { + /** + * destroy xdg_wm_base + * + * Destroy this xdg_wm_base object. + * + * Destroying a bound xdg_wm_base object while there are surfaces + * still alive created by this xdg_wm_base object instance is + * illegal and will result in a protocol error. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * create a positioner object + * + * Create a positioner object. A positioner object is used to + * position surfaces relative to some parent surface. See the + * interface description and xdg_surface.get_popup for details. + */ + void (*create_positioner)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * create a shell surface from a surface + * + * This creates an xdg_surface for the given surface. While + * xdg_surface itself is not a role, the corresponding surface may + * only be assigned a role extending xdg_surface, such as + * xdg_toplevel or xdg_popup. It is illegal to create an + * xdg_surface for a wl_surface which already has an assigned role + * and this will result in a protocol error. + * + * This creates an xdg_surface for the given surface. An + * xdg_surface is used as basis to define a role to a given + * surface, such as xdg_toplevel or xdg_popup. It also manages + * functionality shared between xdg_surface based surface roles. + * + * See the documentation of xdg_surface for more details about what + * an xdg_surface is and how it is used. + */ + void (*get_xdg_surface)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *surface); + /** + * respond to a ping event + * + * A client must respond to a ping event with a pong request or + * the client may be deemed unresponsive. See xdg_wm_base.ping. + * @param serial serial of the ping event + */ + void (*pong)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); +}; + +#define XDG_WM_BASE_PING 0 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PING_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_wm_base + */ +#define XDG_WM_BASE_PONG_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_wm_base + * Sends an ping event to the client owning the resource. + * @param resource_ The client's resource + * @param serial pass this to the pong request + */ +static inline void +xdg_wm_base_send_ping(struct wl_resource *resource_, uint32_t serial) +{ + wl_resource_post_event(resource_, XDG_WM_BASE_PING, serial); +} + +#ifndef XDG_POSITIONER_ERROR_ENUM +#define XDG_POSITIONER_ERROR_ENUM +enum xdg_positioner_error { + /** + * invalid input provided + */ + XDG_POSITIONER_ERROR_INVALID_INPUT = 0, +}; +#endif /* XDG_POSITIONER_ERROR_ENUM */ + +#ifndef XDG_POSITIONER_ANCHOR_ENUM +#define XDG_POSITIONER_ANCHOR_ENUM +enum xdg_positioner_anchor { + XDG_POSITIONER_ANCHOR_NONE = 0, + XDG_POSITIONER_ANCHOR_TOP = 1, + XDG_POSITIONER_ANCHOR_BOTTOM = 2, + XDG_POSITIONER_ANCHOR_LEFT = 3, + XDG_POSITIONER_ANCHOR_RIGHT = 4, + XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, + XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, + XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_ANCHOR_ENUM */ + +#ifndef XDG_POSITIONER_GRAVITY_ENUM +#define XDG_POSITIONER_GRAVITY_ENUM +enum xdg_positioner_gravity { + XDG_POSITIONER_GRAVITY_NONE = 0, + XDG_POSITIONER_GRAVITY_TOP = 1, + XDG_POSITIONER_GRAVITY_BOTTOM = 2, + XDG_POSITIONER_GRAVITY_LEFT = 3, + XDG_POSITIONER_GRAVITY_RIGHT = 4, + XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, + XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, + XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, +}; +#endif /* XDG_POSITIONER_GRAVITY_ENUM */ + +#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM +/** + * @ingroup iface_xdg_positioner + * vertically resize the surface + * + * Resize the surface vertically so that it is completely unconstrained. + */ +enum xdg_positioner_constraint_adjustment { + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, +}; +#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ + +/** + * @ingroup iface_xdg_positioner + * @struct xdg_positioner_interface + */ +struct xdg_positioner_interface { + /** + * destroy the xdg_positioner object + * + * Notify the compositor that the xdg_positioner will no longer + * be used. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the size of the to-be positioned rectangle + * + * Set the size of the surface that is to be positioned with the + * positioner object. The size is in surface-local coordinates and + * corresponds to the window geometry. See + * xdg_surface.set_window_geometry. + * + * If a zero or negative size is set the invalid_input error is + * raised. + * @param width width of positioned rectangle + * @param height height of positioned rectangle + */ + void (*set_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); + /** + * set the anchor rectangle within the parent surface + * + * Specify the anchor rectangle within the parent surface that + * the child surface will be placed relative to. The rectangle is + * relative to the window geometry as defined by + * xdg_surface.set_window_geometry of the parent surface. + * + * When the xdg_positioner object is used to position a child + * surface, the anchor rectangle may not extend outside the window + * geometry of the positioned child's parent surface. + * + * If a negative size is set the invalid_input error is raised. + * @param x x position of anchor rectangle + * @param y y position of anchor rectangle + * @param width width of anchor rectangle + * @param height height of anchor rectangle + */ + void (*set_anchor_rect)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * set anchor rectangle anchor + * + * Defines the anchor point for the anchor rectangle. The + * specified anchor is used derive an anchor point that the child + * surface will be positioned relative to. If a corner anchor is + * set (e.g. 'top_left' or 'bottom_right'), the anchor point will + * be at the specified corner; otherwise, the derived anchor point + * will be centered on the specified edge, or in the center of the + * anchor rectangle if no edge is specified. + * @param anchor anchor + */ + void (*set_anchor)(struct wl_client *client, + struct wl_resource *resource, + uint32_t anchor); + /** + * set child surface gravity + * + * Defines in what direction a surface should be positioned, + * relative to the anchor point of the parent surface. If a corner + * gravity is specified (e.g. 'bottom_right' or 'top_left'), then + * the child surface will be placed towards the specified gravity; + * otherwise, the child surface will be centered over the anchor + * point on any axis that had no gravity specified. + * @param gravity gravity direction + */ + void (*set_gravity)(struct wl_client *client, + struct wl_resource *resource, + uint32_t gravity); + /** + * set the adjustment to be done when constrained + * + * Specify how the window should be positioned if the originally + * intended position caused the surface to be constrained, meaning + * at least partially outside positioning boundaries set by the + * compositor. The adjustment is set by constructing a bitmask + * describing the adjustment to be made when the surface is + * constrained on that axis. + * + * If no bit for one axis is set, the compositor will assume that + * the child surface should not change its position on that axis + * when constrained. + * + * If more than one bit for one axis is set, the order of how + * adjustments are applied is specified in the corresponding + * adjustment descriptions. + * + * The default adjustment is none. + * @param constraint_adjustment bit mask of constraint adjustments + */ + void (*set_constraint_adjustment)(struct wl_client *client, + struct wl_resource *resource, + uint32_t constraint_adjustment); + /** + * set surface position offset + * + * Specify the surface position offset relative to the position + * of the anchor on the anchor rectangle and the anchor on the + * surface. For example if the anchor of the anchor rectangle is at + * (x, y), the surface has the gravity bottom|right, and the offset + * is (ox, oy), the calculated surface position will be (x + ox, y + * + oy). The offset position of the surface is the one used for + * constraint testing. See set_constraint_adjustment. + * + * An example use case is placing a popup menu on top of a user + * interface element, while aligning the user interface element of + * the parent surface with some user interface element placed + * somewhere in the popup surface. + * @param x surface position x offset + * @param y surface position y offset + */ + void (*set_offset)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y); + /** + * continuously reconstrain the surface + * + * When set reactive, the surface is reconstrained if the + * conditions used for constraining changed, e.g. the parent window + * moved. + * + * If the conditions changed and the popup was reconstrained, an + * xdg_popup.configure event is sent with updated geometry, + * followed by an xdg_surface.configure event. + * @since 3 + */ + void (*set_reactive)(struct wl_client *client, + struct wl_resource *resource); + /** + * + * + * Set the parent window geometry the compositor should use when + * positioning the popup. The compositor may use this information + * to determine the future state the popup should be constrained + * using. If this doesn't match the dimension of the parent the + * popup is eventually positioned against, the behavior is + * undefined. + * + * The arguments are given in the surface-local coordinate space. + * @param parent_width future window geometry width of parent + * @param parent_height future window geometry height of parent + * @since 3 + */ + void (*set_parent_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t parent_width, + int32_t parent_height); + /** + * set parent configure this is a response to + * + * Set the serial of an xdg_surface.configure event this + * positioner will be used in response to. The compositor may use + * this information together with set_parent_size to determine what + * future state the popup should be constrained using. + * @param serial serial of parent configure event + * @since 3 + */ + void (*set_parent_configure)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); +}; + + +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 +/** + * @ingroup iface_xdg_positioner + */ +#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 + +#ifndef XDG_SURFACE_ERROR_ENUM +#define XDG_SURFACE_ERROR_ENUM +enum xdg_surface_error { + XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, + XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, + XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, +}; +#endif /* XDG_SURFACE_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_surface + * @struct xdg_surface_interface + */ +struct xdg_surface_interface { + /** + * destroy the xdg_surface + * + * Destroy the xdg_surface object. An xdg_surface must only be + * destroyed after its role object has been destroyed. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * assign the xdg_toplevel surface role + * + * This creates an xdg_toplevel object for the given xdg_surface + * and gives the associated wl_surface the xdg_toplevel role. + * + * See the documentation of xdg_toplevel for more details about + * what an xdg_toplevel is and how it is used. + */ + void (*get_toplevel)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id); + /** + * assign the xdg_popup surface role + * + * This creates an xdg_popup object for the given xdg_surface and + * gives the associated wl_surface the xdg_popup role. + * + * If null is passed as a parent, a parent surface must be + * specified using some other protocol, before committing the + * initial state. + * + * See the documentation of xdg_popup for more details about what + * an xdg_popup is and how it is used. + */ + void (*get_popup)(struct wl_client *client, + struct wl_resource *resource, + uint32_t id, + struct wl_resource *parent, + struct wl_resource *positioner); + /** + * set the new window geometry + * + * The window geometry of a surface is its "visible bounds" from + * the user's perspective. Client-side decorations often have + * invisible portions like drop-shadows which should be ignored for + * the purposes of aligning, placing and constraining windows. + * + * The window geometry is double buffered, and will be applied at + * the time wl_surface.commit of the corresponding wl_surface is + * called. + * + * When maintaining a position, the compositor should treat the (x, + * y) coordinate of the window geometry as the top left corner of + * the window. A client changing the (x, y) window geometry + * coordinate should in general not alter the position of the + * window. + * + * Once the window geometry of the surface is set, it is not + * possible to unset it, and it will remain the same until + * set_window_geometry is called again, even if a new subsurface or + * buffer is attached. + * + * If never set, the value is the full bounds of the surface, + * including any subsurfaces. This updates dynamically on every + * commit. This unset is meant for extremely simple clients. + * + * The arguments are given in the surface-local coordinate space of + * the wl_surface associated with this xdg_surface. + * + * The width and height must be greater than zero. Setting an + * invalid size will raise an error. When applied, the effective + * window geometry will be the set window geometry clamped to the + * bounding rectangle of the combined geometry of the surface of + * the xdg_surface and the associated subsurfaces. + */ + void (*set_window_geometry)(struct wl_client *client, + struct wl_resource *resource, + int32_t x, + int32_t y, + int32_t width, + int32_t height); + /** + * ack a configure event + * + * When a configure event is received, if a client commits the + * surface in response to the configure event, then the client must + * make an ack_configure request sometime before the commit + * request, passing along the serial of the configure event. + * + * For instance, for toplevel surfaces the compositor might use + * this information to move a surface to the top left only when the + * client has drawn itself for the maximized or fullscreen state. + * + * If the client receives multiple configure events before it can + * respond to one, it only has to ack the last configure event. + * + * A client is not required to commit immediately after sending an + * ack_configure request - it may even ack_configure several times + * before its next surface commit. + * + * A client may send multiple ack_configure requests before + * committing, but only the last request sent before a commit + * indicates which configure event the client really is responding + * to. + * @param serial the serial from the configure event + */ + void (*ack_configure)(struct wl_client *client, + struct wl_resource *resource, + uint32_t serial); +}; + +#define XDG_SURFACE_CONFIGURE 0 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_surface + */ +#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_surface + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + * @param serial serial of the configure event + */ +static inline void +xdg_surface_send_configure(struct wl_resource *resource_, uint32_t serial) +{ + wl_resource_post_event(resource_, XDG_SURFACE_CONFIGURE, serial); +} + +#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM +#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM +/** + * @ingroup iface_xdg_toplevel + * edge values for resizing + * + * These values are used to indicate which edge of a surface + * is being dragged in a resize operation. + */ +enum xdg_toplevel_resize_edge { + XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, + XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, + XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, + XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, + XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, + XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, +}; +#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ + +#ifndef XDG_TOPLEVEL_STATE_ENUM +#define XDG_TOPLEVEL_STATE_ENUM +/** + * @ingroup iface_xdg_toplevel + * the surface is tiled + * + * The window is currently in a tiled layout and the bottom edge is + * considered to be adjacent to another part of the tiling grid. + */ +enum xdg_toplevel_state { + /** + * the surface is maximized + */ + XDG_TOPLEVEL_STATE_MAXIMIZED = 1, + /** + * the surface is fullscreen + */ + XDG_TOPLEVEL_STATE_FULLSCREEN = 2, + /** + * the surface is being resized + */ + XDG_TOPLEVEL_STATE_RESIZING = 3, + /** + * the surface is now activated + */ + XDG_TOPLEVEL_STATE_ACTIVATED = 4, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_LEFT = 5, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_TOP = 7, + /** + * @since 2 + */ + XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, +}; +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 +#endif /* XDG_TOPLEVEL_STATE_ENUM */ + +/** + * @ingroup iface_xdg_toplevel + * @struct xdg_toplevel_interface + */ +struct xdg_toplevel_interface { + /** + * destroy the xdg_toplevel + * + * This request destroys the role surface and unmaps the surface; + * see "Unmapping" behavior in interface section for details. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the parent of this surface + * + * Set the "parent" of this surface. This surface should be + * stacked above the parent surface and all other ancestor + * surfaces. + * + * Parent windows should be set on dialogs, toolboxes, or other + * "auxiliary" surfaces, so that the parent is raised when the + * dialog is raised. + * + * Setting a null parent for a child window removes any + * parent-child relationship for the child. Setting a null parent + * for a window which currently has no parent is a no-op. + * + * If the parent is unmapped then its children are managed as + * though the parent of the now-unmapped parent has become the + * parent of this surface. If no parent exists for the now-unmapped + * parent then the children are managed as though they have no + * parent surface. + */ + void (*set_parent)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *parent); + /** + * set surface title + * + * Set a short title for the surface. + * + * This string may be used to identify the surface in a task bar, + * window list, or other user interface elements provided by the + * compositor. + * + * The string must be encoded in UTF-8. + */ + void (*set_title)(struct wl_client *client, + struct wl_resource *resource, + const char *title); + /** + * set application ID + * + * Set an application identifier for the surface. + * + * The app ID identifies the general class of applications to which + * the surface belongs. The compositor can use this to group + * multiple surfaces together, or to determine how to launch a new + * application. + * + * For D-Bus activatable applications, the app ID is used as the + * D-Bus service name. + * + * The compositor shell will try to group application surfaces + * together by their app ID. As a best practice, it is suggested to + * select app ID's that match the basename of the application's + * .desktop file. For example, "org.freedesktop.FooViewer" where + * the .desktop file is "org.freedesktop.FooViewer.desktop". + * + * Like other properties, a set_app_id request can be sent after + * the xdg_toplevel has been mapped to update the property. + * + * See the desktop-entry specification [0] for more details on + * application identifiers and how they relate to well-known D-Bus + * names and .desktop files. + * + * [0] http://standards.freedesktop.org/desktop-entry-spec/ + */ + void (*set_app_id)(struct wl_client *client, + struct wl_resource *resource, + const char *app_id); + /** + * show the window menu + * + * Clients implementing client-side decorations might want to + * show a context menu when right-clicking on the decorations, + * giving the user a menu that they can use to maximize or minimize + * the window. + * + * This request asks the compositor to pop up such a window menu at + * the given position, relative to the local surface coordinates of + * the parent surface. There are no guarantees as to what menu + * items the window menu contains. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + * @param x the x position to pop up the window menu at + * @param y the y position to pop up the window menu at + */ + void (*show_window_menu)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial, + int32_t x, + int32_t y); + /** + * start an interactive move + * + * Start an interactive, user-driven move of the surface. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. The + * passed serial is used to determine the type of interactive move + * (touch, pointer, etc). + * + * The server may ignore move requests depending on the state of + * the surface (e.g. fullscreen or maximized), or if the passed + * serial is no longer valid. + * + * If triggered, the surface will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the move. It is up to the + * compositor to visually indicate that the move is taking place, + * such as updating a pointer cursor, during the move. There is no + * guarantee that the device focus will return when the move is + * completed. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + */ + void (*move)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial); + /** + * start an interactive resize + * + * Start a user-driven, interactive resize of the surface. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. The + * passed serial is used to determine the type of interactive + * resize (touch, pointer, etc). + * + * The server may ignore resize requests depending on the state of + * the surface (e.g. fullscreen or maximized). + * + * If triggered, the client will receive configure events with the + * "resize" state enum value and the expected sizes. See the + * "resize" enum value for more details about what is required. The + * client must also acknowledge configure events using + * "ack_configure". After the resize is completed, the client will + * receive another "configure" event without the resize state. + * + * If triggered, the surface also will lose the focus of the device + * (wl_pointer, wl_touch, etc) used for the resize. It is up to the + * compositor to visually indicate that the resize is taking place, + * such as updating a pointer cursor, during the resize. There is + * no guarantee that the device focus will return when the resize + * is completed. + * + * The edges parameter specifies how the surface should be resized, + * and is one of the values of the resize_edge enum. The compositor + * may use this information to update the surface position for + * example when dragging the top left corner. The compositor may + * also use this information to adapt its behavior, e.g. choose an + * appropriate cursor image. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + * @param edges which edge or corner is being dragged + */ + void (*resize)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial, + uint32_t edges); + /** + * set the maximum size + * + * Set a maximum size for the window. + * + * The client can specify a maximum size so that the compositor + * does not try to configure the window beyond this size. + * + * The width and height arguments are in window geometry + * coordinates. See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get + * applied on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the maximum + * size. The compositor may decide to ignore the values set by the + * client and request a larger size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected maximum size in the given dimension. As a + * result, a client wishing to reset the maximum size to an + * unspecified state can use zero for width and height in the + * request. + * + * Requesting a maximum size to be smaller than the minimum size of + * a surface is illegal and will result in a protocol error. + * + * The width and height must be greater than or equal to zero. + * Using strictly negative values for width and height will result + * in a protocol error. + */ + void (*set_max_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); + /** + * set the minimum size + * + * Set a minimum size for the window. + * + * The client can specify a minimum size so that the compositor + * does not try to configure the window below this size. + * + * The width and height arguments are in window geometry + * coordinates. See xdg_surface.set_window_geometry. + * + * Values set in this way are double-buffered. They will get + * applied on the next commit. + * + * The compositor can use this information to allow or disallow + * different states like maximize or fullscreen and draw accurate + * animations. + * + * Similarly, a tiling window manager may use this information to + * place and resize client windows in a more effective way. + * + * The client should not rely on the compositor to obey the minimum + * size. The compositor may decide to ignore the values set by the + * client and request a smaller size. + * + * If never set, or a value of zero in the request, means that the + * client has no expected minimum size in the given dimension. As a + * result, a client wishing to reset the minimum size to an + * unspecified state can use zero for width and height in the + * request. + * + * Requesting a minimum size to be larger than the maximum size of + * a surface is illegal and will result in a protocol error. + * + * The width and height must be greater than or equal to zero. + * Using strictly negative values for width and height will result + * in a protocol error. + */ + void (*set_min_size)(struct wl_client *client, + struct wl_resource *resource, + int32_t width, + int32_t height); + /** + * maximize the window + * + * Maximize the surface. + * + * After requesting that the surface should be maximized, the + * compositor will respond by emitting a configure event. Whether + * this configure actually sets the window maximized is subject to + * compositor policies. The client must then update its content, + * drawing in the configured state. The client must also + * acknowledge the configure when committing the new content (see + * ack_configure). + * + * It is up to the compositor to decide how and where to maximize + * the surface, for example which output and what region of the + * screen should be used. + * + * If the surface was already maximized, the compositor will still + * emit a configure event with the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no + * direct effect. It may alter the state the surface is returned to + * when unmaximized unless overridden by the compositor. + */ + void (*set_maximized)(struct wl_client *client, + struct wl_resource *resource); + /** + * unmaximize the window + * + * Unmaximize the surface. + * + * After requesting that the surface should be unmaximized, the + * compositor will respond by emitting a configure event. Whether + * this actually un-maximizes the window is subject to compositor + * policies. If available and applicable, the compositor will + * include the window geometry dimensions the window had prior to + * being maximized in the configure event. The client must then + * update its content, drawing it in the configured state. The + * client must also acknowledge the configure when committing the + * new content (see ack_configure). + * + * It is up to the compositor to position the surface after it was + * unmaximized; usually the position the surface had before + * maximizing, if applicable. + * + * If the surface was already not maximized, the compositor will + * still emit a configure event without the "maximized" state. + * + * If the surface is in a fullscreen state, this request has no + * direct effect. It may alter the state the surface is returned to + * when unmaximized unless overridden by the compositor. + */ + void (*unset_maximized)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the window as fullscreen on an output + * + * Make the surface fullscreen. + * + * After requesting that the surface should be fullscreened, the + * compositor will respond by emitting a configure event. Whether + * the client is actually put into a fullscreen state is subject to + * compositor policies. The client must also acknowledge the + * configure when committing the new content (see ack_configure). + * + * The output passed by the request indicates the client's + * preference as to which display it should be set fullscreen on. + * If this value is NULL, it's up to the compositor to choose which + * display will be used to map this surface. + * + * If the surface doesn't cover the whole output, the compositor + * will position the surface in the center of the output and + * compensate with with border fill covering the rest of the + * output. The content of the border fill is undefined, but should + * be assumed to be in some way that attempts to blend into the + * surrounding area (e.g. solid black). + * + * If the fullscreened surface is not opaque, the compositor must + * make sure that other screen content not part of the same surface + * tree (made up of subsurfaces, popups or similarly coupled + * surfaces) are not visible below the fullscreened surface. + */ + void (*set_fullscreen)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *output); + /** + * unset the window as fullscreen + * + * Make the surface no longer fullscreen. + * + * After requesting that the surface should be unfullscreened, the + * compositor will respond by emitting a configure event. Whether + * this actually removes the fullscreen state of the client is + * subject to compositor policies. + * + * Making a surface unfullscreen sets states for the surface based + * on the following: * the state(s) it may have had before becoming + * fullscreen * any state(s) decided by the compositor * any + * state(s) requested by the client while the surface was + * fullscreen + * + * The compositor may include the previous window geometry + * dimensions in the configure event, if applicable. + * + * The client must also acknowledge the configure when committing + * the new content (see ack_configure). + */ + void (*unset_fullscreen)(struct wl_client *client, + struct wl_resource *resource); + /** + * set the window as minimized + * + * Request that the compositor minimize your surface. There is no + * way to know if the surface is currently minimized, nor is there + * any way to unset minimization on this surface. + * + * If you are looking to throttle redrawing when minimized, please + * instead use the wl_surface.frame event for this, as this will + * also work with live previews on windows in Alt-Tab, Expose or + * similar compositor features. + */ + void (*set_minimized)(struct wl_client *client, + struct wl_resource *resource); +}; + +#define XDG_TOPLEVEL_CONFIGURE 0 +#define XDG_TOPLEVEL_CLOSE 1 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_toplevel + */ +#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 + +/** + * @ingroup iface_xdg_toplevel + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_toplevel_send_configure(struct wl_resource *resource_, int32_t width, int32_t height, struct wl_array *states) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_CONFIGURE, width, height, states); +} + +/** + * @ingroup iface_xdg_toplevel + * Sends an close event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_toplevel_send_close(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, XDG_TOPLEVEL_CLOSE); +} + +#ifndef XDG_POPUP_ERROR_ENUM +#define XDG_POPUP_ERROR_ENUM +enum xdg_popup_error { + /** + * tried to grab after being mapped + */ + XDG_POPUP_ERROR_INVALID_GRAB = 0, +}; +#endif /* XDG_POPUP_ERROR_ENUM */ + +/** + * @ingroup iface_xdg_popup + * @struct xdg_popup_interface + */ +struct xdg_popup_interface { + /** + * remove xdg_popup interface + * + * This destroys the popup. Explicitly destroying the xdg_popup + * object will also dismiss the popup, and unmap the surface. + * + * If this xdg_popup is not the "topmost" popup, a protocol error + * will be sent. + */ + void (*destroy)(struct wl_client *client, + struct wl_resource *resource); + /** + * make the popup take an explicit grab + * + * This request makes the created popup take an explicit grab. An + * explicit grab will be dismissed when the user dismisses the + * popup, or when the client destroys the xdg_popup. This can be + * done by the user clicking outside the surface, using the + * keyboard, or even locking the screen through closing the lid or + * a timeout. + * + * If the compositor denies the grab, the popup will be immediately + * dismissed. + * + * This request must be used in response to some sort of user + * action like a button press, key press, or touch down event. The + * serial number of the event should be passed as 'serial'. + * + * The parent of a grabbing popup must either be an xdg_toplevel + * surface or another xdg_popup with an explicit grab. If the + * parent is another xdg_popup it means that the popups are nested, + * with this popup now being the topmost popup. + * + * Nested popups must be destroyed in the reverse order they were + * created in, e.g. the only popup you are allowed to destroy at + * all times is the topmost one. + * + * When compositors choose to dismiss a popup, they may dismiss + * every nested grabbing popup as well. When a compositor dismisses + * popups, it will follow the same dismissing order as required + * from the client. + * + * The parent of a grabbing popup must either be another xdg_popup + * with an active explicit grab, or an xdg_popup or xdg_toplevel, + * if there are no explicit grabs already taken. + * + * If the topmost grabbing popup is destroyed, the grab will be + * returned to the parent of the popup, if that parent previously + * had an explicit grab. + * + * If the parent is a grabbing popup which has already been + * dismissed, this popup will be immediately dismissed. If the + * parent is a popup that did not take an explicit grab, an error + * will be raised. + * + * During a popup grab, the client owning the grab will receive + * pointer and touch events for all their surfaces as normal + * (similar to an "owner-events" grab in X11 parlance), while the + * top most grabbing popup will always have keyboard focus. + * @param seat the wl_seat of the user event + * @param serial the serial of the user event + */ + void (*grab)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *seat, + uint32_t serial); + /** + * recalculate the popup's location + * + * Reposition an already-mapped popup. The popup will be placed + * given the details in the passed xdg_positioner object, and a + * xdg_popup.repositioned followed by xdg_popup.configure and + * xdg_surface.configure will be emitted in response. Any + * parameters set by the previous positioner will be discarded. + * + * The passed token will be sent in the corresponding + * xdg_popup.repositioned event. The new popup position will not + * take effect until the corresponding configure event is + * acknowledged by the client. See xdg_popup.repositioned for + * details. The token itself is opaque, and has no other special + * meaning. + * + * If multiple reposition requests are sent, the compositor may + * skip all but the last one. + * + * If the popup is repositioned in response to a configure event + * for its parent, the client should send an + * xdg_positioner.set_parent_configure and possibly an + * xdg_positioner.set_parent_size request to allow the compositor + * to properly constrain the popup. + * + * If the popup is repositioned together with a parent that is + * being resized, but not in response to a configure event, the + * client should send an xdg_positioner.set_parent_size request. + * @param token reposition request token + * @since 3 + */ + void (*reposition)(struct wl_client *client, + struct wl_resource *resource, + struct wl_resource *positioner, + uint32_t token); +}; + +#define XDG_POPUP_CONFIGURE 0 +#define XDG_POPUP_POPUP_DONE 1 +#define XDG_POPUP_REPOSITIONED 2 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_DESTROY_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_GRAB_SINCE_VERSION 1 +/** + * @ingroup iface_xdg_popup + */ +#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 + +/** + * @ingroup iface_xdg_popup + * Sends an configure event to the client owning the resource. + * @param resource_ The client's resource + * @param x x position relative to parent surface window geometry + * @param y y position relative to parent surface window geometry + * @param width window geometry width + * @param height window geometry height + */ +static inline void +xdg_popup_send_configure(struct wl_resource *resource_, int32_t x, int32_t y, int32_t width, int32_t height) +{ + wl_resource_post_event(resource_, XDG_POPUP_CONFIGURE, x, y, width, height); +} + +/** + * @ingroup iface_xdg_popup + * Sends an popup_done event to the client owning the resource. + * @param resource_ The client's resource + */ +static inline void +xdg_popup_send_popup_done(struct wl_resource *resource_) +{ + wl_resource_post_event(resource_, XDG_POPUP_POPUP_DONE); +} + +/** + * @ingroup iface_xdg_popup + * Sends an repositioned event to the client owning the resource. + * @param resource_ The client's resource + * @param token reposition request token + */ +static inline void +xdg_popup_send_repositioned(struct wl_resource *resource_, uint32_t token) +{ + wl_resource_post_event(resource_, XDG_POPUP_REPOSITIONED, token); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/cmd/wm/xdg.c b/src/cmd/wm/xdg.c new file mode 100644 index 0000000..6a0c2c8 --- /dev/null +++ b/src/cmd/wm/xdg.c @@ -0,0 +1,118 @@ +#include "wm.h" + +static +void +map(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.map); + + wl_list_insert(&server.client.list, &client->link); + wl_list_insert(&server.client.stack, &client->stack); + wl_list_insert(&server.client.focus, &client->focus); + + wlr_xdg_surface_get_geometry(client->xdg, &client->geometry); + client->geometry.width += 2 * client->border; + client->geometry.height += 2 * client->border; + + wlr_xdg_toplevel_set_tiled(client->xdg, + WLR_EDGE_TOP|WLR_EDGE_BOTTOM|WLR_EDGE_LEFT|WLR_EDGE_RIGHT + ); + + rules(client); +} + +static +void +unmap(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.unmap); + + wl_list_remove(&client->link); + attach(client, nil, 0); + + wl_list_remove(&client->stack); + wl_list_remove(&client->focus); +} + +static +void +commit(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.commit); + if(client->resize && client->resize <= client->xdg->configure_serial) + client->resize = 0; +} + +static +void +destroy(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.destroy); + free(client); +} + +static +void +request_move(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.request_move); +} + +static +void +request_resize(struct wl_listener *l, void *data) +{ + struct wlr_xdg_toplevel_resize_event *event = data; + Client *client = wl_container_of(l, client, event.request_resize); +} + + +static +void +request_title(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.request_title); +} + +static +void +request_fullscreen(struct wl_listener *l, void *data) +{ + Client *client = wl_container_of(l, client, event.request_fullscreen); + client->isfullscreen = 1; +} + +void +make_xdg_surface(struct wl_listener *l, void *data) +{ + Client *client; + struct wlr_xdg_toplevel *toplevel; + struct wlr_xdg_surface *xdg = data; + + if(xdg->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) + return; + + client = xdg->surface->data = calloc(1, sizeof(*client)); + client->xdg = xdg; + client->border = cfg·borderpixel; + + client->event.map.notify = map; + wl_signal_add(&xdg->events.map, &client->event.map); + client->event.unmap.notify = unmap; + wl_signal_add(&xdg->events.unmap, &client->event.unmap); + client->event.destroy.notify = destroy; + wl_signal_add(&xdg->events.destroy, &client->event.destroy); + + client->event.commit.notify = commit; + wl_signal_add(&xdg->surface->events.commit, &client->event.commit); + + toplevel = xdg->toplevel; + client->event.request_move.notify = request_move; + wl_signal_add(&toplevel->events.request_move, &client->event.request_move); + client->event.request_title.notify = request_title; + wl_signal_add(&toplevel->events.set_title, &client->event.request_title); + client->event.request_resize.notify = request_resize; + wl_signal_add(&toplevel->events.request_resize, &client->event.request_resize); + client->event.request_fullscreen.notify = request_fullscreen; + wl_signal_add(&toplevel->events.request_fullscreen, &client->event.request_fullscreen); +} -- cgit v1.2.1