Simple Terminal from SuckLess
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

943 lines
19 KiB

  1. /* See LICENSE for licence details. */
  2. #include "st.h"
  3. /* Globals */
  4. DC dc;
  5. XWindow xw;
  6. Term term;
  7. Escseq escseq;
  8. int cmdfd;
  9. int running;
  10. void
  11. die(const char *errstr, ...) {
  12. va_list ap;
  13. va_start(ap, errstr);
  14. vfprintf(stderr, errstr, ap);
  15. va_end(ap);
  16. exit(EXIT_FAILURE);
  17. }
  18. void
  19. execsh(void) {
  20. char *args[3] = {SHELL, "-i", NULL};
  21. putenv("TERM=" TNAME);
  22. execvp(SHELL, args);
  23. }
  24. void
  25. xbell(void) { /* visual bell */
  26. XRectangle r = { 0, 0, xw.w, xw.h };
  27. XSetForeground(xw.dis, dc.gc, dc.col[BellCol]);
  28. XFillRectangles(xw.dis, xw.win, dc.gc, &r, 1);
  29. usleep(30000);
  30. draw(SCredraw);
  31. }
  32. void
  33. ttynew(void) {
  34. int m, s;
  35. pid_t pid;
  36. char *pts;
  37. if((m = posix_openpt(O_RDWR | O_NOCTTY)) < 0)
  38. die("openpt");
  39. if(grantpt(m) == -1)
  40. die("grandpt");
  41. if(unlockpt(m) == -1)
  42. die("unlockpt");
  43. if((pts = ptsname(m)) == NULL)
  44. die("ptsname");
  45. if((s = open(pts, O_RDWR | O_NOCTTY)) < 0)
  46. die("slave open");
  47. fcntl(s, F_SETFL, O_NDELAY);
  48. switch(pid = fork()) {
  49. case -1:
  50. die("fork");
  51. break;
  52. case 0:
  53. setsid(); /* create a new process group */
  54. dup2(s, STDIN_FILENO);
  55. dup2(s, STDOUT_FILENO);
  56. dup2(s, STDERR_FILENO);
  57. if(ioctl(s, TIOCSCTTY, NULL) < 0)
  58. die("slave TTIOCSTTY");
  59. execsh();
  60. break;
  61. default:
  62. close(s);
  63. cmdfd = m;
  64. }
  65. }
  66. void
  67. dump(char c) {
  68. static int col;
  69. fprintf(stderr, " %02x %c ", c, isprint(c)?c:'.');
  70. if(++col % 10 == 0)
  71. fprintf(stderr, "\n");
  72. }
  73. void
  74. ttyread(void) {
  75. char buf[BUFSIZ] = {0};
  76. int ret;
  77. switch(ret = read(cmdfd, buf, BUFSIZ)) {
  78. case -1: /* error or exit */
  79. /* XXX: be more precise */
  80. running = 0;
  81. break;
  82. default:
  83. tputs(buf, ret);
  84. }
  85. }
  86. void
  87. ttywrite(char *s, size_t n) {
  88. if(write(cmdfd, s, n) == -1)
  89. die("write error on tty.");
  90. }
  91. void
  92. ttyresize(int x, int y) {
  93. struct winsize w;
  94. w.ws_row = term.row;
  95. w.ws_col = term.col;
  96. w.ws_xpixel = w.ws_ypixel = 0;
  97. if(ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
  98. fprintf(stderr, "Couldn't set window size: %m\n");
  99. }
  100. int
  101. escfinal(char c) {
  102. if(escseq.len == 1)
  103. switch(c) {
  104. case '[':
  105. case ']':
  106. case '(':
  107. return 0;
  108. case '=':
  109. case '>':
  110. default:
  111. return 1;
  112. }
  113. else if(BETWEEN(c, 0x40, 0x7E))
  114. return 1;
  115. return 0;
  116. }
  117. void
  118. tcpos(int mode) {
  119. static int x = 0;
  120. static int y = 0;
  121. if(mode == CSsave)
  122. x = term.c.x, y = term.c.y;
  123. else if(mode == CSload)
  124. tmoveto(x, y);
  125. }
  126. void
  127. tnew(int col, int row) { /* screen size */
  128. term.row = row, term.col = col;
  129. term.top = 0, term.bot = term.row - 1;
  130. /* mode */
  131. term.mode = TMwrap;
  132. /* cursor */
  133. term.c.attr.mode = ATnone;
  134. term.c.attr.fg = DefaultFG;
  135. term.c.attr.bg = DefaultBG;
  136. term.c.x = term.c.y = 0;
  137. term.c.hidden = 0;
  138. /* allocate screen */
  139. term.line = calloc(term.row, sizeof(Line));
  140. for(row = 0 ; row < term.row; row++)
  141. term.line[row] = calloc(term.col, sizeof(Glyph));
  142. }
  143. void
  144. tscroll(void) {
  145. Line temp = term.line[term.top];
  146. int i;
  147. for(i = term.top; i < term.bot; i++)
  148. term.line[i] = term.line[i+1];
  149. memset(temp, 0, sizeof(Glyph) * term.col);
  150. term.line[term.bot] = temp;
  151. xscroll();
  152. }
  153. void
  154. tnewline(void) {
  155. int y = term.c.y + 1;
  156. if(y > term.bot) {
  157. tscroll(), y = term.bot;
  158. }
  159. tmoveto(0, y);
  160. }
  161. int
  162. escaddc(char c) {
  163. escseq.buf[escseq.len++] = c;
  164. if(escfinal(c) || escseq.len >= ESCSIZ) {
  165. escparse(), eschandle();
  166. return 0;
  167. }
  168. return 1;
  169. }
  170. void
  171. escparse(void) {
  172. /* int noarg = 1; */
  173. char *p = escseq.buf;
  174. escseq.narg = 0;
  175. switch(escseq.pre = *p++) {
  176. case '[': /* CSI */
  177. if(*p == '?')
  178. escseq.priv = 1, p++;
  179. while(p < escseq.buf+escseq.len) {
  180. while(isdigit(*p)) {
  181. escseq.arg[escseq.narg] *= 10;
  182. escseq.arg[escseq.narg] += *(p++) - '0'/*, noarg = 0 */;
  183. }
  184. if(*p == ';')
  185. escseq.narg++, p++;
  186. else {
  187. escseq.mode = *p;
  188. escseq.narg++;
  189. return;
  190. }
  191. }
  192. break;
  193. case '(':
  194. /* XXX: graphic character set */
  195. break;
  196. }
  197. }
  198. void
  199. tmoveto(int x, int y) {
  200. term.c.x = x < 0 ? 0 : x >= term.col ? term.col-1 : x;
  201. term.c.y = y < 0 ? 0 : y >= term.row ? term.row-1 : y;
  202. }
  203. void
  204. tcursor(int dir) {
  205. int xf = term.c.x, yf = term.c.y;
  206. switch(dir) {
  207. case CSup:
  208. yf--;
  209. break;
  210. case CSdown:
  211. yf++;
  212. break;
  213. case CSleft:
  214. xf--;
  215. if(xf < 0) {
  216. xf = term.col-1, yf--;
  217. if(yf < term.top)
  218. yf = term.top, xf = 0;
  219. }
  220. break;
  221. case CSright:
  222. xf++;
  223. if(xf >= term.col) {
  224. xf = 0, yf++;
  225. if(yf > term.bot)
  226. yf = term.bot, tscroll();
  227. }
  228. break;
  229. }
  230. tmoveto(xf, yf);
  231. }
  232. void
  233. tsetchar(char c) {
  234. term.line[term.c.y][term.c.x] = term.c.attr;
  235. term.line[term.c.y][term.c.x].c = c;
  236. term.line[term.c.y][term.c.x].state |= CRset | CRupdate;
  237. }
  238. void
  239. tclearregion(int x1, int y1, int x2, int y2) {
  240. int x, y;
  241. LIMIT(x1, 0, term.col-1);
  242. LIMIT(x2, 0, term.col-1);
  243. LIMIT(y1, 0, term.row-1);
  244. LIMIT(y2, 0, term.row-1);
  245. /* XXX: could be optimized */
  246. for(x = x1; x <= x2; x++)
  247. for(y = y1; y <= y2; y++)
  248. memset(&term.line[y][x], 0, sizeof(Glyph));
  249. xclear(x1, y1, x2, y2);
  250. }
  251. void
  252. tdeletechar(int n) {
  253. int src = term.c.x + n;
  254. int dst = term.c.x;
  255. int size = term.col - src;
  256. if(src >= term.col) {
  257. tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
  258. return;
  259. }
  260. memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
  261. tclearregion(term.col-size, term.c.y, term.col-1, term.c.y);
  262. }
  263. void
  264. tinsertblank(int n) {
  265. int src = term.c.x;
  266. int dst = src + n;
  267. int size = term.col - n - src;
  268. if(dst >= term.col) {
  269. tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
  270. return;
  271. }
  272. memmove(&term.line[term.c.y][dst], &term.line[term.c.y][src], size * sizeof(Glyph));
  273. tclearregion(src, term.c.y, dst, term.c.y);
  274. }
  275. void
  276. tinsertblankline (int n) {
  277. int i;
  278. Line blank;
  279. int bot = term.bot;
  280. if(term.c.y > term.bot)
  281. bot = term.row - 1;
  282. else if(term.c.y < term.top)
  283. bot = term.top - 1;
  284. if(term.c.y + n >= bot) {
  285. tclearregion(0, term.c.y, term.col-1, bot);
  286. return;
  287. }
  288. for(i = bot; i >= term.c.y+n; i--) {
  289. /* swap deleted line <-> blanked line */
  290. blank = term.line[i];
  291. term.line[i] = term.line[i-n];
  292. term.line[i-n] = blank;
  293. /* blank it */
  294. memset(blank, 0, term.col * sizeof(Glyph));
  295. }
  296. }
  297. void
  298. tdeleteline(int n) {
  299. int i;
  300. Line blank;
  301. int bot = term.bot;
  302. if(term.c.y > term.bot)
  303. bot = term.row - 1;
  304. else if(term.c.y < term.top)
  305. bot = term.top - 1;
  306. if(term.c.y + n >= bot) {
  307. tclearregion(0, term.c.y, term.col-1, bot);
  308. return;
  309. }
  310. for(i = term.c.y; i <= bot-n; i++) {
  311. /* swap deleted line <-> blanked line */
  312. blank = term.line[i];
  313. term.line[i] = term.line[i+n];
  314. term.line[i+n] = blank;
  315. /* blank it */
  316. memset(blank, 0, term.col * sizeof(Glyph));
  317. }
  318. }
  319. void
  320. tsetattr(int *attr, int l) {
  321. int i;
  322. for(i = 0; i < l; i++) {
  323. switch(attr[i]) {
  324. case 0:
  325. memset(&term.c.attr, 0, sizeof(term.c.attr));
  326. term.c.attr.fg = DefaultFG;
  327. term.c.attr.bg = DefaultBG;
  328. break;
  329. case 1:
  330. term.c.attr.mode |= ATbold;
  331. break;
  332. case 4:
  333. term.c.attr.mode |= ATunderline;
  334. break;
  335. case 7:
  336. term.c.attr.mode |= ATreverse;
  337. break;
  338. case 8:
  339. term.c.hidden = CShide;
  340. break;
  341. case 22:
  342. term.c.attr.mode &= ~ATbold;
  343. break;
  344. case 24:
  345. term.c.attr.mode &= ~ATunderline;
  346. break;
  347. case 27:
  348. term.c.attr.mode &= ~ATreverse;
  349. break;
  350. case 39:
  351. term.c.attr.fg = DefaultFG;
  352. break;
  353. case 49:
  354. term.c.attr.fg = DefaultBG;
  355. break;
  356. default:
  357. if(BETWEEN(attr[i], 30, 37))
  358. term.c.attr.fg = attr[i] - 30;
  359. else if(BETWEEN(attr[i], 40, 47))
  360. term.c.attr.bg = attr[i] - 40;
  361. break;
  362. }
  363. }
  364. }
  365. void
  366. tsetscroll(int t, int b) {
  367. int temp;
  368. LIMIT(t, 0, term.row-1);
  369. LIMIT(b, 0, term.row-1);
  370. if(t > b) {
  371. temp = t;
  372. t = b;
  373. b = temp;
  374. }
  375. term.top = t;
  376. term.bot = b;
  377. }
  378. void
  379. eschandle(void) {
  380. /* escdump(); */
  381. switch(escseq.pre) {
  382. case '[':
  383. switch(escseq.mode) {
  384. case '@': /* Insert <n> blank char */
  385. DEFAULT(escseq.arg[0], 1);
  386. tinsertblank(escseq.arg[0]);
  387. break;
  388. case 'A': /* Cursor <n> Up */
  389. case 'e':
  390. DEFAULT(escseq.arg[0], 1);
  391. tmoveto(term.c.x, term.c.y-escseq.arg[0]);
  392. break;
  393. case 'B': /* Cursor <n> Down */
  394. DEFAULT(escseq.arg[0], 1);
  395. tmoveto(term.c.x, term.c.y+escseq.arg[0]);
  396. break;
  397. case 'C': /* Cursor <n> Forward */
  398. case 'a':
  399. DEFAULT(escseq.arg[0], 1);
  400. tmoveto(term.c.x+escseq.arg[0], term.c.y);
  401. break;
  402. case 'D': /* Cursor <n> Backward */
  403. DEFAULT(escseq.arg[0], 1);
  404. tmoveto(term.c.x-escseq.arg[0], term.c.y);
  405. break;
  406. case 'E': /* Cursor <n> Down and first col */
  407. DEFAULT(escseq.arg[0], 1);
  408. tmoveto(0, term.c.y+escseq.arg[0]);
  409. break;
  410. case 'F': /* Cursor <n> Up and first col */
  411. DEFAULT(escseq.arg[0], 1);
  412. tmoveto(0, term.c.y-escseq.arg[0]);
  413. break;
  414. case 'G': /* Move to <col> */
  415. case '`':
  416. DEFAULT(escseq.arg[0], 1);
  417. tmoveto(escseq.arg[0]-1, term.c.y);
  418. break;
  419. case 'H': /* Move to <row> <col> */
  420. case 'f':
  421. DEFAULT(escseq.arg[0], 1);
  422. DEFAULT(escseq.arg[1], 1);
  423. tmoveto(escseq.arg[1]-1, escseq.arg[0]-1);
  424. break;
  425. case 'J': /* Clear screen */
  426. switch(escseq.arg[0]) {
  427. case 0: /* below */
  428. tclearregion(term.c.x, term.c.y, term.col-1, term.row-1);
  429. break;
  430. case 1: /* above */
  431. tclearregion(0, 0, term.c.x, term.c.y);
  432. break;
  433. case 2: /* all */
  434. tclearregion(0, 0, term.col-1, term.row-1);
  435. break;
  436. }
  437. break;
  438. case 'K': /* Clear line */
  439. switch(escseq.arg[0]) {
  440. case 0: /* right */
  441. tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
  442. break;
  443. case 1: /* left */
  444. tclearregion(0, term.c.y, term.c.x, term.c.y);
  445. break;
  446. case 2: /* all */
  447. tclearregion(0, term.c.y, term.col-1, term.c.y);
  448. break;
  449. }
  450. break;
  451. case 'L': /* Insert <n> blank lines */
  452. DEFAULT(escseq.arg[0], 1);
  453. tinsertblankline(escseq.arg[0]);
  454. break;
  455. case 'l':
  456. if(escseq.priv && escseq.arg[0] == 25)
  457. term.c.hidden = 1;
  458. break;
  459. case 'M': /* Delete <n> lines */
  460. DEFAULT(escseq.arg[0], 1);
  461. tdeleteline(escseq.arg[0]);
  462. break;
  463. case 'P': /* Delete <n> char */
  464. DEFAULT(escseq.arg[0], 1);
  465. tdeletechar(escseq.arg[0]);
  466. break;
  467. case 'd': /* Move to <row> */
  468. DEFAULT(escseq.arg[0], 1);
  469. tmoveto(term.c.x, escseq.arg[0]-1);
  470. break;
  471. case 'h': /* Set terminal mode */
  472. if(escseq.priv && escseq.arg[0] == 25)
  473. term.c.hidden = 0;
  474. break;
  475. case 'm': /* Terminal attribute (color) */
  476. tsetattr(escseq.arg, escseq.narg);
  477. break;
  478. case 'r':
  479. if(escseq.priv)
  480. ;
  481. else {
  482. DEFAULT(escseq.arg[0], 1);
  483. DEFAULT(escseq.arg[1], term.row);
  484. tsetscroll(escseq.arg[0]-1, escseq.arg[1]-1);
  485. }
  486. break;
  487. case 's': /* Save cursor position */
  488. tcpos(CSsave);
  489. break;
  490. case 'u': /* Load cursor position */
  491. tcpos(CSload);
  492. break;
  493. }
  494. break;
  495. }
  496. }
  497. void
  498. escdump(void) {
  499. int i;
  500. puts("------");
  501. printf("rawbuf : %s\n", escseq.buf);
  502. printf("prechar : %c\n", escseq.pre);
  503. printf("private : %c\n", escseq.priv ? '?' : ' ');
  504. printf("narg : %d\n", escseq.narg);
  505. if(escseq.narg) {
  506. for(i = 0; i < escseq.narg; i++)
  507. printf("\targ %d = %d\n", i, escseq.arg[i]);
  508. }
  509. printf("mode : %c\n", escseq.mode);
  510. }
  511. void
  512. escreset(void) {
  513. memset(&escseq, 0, sizeof(escseq));
  514. }
  515. void
  516. tputtab(void) {
  517. int space = TAB - term.c.x % TAB;
  518. if(term.c.x + space >= term.col)
  519. space--;
  520. for(; space > 0; space--)
  521. tcursor(CSright);
  522. }
  523. void
  524. tputc(char c) {
  525. static int inesc = 0;
  526. #if 0
  527. dump(c);
  528. #endif
  529. /* start of escseq */
  530. if(c == '\033')
  531. escreset(), inesc = 1;
  532. else if(inesc) {
  533. inesc = escaddc(c);
  534. } /* normal char */
  535. else switch(c) {
  536. default:
  537. tsetchar(c);
  538. tcursor(CSright);
  539. break;
  540. case '\t':
  541. tputtab();
  542. break;
  543. case '\b':
  544. tcursor(CSleft);
  545. break;
  546. case '\r':
  547. tmoveto(0, term.c.y);
  548. break;
  549. case '\n':
  550. tnewline();
  551. break;
  552. case '\a':
  553. xbell();
  554. break;
  555. }
  556. }
  557. void
  558. tputs(char *s, int len) {
  559. for(; len > 0; len--)
  560. tputc(*s++);
  561. }
  562. void
  563. tdump(void) {
  564. int row, col;
  565. Glyph c;
  566. for(row = 0; row < term.row; row++) {
  567. for(col = 0; col < term.col; col++) {
  568. if(col == term.c.x && row == term.c.y)
  569. putchar('#');
  570. else {
  571. c = term.line[row][col];
  572. putchar(c.state & CRset ? c.c : '.');
  573. }
  574. }
  575. putchar('\n');
  576. }
  577. }
  578. void
  579. tresize(int col, int row) {
  580. int i;
  581. Line *line;
  582. int minrow = MIN(row, term.row);
  583. int mincol = MIN(col, term.col);
  584. if(col < 1 || row < 1)
  585. return;
  586. /* alloc */
  587. line = calloc(row, sizeof(Line));
  588. for(i = 0 ; i < row; i++)
  589. line[i] = calloc(col, sizeof(Glyph));
  590. /* copy */
  591. for(i = 0 ; i < minrow; i++)
  592. memcpy(line[i], term.line[i], mincol * sizeof(Glyph));
  593. /* free */
  594. for(i = 0; i < term.row; i++)
  595. free(term.line[i]);
  596. free(term.line);
  597. LIMIT(term.c.x, 0, col-1);
  598. LIMIT(term.c.y, 0, row-1);
  599. LIMIT(term.top, 0, row-1);
  600. LIMIT(term.bot, 0, row-1);
  601. term.bot = row-1;
  602. term.line = line;
  603. term.col = col, term.row = row;
  604. }
  605. unsigned long
  606. xgetcol(const char *s) {
  607. XColor color;
  608. Colormap cmap = DefaultColormap(xw.dis, xw.scr);
  609. if(!XAllocNamedColor(xw.dis, cmap, s, &color, &color)) {
  610. color.pixel = WhitePixel(xw.dis, xw.scr);
  611. fprintf(stderr, "Could not allocate color '%s'\n", s);
  612. }
  613. return color.pixel;
  614. }
  615. void
  616. xclear(int x1, int y1, int x2, int y2) {
  617. XClearArea(xw.dis, xw.win,
  618. x1 * xw.cw, y1 * xw.ch,
  619. (x2-x1+1) * xw.cw, (y2-y1+1) * xw.ch,
  620. False);
  621. }
  622. void
  623. xscroll(void) {
  624. int srcy = (term.top+1) * xw.ch;
  625. int dsty = term.top * xw.ch;
  626. int height = (term.bot-term.top) * xw.ch;
  627. xcursor(CShide);
  628. XCopyArea(xw.dis, xw.win, xw.win, dc.gc, 0, srcy, xw.w, height, 0, dsty);
  629. xclear(0, term.bot, term.col-1, term.bot);
  630. }
  631. void
  632. xinit(void) {
  633. XGCValues values;
  634. unsigned long valuemask;
  635. XClassHint chint;
  636. XWMHints wmhint;
  637. XSizeHints shint;
  638. char *args[] = {NULL};
  639. int i;
  640. xw.dis = XOpenDisplay(NULL);
  641. xw.scr = XDefaultScreen(xw.dis);
  642. if(!xw.dis)
  643. die("can not open display");
  644. /* font */
  645. if(!(dc.font = XLoadQueryFont(xw.dis, FONT)))
  646. die("can not find font " FONT);
  647. xw.cw = dc.font->max_bounds.rbearing - dc.font->min_bounds.lbearing;
  648. xw.ch = dc.font->ascent + dc.font->descent + LINESPACE;
  649. /* colors */
  650. for(i = 0; i < LEN(colorname); i++)
  651. dc.col[i] = xgetcol(colorname[i]);
  652. term.c.attr.fg = DefaultFG;
  653. term.c.attr.bg = DefaultBG;
  654. term.c.attr.mode = ATnone;
  655. /* windows */
  656. xw.h = term.row * xw.ch;
  657. xw.w = term.col * xw.cw;
  658. /* XXX: this BORDER is useless after the first resize, handle it in xdraws() */
  659. xw.win = XCreateSimpleWindow(xw.dis, XRootWindow(xw.dis, xw.scr), 0, 0,
  660. xw.w, xw.h, BORDER,
  661. dc.col[DefaultBG],
  662. dc.col[DefaultBG]);
  663. /* gc */
  664. values.foreground = XWhitePixel(xw.dis, xw.scr);
  665. values.font = dc.font->fid;
  666. valuemask = GCForeground | GCFont;
  667. dc.gc = XCreateGC(xw.dis, xw.win, valuemask, &values);
  668. XMapWindow(xw.dis, xw.win);
  669. /* wm stuff */
  670. chint.res_name = TNAME, chint.res_class = TNAME;
  671. wmhint.input = 1, wmhint.flags = InputHint;
  672. shint.height_inc = xw.ch, shint.width_inc = xw.cw;
  673. shint.height = xw.h, shint.width = xw.w;
  674. shint.flags = PSize | PResizeInc;
  675. XSetWMProperties(xw.dis, xw.win, NULL, NULL, &args[0], 0, &shint, &wmhint, &chint);
  676. XStoreName(xw.dis, xw.win, TNAME);
  677. XSync(xw.dis, 0);
  678. }
  679. void
  680. xdrawc(int x, int y, Glyph g) {
  681. XRectangle r = { x * xw.cw, y * xw.ch, xw.cw, xw.ch };
  682. unsigned long xfg, xbg;
  683. /* reverse video */
  684. if(g.mode & ATreverse)
  685. xfg = dc.col[g.bg], xbg = dc.col[g.fg];
  686. else
  687. xfg = dc.col[g.fg], xbg = dc.col[g.bg];
  688. /* background */
  689. XSetForeground(xw.dis, dc.gc, xbg);
  690. XFillRectangles(xw.dis, xw.win, dc.gc, &r, 1);
  691. /* string */
  692. XSetForeground(xw.dis, dc.gc, xfg);
  693. XDrawString(xw.dis, xw.win, dc.gc, r.x, r.y+dc.font->ascent, &(g.c), 1);
  694. if(g.mode & ATbold) /* XXX: bold hack (draw again at x+1) */
  695. XDrawString(xw.dis, xw.win, dc.gc, r.x+1, r.y+dc.font->ascent, &(g.c), 1);
  696. /* underline */
  697. if(g.mode & ATunderline) {
  698. r.y += dc.font->ascent + 1;
  699. XDrawLine(xw.dis, xw.win, dc.gc, r.x, r.y, r.x+r.width-1, r.y);
  700. }
  701. }
  702. void
  703. xcursor(int mode) {
  704. static int oldx = 0;
  705. static int oldy = 0;
  706. Glyph g = {' ', ATnone, DefaultBG, DefaultCS, 0};
  707. LIMIT(oldx, 0, term.col-1);
  708. LIMIT(oldy, 0, term.row-1);
  709. if(term.line[term.c.y][term.c.x].state & CRset)
  710. g.c = term.line[term.c.y][term.c.x].c;
  711. /* remove the old cursor */
  712. if(term.line[oldy][oldx].state & CRset)
  713. xdrawc(oldx, oldy, term.line[oldy][oldx]);
  714. else xclear(oldx, oldy, oldx, oldy); /* XXX: maybe a bug */
  715. if(mode == CSdraw && !term.c.hidden) {
  716. xdrawc(term.c.x, term.c.y, g);
  717. oldx = term.c.x, oldy = term.c.y;
  718. }
  719. }
  720. void
  721. draw(int redraw_all) {
  722. int x, y;
  723. int changed, set;
  724. if(redraw_all)
  725. XClearWindow(xw.dis, xw.win);
  726. /* XXX: drawing could be optimised */
  727. for(y = 0; y < term.row; y++) {
  728. for(x = 0; x < term.col; x++) {
  729. changed = term.line[y][x].state & CRupdate;
  730. set = term.line[y][x].state & CRset;
  731. if((changed && set) || (redraw_all && set)) {
  732. term.line[y][x].state &= ~CRupdate;
  733. xdrawc(x, y, term.line[y][x]);
  734. }
  735. }
  736. }
  737. xcursor(CSdraw);
  738. }
  739. void
  740. kpress(XKeyEvent *e) {
  741. KeySym ksym;
  742. char buf[32];
  743. int len;
  744. int meta;
  745. int shift;
  746. meta = e->state & Mod1Mask;
  747. shift = e->state & ShiftMask;
  748. len = XLookupString(e, buf, sizeof(buf), &ksym, NULL);
  749. if(len > 0) {
  750. buf[sizeof(buf)-1] = '\0';
  751. if(meta && len == 1)
  752. ttywrite("\033", 1);
  753. ttywrite(buf, len);
  754. return;
  755. }
  756. switch(ksym) {
  757. default:
  758. fprintf(stderr, "errkey: %d\n", (int)ksym);
  759. break;
  760. case XK_Up:
  761. case XK_Down:
  762. case XK_Left:
  763. case XK_Right:
  764. sprintf(buf, "\033[%c", "DACB"[ksym - XK_Left]);
  765. ttywrite(buf, 3);
  766. break;
  767. case XK_Delete: ttywrite(KEYDELETE, sizeof(KEYDELETE)-1); break;
  768. case XK_Home: ttywrite(KEYHOME, sizeof(KEYHOME)-1); break;
  769. case XK_End: ttywrite(KEYEND, sizeof(KEYEND) -1); break;
  770. case XK_Prior: ttywrite(KEYPREV, sizeof(KEYPREV)-1); break;
  771. case XK_Next: ttywrite(KEYNEXT, sizeof(KEYNEXT)-1); break;
  772. case XK_Insert:
  773. /* XXX: paste X clipboard */
  774. if(shift)
  775. ;
  776. break;
  777. }
  778. }
  779. void
  780. resize(XEvent *e) {
  781. int col, row;
  782. col = e->xconfigure.width / xw.cw;
  783. row = e->xconfigure.height / xw.ch;
  784. if(term.col != col || term.row != row) {
  785. tresize(col, row);
  786. ttyresize(col, row);
  787. xw.w = e->xconfigure.width;
  788. xw.h = e->xconfigure.height;
  789. draw(SCredraw);
  790. }
  791. }
  792. void
  793. run(void) {
  794. int ret;
  795. XEvent ev;
  796. fd_set rfd;
  797. int xfd = XConnectionNumber(xw.dis);
  798. running = 1;
  799. XSelectInput(xw.dis, xw.win, ExposureMask | KeyPressMask | StructureNotifyMask);
  800. XResizeWindow(xw.dis, xw.win, xw.w , xw.h); /* seems to fix the resize bug in wmii */
  801. while(running) {
  802. FD_ZERO(&rfd);
  803. FD_SET(cmdfd, &rfd);
  804. FD_SET(xfd, &rfd);
  805. XFlush(xw.dis);
  806. ret = select(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, NULL);
  807. if(ret < 0) {
  808. fprintf(stderr, "select: %m\n");
  809. running = 0;
  810. }
  811. if(FD_ISSET(xfd, &rfd)) {
  812. while(XPending(xw.dis)) {
  813. XNextEvent(xw.dis, &ev);
  814. switch (ev.type) {
  815. default:
  816. break;
  817. case KeyPress:
  818. kpress(&ev.xkey);
  819. break;
  820. case Expose:
  821. draw(SCredraw);
  822. break;
  823. case ConfigureNotify:
  824. resize(&ev);
  825. break;
  826. }
  827. }
  828. }
  829. if(FD_ISSET(cmdfd, &rfd)) {
  830. ttyread();
  831. draw(SCupdate);
  832. }
  833. }
  834. }
  835. int
  836. main(int argc, char *argv[]) {
  837. if(argc == 2 && !strncmp("-v", argv[1], 3))
  838. die("st-" VERSION ", © 2009 st engineers\n");
  839. else if(argc != 1)
  840. die("usage: st [-v]\n");
  841. setlocale(LC_CTYPE, "");
  842. tnew(80, 24);
  843. ttynew();
  844. xinit();
  845. run();
  846. return 0;
  847. }