#include <X11/Xlib.h>
#include <X11/Xutil.h>
#define XK_MISCELLANY
#include <X11/keysymdef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
#include <locale.h>
#include <errno.h>
#include <libutil.h>
#define BW 2
#define FONT_US "-ncd-terminal-medium-r-normal--18-*-iso646-us"
#define FONT_SE "-ncd-terminal-medium-r-normal--18-*-iso646-se2"
Display *d;
Window w;
GC tgc, fgc;
Pixmap wsave, csave;
Atom wm_protocols, wm_delete_window;
int cols = 80;
int lines = 25;
int pheight = 66;
int wx = 0, wy = 0, ww, wh;
char *fg = "#acaaac";
char *bg = "black";
char *font_us = FONT_US;
char *font_se = FONT_SE;
XFontStruct *fi_us, *fi_se;
int cw, ch, asc, desc;
int x, y;
char *shell;
int ptyfd, xfd;
int glass = 0;
int baud, delay;
static void wrestore()
{
XCopyArea(d, wsave, w, tgc, 0, 0, ww + (BW*2), wh + (BW*2), 0, 0);
}
static void cur(int state)
{
static int first = 1;
if (state) {
XCopyArea(d, w, csave, tgc, x, y - asc, cw, ch, 0, 0);
XFillRectangle(d, w, tgc, x, y - asc, cw, ch);
XCopyArea(d, wsave, csave, tgc, x, y - asc, cw, ch, 0, 0);
XFillRectangle(d, wsave, tgc, x, y - asc, cw, ch);
first = 0;
} else {
if (! first) {
XCopyArea(d, csave, w, tgc, 0, 0, cw, ch, x, y - asc);
XCopyArea(d, csave, wsave, tgc, 0, 0, cw, ch, x, y - asc);
}
}
}
static void feed(void)
{
XCopyArea(d, w, w, tgc, BW, BW + ch, ww, wh - ch, BW, BW);
XFillRectangle(d, w, fgc, BW, BW + wh - ch, ww, ch);
XCopyArea(d, wsave, wsave, tgc, BW, BW + ch, ww, wh - ch, BW, BW);
XFillRectangle(d, wsave, fgc, BW, BW + wh - ch, ww, ch);
}
static int read_pty(void)
{
static int line = 0;
int i, n;
unsigned char c;
#define print() if (glass) { \
XDrawImageString(d, w, tgc, x, y, &c, 1); \
XDrawImageString(d, wsave, tgc, x, y, &c, 1); \
} else { \
XDrawString(d, w, tgc, x, y, &c, 1); \
XDrawString(d, wsave, tgc, x, y, &c, 1); \
}
if ((n = read(ptyfd, &c, 1)) <= 0)
return n;
if (delay)
usleep(delay);
if (glass)
cur(0);
switch (c) {
case 8:
if (x > BW)
x -= cw;
break;
case 10:
feed();
if (++line == pheight)
line = 0;
break;
case 12:
for (i = 0; i < (pheight - line); i++)
feed();
line = 0;
break;
case 13:
x = BW;
break;
default:
if ((c < 32) || ((c > 126) && (c < 160)))
break;
if (x == BW + ww) {
feed();
x = BW;
}
print();
x += cw;
}
if (glass)
cur(1);
return n;
}
static void write_pty(XEvent *e)
{
KeySym ks;
unsigned char c;
XLookupString((XKeyEvent *)e, &c, 1, &ks, NULL);
switch (ks) {
case XK_F9:
XSetFont(d, tgc, fi_us->fid);
return;
case XK_F10:
XSetFont(d, tgc, fi_se->fid);
return;
}
if (c != 0) {
if (delay)
usleep(delay);
write(ptyfd, &c, 1);
}
}
static void usage(char *name)
{
fprintf(stderr, "usage: %s [-x xpos] [-y ypos] [-c columns] [-l lines] [-f us_font] [-s se_font] [-b baud]\n", name);
exit(1);
}
static void init(int argc, char **argv)
{
XColor fgcol, bgcol;
XClassHint *chint;
XSizeHints *shints;
char *name;
int pid;
int c, mod;
if ((name = strrchr(argv[0], '/')))
name++;
else
name = argv[0];
if (! strcmp(name, "ttyemu")) {
fprintf(stderr, "Error: wrong invocation name\n");
exit(1);
}
glass = ! strcmp(name, "glasstty");
baud = glass ? 300 : 110;
while ((c = getopt(argc, argv, ":x:y:c:l:f:s:b:")) != -1) {
switch (c) {
case 'x':
wx = atoi(optarg);
break;
case 'y':
wy = atoi(optarg);
break;
case 'c':
cols = atoi(optarg);
break;
case 'l':
lines = atoi(optarg);
break;
case 'f':
font_us = (char *)malloc(strlen(optarg) + 1);
strcpy(font_us, optarg);
break;
case 's':
font_se = (char *)malloc(strlen(optarg) + 1);
strcpy(font_se, optarg);
break;
case 'b':
baud = atoi(optarg);
break;
case ':':
case '?':
default:
usage(name);
}
}
if (! (pid = forkpty(&ptyfd, NULL, NULL, NULL))) {
char *shell, *tail, shminus[80];
setenv(glass ? "GLASSTTY" : "HCTTY", "1", 1);
if (! (shell = getenv("SHELL")))
shell = "/bin/sh";
tail = strrchr(shell, '/') + 1;
sprintf(shminus, "-%s", tail);
execl(shell, shminus, (char *)NULL);
perror("execl");
_exit(1);
} else if (pid < 0) {
perror("fork");
exit(1);
}
setlocale(LC_ALL, "");
if (! (d = XOpenDisplay(NULL))) {
fprintf(stderr, "Error opening display!\n");
exit(1);
}
xfd = ConnectionNumber(d);
if (! (fi_us = XLoadQueryFont(d, font_us))) {
fprintf(stderr, "Error loading font!\n");
exit(1);
}
if (! (fi_se = XLoadQueryFont(d, font_se))) {
fprintf(stderr, "Error loading font!\n");
exit(1);
}
cw = fi_us->per_char[32].width;
ch = fi_us->ascent + fi_us->descent;
XAllocNamedColor(d, DefaultColormap(d, 0), fg, &fgcol, &fgcol);
XAllocNamedColor(d, DefaultColormap(d, 0), bg, &bgcol, &bgcol);
ww = cw * cols;
wh = ch * lines;
if (! (w = XCreateSimpleWindow(d, DefaultRootWindow(d), wx, wy,
ww + (BW*2), wh + (BW*2), 0, bgcol.pixel, bgcol.pixel)) ) {
printf("Error creating window!\n");
exit(1);
}
XSelectInput(d, w, KeyPressMask|ExposureMask);
chint = XAllocClassHint();
chint->res_name = (glass ? "glasstty" : "hctty");
chint->res_class = (glass ? "Glasstty" : "Hctty");
XSetClassHint(d, w, chint);
XStoreName(d, w, glass ? "Glass TTY" : "Hardcopy TTY");
shints = XAllocSizeHints();
shints->x = wx;
shints->y = wy;
shints->flags = USPosition;
XSetWMNormalHints(d, w, shints);
wm_protocols = XInternAtom(d, "WM_PROTOCOLS", False);
wm_delete_window = XInternAtom(d, "WM_DELETE_WINDOW", False);
XSetWMProtocols(d, w, &wm_delete_window, 1);
tgc = XCreateGC(d, w, 0, NULL);
XSetForeground(d, tgc, fgcol.pixel);
XSetBackground(d, tgc, bgcol.pixel);
XSetFont(d, tgc, fi_us->fid);
fgc = XCreateGC(d, w, 0, NULL);
XSetForeground(d, fgc, bgcol.pixel);
wsave = XCreatePixmap(d, w, ww + (BW*2), wh + (BW*2), DefaultDepth(d, 0));
XFillRectangle(d, wsave, fgc, 0, 0, ww + (BW*2), wh + (BW*2));
if (glass)
csave = XCreatePixmap(d, w, cw, ch, DefaultDepth(d, 0));
x = BW;
y = BW + wh - fi_us->descent;
asc = fi_us->ascent;
desc = fi_us->descent;
delay = 1000000 / ((double)baud / (glass ? 10. : 11.));
mod = delay % 10000;
delay = (mod > 5000 ? delay + (10000 - mod) : delay - mod) - 10000;
if (delay < 0)
delay = 0;
XMapWindow(d, w);
}
static void quit()
{
XDestroyWindow(d, w);
XCloseDisplay(d);
exit(0);
}
int main(int argc, char **argv)
{
init(argc, argv);
for (;;) {
XEvent e;
fd_set io_set;
struct timeval timeout;
while (XPending(d)) {
XNextEvent(d, &e);
switch (e.type) {
case Expose:
if (! e.xexpose.count)
wrestore();
break;
case KeyPress:
write_pty(&e);
break;
case ClientMessage:
if (e.xclient.message_type == wm_protocols &&
e.xclient.data.l[0] == wm_delete_window)
quit();
break;
}
}
FD_ZERO(&io_set);
FD_SET(ptyfd, &io_set);
FD_SET(xfd, &io_set);
timeout.tv_sec = 0;
timeout.tv_usec = 5000;
if (select(xfd + 1, &io_set, NULL, NULL, &timeout) <= 0)
continue;
if (FD_ISSET(ptyfd, &io_set)) {
if (read_pty() <= 0)
quit();
}
}
}