X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=st.c;h=ab43a43b88db487ef0bb85cec68eadc778726c84;hb=c43526153fbe368351f6139f5e672e1798f6d2f1;hp=f42797f0967138e854ebcdc1931f63a7fcb76e81;hpb=ae5baac932af877cb18df6853d45f033ed9b270f;p=st.git diff --git a/st.c b/st.c index f42797f..ab43a43 100644 --- a/st.c +++ b/st.c @@ -28,6 +28,10 @@ #include #endif +#define USAGE \ + "st-" VERSION ", (c) 2010 st engineers\n" \ + "usage: st [-t title] [-e cmd] [-v]\n" + /* Arbitrary sizes */ #define ESC_TITLE_SIZ 256 #define ESC_BUF_SIZ 256 @@ -50,7 +54,7 @@ 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 { MODE_WRAP=1, MODE_INSERT=2, MODE_APPKEYPAD=4, MODE_ALTSCREEN=8 }; enum { ESC_START=1, ESC_CSI=2, ESC_OSC=4, ESC_TITLE=8, ESC_ALTCHARSET=16 }; enum { SCREEN_UPDATE, SCREEN_REDRAW }; @@ -86,7 +90,8 @@ typedef struct { typedef struct { int row; /* nb row */ int col; /* nb col */ - Line* line; /* screen */ + Line* line; /* screen */ + Line* alt; /* alternate screen */ TCursor c; /* cursor */ int top; /* top scroll limit */ int bot; /* bottom scroll limit */ @@ -124,6 +129,15 @@ typedef struct { GC gc; } DC; +/* TODO: use better name for vars... */ +typedef struct { + int mode; + int bx, by; + int ex, ey; + struct {int x, y;} b, e; + char *clip; +} Selection; + #include "config.h" static void die(const char *errstr, ...); @@ -151,11 +165,12 @@ static void tputc(char); static void tputs(char*, int); static void treset(void); static void tresize(int, int); -static void tscrollup(int); -static void tscrolldown(int); +static void tscrollup(int, int); +static void tscrolldown(int, int); static void tsetattr(int*, int); static void tsetchar(char); static void tsetscroll(int, int); +static void tswapscreen(void); static void ttynew(void); static void ttyread(void); @@ -175,6 +190,9 @@ static char* kmap(KeySym); static void kpress(XEvent *); static void resize(XEvent *); static void focus(XEvent *); +static void brelease(XEvent *); +static void bpress(XEvent *); +static void bmotion(XEvent *); static void (*handler[LASTEvent])(XEvent *) = { @@ -183,6 +201,9 @@ static void (*handler[LASTEvent])(XEvent *) = { [ConfigureNotify] = resize, [FocusIn] = focus, [FocusOut] = focus, + [MotionNotify] = bmotion, + [ButtonPress] = bpress, + [ButtonRelease] = brelease, }; /* Globals */ @@ -192,6 +213,95 @@ static Term term; static CSIEscape escseq; static int cmdfd; static pid_t pid; +static Selection sel; +static char *opt_cmd = NULL; +static char *opt_title = NULL; + +void +selinit(void) { + sel.mode = 0; + sel.bx = -1; + sel.clip = NULL; +} + +static inline int selected(int x, int y) { + if(sel.ey == y && sel.by == y) { + int bx = MIN(sel.bx, sel.ex); + int ex = MAX(sel.bx, sel.ex); + return BETWEEN(x, bx, ex); + } + return ((sel.b.y < y&&y < sel.e.y) || (y==sel.e.y && x<=sel.e.x)) + || (y==sel.b.y && x>=sel.b.x && (x<=sel.e.x || sel.b.y!=sel.e.y)); +} + +static void getbuttoninfo(XEvent *e, int *b, int *x, int *y) { + if(b) *b = e->xbutton.state, + *b=*b==4096?5:*b==2048?4:*b==1024?3:*b==512?2:*b==256?1:-1; + *x = e->xbutton.x/xw.cw; + *y = e->xbutton.y/xw.ch; + sel.b.x = sel.by < sel.ey ? sel.bx : sel.ex; + sel.b.y = MIN(sel.by, sel.ey); + sel.e.x = sel.by < sel.ey ? sel.ex : sel.bx; + sel.e.y = MAX(sel.by, sel.ey); +} + +static void bpress(XEvent *e) { + sel.mode = 1; + sel.ex = sel.bx = e->xbutton.x/xw.cw; + sel.ey = sel.by = e->xbutton.y/xw.ch; +} + +static char *getseltext() { + char *str, *ptr; + int ls, x, y, sz; + if(sel.bx == -1) + return NULL; + sz = (term.col+1) * (sel.e.y-sel.b.y+1); + ptr = str = malloc(sz); + for(y = 0; y < term.row; y++) { + for(x = 0; x < term.col; x++) + if(term.line[y][x].state & GLYPH_SET && (ls = selected(x, y))) + *ptr = term.line[y][x].c, ptr++; + if(ls) + *ptr = '\n', ptr++; + } + *ptr = 0; + return str; +} + +/* TODO: use X11 clipboard */ +static void selcopy(char *str) { + free(sel.clip); + sel.clip = str; +} + +static void selpaste() { + if(sel.clip) + ttywrite(sel.clip, strlen(sel.clip)); +} + +/* TODO: doubleclick to select word */ +static void brelease(XEvent *e) { + int b; + sel.mode = 0; + getbuttoninfo(e, &b, &sel.ex, &sel.ey); + if(sel.bx==sel.ex && sel.by==sel.ey) { + sel.bx = -1; + if(b==2) + selpaste(); + } else { + if(b==1) + selcopy(getseltext()); + } + draw(1); +} + +static void bmotion(XEvent *e) { + if (sel.mode) { + getbuttoninfo(e, NULL, &sel.ex, &sel.ey); + draw(1); + } +} #ifdef DEBUG void @@ -225,9 +335,12 @@ die(const char *errstr, ...) { void execsh(void) { - char *args[3] = {getenv("SHELL"), "-i", NULL}; - DEFAULT(args[0], "/bin/sh"); /* if getenv() failed */ - putenv("TERM=" TNAME); + char *args[] = {getenv("SHELL"), "-i", NULL}; + if(opt_cmd) + args[0] = opt_cmd, args[1] = NULL; + else + DEFAULT(args[0], SHELL); + putenv("TERM="TNAME); execvp(args[0], args); } @@ -283,10 +396,10 @@ dump(char c) { void ttyread(void) { - char buf[BUFSIZ] = {0}; + char buf[BUFSIZ]; int ret; - if((ret = read(cmdfd, buf, BUFSIZ)) < 0) + if((ret = read(cmdfd, buf, LEN(buf))) < 0) die("Couldn't read from shell: %s\n", SERRNO); else tputs(buf, ret); @@ -337,23 +450,33 @@ tnew(int col, int row) { /* set screen size */ term.row = row, term.col = col; term.line = malloc(term.row * sizeof(Line)); - for(row = 0 ; row < term.row; row++) + term.alt = malloc(term.row * sizeof(Line)); + for(row = 0 ; row < term.row; row++) { term.line[row] = malloc(term.col * sizeof(Glyph)); + term.alt [row] = malloc(term.col * sizeof(Glyph)); + } /* setup screen */ treset(); } void -tscrolldown (int n) { +tswapscreen(void) { + Line* tmp = term.line; + term.line = term.alt; + term.alt = tmp; + term.mode ^= MODE_ALTSCREEN; +} + +void +tscrolldown(int orig, int n) { int i; Line temp; - LIMIT(n, 0, term.bot-term.top+1); + LIMIT(n, 0, term.bot-orig+1); - for(i = 0; i < n; i++) - memset(term.line[term.bot-i], 0, term.col*sizeof(Glyph)); + tclearregion(0, term.bot-n+1, term.col-1, term.bot); - for(i = term.bot; i >= term.top+n; i--) { + for(i = term.bot; i >= orig+n; i--) { temp = term.line[i]; term.line[i] = term.line[i-n]; term.line[i-n] = temp; @@ -361,15 +484,14 @@ tscrolldown (int n) { } void -tscrollup (int n) { +tscrollup(int orig, int n) { int i; Line temp; - LIMIT(n, 0, term.bot-term.top+1); + LIMIT(n, 0, term.bot-orig+1); - for(i = 0; i < n; i++) - memset(term.line[term.top+i], 0, term.col*sizeof(Glyph)); + tclearregion(0, orig, term.col-1, orig+n-1); - for(i = term.top; i <= term.bot-n; i++) { + for(i = orig; i <= term.bot-n; i++) { temp = term.line[i]; term.line[i] = term.line[i+n]; term.line[i+n] = temp; @@ -378,9 +500,11 @@ tscrollup (int n) { void tnewline(void) { - int y = term.c.y + 1; - if(y > term.bot) - tscrollup(1), y = term.bot; + int y = term.c.y; + if(term.c.y == term.bot) + tscrollup(term.top, 1); + else + y++; tmoveto(0, y); } @@ -472,50 +596,18 @@ tinsertblank(int n) { 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); + if(term.c.y < term.top || term.c.y > term.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)); - } + + tscrolldown(term.c.y, n); } 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); + if(term.c.y < term.top || term.c.y > term.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)); - } + + tscrollup(term.c.y, n); } void @@ -585,7 +677,8 @@ tsetattr(int *attr, int l) { else if(BETWEEN(attr[i], 100, 107)) term.c.attr.fg = attr[i] - 100 + 8; else - fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]); + fprintf(stderr, "erresc: gfx attr %d unknown\n", attr[i]), csidump(); + break; } } @@ -668,7 +761,6 @@ csihandle(void) { case 2: /* all */ tclearregion(0, 0, term.col-1, term.row-1); break; - case 3: /* XXX: erase saved lines (xterm) */ default: goto unknown; } @@ -688,11 +780,11 @@ csihandle(void) { break; case 'S': /* SU -- Scroll line up */ DEFAULT(escseq.arg[0], 1); - tscrollup(escseq.arg[0]); + tscrollup(term.top, escseq.arg[0]); break; case 'T': /* SD -- Scroll line down */ DEFAULT(escseq.arg[0], 1); - tscrolldown(escseq.arg[0]); + tscrolldown(term.top, escseq.arg[0]); break; case 'L': /* IL -- Insert blank lines */ DEFAULT(escseq.arg[0], 1); @@ -712,10 +804,16 @@ csihandle(void) { case 25: term.c.state |= CURSOR_HIDE; break; - case 1048: /* XXX: no alt. screen to erase/save */ - case 1049: + case 1049: /* = 1047 and 1048 */ + case 1047: + if(IS_SET(MODE_ALTSCREEN)) { + tclearregion(0, 0, term.col-1, term.row-1); + tswapscreen(); + } + if(escseq.arg[0] == 1047) + break; + case 1048: tcursor(CURSOR_LOAD); - tclearregion(0, 0, term.col-1, term.row-1); break; default: goto unknown; @@ -757,14 +855,22 @@ csihandle(void) { term.mode |= MODE_WRAP; break; case 12: /* att610 -- Start blinking cursor (IGNORED) */ - break; + /* fallthrough for xterm cvvis = CSI [ ? 12 ; 25 h */ + if(escseq.narg > 1 && escseq.arg[1] != 25) + break; case 25: term.c.state &= ~CURSOR_HIDE; break; - case 1048: - case 1049: /* XXX: no alt. screen to erase/save */ + case 1049: /* = 1047 and 1048 */ + case 1047: + if(IS_SET(MODE_ALTSCREEN)) + tclearregion(0, 0, term.col-1, term.row-1); + else + tswapscreen(); + if(escseq.arg[0] == 1047) + break; + case 1048: tcursor(CURSOR_SAVE); - tclearregion(0, 0, term.col-1, term.row-1); break; default: goto unknown; } @@ -831,7 +937,8 @@ tputc(char c) { term.esc = 0; csiparse(), csihandle(); } - } else if(term.esc & ESC_OSC) { + /* TODO: handle other OSC */ + } else if(term.esc & ESC_OSC) { if(c == ';') { term.titlelen = 0; term.esc = ESC_START | ESC_TITLE; @@ -869,7 +976,7 @@ tputc(char c) { break; case 'D': /* IND -- Linefeed */ if(term.c.y == term.bot) - tscrollup(1); + tscrollup(term.top, 1); else tmoveto(term.c.x, term.c.y+1); term.esc = 0; @@ -880,7 +987,7 @@ tputc(char c) { break; case 'M': /* RI -- Reverse index */ if(term.c.y == term.top) - tscrolldown(1); + tscrolldown(term.top, 1); else tmoveto(term.c.x, term.c.y-1); term.esc = 0; @@ -889,19 +996,19 @@ tputc(char c) { treset(); term.esc = 0; break; - case '=': /* DECPAM */ + case '=': /* DECPAM -- Application keypad */ term.mode |= MODE_APPKEYPAD; term.esc = 0; break; - case '>': /* DECPNM */ + case '>': /* DECPNM -- Normal keypad */ term.mode &= ~MODE_APPKEYPAD; term.esc = 0; break; - case '7': + case '7': /* DECSC -- Save Cursor */ tcursor(CURSOR_SAVE); term.esc = 0; break; - case '8': + case '8': /* DECRC -- Restore Cursor */ tcursor(CURSOR_LOAD); term.esc = 0; break; @@ -956,26 +1063,46 @@ tresize(int col, int row) { int i; int minrow = MIN(row, term.row); int mincol = MIN(col, term.col); + int slide = term.c.y - row + 1; if(col < 1 || row < 1) return; - /* free uneeded rows */ - for(i = row; i < term.row; i++) + /* free unneeded rows */ + i = 0; + if(slide > 0) { + /* slide screen to keep cursor where we expect it - + * tscrollup would work here, but we can optimize to + * memmove because we're freeing the earlier lines */ + for(/* i = 0 */; i < slide; i++) { + free(term.line[i]); + free(term.alt[i]); + } + memmove(term.line, term.line + slide, row * sizeof(Line)); + memmove(term.alt, term.alt + slide, row * sizeof(Line)); + } + for(i += row; i < term.row; i++) { free(term.line[i]); + free(term.alt[i]); + } /* resize to new height */ term.line = realloc(term.line, row * sizeof(Line)); + term.alt = realloc(term.alt, row * sizeof(Line)); /* resize each row to new width, zero-pad if needed */ for(i = 0; i < minrow; i++) { term.line[i] = realloc(term.line[i], col * sizeof(Glyph)); + term.alt[i] = realloc(term.alt[i], col * sizeof(Glyph)); memset(term.line[i] + mincol, 0, (col - mincol) * sizeof(Glyph)); + memset(term.alt[i] + mincol, 0, (col - mincol) * sizeof(Glyph)); } /* allocate any new rows */ - for(/* i == minrow */; i < row; i++) + for(/* i == minrow */; i < row; i++) { term.line[i] = calloc(col, sizeof(Glyph)); + term.alt [i] = calloc(col, sizeof(Glyph)); + } /* update terminal size */ term.col = col, term.row = row; @@ -1068,20 +1195,26 @@ xinit(void) { xloadcols(); /* windows */ - xw.h = term.row * xw.ch + 2*BORDER; - xw.w = term.col * xw.cw + 2*BORDER; + xw.bufh = term.row * xw.ch; + xw.bufw = term.col * xw.cw; + xw.h = xw.bufh + 2*BORDER; + xw.w = xw.bufw + 2*BORDER; xw.win = XCreateSimpleWindow(xw.dis, XRootWindow(xw.dis, xw.scr), 0, 0, xw.w, xw.h, 0, dc.col[DefaultBG], dc.col[DefaultBG]); - xw.bufw = xw.w - 2*BORDER; - xw.bufh = xw.h - 2*BORDER; xw.buf = XCreatePixmap(xw.dis, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dis, xw.scr)); /* gc */ dc.gc = XCreateGC(xw.dis, xw.win, 0, NULL); + + /* event mask */ + XSelectInput(xw.dis, xw.win, ExposureMask | KeyPressMask + | StructureNotifyMask | FocusChangeMask | PointerMotionMask + | ButtonPressMask | ButtonReleaseMask); + XMapWindow(xw.dis, xw.win); xhints(); - XStoreName(xw.dis, xw.win, "st"); + XStoreName(xw.dis, xw.win, opt_title ? opt_title : "st"); XSync(xw.dis, 0); } @@ -1100,8 +1233,13 @@ xdraws(char *s, Glyph base, int x, int y, int len) { XSetForeground(xw.dis, dc.gc, xfg); if(base.mode & ATTR_GFX) - for(i = 0; i < len; i++) - s[i] = gfx[(int)s[i]]; + for(i = 0; i < len; i++) { + char c = gfx[(unsigned int)s[i] % 256]; + if(c) + s[i] = c; + else if(s[i] > 0x5f) + s[i] -= 0x5f; + } XSetFont(xw.dis, dc.gc, base.mode & ATTR_BOLD ? dc.bfont->fid : dc.font->fid); XDrawImageString(xw.dis, xw.buf, dc.gc, winx, winy, s, len); @@ -1129,7 +1267,7 @@ xdrawcursor(void) { xclear(oldx, oldy, oldx, oldy); /* draw the new one */ - if(!(term.c.state & CURSOR_HIDE)) { + if(!(term.c.state & CURSOR_HIDE) && xw.hasfocus) { xdraws(&g.c, g, term.c.x, term.c.y, 1); oldx = term.c.x, oldy = term.c.y; } @@ -1168,7 +1306,7 @@ draw(int redraw_all) { int i, x, y, ox; Glyph base, new; char buf[DRAW_BUF_SIZ]; - + XSetForeground(xw.dis, dc.gc, dc.col[DefaultBG]); XFillRectangle(xw.dis, xw.buf, dc.gc, 0, 0, xw.bufw, xw.bufh); for(y = 0; y < term.row; y++) { @@ -1176,8 +1314,10 @@ draw(int redraw_all) { i = ox = 0; for(x = 0; x < term.col; x++) { new = term.line[y][x]; + if(sel.bx!=-1 && new.c && selected(x, y)) + new.mode ^= ATTR_REVERSE; if(i > 0 && (!(new.state & GLYPH_SET) || ATTRCMP(base, new) || - i >= DRAW_BUF_SIZ)) { + i >= DRAW_BUF_SIZ)) { xdraws(buf, base, ox, y, i); i = 0; } @@ -1216,6 +1356,7 @@ void focus(XEvent *ev) { if((xw.hasfocus = ev->type == FocusIn)) xseturgency(0); + draw(SCREEN_UPDATE); } char* @@ -1259,7 +1400,7 @@ kpress(XEvent *ev) { break; case XK_Insert: if(shift) - draw(1), puts("draw!")/* XXX: paste X clipboard */; + selpaste(), draw(1); break; default: fprintf(stderr, "errkey: %d\n", (int)ksym); @@ -1282,6 +1423,8 @@ resize(XEvent *e) { row = xw.bufh / xw.ch; tresize(col, row); ttyresize(col, row); + xw.bufh = MAX(1, xw.bufh); + xw.bufw = MAX(1, xw.bufw); XFreePixmap(xw.dis, xw.buf); xw.buf = XCreatePixmap(xw.dis, xw.win, xw.bufw, xw.bufh, XDefaultDepth(xw.dis, xw.scr)); draw(SCREEN_REDRAW); @@ -1292,12 +1435,8 @@ run(void) { XEvent ev; fd_set rfd; int xfd = XConnectionNumber(xw.dis); - long mask = ExposureMask | KeyPressMask | StructureNotifyMask | FocusChangeMask; - XSelectInput(xw.dis, xw.win, mask); - XResizeWindow(xw.dis, xw.win, xw.w, xw.h); /* XXX: fix resize bug in wmii (?) */ - - while(1) { + for(;;) { FD_ZERO(&rfd); FD_SET(cmdfd, &rfd); FD_SET(xfd, &rfd); @@ -1320,14 +1459,26 @@ run(void) { int main(int argc, char *argv[]) { - if(argc == 2 && !strncmp("-v", argv[1], 3)) - die("st-" VERSION ", (c) 2010 st engineers\n"); - else if(argc != 1) - die("usage: st [-v]\n"); + int i; + + for(i = 1; i < argc; i++) { + switch(argv[i][0] != '-' || argv[i][2] ? -1 : argv[i][1]) { + case 't': + if(++i < argc) opt_title = argv[i]; + break; + case 'e': + if(++i < argc) opt_cmd = argv[i]; + break; + case 'v': + default: + die(USAGE); + } + } setlocale(LC_CTYPE, ""); tnew(80, 24); ttynew(); xinit(); + selinit(); run(); return 0; }