backport of local changes

This commit is contained in:
Anselm R Garbe 2009-05-10 13:17:09 +01:00
parent 802f1922f9
commit d58dd3b8bc
9 changed files with 1278 additions and 426 deletions

43
LICENSE
View File

@ -1,22 +1,25 @@
MIT/X Consortium License Copyright (c) 2009, Aurélien APTEL <aurelien dot aptel at gmail dot com>
Copyright (c) 2009, Anselm R Garbe <garbeam at gmail dot com>
© 2007-2008 Anselm R Garbe <garbeam at gmail dot com> Redistribution and use in source and binary forms, with or without
© 2008 Matthias Christian Ott <ott at enolink dot de> modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
Permission is hereby granted, free of charge, to any person obtaining a THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
copy of this software and associated documentation files (the "Software"), "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
to deal in the Software without restriction, including without limitation LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
the rights to use, copy, modify, merge, publish, distribute, sublicense, A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
and/or sell copies of the Software, and to permit persons to whom the HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
Software is furnished to do so, subject to the following conditions: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
The above copyright notice and this permission notice shall be included in DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
all copies or substantial portions of the Software. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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.

View File

@ -1,45 +1,50 @@
# st - simple terminal # st - simple terminal
# See LICENSE file for copyright and license details. # See LICENSE file for copyright and license details.
VERSION = 0.0 include config.mk
PREFIX = /usr/local SRC = st.c
MANDIR = $(PREFIX)/share/man OBJ = ${SRC:.c=.o}
CFLAGS = -DVERSION=\"0.0\" -D_GNU_SOURCE all: options st
all: st std options:
@echo st build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
.c.o:
@echo CC $<
@${CC} -c ${CFLAGS} $<
${OBJ}: config.mk
st: ${OBJ}
@echo CC -o $@
@${CC} -o $@ ${OBJ} ${LDFLAGS}
clean: clean:
rm -f st std @echo cleaning
rm -f st.o std.o @rm -f st ${OBJ} st-${VERSION}.tar.gz
rm -f st-$(VERSION).tar.gz
dist: clean dist: clean
mkdir st-$(VERSION) @echo creating dist tarball
cp -f LICENSE README st-$(VERSION) @mkdir -p st-${VERSION}
cp -f Makefile config.mk st-$(VERSION) @cp -R LICENSE Makefile README config.mk st.h ${SRC} st-${VERSION}
cp -f st.1 std.1 st-$(VERSION) @tar -cf st-${VERSION}.tar st-${VERSION}
cp -f st.c std.c st-$(VERSION) @gzip st-${VERSION}.tar
tar -czf st-$(VERSION).tar st-$(VERSION) @rm -rf st-${VERSION}
rm -rf st-$(VERSION)
install: install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin @echo installing executable file to ${DESTDIR}${PREFIX}/bin
cp -f st $(DESTDIR)$(PREFIX)/bin @mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f std $(DESTDIR)$(PREFIX)/bin @cp -f st ${DESTDIR}${PREFIX}/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/st @chmod 755 ${DESTDIR}${PREFIX}/bin/st
chmod 755 $(DESTDIR)$(PREFIX)/bin/std @tic st.info
mkdir -p $(DESTDIR)$(MANDIR)/man1
sed 's/VERSION/$(VERSION)/g' < st.1 > $(DESTDIR)$(MANDIR)/man1/st.1
chmod 644 $(DESTDIR)$(MANDIR)/man1/st.1
sed 's/VERSION/$(VERSION)/g' < std.1 > $(DESTDIR)$(MANDIR)/man1/std.1
chmod 644 $(DESTDIR)$(MANDIR)/man1/std.1
uninstall: uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/st @echo removing executable file from ${DESTDIR}${PREFIX}/bin
rm -f $(DESTDIR)$(PREFIX)/bin/std @rm -f ${DESTDIR}${PREFIX}/bin/st
rm -f $(DESTDIR)$(MANDIR)/man1/st.1
rm -f $(DESTDIR)$(MANDIR)/man1/std.1
.PHONY: all clean dist install uninstall .PHONY: all options clean dist install uninstall

28
README Normal file
View File

@ -0,0 +1,28 @@
st - simple terminal
--------------------
st is a simple virtual terminal emulator for X which sucks less.
Requirements
------------
In order to build st you need the Xlib header files.
Installation
------------
Edit config.mk to match your local setup (st is installed into
the /usr/local namespace by default).
Afterwards enter the following command to build and install st (if
necessary as root):
make clean install
Running st
----------
See the man page for details.
Credits
-------
Based on Aurélien APTEL <aurelien dot aptel at gmail dot com> bt source code.

9
TODO Normal file
View File

@ -0,0 +1,9 @@
- write a clean terminfo entry
- write global "setup" func
- try to split more logic/gfx
- optimize drawing
- handle copy/paste
- fix fork/child exit problem
- fix resize (shrinking should move last line up)
- handle utf8
- refactor/clean code

31
config.mk Normal file
View File

@ -0,0 +1,31 @@
# st version
VERSION = 0.0
# Customize below to fit your system
# paths
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/share/man
X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib
# Xinerama, comment if you don't want it
#XINERAMALIBS = -L${X11LIB} -lXinerama
#XINERAMAFLAGS = -DXINERAMA
# includes and libs
INCS = -I. -I/usr/include -I${X11INC}
LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 ${XINERAMALIBS}
# flags
CPPFLAGS = -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
LDFLAGS = -s ${LIBS}
# Solaris
#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
#LDFLAGS = ${LIBS}
# compiler and linker
CC = cc

