X-Git-Url: https://git.danieliu.xyz/?a=blobdiff_plain;f=st.c;h=6c1638651fe372e829ec57dd47e3e4a0fbb9f920;hb=f7398434;hp=782d18e199cdb97022488887ed85d7b8d39b2996;hpb=f7b80caebe2b96ef65e301634d8dc4e02325e357;p=st.git diff --git a/st.c b/st.c index 782d18e..6c16386 100644 --- a/st.c +++ b/st.c @@ -68,6 +68,7 @@ char *argv0; #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 DIVCEIL(n, d) (((n) + ((d) - 1)) / (d)) #define ISCONTROLC0(c) (BETWEEN(c, 0, 0x1f) || (c) == '\177') #define ISCONTROLC1(c) (BETWEEN(c, 0x80, 0x9f)) #define ISCONTROL(c) (ISCONTROLC0(c) || ISCONTROLC1(c)) @@ -136,6 +137,8 @@ enum term_mode { MODE_MOUSEMANY = 1 << 18, MODE_BRCKTPASTE = 1 << 19, MODE_PRINT = 1 << 20, + MODE_UTF8 = 1 << 21, + MODE_SIXEL = 1 << 22, MODE_MOUSE = MODE_MOUSEBTN|MODE_MOUSEMOTION|MODE_MOUSEX10\ |MODE_MOUSEMANY, }; @@ -153,10 +156,12 @@ enum charset { enum escape_state { ESC_START = 1, ESC_CSI = 2, - ESC_STR = 4, /* DCS, OSC, PM, APC */ + ESC_STR = 4, /* OSC, PM, APC */ ESC_ALTCHARSET = 8, ESC_STR_END = 16, /* a final string was encountered */ ESC_TEST = 32, /* Enter in test mode */ + ESC_UTF8 = 64, + ESC_DCS =128, }; enum window_state { @@ -275,7 +280,7 @@ typedef struct { uint b; uint mask; char *s; -} Mousekey; +} MouseShortcut; typedef struct { KeySym k; @@ -411,11 +416,12 @@ static void tfulldirt(void); static void techo(Rune); static void tcontrolcode(uchar ); static void tdectest(char ); +static void tdefutf8(char); static int32_t tdefcolor(int *, int *, int); static void tdeftran(char); static inline int match(uint, uint); static void ttynew(void); -static void ttyread(void); +static size_t ttyread(void); static void ttyresize(void); static void ttysend(char *, size_t); static void ttywrite(const char *, size_t); @@ -439,7 +445,6 @@ static void xresettitle(void); static void xsetpointermotion(int); static void xseturgency(int); static void xsetsel(char *, Time); -static void xtermclear(int, int, int, int); static void xunloadfont(Font *); static void xunloadfonts(void); static void xresize(int, int); @@ -523,14 +528,15 @@ static int cmdfd; static pid_t pid; static Selection sel; static int iofd = 1; -static char **opt_cmd = NULL; -static char *opt_io = NULL; -static char *opt_title = NULL; -static char *opt_embed = NULL; +static char **opt_cmd = NULL; static char *opt_class = NULL; -static char *opt_font = NULL; -static char *opt_line = NULL; -static int oldbutton = 3; /* button event on startup: 3 = release */ +static char *opt_embed = NULL; +static char *opt_font = NULL; +static char *opt_io = NULL; +static char *opt_line = NULL; +static char *opt_name = NULL; +static char *opt_title = NULL; +static int oldbutton = 3; /* button event on startup: 3 = release */ static char *usedfont = NULL; static double usedfontsize = 0; @@ -695,9 +701,10 @@ utf8validate(Rune *u, size_t i) void selinit(void) { - memset(&sel.tclick1, 0, sizeof(sel.tclick1)); - memset(&sel.tclick2, 0, sizeof(sel.tclick2)); + clock_gettime(CLOCK_MONOTONIC, &sel.tclick1); + clock_gettime(CLOCK_MONOTONIC, &sel.tclick2); sel.mode = SEL_IDLE; + sel.snap = 0; sel.ob.x = -1; sel.primary = NULL; sel.clipboard = NULL; @@ -943,17 +950,17 @@ void bpress(XEvent *e) { struct timespec now; - Mousekey *mk; + MouseShortcut *ms; if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { mousereport(e); return; } - for (mk = mshortcuts; mk < mshortcuts + LEN(mshortcuts); mk++) { - if (e->xbutton.button == mk->b - && match(mk->mask, e->xbutton.state)) { - ttysend(mk->s, strlen(mk->s)); + for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) { + if (e->xbutton.button == ms->b + && match(ms->mask, e->xbutton.state)) { + ttysend(ms->s, strlen(ms->s)); return; } } @@ -1149,8 +1156,7 @@ selnotify(XEvent *e) * Deleting the property again tells the selection owner to send the * next data chunk in the property. */ - if (e->type == PropertyNotify) - XDeleteProperty(xw.dpy, xw.win, (int)property); + XDeleteProperty(xw.dpy, xw.win, (int)property); } void @@ -1402,9 +1408,9 @@ stty(void) if ((n = strlen(s)) > siz-1) die("stty parameter length too long\n"); *q++ = ' '; - q = memcpy(q, s, n); + memcpy(q, s, n); q += n; - siz-= n + 1; + siz -= n + 1; } *q = '\0'; if (system(cmd) != 0) @@ -1463,7 +1469,7 @@ ttynew(void) } } -void +size_t ttyread(void) { static char buf[BUFSIZ]; @@ -1477,25 +1483,39 @@ ttyread(void) if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0) die("Couldn't read from shell: %s\n", strerror(errno)); - /* process every complete utf8 char */ buflen += ret; ptr = buf; - while ((charsize = utf8decode(ptr, &unicodep, buflen))) { - tputc(unicodep); - ptr += charsize; - buflen -= charsize; - } + for (;;) { + if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { + /* process a complete utf8 char */ + charsize = utf8decode(ptr, &unicodep, buflen); + if (charsize == 0) + break; + tputc(unicodep); + ptr += charsize; + buflen -= charsize; + + } else { + if (buflen <= 0) + break; + tputc(*ptr++ & 0xFF); + buflen--; + } + } /* keep any uncomplete utf8 char for the next call */ - memmove(buf, ptr, buflen); + if (buflen > 0) + memmove(buf, ptr, buflen); + + return ret; } void ttywrite(const char *s, size_t n) { - fd_set wfd; - struct timespec tv; + fd_set wfd, rfd; ssize_t r; + size_t lim = 256; /* * Remember that we are using a pty, which might be a modem line. @@ -1505,38 +1525,32 @@ ttywrite(const char *s, size_t n) */ while (n > 0) { FD_ZERO(&wfd); + FD_ZERO(&rfd); FD_SET(cmdfd, &wfd); - tv.tv_sec = 0; - tv.tv_nsec = 0; + FD_SET(cmdfd, &rfd); /* Check if we can write. */ - if (pselect(cmdfd+1, NULL, &wfd, NULL, &tv, NULL) < 0) { + if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) { if (errno == EINTR) continue; die("select failed: %s\n", strerror(errno)); } - if(!FD_ISSET(cmdfd, &wfd)) { - /* No, then free some buffer space. */ - ttyread(); - } else { + if (FD_ISSET(cmdfd, &wfd)) { /* - * Only write 256 bytes at maximum. This seems to be a - * reasonable value for a serial line. Bigger values - * might clog the I/O. + * Only write the bytes written by ttywrite() or the + * default of 256. This seems to be a reasonable value + * for a serial line. Bigger values might clog the I/O. */ - r = write(cmdfd, s, (n < 256)? n : 256); - if (r < 0) { - die("write error on tty: %s\n", - strerror(errno)); - } + if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0) + goto write_error; if (r < n) { /* * We weren't able to write out everything. * This means the buffer is getting full * again. Empty it. */ - if (n < 256) - ttyread(); + if (n < lim) + lim = ttyread(); n -= r; s += r; } else { @@ -1544,22 +1558,39 @@ ttywrite(const char *s, size_t n) break; } } + if (FD_ISSET(cmdfd, &rfd)) + lim = ttyread(); } + return; + +write_error: + die("write error on tty: %s\n", strerror(errno)); } void ttysend(char *s, size_t n) { int len; + char *t, *lim; Rune u; ttywrite(s, n); - if (IS_SET(MODE_ECHO)) - while ((len = utf8decode(s, &u, n)) > 0) { - techo(u); - n -= len; - s += len; + if (!IS_SET(MODE_ECHO)) + return; + + lim = &s[n]; + for (t = s; t < lim; t += len) { + if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { + len = utf8decode(t, &u, n); + } else { + u = *t & 0xFF; + len = 1; } + if (len <= 0) + break; + techo(u); + n -= len; + } } void @@ -1653,7 +1684,7 @@ treset(void) term.tabs[i] = 1; term.top = 0; term.bot = term.row - 1; - term.mode = MODE_WRAP; + term.mode = MODE_WRAP|MODE_UTF8; memset(term.trantbl, CS_USA, sizeof(term.trantbl)); term.charset = 0; @@ -2519,6 +2550,7 @@ strhandle(void) xsettitle(strescseq.args[0]); return; case 'P': /* DCS -- Device Control String */ + term.mode |= ESC_DCS; case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ return; @@ -2686,6 +2718,15 @@ techo(Rune u) tputc(u); } +void +tdefutf8(char ascii) +{ + if (ascii == 'G') + term.mode |= MODE_UTF8; + else if (ascii == '@') + term.mode &= ~MODE_UTF8; +} + void tdeftran(char ascii) { @@ -2716,9 +2757,12 @@ tdectest(char c) void tstrsequence(uchar c) { + strreset(); + switch (c) { case 0x90: /* DCS -- Device Control String */ c = 'P'; + term.esc |= ESC_DCS; break; case 0x9f: /* APC -- Application Program Command */ c = '_'; @@ -2730,7 +2774,6 @@ tstrsequence(uchar c) c = ']'; break; } - strreset(); strescseq.type = c; term.esc |= ESC_STR; } @@ -2848,6 +2891,9 @@ eschandle(uchar ascii) case '#': term.esc |= ESC_TEST; return 0; + case '%': + term.esc |= ESC_UTF8; + return 0; case 'P': /* DCS -- Device Control String */ case '_': /* APC -- Application Program Command */ case '^': /* PM -- Privacy Message */ @@ -2927,10 +2973,15 @@ tputc(Rune u) Glyph *gp; control = ISCONTROL(u); - len = utf8encode(u, c); - if (!control && (width = wcwidth(u)) == -1) { - memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ - width = 1; + if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) { + c[0] = u; + width = len = 1; + } else { + len = utf8encode(u, c); + if (!control && (width = wcwidth(u)) == -1) { + memcpy(c, "\357\277\275", 4); /* UTF_INVALID */ + width = 1; + } } if (IS_SET(MODE_PRINT)) @@ -2945,30 +2996,47 @@ tputc(Rune u) if (term.esc & ESC_STR) { if (u == '\a' || u == 030 || u == 032 || u == 033 || ISCONTROLC1(u)) { - term.esc &= ~(ESC_START|ESC_STR); + term.esc &= ~(ESC_START|ESC_STR|ESC_DCS); + if (IS_SET(MODE_SIXEL)) { + /* TODO: render sixel */; + term.mode &= ~MODE_SIXEL; + return; + } term.esc |= ESC_STR_END; - } else if (strescseq.len + len < sizeof(strescseq.buf) - 1) { - memmove(&strescseq.buf[strescseq.len], c, len); - strescseq.len += len; + goto check_control_code; + } + + + if (IS_SET(MODE_SIXEL)) { + /* TODO: implement sixel mode */ return; - } else { - /* - * Here is a bug in terminals. If the user never sends - * some code to stop the str or esc command, then st - * will stop responding. But this is better than - * silently failing with unknown characters. At least - * then users will report back. - * - * In the case users ever get fixed, here is the code: - */ - /* - * term.esc = 0; - * strhandle(); - */ + } + if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q') + term.mode |= MODE_SIXEL; + + if (strescseq.len+len >= sizeof(strescseq.buf)-1) { + /* + * Here is a bug in terminals. If the user never sends + * some code to stop the str or esc command, then st + * will stop responding. But this is better than + * silently failing with unknown characters. At least + * then users will report back. + * + * In the case users ever get fixed, here is the code: + */ + /* + * term.esc = 0; + * strhandle(); + */ return; } + + memmove(&strescseq.buf[strescseq.len], c, len); + strescseq.len += len; + return; } +check_control_code: /* * Actions of control codes must be performed as soon they arrive * because they can be embedded inside a control sequence, and @@ -2991,6 +3059,8 @@ tputc(Rune u) csihandle(); } return; + } else if (term.esc & ESC_UTF8) { + tdefutf8(u); } else if (term.esc & ESC_ALTCHARSET) { tdeftran(u); } else if (term.esc & ESC_TEST) { @@ -3209,17 +3279,6 @@ xsetcolorname(int x, const char *name) return 0; } -void -xtermclear(int col1, int row1, int col2, int row2) -{ - XftDrawRect(xw.draw, - &dc.col[IS_SET(MODE_REVERSE) ? defaultfg : defaultbg], - borderpx + col1 * xw.cw, - borderpx + row1 * xw.ch, - (col2-col1+1) * xw.cw, - (row2-row1+1) * xw.ch); -} - /* * Absolute coordinates. */ @@ -3234,7 +3293,8 @@ xclear(int x1, int y1, int x2, int y2) void xhints(void) { - XClassHint class = {opt_class ? opt_class : termname, termname}; + XClassHint class = {opt_name ? opt_name : termname, + opt_class ? opt_class : termname}; XWMHints wm = {.flags = InputHint, .input = 1}; XSizeHints *sizeh = NULL; @@ -3284,8 +3344,9 @@ xloadfont(Font *f, FcPattern *pattern) { FcPattern *match; FcResult result; + XGlyphInfo extents; - match = FcFontMatch(NULL, pattern, &result); + match = XftFontMatch(xw.dpy, xw.scr, pattern, &result); if (!match) return 1; @@ -3294,6 +3355,10 @@ xloadfont(Font *f, FcPattern *pattern) return 1; } + XftTextExtentsUtf8(xw.dpy, f->match, + (const FcChar8 *) ascii_printable, + strlen(ascii_printable), &extents); + f->set = NULL; f->pattern = FcPatternDuplicate(pattern); @@ -3303,7 +3368,7 @@ xloadfont(Font *f, FcPattern *pattern) f->rbearing = f->match->max_advance_width; f->height = f->ascent + f->descent; - f->width = f->lbearing + f->rbearing; + f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); return 0; } @@ -3347,9 +3412,6 @@ xloadfonts(char *fontstr, double fontsize) defaultfontsize = usedfontsize; } - FcConfigSubstitute(0, pattern, FcMatchPattern); - FcDefaultSubstitute(pattern); - if (xloadfont(&dc.font, pattern)) die("st: can't open font %s\n", fontstr); @@ -3420,6 +3482,7 @@ xzoomabs(const Arg *arg) xunloadfonts(); xloadfonts(usedfont, arg->f); cresize(0, 0); + ttyresize(); redraw(); xhints(); } @@ -3466,7 +3529,7 @@ xinit(void) if (xw.gm & XNegative) xw.l += DisplayWidth(xw.dpy, xw.scr) - xw.w - 2; if (xw.gm & YNegative) - xw.t += DisplayWidth(xw.dpy, xw.scr) - xw.h - 2; + xw.t += DisplayHeight(xw.dpy, xw.scr) - xw.h - 2; /* Events */ xw.attrs.background_pixel = dc.col[defaultbg].pixel; @@ -3671,7 +3734,7 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x specs[numspecs].font = frc[f].font; specs[numspecs].glyph = glyphidx; specs[numspecs].x = (short)xp; - specs[numspecs].y = (short)(winy + frc[f].font->ascent); + specs[numspecs].y = (short)yp; xp += runewidth; numspecs++; } @@ -3850,11 +3913,28 @@ xdrawcursor(void) xdrawglyph(og, oldx, oldy); g.u = term.line[term.c.y][term.c.x].u; - if (ena_sel && selected(term.c.x, term.c.y)) { - drawcol = dc.col[defaultrcs]; - g.mode ^= ATTR_REVERSE; + + /* + * Select the right color for the right mode. + */ + if (IS_SET(MODE_REVERSE)) { + g.mode |= ATTR_REVERSE; + g.bg = defaultfg; + if (ena_sel && selected(term.c.x, term.c.y)) { + drawcol = dc.col[defaultcs]; + g.fg = defaultrcs; + } else { + drawcol = dc.col[defaultrcs]; + g.fg = defaultcs; + } } else { - drawcol = dc.col[defaultcs]; + if (ena_sel && selected(term.c.x, term.c.y)) { + drawcol = dc.col[defaultrcs]; + g.fg = defaultfg; + g.bg = defaultrcs; + } else { + drawcol = dc.col[defaultcs]; + } } if (IS_SET(MODE_HIDE)) @@ -3863,15 +3943,11 @@ xdrawcursor(void) /* draw the new one */ if (xw.state & WIN_FOCUSED) { switch (xw.cursor) { + case 7: /* st extension: snowman */ + utf8decode("☃", &g.u, UTF_SIZ); case 0: /* Blinking Block */ case 1: /* Blinking Block (Default) */ case 2: /* Steady Block */ - if (IS_SET(MODE_REVERSE)) { - g.mode |= ATTR_REVERSE; - g.fg = defaultcs; - g.bg = defaultfg; - } - g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE; xdrawglyph(g, term.c.x, term.c.y); break; @@ -3964,7 +4040,6 @@ drawregion(int x1, int y1, int x2, int y2) if (!term.dirty[y]) continue; - xtermclear(0, y, term.col, y); term.dirty[y] = 0; specs = term.specbuf; @@ -4190,7 +4265,6 @@ cresize(int width, int height) tresize(col, row); xresize(col, row); - ttyresize(); } void @@ -4200,6 +4274,7 @@ resize(XEvent *e) return; cresize(e->xconfigure.width, e->xconfigure.height); + ttyresize(); } void @@ -4228,8 +4303,9 @@ run(void) } } while (ev.type != MapNotify); - ttynew(); cresize(w, h); + ttynew(); + ttyresize(); clock_gettime(CLOCK_MONOTONIC, &last); lastblink = last; @@ -4313,14 +4389,14 @@ run(void) void usage(void) { - die("%s " VERSION " (c) 2010-2015 st engineers\n" - "usage: st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]\n" - " [-i] [-t title] [-T title] [-w windowid] [-e command ...]" - " [command ...]\n" - " st [-a] [-v] [-c class] [-f font] [-g geometry] [-o file]\n" - " [-i] [-t title] [-T title] [-w windowid] -l line" - " [stty_args ...]\n", - argv0); + die("usage: %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid]" + " [[-e] command [args ...]]\n" + " %s [-aiv] [-c class] [-f font] [-g geometry]" + " [-n name] [-o file]\n" + " [-T title] [-t title] [-w windowid] -l line" + " [stty_args ...]\n", argv0, argv0); } int @@ -4359,6 +4435,9 @@ main(int argc, char *argv[]) case 'l': opt_line = EARGF(usage()); break; + case 'n': + opt_name = EARGF(usage()); + break; case 't': case 'T': opt_title = EARGF(usage()); @@ -4367,6 +4446,8 @@ main(int argc, char *argv[]) opt_embed = EARGF(usage()); break; case 'v': + die("%s " VERSION " (c) 2010-2016 st engineers\n", argv0); + break; default: usage(); } ARGEND;