@ -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> | |||||
© 2008 Matthias Christian Ott <ott at enolink dot de> | |||||
Redistribution and use in source and binary forms, with or without | |||||
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 | |||||
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. | |||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -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 | |||||
MANDIR = $(PREFIX)/share/man | |||||
SRC = st.c | |||||
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 | |||||
rm -f st.o std.o | |||||
rm -f st-$(VERSION).tar.gz | |||||
@echo cleaning | |||||
@rm -f st ${OBJ} st-${VERSION}.tar.gz | |||||
dist: clean | dist: clean | ||||
mkdir st-$(VERSION) | |||||
cp -f LICENSE README st-$(VERSION) | |||||
cp -f Makefile config.mk st-$(VERSION) | |||||
cp -f st.1 std.1 st-$(VERSION) | |||||
cp -f st.c std.c st-$(VERSION) | |||||
tar -czf st-$(VERSION).tar st-$(VERSION) | |||||
rm -rf st-$(VERSION) | |||||
install: | |||||
mkdir -p $(DESTDIR)$(PREFIX)/bin | |||||
cp -f st $(DESTDIR)$(PREFIX)/bin | |||||
cp -f std $(DESTDIR)$(PREFIX)/bin | |||||
chmod 755 $(DESTDIR)$(PREFIX)/bin/st | |||||
chmod 755 $(DESTDIR)$(PREFIX)/bin/std | |||||
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 | |||||
@echo creating dist tarball | |||||
@mkdir -p st-${VERSION} | |||||
@cp -R LICENSE Makefile README config.mk st.h ${SRC} st-${VERSION} | |||||
@tar -cf st-${VERSION}.tar st-${VERSION} | |||||
@gzip st-${VERSION}.tar | |||||
@rm -rf st-${VERSION} | |||||
install: all | |||||
@echo installing executable file to ${DESTDIR}${PREFIX}/bin | |||||
@mkdir -p ${DESTDIR}${PREFIX}/bin | |||||
@cp -f st ${DESTDIR}${PREFIX}/bin | |||||
@chmod 755 ${DESTDIR}${PREFIX}/bin/st | |||||
@tic st.info | |||||
uninstall: | uninstall: | ||||
rm -f $(DESTDIR)$(PREFIX)/bin/st | |||||
rm -f $(DESTDIR)$(PREFIX)/bin/std | |||||
rm -f $(DESTDIR)$(MANDIR)/man1/st.1 | |||||
rm -f $(DESTDIR)$(MANDIR)/man1/std.1 | |||||
@echo removing executable file from ${DESTDIR}${PREFIX}/bin | |||||
@rm -f ${DESTDIR}${PREFIX}/bin/st | |||||
.PHONY: all clean dist install uninstall | |||||
.PHONY: all options clean dist install uninstall |
@ -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. |
@ -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 |
@ -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 |
@ -1,17 +1,921 @@ | |||||
/* See LICENSE file for copyright and license details. */ | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
/* See LICENSE for licence details. */ | |||||
#include "st.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 | int | ||||
main(int argc, char *argv[]) { | |||||
if(argc == 2 && !strcmp("-v", argv[1])) { | |||||
fprintf(stderr, "st-"VERSION", © 2007-2008 st engineers, see LICENSE for details\n"); | |||||
exit(EXIT_SUCCESS); | |||||
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; | |||||
} | } | ||||
else if(argc != 1) { | |||||
fprintf(stderr, "usage: st [-v]\n"); | |||||
exit(EXIT_FAILURE); | |||||
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 | |||||
main(int argc, char *argv[]) { | |||||
if(argc == 2 && !strncmp("-v", argv[1], 3)) | |||||
die("st-"VERSION", © 2009 st engineers\n"); | |||||
else if(argc != 1) | |||||
die("usage: st [-v]\n"); | |||||
setlocale(LC_CTYPE, ""); | |||||
tnew(80, 24); | |||||
ttynew(); | |||||
xinit(); | |||||
run(); | |||||
return 0; | return 0; | ||||
} | } |
@ -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); |
@ -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, |
@ -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; | |||||
} |