928
st.c
View File

@ -1,17 +1,921 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE for licence details. */
#include <stdio.h> #include "st.h"
#include <stdlib.h>
#include <string.h> /* Globals */
DC dc;
XWindow xw;
Term term;
Escseq escseq;
int cmdfd;
int running;
void
die(const char *errstr, ...) {
va_list ap;
va_start(ap, errstr);
vfprintf(stderr, errstr, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
void
execsh(void) {
char *args[3] = {SHELL, "-i", NULL};
putenv("TERM=" TNAME);
execvp(SHELL, args);
}
void
xbell(void) { /* visual bell */
XRectangle r = { 0, 0, xw.w, xw.h };
XSetForeground(xw.dis, dc.gc, dc.col[BellCol]);
XFillRectangles(xw.dis, xw.win, dc.gc, &r, 1);
XFlush(xw.dis);
usleep(30000);
draw(SCredraw);
}
void
ttynew(void) {
int m, s;
pid_t pid;
char *pts;
if((m = posix_openpt(O_RDWR | O_NOCTTY)) < 0)
die("openpt");
if(grantpt(m) == -1)
die("grandpt");
if(unlockpt(m) == -1)
die("unlockpt");
if((pts = ptsname(m)) == NULL)
die("ptsname");
if((s = open(pts, O_RDWR | O_NOCTTY)) < 0)
die("slave open");
fcntl(s, F_SETFL, O_NDELAY);
switch(pid = fork()) {
case -1:
die("fork");
break;
case 0:
setsid(); /* create a new process group */
dup2(s, STDIN_FILENO);
dup2(s, STDOUT_FILENO);
dup2(s, STDERR_FILENO);
if(ioctl(s, TIOCSCTTY, NULL) < 0)
die("slave TTIOCSTTY");
execsh();
break;
default:
close(s);
cmdfd = m;
}
}
void
dump(char c) {
static int col;
fprintf(stderr, " %02x %c ", c, isprint(c)?c:'.');
if(++col % 10 == 0)
fprintf(stderr, "\n");
}
void
ttyread(void) {
char buf[BUFSIZ] = {0};
int ret;
switch(ret = read(cmdfd, buf, BUFSIZ)) {
case -1: /* error or exit */
/* XXX: be more precise */
running = 0;
break;
default:
tputs(buf, ret);
}
}
void
ttywrite(char *s, size_t n) {
if(write(cmdfd, s, n) == -1)
die("write error on tty.");
}
void
ttyresize(int x, int y) {
struct winsize w;
w.ws_row = term.row;
w.ws_col = term.col;
w.ws_xpixel = w.ws_ypixel = 0;
if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
fprintf(stderr, "Couldn't set window size: %m\n");
}
int
escfinal(char c) {
if(escseq.len == 1)
switch(c) {
case '[':
case ']':
case '(':
return 0;
case '=':
case '>':
default:
return 1;
}
else if(BETWEEN(c, 0x40, 0x7E))
return 1;
return 0;
}
void
tcpos(int mode) {
static int x = 0;
static int y = 0;
if(mode == CSsave)
x = term.c.x, y = term.c.y;
else if(mode == CSload)
tmoveto(x, y);
}
void
tnew(int col, int row) { /* screen size */
term.row = row, term.col = col;
term.top = 0, term.bot = term.row - 1;
/* mode */
term.mode = TMwrap;
/* cursor */
term.c.attr.mode = ATnone;
term.c.attr.fg = DefaultFG;
term.c.attr.bg = DefaultBG;
term.c.x = term.c.y = 0;
term.c.hidden = 0;
/* allocate screen */
term.line = calloc(term.row, sizeof(Line));
for(row = 0 ; row < term.row; row++)
term.line[row] = calloc(term.col, sizeof(Glyph));
}
void
tscroll(void) {
Line temp = term.line[term.top];
int i;
for(i = term.top; i < term.bot; i++)
term.line[i] = term.line[i+1];
memset(temp, 0, sizeof(Glyph) * term.col);
term.line[term.bot] = temp;
xscroll();
}
void
tnewline(void) {
int y = term.c.y + 1;
if(y > term.bot) {
tscroll(), y = term.bot;
}
tmoveto(0, y);
}
int
escaddc(char c) {
escseq.buf[escseq.len++] = c;
if(escfinal(c) || escseq.len >= ESCSIZ) {
escparse(), eschandle();
return 0;
}
return 1;
}
void
escparse(void) {
/* int noarg = 1; */
char *p = escseq.buf;
escseq.narg = 0;
switch(escseq.pre = *p++) {
case '[': /* CSI */
if(*p == '?')
escseq.priv = 1, p++;
while(p < escseq.buf+escseq.len) {
while(isdigit(*p)) {
escseq.arg[escseq.narg] *= 10;
escseq.arg[escseq.narg] += *(p++) - '0'/*, noarg = 0 */;
}
if(*p == ';')
escseq.narg++, p++;
else {
escseq.mode = *p;
escseq.narg++;
return;
}
}
break;
case '(':
/* humf charset stuff */
break;
}
}
void
tmoveto(int x, int y) {
term.c.x = x < 0 ? 0 : x >= term.col ? term.col-1 : x;
term.c.y = y < 0 ? 0 : y >= term.row ? term.row-1 : y;
}
void
tcursor(int dir) {
int xi = term.c.x, yi = term.c.y;
int xf = xi, yf = yi;
switch(dir) {
case CSup:
yf--;
break;
case CSdown:
yf++;
break;
case CSleft:
xf--;
if(xf < 0) {
xf = term.col-1, yf--;
if(yf < term.top)
yf = term.top, xf = 0;
}
break;
case CSright:
xf++;
if(xf >= term.col) {
xf = 0, yf++;
if(yf > term.bot)
yf = term.bot, tscroll();
}
break;
}
tmoveto(xf, yf);
}
void
tsetchar(char c) {
term.line[term.c.y][term.c.x] = term.c.attr;
term.line[term.c.y][term.c.x].c = c;
term.line[term.c.y][term.c.x].state |= CRset | CRupdate;
}
void
tclearregion(int x1, int y1, int x2, int y2) {
int x, y;
LIMIT(x1, 0, term.col-1);
LIMIT(x2, 0, term.col-1);
LIMIT(y1, 0, term.row-1);
LIMIT(y2, 0, term.row-1);
/* XXX: could be optimized */
for(x = x1; x <= x2; x++)
for(y = y1; y <= y2; y++)
memset(&term.line[y][x], 0, sizeof(Glyph));
xclear(x1, y1, x2, y2);
}
void
tdeletechar(int n) {
int src = term.c.x + n;
int dst = term.c.x;
int size = term.col - src;
if(src >= term.col) {
tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
return;
}
memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
tclearregion(term.col-size, term.c.y, term.col-1, term.c.y);
}
void
tinsertblank(int n) {
int src = term.c.x;
int dst = src + n;
int size = term.col - n - src;
if(dst >= term.col) {
tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
return;
}
memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
tclearregion(src, term.c.y, dst, term.c.y);
}
void
tinsertblankline (int n) {
int i;
Line blank;
int bot = term.bot;
if(term.c.y > term.bot)
bot = term.row - 1;
else if(term.c.y < term.top)
bot = term.top - 1;
if(term.c.y + n >= bot) {
tclearregion(0, term.c.y, term.col-1, bot);
return;
}
for(i = bot; i >= term.c.y+n; i--) {
/* swap deleted line <-> blanked line */
blank = term.line[i];
term.line[i] = term.line[i-n];
term.line[i-n] = blank;
/* blank it */
memset(blank, 0, term.col * sizeof(Glyph));
}
}
void
tdeleteline(int n) {
int i;
Line blank;
int bot = term.bot;
if(term.c.y > term.bot)
bot = term.row - 1;
else if(term.c.y < term.top)
bot = term.top - 1;
if(term.c.y + n >= bot) {
tclearregion(0, term.c.y, term.col-1, bot);
return;
}
for(i = term.c.y; i <= bot-n; i++) {
/* swap deleted line <-> blanked line */
blank = term.line[i];
term.line[i] = term.line[i+n];
term.line[i+n] = blank;
/* blank it */
memset(blank, 0, term.col * sizeof(Glyph));
}
}
void
tsetattr(int *attr, int l) {
int i;
#ifdef TRUECOLOR /* ESC [ ? <fg/bg> ; <r> ; <g> ; <b> m */
Color col;
if(escseq.priv && escseq.len == 4) { /* True color extension :) */
col = (escseq.arg[1]<<16) + (escseq.arg[2]<<8) + escseq.arg[3];
switch(escseq.arg[0]) {
case 3: /* foreground */
term.c.attr.fg = col;
break;
case 4: /* background */
term.c.attr.bg = col;
break;
}
}
else
#endif
for(i = 0; i < l; i++) {
switch(attr[i]) {
case 0:
memset(&term.c.attr, 0, sizeof(term.c.attr));
term.c.attr.fg = DefaultFG;
term.c.attr.bg = DefaultBG;
break;
case 1:
term.c.attr.mode |= ATbold;
break;
case 4:
term.c.attr.mode |= ATunderline;
break;
case 7:
term.c.attr.mode |= ATreverse;
break;
case 8:
term.c.hidden = CShide;
break;
case 22:
term.c.attr.mode &= ~ATbold;
break;
case 24:
term.c.attr.mode &= ~ATunderline;
break;
case 27:
term.c.attr.mode &= ~ATreverse;
break;
case 39:
term.c.attr.fg = DefaultFG;
break;
case 49:
term.c.attr.fg = 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;
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
eschandle(void) {
/* escdump(); */
switch(escseq.pre) {
case '[':
switch(escseq.mode) {
case '@': /* Insert <n> blank char */
DEFAULT(escseq.arg[0], 1);
tinsertblank(escseq.arg[0]);
break;
case 'A': /* Cursor <n> Up */
case 'e':
DEFAULT(escseq.arg[0], 1);
tmoveto(term.c.x, term.c.y-escseq.arg[0]);
break;
case 'B': /* Cursor <n> Down */
DEFAULT(escseq.arg[0], 1);
tmoveto(term.c.x, term.c.y+escseq.arg[0]);
break;
case 'C': /* Cursor <n> Forward */
case 'a':
DEFAULT(escseq.arg[0], 1);
tmoveto(term.c.x+escseq.arg[0], term.c.y);
break;
case 'D': /* Cursor <n> Backward */
DEFAULT(escseq.arg[0], 1);
tmoveto(term.c.x-escseq.arg[0], term.c.y);
break;
case 'E': /* Cursor <n> Down and first col */
DEFAULT(escseq.arg[0], 1);
tmoveto(0, term.c.y+escseq.arg[0]);
break;
case 'F': /* Cursor <n> Up and first col */
DEFAULT(escseq.arg[0], 1);
tmoveto(0, term.c.y-escseq.arg[0]);
break;
case 'G': /* Move to <col> */
case '`':
DEFAULT(escseq.arg[0], 1);
tmoveto(escseq.arg[0]-1, term.c.y);
break;
case 'H': /* Move to <row> <col> */
case 'f':
DEFAULT(escseq.arg[0], 1);
DEFAULT(escseq.arg[1], 1);
tmoveto(escseq.arg[1]-1, escseq.arg[0]-1);
break;
case 'J': /* Clear screen */
switch(escseq.arg[0]) {
case 0: /* below */
tclearregion(term.c.x, term.c.y, term.col-1, term.row-1);
break;
case 1: /* above */
tclearregion(0, 0, term.c.x, term.c.y);
break;
case 2: /* all */
tclearregion(0, 0, term.col-1, term.row-1);
break;
}
break;
case 'K': /* Clear line */
switch(escseq.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 'L': /* Insert <n> blank lines */
DEFAULT(escseq.arg[0], 1);
tinsertblankline(escseq.arg[0]);
break;
case 'M': /* Delete <n> lines */
DEFAULT(escseq.arg[0], 1);
tdeleteline(escseq.arg[0]);
break;
case 'P': /* Delete <n> char */
DEFAULT(escseq.arg[0], 1);
tdeletechar(escseq.arg[0]);
break;
case 'd': /* Move to <row> */
DEFAULT(escseq.arg[0], 1);
tmoveto(term.c.x, escseq.arg[0]-1);
break;
case 'h': /* Set terminal mode */
break;
case 'm': /* Terminal attribute (color) */
tsetattr(escseq.arg, escseq.narg);
break;
case 'r':
if(escseq.priv)
;
else {
DEFAULT(escseq.arg[0], 1);
DEFAULT(escseq.arg[1], term.row);
tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
}
break;
case 's': /* Save cursor position */
tcpos(CSsave);
break;
case 'u': /* Load cursor position */
tcpos(CSload);
break;
}
break;
}
}
void
escdump(void) {
int i;
puts("------");
printf("rawbuf : %s\n", escseq.buf);
printf("prechar : %c\n", escseq.pre);
printf("private : %c\n", escseq.priv ? '?' : ' ');
printf("narg : %d\n", escseq.narg);
if(escseq.narg) {
for(i = 0; i < escseq.narg; i++)
printf("\targ %d = %d\n", i, escseq.arg[i]);
}
printf("mode : %c\n", escseq.mode);
}
void
escreset(void) {
memset(&escseq, 0, sizeof(escseq));
}
void
tputc(char c) {
static int inesc = 0;
dump(c);
/* start of escseq */
if(c == '\033')
escreset(), inesc = 1;
else if(inesc) {
inesc = escaddc(c);
} /* normal char */
else switch(c) {
default:
tsetchar(c);
tcursor(CSright);
break;
case '\b':
tcursor(CSleft);
break;
case '\r':
tmoveto(0, term.c.y);
break;
case '\n':
tnewline();
break;
case '\a':
xbell();
break;
}
}
void
tputs(char *s, int len) {
for(; len > 0; len--)
tputc(*s++);
}
void
tdump(void) {
int row, col;
Glyph c;
for(row = 0; row < term.row; row++) {
for(col = 0; col < term.col; col++) {
if(col == term.c.x && row == term.c.y)
putchar('#');
else {
c = term.line[row][col];
putchar(c.state & CRset ? c.c : '.');
}
}
putchar('\n');
}
}
void
tresize(int col, int row) {
int i;
Line *line;
int minrow = MIN(row, term.row);
int mincol = MIN(col, term.col);
if(col < 1 || row < 1)
return;
line = calloc(row, sizeof(Line));
for(i = 0 ; i < row; i++)
line[i] = calloc(col, sizeof(Glyph));
for(i = 0 ; i < minrow; i++) {
memcpy(line[i], term.line[i], mincol * sizeof(Glyph));
free(term.line[i]);
}
free(term.line);
LIMIT(term.c.x, 0, col-1);
LIMIT(term.c.y, 0, row-1);
LIMIT(term.top, 0, row-1);
LIMIT(term.bot, 0, row-1);
// if(term.bot == term.row-1)
term.bot = row-1;
term.line = line;
term.col = col, term.row = row;
}
unsigned long
xgetcol(const char *s) {
XColor color;
Colormap cmap = DefaultColormap(xw.dis, xw.scr);
if(!XAllocNamedColor(xw.dis, cmap, s, &color, &color)) {
color.pixel = WhitePixel(xw.dis, xw.scr);
fprintf(stderr, "Could not allocate color '%s'\n", s);
}
return color.pixel;
}
void
xclear(int x1, int y1, int x2, int y2) {
XClearArea(xw.dis, xw.win,
x1 * xw.cw, y1 * xw.ch,
(x2-x1+1) * xw.cw, (y2-y1+1) * xw.ch,
False);
}
void
xscroll(void) {
int srcy = (term.top+1) * xw.ch;
int dsty = term.top * xw.ch;
int height = (term.bot-term.top) * xw.ch;
xcursor(CShide);
XCopyArea(xw.dis, xw.win, xw.win, dc.gc, 0, srcy, xw.w, height, 0, dsty);
xclear(0, term.bot, term.col-1, term.bot);
}
void
xinit(void) {
XGCValues values;
unsigned long valuemask;
XClassHint chint;
XWMHints wmhint;
XSizeHints shint;
char *args[] = {NULL};
int i;
xw.dis = XOpenDisplay(NULL);
xw.scr = XDefaultScreen(xw.dis);
/* font */
dc.font = XLoadQueryFont(xw.dis, FONT);
xw.cw = dc.font->max_bounds.rbearing - dc.font->min_bounds.lbearing;
xw.ch = dc.font->ascent + dc.font->descent + LINESPACE;
/* colors */
for(i = 0; i < LEN(colorname); i++)
dc.col[i] = xgetcol(colorname[i]);
term.c.attr.fg = DefaultFG;
term.c.attr.bg = DefaultBG;
term.c.attr.mode = ATnone;
/* windows */
xw.h = term.row * xw.ch;
xw.w = term.col * xw.cw;
/* XXX: this BORDER is useless after the first resize, handle it in xdraws() */
xw.win = XCreateSimpleWindow(xw.dis, XRootWindow(xw.dis, xw.scr), 0, 0,
xw.w, xw.h, BORDER,
dc.col[DefaultBG],
dc.col[DefaultBG]);
/* gc */
values.foreground = XWhitePixel(xw.dis, xw.scr);
values.font = dc.font->fid;
valuemask = GCForeground | GCFont;
dc.gc = XCreateGC(xw.dis, xw.win, valuemask, &values);
XMapWindow(xw.dis, xw.win);
/* wm stuff */
chint.res_name = TNAME, chint.res_class = TNAME;
wmhint.input = 1, wmhint.flags = InputHint;
shint.height_inc = xw.ch, shint.width_inc = xw.cw;
shint.height = xw.h, shint.width = xw.w;
shint.flags = PSize | PResizeInc;
XSetWMProperties(xw.dis, xw.win, NULL, NULL, &args[0], 0, &shint, &wmhint, &chint);
XStoreName(xw.dis, xw.win, TNAME);
XSync(xw.dis, 0);
}
void
xdrawc(int x, int y, Glyph g) {
XRectangle r = { x * xw.cw, y * xw.ch, xw.cw, xw.ch };
unsigned long xfg, xbg;
/* reverse video */
if(g.mode & ATreverse)
xfg = dc.col[g.bg], xbg = dc.col[g.fg];
else
xfg = dc.col[g.fg], xbg = dc.col[g.bg];
/* background */
XSetForeground(xw.dis, dc.gc, xbg);
XFillRectangles(xw.dis, xw.win, dc.gc, &r, 1);
/* string */
XSetForeground(xw.dis, dc.gc, xfg);
XDrawString(xw.dis, xw.win, dc.gc, r.x, r.y+dc.font->ascent, &(g.c), 1);
if(g.mode & ATbold) /* XXX: bold hack (draw again at x+1) */
XDrawString(xw.dis, xw.win, dc.gc, r.x+1, r.y+dc.font->ascent, &(g.c), 1);
/* underline */
if(g.mode & ATunderline) {
r.y += dc.font->ascent + 1;
XDrawLine(xw.dis, xw.win, dc.gc, r.x, r.y, r.x+r.width-1, r.y);
}
}
void
xcursor(int mode) {
static int oldx = 0;
static int oldy = 0;
Glyph g = {' ', ATnone, DefaultBG, DefaultCS, 0};
if(term.line[term.c.y][term.c.x].state & CRset)
g.c = term.line[term.c.y][term.c.x].c;
/* remove the old cursor */
if(term.line[oldy][oldx].state & CRset)
xdrawc(oldx, oldy, term.line[oldy][oldx]);
else xclear(oldx, oldy, oldx, oldy); /* XXX: maybe a bug */
if(mode == CSdraw && !term.c.hidden) {
xdrawc(term.c.x, term.c.y, g);
oldx = term.c.x, oldy = term.c.y;
}
}
void
draw(int redraw_all) {
int x, y;
int changed, set;
if(redraw_all)
XClearWindow(xw.dis, xw.win);
/* XXX: drawing could be optimised */
for(y = 0; y < term.row; y++) {
for(x = 0; x < term.col; x++) {
changed = term.line[y][x].state & CRupdate;
set = term.line[y][x].state & CRset;
if((changed && set) || (redraw_all && set)) {
term.line[y][x].state &= ~CRupdate;
xdrawc(x, y, term.line[y][x]);
}
}
}
xcursor(CSdraw);
}
void
kpress(XKeyEvent *e) {
KeySym ksym;
char buf[32];
int len;
int meta;
int shift;
meta = e->state & Mod4Mask;
shift = e->state & ShiftMask;
len = XLookupString(e, buf, sizeof(buf), &ksym, NULL);
if(len > 0) {
buf[sizeof(buf)-1] = '\0';
if(meta && len == 1)
ttywrite("\033", 1);
ttywrite(buf, len);
return;
}
switch(ksym) {
#ifdef DEBUG1
default:
printf("errkey: %d\n", (int)ksym);
break;
#endif
case XK_Up:
case XK_Down:
case XK_Left:
case XK_Right:
sprintf(buf, "\033[%c", "DACB"[ksym - XK_Left]);
ttywrite(buf, 3);
break;
case XK_Delete: ttywrite(KEYDELETE, sizeof(KEYDELETE)-1); break;
case XK_Home: ttywrite( KEYHOME, sizeof( KEYHOME)-1); break;
case XK_End: ttywrite( KEYEND, sizeof( KEYEND)-1); break;
case XK_Prior: ttywrite( KEYPREV, sizeof( KEYPREV)-1); break;
case XK_Next: ttywrite( KEYNEXT, sizeof( KEYNEXT)-1); break;
case XK_Insert:
/* XXX: paste X clipboard */
if(shift);
break;
}
}
void
resize(XEvent *e) {
int col, row;
col = e->xconfigure.width / xw.cw;
row = e->xconfigure.height / xw.ch;
if(term.col != col && term.row != row) {
tresize(col, row);
ttyresize(col, row);
xw.w = e->xconfigure.width;
xw.h = e->xconfigure.height;
draw(SCredraw);
}
}
void
run(void) {
int ret;
XEvent ev;
fd_set rfd;
struct timeval tv = {0, 10000};
running = 1;
XSelectInput(xw.dis, xw.win, ExposureMask | KeyPressMask | StructureNotifyMask);
XResizeWindow(xw.dis, xw.win, xw.w , xw.h); /* seems to fix the resize bug in wmii */
while(running) {
while(XPending(xw.dis)) {
XNextEvent(xw.dis, &ev);
switch (ev.type) {
default:
break;
case KeyPress:
kpress(&ev.xkey);
break;
case Expose:
draw(SCredraw);
break;
case ConfigureNotify:
resize(&ev);
break;
}
}
FD_ZERO(&rfd);
FD_SET(cmdfd, &rfd);
ret = select(cmdfd+1, &rfd, NULL, NULL, &tv);
if(ret < 0) {
fprintf(stderr, "select: %m\n");
running = 0;
}
if(!ret)
continue;
if(FD_ISSET(cmdfd, &rfd)) {
ttyread();
draw(SCupdate);
}
}
}
int int
main(int argc, char *argv[]) { main(int argc, char *argv[]) {
if(argc == 2 && !strcmp("-v", argv[1])) { if(argc == 2 && !strncmp("-v", argv[1], 3))
fprintf(stderr, "st-"VERSION", © 2007-2008 st engineers, see LICENSE for details\n"); die("st-"VERSION", © 2009 st engineers\n");
exit(EXIT_SUCCESS); else if(argc != 1)
} die("usage: st [-v]\n");
else if(argc != 1) { setlocale(LC_CTYPE, "");
fprintf(stderr, "usage: st [-v]\n"); tnew(80, 24);
exit(EXIT_FAILURE); ttynew();
} xinit();
run();
return 0; return 0;
} }

181
st.h Normal file
View File

@ -0,0 +1,181 @@
/* See LICENSE for licence details. */
#define _XOPEN_SOURCE
#include <ctype.h>
#include <fcntl.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/ioctl.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>
/* special keys */
#define KEYDELETE "\033[3~"
#define KEYHOME "\033[1~"
#define KEYEND "\033[4~"
#define KEYPREV "\033[5~"
#define KEYNEXT "\033[6~"
#define TNAME "st"
#define SHELL "/bin/bash"
#define TAB 8
#define FONT "-*-terminus-medium-r-normal-*-14-*-*-*-*-*-*-*"
#define BORDER 3
#define LINESPACE 1 /* additional pixel between each line */
/* Default colors */
#define DefaultFG 7
#define DefaultBG 0
#define DefaultCS 1
#define BellCol DefaultFG /* visual bell color */
static char* colorname[] = {
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
};
/* Arbitrary sizes */
#define ESCSIZ 256
#define ESCARG 16
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) < (b) ? (b) : (a))
#define LEN(a) (sizeof(a) / sizeof(a[0]))
#define DEFAULT(a, b) (a) = (a) ? (a) : (b)
#define BETWEEN(x, a, b) ((a) <= (x) && (x) <= (b))
#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
enum { ATnone=0 , ATreverse=1 , ATunderline=2, ATbold=4 }; /* Attribute */
enum { CSup, CSdown, CSright, CSleft, CShide, CSdraw, CSwrap, CSsave, CSload }; /* Cursor */
enum { CRset=1 , CRupdate=2 }; /* Character state */
enum { TMwrap=1 , TMinsert=2 }; /* Terminal mode */
enum { SCupdate, SCredraw }; /* screen draw mode */
#ifdef TRUECOLOR
#error Truecolor not implemented yet
typedef int Color;
#else
typedef char Color;
#endif
typedef struct {
char c; /* character code */
char mode; /* attribute flags */
Color fg; /* foreground */
Color bg; /* background */
char state; /* state flag */
} Glyph;
typedef Glyph* Line;
typedef struct {
Glyph attr; /* current char attributes */
char hidden;
int x;
int y;
} TCursor;
/* Escape sequence structs */
typedef struct {
char buf[ESCSIZ+1]; /* raw string */
int len; /* raw string length */
/* ESC <pre> [[ [<priv>] <arg> [;]] <mode>] */
char pre;
char priv;
int arg[ESCARG+1];
int narg; /* nb of args */
char mode;
} Escseq;
/* Internal representation of the screen */
typedef struct {
int row; /* nb row */
int col; /* nb col */
Line* line; /* screen */
TCursor c; /* cursor */
int top; /* top scroll limit */
int bot; /* bottom scroll limit */
int mode; /* terminal mode */
} Term;
/* Purely graphic info */
typedef struct {
Display* dis;
Window win;
int scr;
int w; /* window width */
int h; /* window height */
int ch; /* char height */
int cw; /* char width */
} XWindow;
/* Drawing Context */
typedef struct {
unsigned long col[LEN(colorname)];
XFontStruct* font;
GC gc;
} DC;
void die(const char *errstr, ...);
void draw(int);
void execsh(void);
void kpress(XKeyEvent *);
void resize(XEvent *);
void run(void);
int escaddc(char);
int escfinal(char);
void escdump(void);
void eschandle(void);
void escparse(void);
void escreset(void);
void tclearregion(int, int, int, int);
void tcpos(int);
void tcursor(int);
void tdeletechar(int);
void tdeleteline(int);
void tdump(void);
void tinsertblank(int);
void tinsertblankline(int);
void tmoveto(int, int);
void tnew(int, int);
void tnewline(void);
void tputc(char);
void tputs(char*, int);
void tresize(int, int);
void tscroll(void);
void tsetattr(int*, int);
void tsetchar(char);
void tsetscroll(int, int);
void ttynew(void);
void ttyread(void);
void ttyresize(int, int);
void ttywrite(char *, size_t);
unsigned long xgetcol(const char *);
void xclear(int, int, int, int);
void xcursor(int);
void xdrawc(int, int, Glyph);
void xinit(void);
void xscroll(void);

54
st.info Normal file
View File

@ -0,0 +1,54 @@
# Reconstructed via infocmp from file: /lib/terminfo/p/pcansi
st| simpleterm,
am,
ul,
mir,
msgr,
colors#8,
cols#80,
it#8,
lines#24,
ncv#3,
pairs#64,
acsc=*`#aof+g+j+k+l+m+n-o-p-q-r-s+t+u+v+w|x<y>z{{||}}-~,
bel=^G,
bold=\E[1m,
cbt=\E[Z,
clear=\E[H\E[2J,
cr=^M,
cub1=\E[D,
cud1=\E[B,
cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH,
cuu1=\E[A,
dch1=\E[P,
dl1=\E[M,
ed=\E[J,
el=\E[K,
home=\E[H,
ht=^I,
hts=\EH,
il1=\E[L,
ind=^J,
invis=\E[8m,
kbs=^H,
kcub1=\E[D,
kcud1=\E[B,
kcuf1=\E[C,
kcuu1=\E[A,
khome=\E[1~,
knp=\E[6~,
kpp=\E[5~,
op=\E[37;40m,
rev=\E[7m,
rmacs=\E[10m,
rmso=\E[m,
rmul=\E[m,
setab=\E[4%p1%dm,
setaf=\E[3%p1%dm,
# sgr=\E[0;10%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p6%t;1%;%?%p7%t;8%;%?%p9%t;12%;m,
sgr0=\E[0m,
smacs=\E[12m,
smso=\E[7m,
smul=\E[4m,
tbc=\E[2g,

363
std.c
View File

@ -1,363 +0,0 @@
/* See LICENSE file for copyright and license details.
*
* Simple terminal daemon is a terminal emulator. It can be used in
* combination with simple terminal to emulate a mostly VT100-compatible
* terminal.
*
* In this process std works like a filter. It reads data from a
* pseudo-terminal and parses the escape sequences and transforms them
* into an ed(1)-like language. The resulting data is buffered and
* written to stdout.
* Parallely it reads data from stdin and parses and executes the
* commands. The resulting data is written to the pseudo-terminal.
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#if !(_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600)
#include <pty.h>
#endif
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define LENGTH(x) (sizeof(x) / sizeof((x)[0]))
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
typedef struct {
unsigned char data[BUFSIZ];
int s, e;
int n;
} RingBuffer;
static void buffer(char c);
static void cmd(const char *cmdstr, ...);
static void getpty(void);
static void movea(int x, int y);
static void mover(int x, int y);
static void parsecmd(void);
static void parseesc(void);
static void scroll(int l);
static void shell(void);
static void sigchld(int n);
static char unbuffer(void);
static int cols = 80, lines = 25;
static int cx = 0, cy = 0;
static int c;
static int ptm, pts;
static _Bool bold, digit, qmark;
static pid_t pid;
static RingBuffer buf;
static FILE *fptm;
void
buffer(char c) {
if(buf.n < LENGTH(buf.data))
buf.n++;
else
buf.s = (buf.s + 1) % LENGTH(buf.data);
buf.data[buf.e++] = c;
buf.e %= LENGTH(buf.data);
}
void
cmd(const char *cmdstr, ...) {
va_list ap;
putchar('\n');
putchar(':');
va_start(ap, cmdstr);
vfprintf(stdout, cmdstr, ap);
va_end(ap);
}
void
movea(int x, int y) {
x = MAX(x, cols);
y = MAX(y, lines);
cx = x;
cy = y;
cmd("seek(%d,%d)", x, y);
}
void
mover(int x, int y) {
movea(cx + x, cy + y);
}
void
parsecmd(void) {
}
void
parseesc(void) {
int i, j;
int arg[16];
memset(arg, 0, LENGTH(arg));
c = getc(fptm);
switch(c) {
case '[':
c = getc(fptm);
for(j = 0; j < LENGTH(arg);) {
if(isdigit(c)) {
digit = 1;
arg[j] *= 10;
arg[j] += c - '0';
}
else if(c == '?')
qmark = 1;
else if(c == ';') {
if(!digit)
errx(EXIT_FAILURE, "syntax error");
digit = 0;
j++;
}
else {
if(digit) {
digit = 0;
j++;
}
break;
}
c = getc(fptm);
}
switch(c) {
case '@':
break;
case 'A':
mover(0, j ? arg[0] : 1);
break;
case 'B':
mover(0, j ? -arg[0] : -1);
break;
case 'C':
mover(j ? arg[0] : 1, 0);
break;
case 'D':
mover(j ? -arg[0] : -1, 0);
break;
case 'E':
/* movel(j ? arg[0] : 1); */
break;
case 'F':
/* movel(j ? -arg[0] : -1); */
break;
case '`':
case 'G':
movea(j ? arg[0] : 1, cy);
break;
case 'f':
case 'H':
movea(arg[1] ? arg[1] : 1, arg[0] ? arg[0] : 1);
case 'L':
/* insline(j ? arg[0] : 1); */
break;
case 'M':
/* delline(j ? arg[0] : 1); */
break;
case 'P':
break;
case 'S':
scroll(j ? arg[0] : 1);
break;
case 'T':
scroll(j ? -arg[0] : -1);
break;
case 'd':
movea(cx, j ? arg[0] : 1);
break;
case 'm':
for(i = 0; i < j; i++) {
if(arg[i] >= 30 && arg[i] <= 37)
cmd("#%d", arg[i] - 30);
if(arg[i] >= 40 && arg[i] <= 47)
cmd("|%d", arg[i] - 40);
/* xterm bright colors */
if(arg[i] >= 90 && arg[i] <= 97)
cmd("#%d", arg[i] - 90);
if(arg[i] >= 100 && arg[i] <= 107)
cmd("|%d", arg[i] - 100);
switch(arg[i]) {
case 0:
case 22:
if(bold)
cmd("bold");
case 1:
if(!bold)
cmd("bold");
break;
}
}
break;
}
break;
default:
putchar('\033');
ungetc(c, fptm);
}
}
void
scroll(int l) {
cmd("seek(%d,%d)", cx, cy + l);
}
void
getpty(void) {
char *ptsdev;
#if defined(_GNU_SOURCE)
ptm = getpt();
#elif _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
ptm = posix_openpt(O_RDWR);
#else
ptm = open("/dev/ptmx", O_RDWR);
if(ptm == -1)
if(openpty(&ptm, &pts, NULL, NULL, NULL) == -1)
err(EXIT_FAILURE, "cannot open pty");
#endif
#if defined(_XOPEN_SOURCE)
if(ptm != -1) {
if(grantpt(ptm) == -1)
err(EXIT_FAILURE, "cannot grant access to pty");
if(unlockpt(ptm) == -1)
err(EXIT_FAILURE, "cannot unlock pty");
ptsdev = ptsname(ptm);
if(!ptsdev)
err(EXIT_FAILURE, "slave pty name undefined");
pts = open(ptsdev, O_RDWR);
if(pts == -1)
err(EXIT_FAILURE, "cannot open slave pty");
}
else
err(EXIT_FAILURE, "cannot open pty");
#endif
}
void
shell(void) {
static char *shell = NULL;
if(!shell && !(shell = getenv("SHELL")))
shell = "/bin/sh";
pid = fork();
switch(pid) {
case -1:
err(EXIT_FAILURE, "cannot fork");
case 0:
setsid();
dup2(pts, STDIN_FILENO);
dup2(pts, STDOUT_FILENO);
dup2(pts, STDERR_FILENO);
close(ptm);
putenv("TERM=vt102");
execvp(shell, NULL);
break;
default:
close(pts);
signal(SIGCHLD, sigchld);
}
}
void
sigchld(int n) {
int ret;
if(waitpid(pid, &ret, 0) == -1)
err(EXIT_FAILURE, "waiting for child failed");
if(WIFEXITED(ret))
exit(WEXITSTATUS(ret));
else
exit(EXIT_SUCCESS);
}
char
unbuffer(void) {
char c;
c = buf.data[buf.s++];
buf.s %= LENGTH(buf.data);
buf.n--;
return c;
}
int
main(int argc, char *argv[]) {
fd_set rfds;
if(argc == 2 && !strcmp("-v", argv[1]))
errx(EXIT_SUCCESS, "std-"VERSION", © 2008 Matthias-Christian Ott");
else if(argc == 1)
errx(EXIT_FAILURE, "usage: std [-v]");
getpty();
shell();
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
FD_SET(ptm, &rfds);
if(!(fptm = fdopen(ptm, "r+")))
err(EXIT_FAILURE, "cannot open pty");
if(fcntl(ptm, F_SETFL, O_NONBLOCK) == -1)
err(EXIT_FAILURE, "cannot set pty to non-blocking mode");
if(fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1)
err(EXIT_FAILURE, "cannot set stdin to non-blocking mode");
for(;;) {
if(select(MAX(ptm, STDIN_FILENO) + 1, &rfds, NULL, NULL, NULL) == -1)
err(EXIT_FAILURE, "cannot select");
if(FD_ISSET(ptm, &rfds)) {
for(;;) {
if((c = getc(fptm)) == EOF) {
if(feof(fptm)) {
FD_CLR(ptm, &rfds);
fflush(fptm);
break;
}
if(errno != EAGAIN)
err(EXIT_FAILURE, "cannot read from pty");
fflush(stdout);
break;
}
switch(c) {
case '\033':
parseesc();
break;
default:
putchar(c);
}
}
fflush(stdout);
}
if(FD_ISSET(STDIN_FILENO, &rfds)) {
for(;;) {
if((c = getchar()) == EOF) {
if(feof(stdin)) {
FD_CLR(STDIN_FILENO, &rfds);
fflush(fptm);
break;
}
if(errno != EAGAIN)
err(EXIT_FAILURE, "cannot read from stdin");
fflush(fptm);
break;
}
switch(c) {
case ':':
parsecmd();
break;
default:
putc(c, fptm);
}
}
fflush(fptm);
}
}
return 0;
}