JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
use predefined OS macro instead of uname.
[st.git] / st.c
diff --git a/st.c b/st.c
index ebe896c..f42797f 100644 (file)
--- a/st.c
+++ b/st.c
 #include <X11/keysym.h>
 #include <X11/Xutil.h>
 
+#if   defined(__linux)
+ #include <pty.h>
+#elif defined(__OpenBSD__) || defined(__NetBSD__)
+ #include <util.h>
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+ #include <libutil.h>
+#endif
+
 /* Arbitrary sizes */
 #define ESC_TITLE_SIZ 256
 #define ESC_BUF_SIZ   256
 #define BETWEEN(x, a, b)  ((a) <= (x) && (x) <= (b))
 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
 #define ATTRCMP(a, b) ((a).mode != (b).mode || (a).fg != (b).fg || (a).bg != (b).bg)
-#define IS_SET(flag) (term.mode & flag)
+#define IS_SET(flag) (term.mode & (flag))
 
 /* Attribute, Cursor, Character state, Terminal mode, Screen draw mode */
 enum { ATTR_NULL=0 , ATTR_REVERSE=1 , ATTR_UNDERLINE=2, ATTR_BOLD=4, ATTR_GFX=8 };
-enum { CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT, CURSOR_HIDE, CURSOR_DRAW, 
+enum { CURSOR_UP, CURSOR_DOWN, CURSOR_LEFT, CURSOR_RIGHT,
        CURSOR_SAVE, CURSOR_LOAD };
+enum { CURSOR_DEFAULT = 0, CURSOR_HIDE = 1, CURSOR_WRAPNEXT = 2 };
 enum { GLYPH_SET=1, GLYPH_DIRTY=2 };
 enum { MODE_WRAP=1, MODE_INSERT=2, MODE_APPKEYPAD=4 };
 enum { ESC_START=1, ESC_CSI=2, ESC_OSC=4, ESC_TITLE=8, ESC_ALTCHARSET=16 };
@@ -59,6 +68,7 @@ typedef struct {
        Glyph attr;      /* current char attributes */
        int x;
        int y;
+       char state;
 } TCursor;
 
 /* CSI Escape sequence structs */
@@ -78,7 +88,6 @@ typedef struct {
        int col;        /* nb col */
        Line* line; /* screen */
        TCursor c;      /* cursor */
-       char hidec;
        int top;        /* top    scroll limit */
        int bot;        /* bottom scroll limit */
        int mode;       /* terminal mode flags */
@@ -99,6 +108,7 @@ typedef struct {
        int bufh; /* pixmap height */
        int ch; /* char height */
        int cw; /* char width  */
+       int hasfocus;
 } XWindow; 
 
 typedef struct {
@@ -141,7 +151,6 @@ static void tputc(char);
 static void tputs(char*, int);
 static void treset(void);
 static void tresize(int, int);
-static void tscroll(void);
 static void tscrollup(int);
 static void tscrolldown(int);
 static void tsetattr(int*, int);
@@ -153,23 +162,27 @@ static void ttyread(void);
 static void ttyresize(int, int);
 static void ttywrite(const char *, size_t);
 
-static void xbell(void);
 static void xdraws(char *, Glyph, int, int, int);
 static void xhints(void);
 static void xclear(int, int, int, int);
-static void xcursor(int);
+static void xdrawcursor(void);
 static void xinit(void);
 static void xloadcols(void);
+static void xseturgency(int);
 
 static void expose(XEvent *);
 static char* kmap(KeySym);
 static void kpress(XEvent *);
 static void resize(XEvent *);
+static void focus(XEvent *);
+
 
 static void (*handler[LASTEvent])(XEvent *) = {
        [KeyPress] = kpress,
        [Expose] = expose,
-       [ConfigureNotify] = resize
+       [ConfigureNotify] = resize,
+       [FocusIn] = focus,
+       [FocusOut] = focus,
 };
 
 /* Globals */
@@ -179,7 +192,6 @@ static Term term;
 static CSIEscape escseq;
 static int cmdfd;
 static pid_t pid;
-static int running;
 
 #ifdef DEBUG
 void
@@ -214,20 +226,11 @@ die(const char *errstr, ...) {
 void
 execsh(void) {
        char *args[3] = {getenv("SHELL"), "-i", NULL};
-       DEFAULT(args[0], "/bin/sh"); /* default shell if getenv() failed */
+       DEFAULT(args[0], "/bin/sh"); /* if getenv() failed */
        putenv("TERM=" TNAME);
        execvp(args[0], args);
 }
 
-void
-xbell(void) { /* visual bell */
-       XRectangle r = { BORDER, BORDER, xw.bufw, xw.bufh };
-       XSetForeground(xw.dis, dc.gc, dc.col[BellCol]);
-       XFillRectangles(xw.dis, xw.win, dc.gc, &r, 1);
-       /* usleep(30000); */
-       draw(SCREEN_REDRAW);
-}
-
 void 
 sigchld(int a) {
        int stat = 0;
@@ -242,19 +245,12 @@ sigchld(int a) {
 void
 ttynew(void) {
        int m, s;
-       char *pts;
-
-       if((m = posix_openpt(O_RDWR | O_NOCTTY)) < 0)
-               die("openpt failed: %s\n", SERRNO);
-       if(grantpt(m) < 0)
-               die("grantpt failed: %s\n", SERRNO);
-       if(unlockpt(m) < 0)
-               die("unlockpt failed: %s\n", SERRNO);
-       if(!(pts = ptsname(m)))
-               die("ptsname failed: %s\n", SERRNO);
-       if((s = open(pts, O_RDWR | O_NOCTTY)) < 0)
-               die("Couldn't open slave: %s\n", SERRNO);
-       fcntl(s, F_SETFL, O_NDELAY);
+       
+       /* seems to work fine on linux, openbsd and freebsd */
+       struct winsize w = {term.row, term.col, 0, 0};
+       if(openpty(&m, &s, NULL, NULL, &w) < 0)
+               die("openpty failed: %s\n", SERRNO);
+
        switch(pid = fork()) {
        case -1:
                die("fork failed\n");
@@ -325,11 +321,12 @@ tcursor(int mode) {
 
 void
 treset(void) {
-       term.c.attr.mode = ATTR_NULL;
-       term.c.attr.fg = DefaultFG;
-       term.c.attr.bg = DefaultBG;
-       term.c.x = term.c.y = 0;
-       term.hidec = 0;
+       term.c = (TCursor){{
+               .mode = ATTR_NULL, 
+               .fg = DefaultFG, 
+               .bg = DefaultBG
+       }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
+       
        term.top = 0, term.bot = term.row - 1;
        term.mode = MODE_WRAP;
        tclearregion(0, 0, term.col-1, term.row-1);
@@ -337,33 +334,13 @@ treset(void) {
 
 void
 tnew(int col, int row) {
-       /* screen size */
+       /* set screen size */
        term.row = row, term.col = col;
-       term.top = 0, term.bot = term.row - 1;
-       /* mode */
-       term.mode = MODE_WRAP;
-       /* cursor */
-       term.c.attr.mode = ATTR_NULL;
-       term.c.attr.fg = DefaultFG;
-       term.c.attr.bg = DefaultBG;
-       term.c.x = term.c.y = 0;
-       term.hidec = 0;
-       /* allocate screen */
-       term.line = calloc(term.row, sizeof(Line));
+       term.line = malloc(term.row * sizeof(Line));
        for(row = 0 ; row < term.row; row++)
-               term.line[row] = calloc(term.col, sizeof(Glyph));
-}
-
-/* TODO: Replace with scrollup/scolldown */
-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;
+               term.line[row] = malloc(term.col * sizeof(Glyph));
+       /* setup screen */
+       treset();
 }
 
 void
@@ -392,18 +369,18 @@ tscrollup (int n) {
        for(i = 0; i < n; i++)
                memset(term.line[term.top+i], 0, term.col*sizeof(Glyph));
        
-        for(i = term.top; i <= term.bot-n; i++) { 
+       for(i = term.top; i <= term.bot-n; i++) { 
                 temp = term.line[i];
                 term.line[i] = term.line[i+n]; 
                 term.line[i+n] = temp;
-        }
+       }
 }
 
 void
 tnewline(void) {
        int y = term.c.y + 1;
        if(y > term.bot)
-               tscroll(), y = term.bot;
+               tscrollup(1), y = term.bot;
        tmoveto(0, y);
 }
 
@@ -433,8 +410,11 @@ csiparse(void) {
 
 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;
+       LIMIT(x, 0, term.col-1);
+       LIMIT(y, 0, term.row-1);
+       term.c.state &= ~CURSOR_WRAPNEXT;
+       term.c.x = x;
+       term.c.y = y;
 }
 
 void
@@ -730,7 +710,7 @@ csihandle(void) {
                        case 12: /* att610 -- Stop blinking cursor (IGNORED) */
                                break;
                        case 25:
-                               term.hidec = 1;
+                               term.c.state |= CURSOR_HIDE;
                                break;
                        case 1048: /* XXX: no alt. screen to erase/save */
                        case 1049:
@@ -779,7 +759,7 @@ csihandle(void) {
                        case 12: /* att610 -- Start blinking cursor (IGNORED) */
                                break;
                        case 25:
-                               term.hidec = 0;
+                               term.c.state &= ~CURSOR_HIDE;
                                break;
                        case 1048: 
                        case 1049: /* XXX: no alt. screen to erase/save */
@@ -807,6 +787,7 @@ csihandle(void) {
                        DEFAULT(escseq.arg[0], 1);
                        DEFAULT(escseq.arg[1], term.row);
                        tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
+                       tmoveto(0, 0);
                }
                break;
        case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
@@ -886,20 +867,11 @@ tputc(char c) {
                        case '(':
                                term.esc |= ESC_ALTCHARSET;
                                break;
-                       case 'A':
-                               tmoveto(term.c.x, term.c.y-1);
-                               term.esc = 0;
-                               break;
-                       case 'B':
-                               tmoveto(term.c.x, term.c.y+1);
-                               term.esc = 0;
-                               break;
-                       case 'C':
-                               tmoveto(term.c.x+1, term.c.y);
-                               term.esc = 0;
-                               break;
-                       case 'D': /* XXX: CUP (VT100) or IND (VT52) ... */
-                               tmoveto(term.c.x-1, term.c.y);
+                       case 'D': /* IND -- Linefeed */
+                               if(term.c.y == term.bot)
+                                       tscrollup(1);
+                               else
+                                       tmoveto(term.c.x, term.c.y+1);
                                term.esc = 0;
                                break;
                        case 'E': /* NEL -- Next line */
@@ -953,18 +925,21 @@ tputc(char c) {
                        tnewline();
                        break;
                case '\a':
-                       xbell();
+                       if(!xw.hasfocus)
+                               xseturgency(1);
                        break;
                case '\033':
                        csireset();
                        term.esc = ESC_START;
                        break;
                default:
+                       if(IS_SET(MODE_WRAP) && term.c.state & CURSOR_WRAPNEXT)
+                               tnewline();
                        tsetchar(c);
-                       if(term.c.x+1 < term.col) {
+                       if(term.c.x+1 < term.col)
                                tmoveto(term.c.x+1, term.c.y);
-                       } else if(IS_SET(MODE_WRAP))
-                               tnewline();
+                       else
+                               term.c.state |= CURSOR_WRAPNEXT;
                        break;
                }
        }
@@ -1002,13 +977,12 @@ tresize(int col, int row) {
        for(/* i == minrow */; i < row; i++)
                term.line[i] = calloc(col, sizeof(Glyph));
        
-       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);
-       
-       term.bot = row-1;
+       /* update terminal size */
        term.col = col, term.row = row;
+       /* make use of the LIMIT in tmoveto */
+       tmoveto(term.c.x, term.c.y);
+       /* reset scrolling region */
+       tsetscroll(0, row-1);
 }
 
 void
@@ -1078,10 +1052,9 @@ xhints(void)
 
 void
 xinit(void) {
-       xw.dis = XOpenDisplay(NULL);
-       xw.scr = XDefaultScreen(xw.dis);
-       if(!xw.dis)
+       if(!(xw.dis = XOpenDisplay(NULL)))
                die("Can't open display\n");
+       xw.scr = XDefaultScreen(xw.dis);
        
        /* font */
        if(!(dc.font = XLoadQueryFont(xw.dis, FONT)) || !(dc.bfont = XLoadQueryFont(xw.dis, BOLDFONT)))
@@ -1094,9 +1067,6 @@ xinit(void) {
        /* colors */
        xloadcols();
 
-       term.c.attr.fg = DefaultFG;
-       term.c.attr.bg = DefaultBG;
-       term.c.attr.mode = ATTR_NULL;
        /* windows */
        xw.h = term.row * xw.ch + 2*BORDER;
        xw.w = term.col * xw.cw + 2*BORDER;
@@ -1141,7 +1111,7 @@ xdraws(char *s, Glyph base, int x, int y, int len) {
 }
 
 void
-xcursor(int mode) {
+xdrawcursor(void) {
        static int oldx = 0;
        static int oldy = 0;
        Glyph g = {' ', ATTR_NULL, DefaultBG, DefaultCS, 0};
@@ -1159,7 +1129,7 @@ xcursor(int mode) {
                xclear(oldx, oldy, oldx, oldy);
        
        /* draw the new one */
-       if(mode == CURSOR_DRAW) {
+       if(!(term.c.state & CURSOR_HIDE)) {
                xdraws(&g.c, g, term.c.x, term.c.y, 1);
                oldx = term.c.x, oldy = term.c.y;
        }
@@ -1186,8 +1156,7 @@ draw(int dummy) {
                        if(term.line[y][x].state & GLYPH_SET)
                                xdrawc(x, y, term.line[y][x]);
 
-       if(!term.hidec)
-               xcursor(CURSOR_DRAW);
+       xdrawcursor();
        XCopyArea(xw.dis, xw.buf, xw.win, dc.gc, 0, 0, xw.bufw, xw.bufh, BORDER, BORDER);
        XFlush(xw.dis);
 }
@@ -1201,25 +1170,29 @@ draw(int redraw_all) {
        char buf[DRAW_BUF_SIZ];
        
        XSetForeground(xw.dis, dc.gc, dc.col[DefaultBG]);
-       XFillRectangle(xw.dis, xw.buf, dc.gc, 0, 0, xw.w, xw.h);
+       XFillRectangle(xw.dis, xw.buf, dc.gc, 0, 0, xw.bufw, xw.bufh);
        for(y = 0; y < term.row; y++) {
                base = term.line[y][0];
                i = ox = 0;
                for(x = 0; x < term.col; x++) {
                        new = term.line[y][x];
-                       if(!ATTRCMP(base, new) && i < DRAW_BUF_SIZ)
-                               buf[i++] = new.c;
-                       else {
+                       if(i > 0 && (!(new.state & GLYPH_SET) || ATTRCMP(base, new) ||
+                                               i >= DRAW_BUF_SIZ)) {
                                xdraws(buf, base, ox, y, i);
-                               buf[0] = new.c;
-                               i = 1;
-                               ox = x;
-                               base = new;
+                               i = 0;
+                       }
+                       if(new.state & GLYPH_SET) {
+                               if(i == 0) {
+                                       ox = x;
+                                       base = new;
+                               }
+                               buf[i++] = new.c;
                        }
                }
-               xdraws(buf, base, ox, y, i);
+               if(i > 0)
+                       xdraws(buf, base, ox, y, i);
        }
-       xcursor(term.hidec ? CURSOR_HIDE : CURSOR_DRAW);
+       xdrawcursor();
        XCopyArea(xw.dis, xw.buf, xw.win, dc.gc, 0, 0, xw.bufw, xw.bufh, BORDER, BORDER);
        XFlush(xw.dis);
 }
@@ -1231,6 +1204,20 @@ expose(XEvent *ev) {
        draw(SCREEN_REDRAW);
 }
 
+void
+xseturgency(int add) {
+       XWMHints *h = XGetWMHints(xw.dis, xw.win);
+       h->flags = add ? (h->flags | XUrgencyHint) : (h->flags & ~XUrgencyHint);
+       XSetWMHints(xw.dis, xw.win, h);
+       XFree(h);
+}
+
+void
+focus(XEvent *ev) {
+       if((xw.hasfocus = ev->type == FocusIn))
+               xseturgency(0);
+}
+
 char*
 kmap(KeySym k) {
        int i;
@@ -1305,16 +1292,16 @@ run(void) {
        XEvent ev;
        fd_set rfd;
        int xfd = XConnectionNumber(xw.dis);
+       long mask = ExposureMask | KeyPressMask | StructureNotifyMask | FocusChangeMask;
 
-       running = 1;
-       XSelectInput(xw.dis, xw.win, ExposureMask | KeyPressMask | StructureNotifyMask);
+       XSelectInput(xw.dis, xw.win, mask);
        XResizeWindow(xw.dis, xw.win, xw.w, xw.h); /* XXX: fix resize bug in wmii (?) */
 
-       while(running) {
+       while(1) {
                FD_ZERO(&rfd);
                FD_SET(cmdfd, &rfd);
                FD_SET(xfd, &rfd);
-               if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, NULL) == -1) {
+               if(select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, NULL) < 0) {
                        if(errno == EINTR)
                                continue;
                        die("select failed: %s\n", SERRNO);