💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Menkar › files › 0ea79bb4baa421c2094ee24a509… captured on 2022-07-16 at 17:10:28. Gemini links have been rewritten to link to archived content

View Raw

More Information

➡️ Next capture (2023-01-29)

-=-=-=-=-=-=-

0 /* See LICENSE for license details. */

1 #include <errno.h>

2 #include <math.h>

3 #include <limits.h>

4 #include <locale.h>

5 #include <signal.h>

6 #include <sys/select.h>

7 #include <time.h>

8 #include <unistd.h>

9 #include <libgen.h>

10 #include <X11/Xatom.h>

11 #include <X11/Xlib.h>

12 #include <X11/cursorfont.h>

13 #include <X11/keysym.h>

14 #include <X11/Xft/Xft.h>

15 #include <X11/XKBlib.h>

16

17 char *argv0;

18 #include "arg.h"

19 #include "st.h"

20 #include "win.h"

21

22 /* types used in config.h */

23 typedef struct {

24 uint mod;

25 KeySym keysym;

26 void (*func)(const Arg *);

27 const Arg arg;

28 } Shortcut;

29

30 typedef struct {

31 uint mod;

32 uint button;

33 void (*func)(const Arg *);

34 const Arg arg;

35 uint release;

36 int altscrn;

37 } MouseShortcut;

38

39 typedef struct {

40 KeySym k;

41 uint mask;

42 char *s;

43 /* three-valued logic variables: 0 indifferent, 1 on, -1 off */

44 signed char appkey; /* application keypad */

45 signed char appcursor; /* application cursor */

46 } Key;

47

48 /* X modifiers */

49 #define XK_ANY_MOD UINT_MAX

50 #define XK_NO_MOD 0

51 #define XK_SWITCH_MOD (1<<13|1<<14)

52

53 /* function definitions used in config.h */

54 static void clipcopy(const Arg *);

55 static void clippaste(const Arg *);

56 static void numlock(const Arg *);

57 static void selpaste(const Arg *);

58 static void zoom(const Arg *);

59 static void zoomabs(const Arg *);

60 static void zoomreset(const Arg *);

61 static void ttysend(const Arg *);

62

63 /* config.h for applying patches and the configuration. */

64 #include "config.h"

65

66 /* XEMBED messages */

67 #define XEMBED_FOCUS_IN 4

68 #define XEMBED_FOCUS_OUT 5

69

70 /* macros */

71 #define IS_SET(flag) ((win.mode & (flag)) != 0)

72 #define TRUERED(x) (((x) & 0xff0000) >> 8)

73 #define TRUEGREEN(x) (((x) & 0xff00))

74 #define TRUEBLUE(x) (((x) & 0xff) << 8)

75

76 typedef XftDraw *Draw;

77 typedef XftColor Color;

78 typedef XftGlyphFontSpec GlyphFontSpec;

79

80 /* Purely graphic info */

81 typedef struct {

82 int tw, th; /* tty width and height */

83 int w, h; /* window width and height */

84 int ch; /* char height */

85 int cw; /* char width */

86 int mode; /* window state/mode flags */

87 int cursor; /* cursor style */

88 } TermWindow;

89

90 typedef struct {

91 Display *dpy;

92 Colormap cmap;

93 Window win;

94 Drawable buf;

95 GlyphFontSpec *specbuf; /* font spec buffer used for rendering */

96 Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;

97 struct {

98 XIM xim;

99 XIC xic;

100 XPoint spot;

101 XVaNestedList spotlist;

102 } ime;

103 Draw draw;

104 Visual *vis;

105 XSetWindowAttributes attrs;

106 int scr;

107 int isfixed; /* is fixed geometry? */

108 int l, t; /* left and top offset */

109 int gm; /* geometry mask */

110 } XWindow;

111

112 typedef struct {

113 Atom xtarget;

114 char *primary, *clipboard;

115 struct timespec tclick1;

116 struct timespec tclick2;

117 } XSelection;

118

119 /* Font structure */

120 #define Font Font_

121 typedef struct {

122 int height;

123 int width;

124 int ascent;

125 int descent;

126 int badslant;

127 int badweight;

128 short lbearing;

129 short rbearing;

130 XftFont *match;

131 FcFontSet *set;

132 FcPattern *pattern;

133 } Font;

134

135 /* Drawing Context */

136 typedef struct {

137 Color *col;

138 size_t collen;

139 Font font, bfont, ifont, ibfont;

140 GC gc;

141 } DC;

142

143 static inline ushort sixd_to_16bit(int);

144 static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int);

145 static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int);

146 static void xdrawglyph(Glyph, int, int);

147 static void xclear(int, int, int, int);

148 static int xgeommasktogravity(int);

149 static int ximopen(Display *);

150 static void ximinstantiate(Display *, XPointer, XPointer);

151 static void ximdestroy(XIM, XPointer, XPointer);

152 static int xicdestroy(XIC, XPointer, XPointer);

153 static void xinit(int, int);

154 static void cresize(int, int);

155 static void xresize(int, int);

156 static void xhints(void);

157 static int xloadcolor(int, const char *, Color *);

158 static int xloadfont(Font *, FcPattern *);

159 static void xloadfonts(const char *, double);

160 static void xunloadfont(Font *);

161 static void xunloadfonts(void);

162 static void xsetenv(void);

163 static void xseturgency(int);

164 static int evcol(XEvent *);

165 static int evrow(XEvent *);

166

167 static void expose(XEvent *);

168 static void visibility(XEvent *);

169 static void unmap(XEvent *);

170 static void kpress(XEvent *);

171 static void cmessage(XEvent *);

172 static void resize(XEvent *);

173 static void focus(XEvent *);

174 static uint buttonmask(uint);

175 static int mouseaction(XEvent *, uint);

176 static void brelease(XEvent *);

177 static void bpress(XEvent *);

178 static void bmotion(XEvent *);

179 static void propnotify(XEvent *);

180 static void selnotify(XEvent *);

181 static void selclear_(XEvent *);

182 static void selrequest(XEvent *);

183 static void setsel(char *, Time);

184 static void mousesel(XEvent *, int);

185 static void mousereport(XEvent *);

186 static char *kmap(KeySym, uint);

187 static int match(uint, uint);

188

189 static void run(void);

190 static void usage(void);

191

192 static void (*handler[LASTEvent])(XEvent *) = {

193 [KeyPress] = kpress,

194 [ClientMessage] = cmessage,

195 [ConfigureNotify] = resize,

196 [VisibilityNotify] = visibility,

197 [UnmapNotify] = unmap,

198 [Expose] = expose,

199 [FocusIn] = focus,

200 [FocusOut] = focus,

201 [MotionNotify] = bmotion,

202 [ButtonPress] = bpress,

203 [ButtonRelease] = brelease,

204 /*

205 * Uncomment if you want the selection to disappear when you select something

206 * different in another window.

207 */

208 /* [SelectionClear] = selclear_, */

209 [SelectionNotify] = selnotify,

210 /*

211 * PropertyNotify is only turned on when there is some INCR transfer happening

212 * for the selection retrieval.

213 */

214 [PropertyNotify] = propnotify,

215 [SelectionRequest] = selrequest,

216 };

217

218 /* Globals */

219 static DC dc;

220 static XWindow xw;

221 static XSelection xsel;

222 static TermWindow win;

223

224 /* Font Ring Cache */

225 enum {

226 FRC_NORMAL,

227 FRC_ITALIC,

228 FRC_BOLD,

229 FRC_ITALICBOLD

230 };

231

232 typedef struct {

233 XftFont *font;

234 int flags;

235 Rune unicodep;

236 } Fontcache;

237

238 /* Fontcache is an array now. A new font will be appended to the array. */

239 static Fontcache *frc = NULL;

240 static int frclen = 0;

241 static int frccap = 0;

242 static char *usedfont = NULL;

243 static double usedfontsize = 0;

244 static double defaultfontsize = 0;

245

246 static char *opt_class = NULL;

247 static char **opt_cmd = NULL;

248 static char *opt_embed = NULL;

249 static char *opt_font = NULL;

250 static char *opt_io = NULL;

251 static char *opt_line = NULL;

252 static char *opt_name = NULL;

253 static char *opt_title = NULL;

254

255 static int oldbutton = 3; /* button event on startup: 3 = release */

256

257 void

258 clipcopy(const Arg *dummy)

259 {

260 Atom clipboard;

261

262 free(xsel.clipboard);

263 xsel.clipboard = NULL;

264

265 if (xsel.primary != NULL) {

266 xsel.clipboard = xstrdup(xsel.primary);

267 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);

268 XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime);

269 }

270 }

271

272 void

273 clippaste(const Arg *dummy)

274 {

275 Atom clipboard;

276

277 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);

278 XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard,

279 xw.win, CurrentTime);

280 }

281

282 void

283 selpaste(const Arg *dummy)

284 {

285 XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,

286 xw.win, CurrentTime);

287 }

288

289 void

290 numlock(const Arg *dummy)

291 {

292 win.mode ^= MODE_NUMLOCK;

293 }

294

295 void

296 zoom(const Arg *arg)

297 {

298 Arg larg;

299

300 larg.f = usedfontsize + arg->f;

301 zoomabs(&larg);

302 }

303

304 void

305 zoomabs(const Arg *arg)

306 {

307 xunloadfonts();

308 xloadfonts(usedfont, arg->f);

309 cresize(0, 0);

310 redraw();

311 xhints();

312 }

313

314 void

315 zoomreset(const Arg *arg)

316 {

317 Arg larg;

318

319 if (defaultfontsize > 0) {

320 larg.f = defaultfontsize;

321 zoomabs(&larg);

322 }

323 }

324

325 void

326 ttysend(const Arg *arg)

327 {

328 ttywrite(arg->s, strlen(arg->s), 1);

329 }

330

331 int

332 evcol(XEvent *e)

333 {

334 int x = e->xbutton.x - borderpx;

335 LIMIT(x, 0, win.tw - 1);

336 return x / win.cw;

337 }

338

339 int

340 evrow(XEvent *e)

341 {

342 int y = e->xbutton.y - borderpx;

343 LIMIT(y, 0, win.th - 1);

344 return y / win.ch;

345 }

346

347 void

348 mousesel(XEvent *e, int done)

349 {

350 int type, seltype = SEL_REGULAR;

351 uint state = e->xbutton.state & ~(Button1Mask | forcemousemod);

352

353 for (type = 1; type < LEN(selmasks); ++type) {

354 if (match(selmasks[type], state)) {

355 seltype = type;

356 break;

357 }

358 }

359 selextend(evcol(e), evrow(e), seltype, done);

360 if (done)

361 setsel(getsel(), e->xbutton.time);

362 }

363

364 void

365 mousereport(XEvent *e)

366 {

367 int len, x = evcol(e), y = evrow(e),

368 button = e->xbutton.button, state = e->xbutton.state;

369 char buf[40];

370 static int ox, oy;

371

372 /* from urxvt */

373 if (e->xbutton.type == MotionNotify) {

374 if (x == ox && y == oy)

375 return;

376 if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY))

377 return;

378 /* MOUSE_MOTION: no reporting if no button is pressed */

379 if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3)

380 return;

381

382 button = oldbutton + 32;

383 ox = x;

384 oy = y;

385 } else {

386 if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) {

387 button = 3;

388 } else {

389 button -= Button1;

390 if (button >= 7)

391 button += 128 - 7;

392 else if (button >= 3)

393 button += 64 - 3;

394 }

395 if (e->xbutton.type == ButtonPress) {

396 oldbutton = button;

397 ox = x;

398 oy = y;

399 } else if (e->xbutton.type == ButtonRelease) {

400 oldbutton = 3;

401 /* MODE_MOUSEX10: no button release reporting */

402 if (IS_SET(MODE_MOUSEX10))

403 return;

404 if (button == 64 || button == 65)

405 return;

406 }

407 }

408

409 if (!IS_SET(MODE_MOUSEX10)) {

410 button += ((state & ShiftMask ) ? 4 : 0)

411 + ((state & Mod4Mask ) ? 8 : 0)

412 + ((state & ControlMask) ? 16 : 0);

413 }

414

415 if (IS_SET(MODE_MOUSESGR)) {

416 len = snprintf(buf, sizeof(buf), "\033[<%!d(MISSING);%!d(MISSING);%!d(MISSING)%!c(MISSING)",

417 button, x+1, y+1,

418 e->xbutton.type == ButtonRelease ? 'm' : 'M');

419 } else if (x < 223 && y < 223) {

420 len = snprintf(buf, sizeof(buf), "\033[M%!c(MISSING)%!c(MISSING)%!c(MISSING)",

421 32+button, 32+x+1, 32+y+1);

422 } else {

423 return;

424 }

425

426 ttywrite(buf, len, 0);

427 }

428

429 uint

430 buttonmask(uint button)

431 {

432 return button == Button1 ? Button1Mask

433 : button == Button2 ? Button2Mask

434 : button == Button3 ? Button3Mask

435 : button == Button4 ? Button4Mask

436 : button == Button5 ? Button5Mask

437 : 0;

438 }

439

440 int

441 mouseaction(XEvent *e, uint release)

442 {

443 MouseShortcut *ms;

444

445 /* ignore Button<N>mask for Button<N> - it's set on release */

446 uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);

447

448 for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {

449 if (ms->release == release &&

450 ms->button == e->xbutton.button &&

451 (!ms->altscrn || (ms->altscrn == (tisaltscr() ? 1 : -1))) &&

452 (match(ms->mod, state) || /* exact or forced */

453 match(ms->mod, state & ~forcemousemod))) {

454 ms->func(&(ms->arg));

455 return 1;

456 }

457 }

458

459 return 0;

460 }

461

462 void

463 bpress(XEvent *e)

464 {

465 struct timespec now;

466 int snap;

467

468 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {

469 mousereport(e);

470 return;

471 }

472

473 if (mouseaction(e, 0))

474 return;

475

476 if (e->xbutton.button == Button1) {

477 /*

478 * If the user clicks below predefined timeouts specific

479 * snapping behaviour is exposed.

480 */

481 clock_gettime(CLOCK_MONOTONIC, &now);

482 if (TIMEDIFF(now, xsel.tclick2) <= tripleclicktimeout) {

483 snap = SNAP_LINE;

484 } else if (TIMEDIFF(now, xsel.tclick1) <= doubleclicktimeout) {

485 snap = SNAP_WORD;

486 } else {

487 snap = 0;

488 }

489 xsel.tclick2 = xsel.tclick1;

490 xsel.tclick1 = now;

491

492 selstart(evcol(e), evrow(e), snap);

493 }

494 }

495

496 void

497 propnotify(XEvent *e)

498 {

499 XPropertyEvent *xpev;

500 Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);

501

502 xpev = &e->xproperty;

503 if (xpev->state == PropertyNewValue &&

504 (xpev->atom == XA_PRIMARY ||

505 xpev->atom == clipboard)) {

506 selnotify(e);

507 }

508 }

509

510 void

511 selnotify(XEvent *e)

512 {

513 ulong nitems, ofs, rem;

514 int format;

515 uchar *data, *last, *repl;

516 Atom type, incratom, property = None;

517

518 incratom = XInternAtom(xw.dpy, "INCR", 0);

519

520 ofs = 0;

521 if (e->type == SelectionNotify)

522 property = e->xselection.property;

523 else if (e->type == PropertyNotify)

524 property = e->xproperty.atom;

525

526 if (property == None)

527 return;

528

529 do {

530 if (XGetWindowProperty(xw.dpy, xw.win, property, ofs,

531 BUFSIZ/4, False, AnyPropertyType,

532 &type, &format, &nitems, &rem,

533 &data)) {

534 fprintf(stderr, "Clipboard allocation failed\n");

535 return;

536 }

537

538 if (e->type == PropertyNotify && nitems == 0 && rem == 0) {

539 /*

540 * If there is some PropertyNotify with no data, then

541 * this is the signal of the selection owner that all

542 * data has been transferred. We won't need to receive

543 * PropertyNotify events anymore.

544 */

545 MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask);

546 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,

547 &xw.attrs);

548 }

549

550 if (type == incratom) {

551 /*

552 * Activate the PropertyNotify events so we receive

553 * when the selection owner does send us the next

554 * chunk of data.

555 */

556 MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask);

557 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask,

558 &xw.attrs);

559

560 /*

561 * Deleting the property is the transfer start signal.

562 */

563 XDeleteProperty(xw.dpy, xw.win, (int)property);

564 continue;

565 }

566

567 /*

568 * As seen in getsel:

569 * Line endings are inconsistent in the terminal and GUI world

570 * copy and pasting. When receiving some selection data,

571 * replace all '\n' with '\r'.

572 * FIXME: Fix the computer world.

573 */

574 repl = data;

575 last = data + nitems * format / 8;

576 while ((repl = memchr(repl, '\n', last - repl))) {

577 *repl++ = '\r';

578 }

579

580 if (IS_SET(MODE_BRCKTPASTE) && ofs == 0)

581 ttywrite("\033[200~", 6, 0);

582 ttywrite((char *)data, nitems * format / 8, 1);

583 if (IS_SET(MODE_BRCKTPASTE) && rem == 0)

584 ttywrite("\033[201~", 6, 0);

585 XFree(data);

586 /* number of 32-bit chunks returned */

587 ofs += nitems * format / 32;

588 } while (rem > 0);

589

590 /*

591 * Deleting the property again tells the selection owner to send the

592 * next data chunk in the property.

593 */

594 XDeleteProperty(xw.dpy, xw.win, (int)property);

595 }

596

597 void

598 xclipcopy(void)

599 {

600 clipcopy(NULL);

601 }

602

603 void

604 selclear_(XEvent *e)

605 {

606 selclear();

607 }

608

609 void

610 selrequest(XEvent *e)

611 {

612 XSelectionRequestEvent *xsre;

613 XSelectionEvent xev;

614 Atom xa_targets, string, clipboard;

615 char *seltext;

616

617 xsre = (XSelectionRequestEvent *) e;

618 xev.type = SelectionNotify;

619 xev.requestor = xsre->requestor;

620 xev.selection = xsre->selection;

621 xev.target = xsre->target;

622 xev.time = xsre->time;

623 if (xsre->property == None)

624 xsre->property = xsre->target;

625

626 /* reject */

627 xev.property = None;

628

629 xa_targets = XInternAtom(xw.dpy, "TARGETS", 0);

630 if (xsre->target == xa_targets) {

631 /* respond with the supported type */

632 string = xsel.xtarget;

633 XChangeProperty(xsre->display, xsre->requestor, xsre->property,

634 XA_ATOM, 32, PropModeReplace,

635 (uchar *) &string, 1);

636 xev.property = xsre->property;

637 } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) {

638 /*

639 * xith XA_STRING non ascii characters may be incorrect in the

640 * requestor. It is not our problem, use utf8.

641 */

642 clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0);

643 if (xsre->selection == XA_PRIMARY) {

644 seltext = xsel.primary;

645 } else if (xsre->selection == clipboard) {

646 seltext = xsel.clipboard;

647 } else {

648 fprintf(stderr,

649 "Unhandled clipboard selection 0x%!l(MISSING)x\n",

650 xsre->selection);

651 return;

652 }

653 if (seltext != NULL) {

654 XChangeProperty(xsre->display, xsre->requestor,

655 xsre->property, xsre->target,

656 8, PropModeReplace,

657 (uchar *)seltext, strlen(seltext));

658 xev.property = xsre->property;

659 }

660 }

661

662 /* all done, send a notification to the listener */

663 if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev))

664 fprintf(stderr, "Error sending SelectionNotify event\n");

665 }

666

667 void

668 setsel(char *str, Time t)

669 {

670 if (!str)

671 return;

672

673 free(xsel.primary);

674 xsel.primary = str;

675

676 XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t);

677 if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win)

678 selclear();

679 }

680

681 void

682 xsetsel(char *str)

683 {

684 setsel(str, CurrentTime);

685 }

686

687 void

688 brelease(XEvent *e)

689 {

690 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {

691 mousereport(e);

692 return;

693 }

694

695 if (mouseaction(e, 1))

696 return;

697 if (e->xbutton.button == Button1)

698 mousesel(e, 1);

699 }

700

701 void

702 bmotion(XEvent *e)

703 {

704 if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forcemousemod)) {

705 mousereport(e);

706 return;

707 }

708

709 mousesel(e, 0);

710 }

711

712 void

713 cresize(int width, int height)

714 {

715 int col, row;

716

717 if (width != 0)

718 win.w = width;

719 if (height != 0)

720 win.h = height;

721

722 col = (win.w - 2 * borderpx) / win.cw;

723 row = (win.h - 2 * borderpx) / win.ch;

724 col = MAX(1, col);

725 row = MAX(1, row);

726

727 tresize(col, row);

728 xresize(col, row);

729 ttyresize(win.tw, win.th);

730 }

731

732 void

733 xresize(int col, int row)

734 {

735 win.tw = col * win.cw;

736 win.th = row * win.ch;

737

738 XFreePixmap(xw.dpy, xw.buf);

739 xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,

740 DefaultDepth(xw.dpy, xw.scr));

741 XftDrawChange(xw.draw, xw.buf);

742 xclear(0, 0, win.w, win.h);

743

744 /* resize to new width */

745 xw.specbuf = xrealloc(xw.specbuf, col * sizeof(GlyphFontSpec));

746 }

747

748 ushort

749 sixd_to_16bit(int x)

750 {

751 return x == 0 ? 0 : 0x3737 + 0x2828 * x;

752 }

753

754 int

755 xloadcolor(int i, const char *name, Color *ncolor)

756 {

757 XRenderColor color = { .alpha = 0xffff };

758

759 if (!name) {

760 if (BETWEEN(i, 16, 255)) { /* 256 color */

761 if (i < 6*6*6+16) { /* same colors as xterm */

762 color.red = sixd_to_16bit( ((i-16)/36)%! (MISSING));

763 color.green = sixd_to_16bit( ((i-16)/6) %! (MISSING));

764 color.blue = sixd_to_16bit( ((i-16)/1) %! (MISSING));

765 } else { /* greyscale */

766 color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16));

767 color.green = color.blue = color.red;

768 }

769 return XftColorAllocValue(xw.dpy, xw.vis,

770 xw.cmap, &color, ncolor);

771 } else

772 name = colorname[i];

773 }

774

775 return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor);

776 }

777

778 void

779 xloadcols(void)

780 {

781 int i;

782 static int loaded;

783 Color *cp;

784

785 if (loaded) {

786 for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp)

787 XftColorFree(xw.dpy, xw.vis, xw.cmap, cp);

788 } else {

789 dc.collen = MAX(LEN(colorname), 256);

790 dc.col = xmalloc(dc.collen * sizeof(Color));

791 }

792

793 for (i = 0; i < dc.collen; i++)

794 if (!xloadcolor(i, NULL, &dc.col[i])) {

795 if (colorname[i])

796 die("could not allocate color '%!s(MISSING)'\n", colorname[i]);

797 else

798 die("could not allocate color %!d(MISSING)\n", i);

799 }

800 loaded = 1;

801 }

802

803 int

804 xsetcolorname(int x, const char *name)

805 {

806 Color ncolor;

807

808 if (!BETWEEN(x, 0, dc.collen))

809 return 1;

810

811 if (!xloadcolor(x, name, &ncolor))

812 return 1;

813

814 XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]);

815 dc.col[x] = ncolor;

816

817 return 0;

818 }

819

820 /*

821 * Absolute coordinates.

822 */

823 void

824 xclear(int x1, int y1, int x2, int y2)

825 {

826 XftDrawRect(xw.draw,

827 &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg],

828 x1, y1, x2-x1, y2-y1);

829 }

830

831 void

832 xhints(void)

833 {

834 XClassHint class = {opt_name ? opt_name : termname,

835 opt_class ? opt_class : termname};

836 XWMHints wm = {.flags = InputHint, .input = 1};

837 XSizeHints *sizeh;

838

839 sizeh = XAllocSizeHints();

840

841 sizeh->flags = PSize | PResizeInc | PBaseSize | PMinSize;

842 sizeh->height = win.h;

843 sizeh->width = win.w;

844 sizeh->height_inc = win.ch;

845 sizeh->width_inc = win.cw;

846 sizeh->base_height = 2 * borderpx;

847 sizeh->base_width = 2 * borderpx;

848 sizeh->min_height = win.ch + 2 * borderpx;

849 sizeh->min_width = win.cw + 2 * borderpx;

850 if (xw.isfixed) {

851 sizeh->flags |= PMaxSize;

852 sizeh->min_width = sizeh->max_width = win.w;

853 sizeh->min_height = sizeh->max_height = win.h;

854 }

855 if (xw.gm & (XValue|YValue)) {

856 sizeh->flags |= USPosition | PWinGravity;

857 sizeh->x = xw.l;

858 sizeh->y = xw.t;

859 sizeh->win_gravity = xgeommasktogravity(xw.gm);

860 }

861

862 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm,

863 &class);

864 XFree(sizeh);

865 }

866

867 int

868 xgeommasktogravity(int mask)

869 {

870 switch (mask & (XNegative|YNegative)) {

871 case 0:

872 return NorthWestGravity;

873 case XNegative:

874 return NorthEastGravity;

875 case YNegative:

876 return SouthWestGravity;

877 }

878

879 return SouthEastGravity;

880 }

881

882 int

883 xloadfont(Font *f, FcPattern *pattern)

884 {

885 FcPattern *configured;

886 FcPattern *match;

887 FcResult result;

888 XGlyphInfo extents;

889 int wantattr, haveattr;

890

891 /*

892 * Manually configure instead of calling XftMatchFont

893 * so that we can use the configured pattern for

894 * "missing glyph" lookups.

895 */

896 configured = FcPatternDuplicate(pattern);

897 if (!configured)

898 return 1;

899

900 FcConfigSubstitute(NULL, configured, FcMatchPattern);

901 XftDefaultSubstitute(xw.dpy, xw.scr, configured);

902

903 match = FcFontMatch(NULL, configured, &result);

904 if (!match) {

905 FcPatternDestroy(configured);

906 return 1;

907 }

908

909 if (!(f->match = XftFontOpenPattern(xw.dpy, match))) {

910 FcPatternDestroy(configured);

911 FcPatternDestroy(match);

912 return 1;

913 }

914

915 if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) ==

916 XftResultMatch)) {

917 /*

918 * Check if xft was unable to find a font with the appropriate

919 * slant but gave us one anyway. Try to mitigate.

920 */

921 if ((XftPatternGetInteger(f->match->pattern, "slant", 0,

922 &haveattr) != XftResultMatch) || haveattr < wantattr) {

923 f->badslant = 1;

924 fputs("font slant does not match\n", stderr);

925 }

926 }

927

928 if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) ==

929 XftResultMatch)) {

930 if ((XftPatternGetInteger(f->match->pattern, "weight", 0,

931 &haveattr) != XftResultMatch) || haveattr != wantattr) {

932 f->badweight = 1;

933 fputs("font weight does not match\n", stderr);

934 }

935 }

936

937 XftTextExtentsUtf8(xw.dpy, f->match,

938 (const FcChar8 *) ascii_printable,

939 strlen(ascii_printable), &extents);

940

941 f->set = NULL;

942 f->pattern = configured;

943

944 f->ascent = f->match->ascent;

945 f->descent = f->match->descent;

946 f->lbearing = 0;

947 f->rbearing = f->match->max_advance_width;

948

949 f->height = f->ascent + f->descent;

950 f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));

951

952 return 0;

953 }

954

955 void

956 xloadfonts(const char *fontstr, double fontsize)

957 {

958 FcPattern *pattern;

959 double fontval;

960

961 if (fontstr[0] == '-')

962 pattern = XftXlfdParse(fontstr, False, False);

963 else

964 pattern = FcNameParse((const FcChar8 *)fontstr);

965

966 if (!pattern)

967 die("can't open font %!s(MISSING)\n", fontstr);

968

969 if (fontsize > 1) {

970 FcPatternDel(pattern, FC_PIXEL_SIZE);

971 FcPatternDel(pattern, FC_SIZE);

972 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize);

973 usedfontsize = fontsize;

974 } else {

975 if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) ==

976 FcResultMatch) {

977 usedfontsize = fontval;

978 } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) ==

979 FcResultMatch) {

980 usedfontsize = -1;

981 } else {

982 /*

983 * Default font size is 12, if none given. This is to

984 * have a known usedfontsize value.

985 */

986 FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12);

987 usedfontsize = 12;

988 }

989 defaultfontsize = usedfontsize;

990 }

991

992 if (xloadfont(&dc.font, pattern))

993 die("can't open font %!s(MISSING)\n", fontstr);

994

995 if (usedfontsize < 0) {

996 FcPatternGetDouble(dc.font.match->pattern,

997 FC_PIXEL_SIZE, 0, &fontval);

998 usedfontsize = fontval;

999 if (fontsize == 0)

1000 defaultfontsize = fontval;

1001 }

1002

1003 /* Setting character width and height. */

1004 win.cw = ceilf(dc.font.width * cwscale);

1005 win.ch = ceilf(dc.font.height * chscale);

1006

1007 FcPatternDel(pattern, FC_SLANT);

1008 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC);

1009 if (xloadfont(&dc.ifont, pattern))

1010 die("can't open font %!s(MISSING)\n", fontstr);

1011

1012 FcPatternDel(pattern, FC_WEIGHT);

1013 FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD);

1014 if (xloadfont(&dc.ibfont, pattern))

1015 die("can't open font %!s(MISSING)\n", fontstr);

1016

1017 FcPatternDel(pattern, FC_SLANT);

1018 FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN);

1019 if (xloadfont(&dc.bfont, pattern))

1020 die("can't open font %!s(MISSING)\n", fontstr);

1021

1022 FcPatternDestroy(pattern);

1023 }

1024

1025 void

1026 xunloadfont(Font *f)

1027 {

1028 XftFontClose(xw.dpy, f->match);

1029 FcPatternDestroy(f->pattern);

1030 if (f->set)

1031 FcFontSetDestroy(f->set);

1032 }

1033

1034 void

1035 xunloadfonts(void)

1036 {

1037 /* Free the loaded fonts in the font cache. */

1038 while (frclen > 0)

1039 XftFontClose(xw.dpy, frc[--frclen].font);

1040

1041 xunloadfont(&dc.font);

1042 xunloadfont(&dc.bfont);

1043 xunloadfont(&dc.ifont);

1044 xunloadfont(&dc.ibfont);

1045 }

1046

1047 int

1048 ximopen(Display *dpy)

1049 {

1050 XIMCallback imdestroy = { .client_data = NULL, .callback = ximdestroy };

1051 XICCallback icdestroy = { .client_data = NULL, .callback = xicdestroy };

1052

1053 xw.ime.xim = XOpenIM(xw.dpy, NULL, NULL, NULL);

1054 if (xw.ime.xim == NULL)

1055 return 0;

1056

1057 if (XSetIMValues(xw.ime.xim, XNDestroyCallback, &imdestroy, NULL))

1058 fprintf(stderr, "XSetIMValues: "

1059 "Could not set XNDestroyCallback.\n");

1060

1061 xw.ime.spotlist = XVaCreateNestedList(0, XNSpotLocation, &xw.ime.spot,

1062 NULL);

1063

1064 if (xw.ime.xic == NULL) {

1065 xw.ime.xic = XCreateIC(xw.ime.xim, XNInputStyle,

1066 XIMPreeditNothing | XIMStatusNothing,

1067 XNClientWindow, xw.win,

1068 XNDestroyCallback, &icdestroy,

1069 NULL);

1070 }

1071 if (xw.ime.xic == NULL)

1072 fprintf(stderr, "XCreateIC: Could not create input context.\n");

1073

1074 return 1;

1075 }

1076

1077 void

1078 ximinstantiate(Display *dpy, XPointer client, XPointer call)

1079 {

1080 if (ximopen(dpy))

1081 XUnregisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,

1082 ximinstantiate, NULL);

1083 }

1084

1085 void

1086 ximdestroy(XIM xim, XPointer client, XPointer call)

1087 {

1088 xw.ime.xim = NULL;

1089 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,

1090 ximinstantiate, NULL);

1091 XFree(xw.ime.spotlist);

1092 }

1093

1094 int

1095 xicdestroy(XIC xim, XPointer client, XPointer call)

1096 {

1097 xw.ime.xic = NULL;

1098 return 1;

1099 }

1100

1101 void

1102 xinit(int cols, int rows)

1103 {

1104 XGCValues gcvalues;

1105 Cursor cursor;

1106 Window parent;

1107 pid_t thispid = getpid();

1108 XColor xmousefg, xmousebg;

1109

1110 if (!(xw.dpy = XOpenDisplay(NULL)))

1111 die("can't open display\n");

1112 xw.scr = XDefaultScreen(xw.dpy);

1113 xw.vis = XDefaultVisual(xw.dpy, xw.scr);

1114

1115 /* font */

1116 if (!FcInit())

1117 die("could not init fontconfig.\n");

1118

1119 usedfont = (opt_font == NULL)? font : opt_font;

1120 xloadfonts(usedfont, 0);

1121

1122 /* colors */

1123 xw.cmap = XDefaultColormap(xw.dpy, xw.scr);

1124 xloadcols();

1125

1126 /* adjust fixed window geometry */

1127 win.w = 2 * borderpx + cols * win.cw;

1128 win.h = 2 * borderpx + rows * win.ch;

1129 if (xw.gm & XNegative)

1130 xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2;

1131 if (xw.gm & YNegative)

1132 xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2;

1133

1134 /* Events */

1135 xw.attrs.background_pixel = dc.col[defaultbg].pixel;

1136 xw.attrs.border_pixel = dc.col[defaultbg].pixel;

1137 xw.attrs.bit_gravity = NorthWestGravity;

1138 xw.attrs.event_mask = FocusChangeMask | KeyPressMask | KeyReleaseMask

1139 | ExposureMask | VisibilityChangeMask | StructureNotifyMask

1140 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;

1141 xw.attrs.colormap = xw.cmap;

1142

1143 if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0))))

1144 parent = XRootWindow(xw.dpy, xw.scr);

1145 xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t,

1146 win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput,

1147 xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity

1148 | CWEventMask | CWColormap, &xw.attrs);

1149

1150 memset(&gcvalues, 0, sizeof(gcvalues));

1151 gcvalues.graphics_exposures = False;

1152 dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures,

1153 &gcvalues);

1154 xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h,

1155 DefaultDepth(xw.dpy, xw.scr));

1156 XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel);

1157 XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h);

1158

1159 /* font spec buffer */

1160 xw.specbuf = xmalloc(cols * sizeof(GlyphFontSpec));

1161

1162 /* Xft rendering context */

1163 xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap);

1164

1165 /* input methods */

1166 if (!ximopen(xw.dpy)) {

1167 XRegisterIMInstantiateCallback(xw.dpy, NULL, NULL, NULL,

1168 ximinstantiate, NULL);

1169 }

1170

1171 /* white cursor, black outline */

1172 cursor = XCreateFontCursor(xw.dpy, mouseshape);

1173 XDefineCursor(xw.dpy, xw.win, cursor);

1174

1175 if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) {

1176 xmousefg.red = 0xffff;

1177 xmousefg.green = 0xffff;

1178 xmousefg.blue = 0xffff;

1179 }

1180

1181 if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) {

1182 xmousebg.red = 0x0000;

1183 xmousebg.green = 0x0000;

1184 xmousebg.blue = 0x0000;

1185 }

1186

1187 XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg);

1188

1189 xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);

1190 xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);

1191 xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);

1192 xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);

1193 XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);

1194

1195 xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);

1196 XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32,

1197 PropModeReplace, (uchar *)&thispid, 1);

1198

1199 win.mode = MODE_NUMLOCK;

1200 resettitle();

1201 xhints();

1202 XMapWindow(xw.dpy, xw.win);

1203 XSync(xw.dpy, False);

1204

1205 clock_gettime(CLOCK_MONOTONIC, &xsel.tclick1);

1206 clock_gettime(CLOCK_MONOTONIC, &xsel.tclick2);

1207 xsel.primary = NULL;

1208 xsel.clipboard = NULL;

1209 xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0);

1210 if (xsel.xtarget == None)

1211 xsel.xtarget = XA_STRING;

1212 }

1213

1214 int

1215 xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y)

1216 {

1217 float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp;

1218 ushort mode, prevmode = USHRT_MAX;

1219 Font *font = &dc.font;

1220 int frcflags = FRC_NORMAL;

1221 float runewidth = win.cw;

1222 Rune rune;

1223 FT_UInt glyphidx;

1224 FcResult fcres;

1225 FcPattern *fcpattern, *fontpattern;

1226 FcFontSet *fcsets[] = { NULL };

1227 FcCharSet *fccharset;

1228 int i, f, numspecs = 0;

1229

1230 for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) {

1231 /* Fetch rune and mode for current glyph. */

1232 rune = glyphs[i].u;

1233 mode = glyphs[i].mode;

1234

1235 /* Skip dummy wide-character spacing. */

1236 if (mode == ATTR_WDUMMY)

1237 continue;

1238

1239 /* Determine font for glyph if different from previous glyph. */

1240 if (prevmode != mode) {

1241 prevmode = mode;

1242 font = &dc.font;

1243 frcflags = FRC_NORMAL;

1244 runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f);

1245 if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) {

1246 font = &dc.ibfont;

1247 frcflags = FRC_ITALICBOLD;

1248 } else if (mode & ATTR_ITALIC) {

1249 font = &dc.ifont;

1250 frcflags = FRC_ITALIC;

1251 } else if (mode & ATTR_BOLD) {

1252 font = &dc.bfont;

1253 frcflags = FRC_BOLD;

1254 }

1255 yp = winy + font->ascent;

1256 }

1257

1258 /* Lookup character index with default font. */

1259 glyphidx = XftCharIndex(xw.dpy, font->match, rune);

1260 if (glyphidx) {

1261 specs[numspecs].font = font->match;

1262 specs[numspecs].glyph = glyphidx;

1263 specs[numspecs].x = (short)xp;

1264 specs[numspecs].y = (short)yp;

1265 xp += runewidth;

1266 numspecs++;

1267 continue;

1268 }

1269

1270 /* Fallback on font cache, search the font cache for match. */

1271 for (f = 0; f < frclen; f++) {

1272 glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune);

1273 /* Everything correct. */

1274 if (glyphidx && frc[f].flags == frcflags)

1275 break;

1276 /* We got a default font for a not found glyph. */

1277 if (!glyphidx && frc[f].flags == frcflags

1278 && frc[f].unicodep == rune) {

1279 break;

1280 }

1281 }

1282

1283 /* Nothing was found. Use fontconfig to find matching font. */

1284 if (f >= frclen) {

1285 if (!font->set)

1286 font->set = FcFontSort(0, font->pattern,

1287 1, 0, &fcres);

1288 fcsets[0] = font->set;

1289

1290 /*

1291 * Nothing was found in the cache. Now use

1292 * some dozen of Fontconfig calls to get the

1293 * font for one single character.

1294 *

1295 * Xft and fontconfig are design failures.

1296 */

1297 fcpattern = FcPatternDuplicate(font->pattern);

1298 fccharset = FcCharSetCreate();

1299

1300 FcCharSetAddChar(fccharset, rune);

1301 FcPatternAddCharSet(fcpattern, FC_CHARSET,

1302 fccharset);

1303 FcPatternAddBool(fcpattern, FC_SCALABLE, 1);

1304

1305 FcConfigSubstitute(0, fcpattern,

1306 FcMatchPattern);

1307 FcDefaultSubstitute(fcpattern);

1308

1309 fontpattern = FcFontSetMatch(0, fcsets, 1,

1310 fcpattern, &fcres);

1311

1312 /* Allocate memory for the new cache entry. */

1313 if (frclen >= frccap) {

1314 frccap += 16;

1315 frc = xrealloc(frc, frccap * sizeof(Fontcache));

1316 }

1317

1318 frc[frclen].font = XftFontOpenPattern(xw.dpy,

1319 fontpattern);

1320 if (!frc[frclen].font)

1321 die("XftFontOpenPattern failed seeking fallback font: %!s(MISSING)\n",

1322 strerror(errno));

1323 frc[frclen].flags = frcflags;

1324 frc[frclen].unicodep = rune;

1325

1326 glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune);

1327

1328 f = frclen;

1329 frclen++;

1330

1331 FcPatternDestroy(fcpattern);

1332 FcCharSetDestroy(fccharset);

1333 }

1334

1335 specs[numspecs].font = frc[f].font;

1336 specs[numspecs].glyph = glyphidx;

1337 specs[numspecs].x = (short)xp;

1338 specs[numspecs].y = (short)yp;

1339 xp += runewidth;

1340 numspecs++;

1341 }

1342

1343 return numspecs;

1344 }

1345

1346 void

1347 xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y)

1348 {

1349 int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1);

1350 int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch,

1351 width = charlen * win.cw;

1352 Color *fg, *bg, *temp, revfg, revbg, truefg, truebg;

1353 XRenderColor colfg, colbg;

1354 XRectangle r;

1355

1356 /* Fallback on color display for attributes not supported by the font */

1357 if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) {

1358 if (dc.ibfont.badslant || dc.ibfont.badweight)

1359 base.fg = defaultattr;

1360 } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) ||

1361 (base.mode & ATTR_BOLD && dc.bfont.badweight)) {

1362 base.fg = defaultattr;

1363 }

1364

1365 if (IS_TRUECOL(base.fg)) {

1366 colfg.alpha = 0xffff;

1367 colfg.red = TRUERED(base.fg);

1368 colfg.green = TRUEGREEN(base.fg);

1369 colfg.blue = TRUEBLUE(base.fg);

1370 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg);

1371 fg = &truefg;

1372 } else {

1373 fg = &dc.col[base.fg];

1374 }

1375

1376 if (IS_TRUECOL(base.bg)) {

1377 colbg.alpha = 0xffff;

1378 colbg.green = TRUEGREEN(base.bg);

1379 colbg.red = TRUERED(base.bg);

1380 colbg.blue = TRUEBLUE(base.bg);

1381 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg);

1382 bg = &truebg;

1383 } else {

1384 bg = &dc.col[base.bg];

1385 }

1386

1387 /* Change basic system colors [0-7] to bright system colors [8-15] */

1388 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7))

1389 fg = &dc.col[base.fg + 8];

1390

1391 if (IS_SET(MODE_REVERSE)) {

1392 if (fg == &dc.col[defaultfg]) {

1393 fg = &dc.col[defaultbg];

1394 } else {

1395 colfg.red = ~fg->color.red;

1396 colfg.green = ~fg->color.green;

1397 colfg.blue = ~fg->color.blue;

1398 colfg.alpha = fg->color.alpha;

1399 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg,

1400 &revfg);

1401 fg = &revfg;

1402 }

1403

1404 if (bg == &dc.col[defaultbg]) {

1405 bg = &dc.col[defaultfg];

1406 } else {

1407 colbg.red = ~bg->color.red;

1408 colbg.green = ~bg->color.green;

1409 colbg.blue = ~bg->color.blue;

1410 colbg.alpha = bg->color.alpha;

1411 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg,

1412 &revbg);

1413 bg = &revbg;

1414 }

1415 }

1416

1417 if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) {

1418 colfg.red = fg->color.red / 2;

1419 colfg.green = fg->color.green / 2;

1420 colfg.blue = fg->color.blue / 2;

1421 colfg.alpha = fg->color.alpha;

1422 XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg);

1423 fg = &revfg;

1424 }

1425

1426 if (base.mode & ATTR_REVERSE) {

1427 temp = fg;

1428 fg = bg;

1429 bg = temp;

1430 }

1431

1432 if (base.mode & ATTR_BLINK && win.mode & MODE_BLINK)

1433 fg = bg;

1434

1435 if (base.mode & ATTR_INVISIBLE)

1436 fg = bg;

1437

1438 /* Intelligent cleaning up of the borders. */

1439 if (x == 0) {

1440 xclear(0, (y == 0)? 0 : winy, borderpx,

1441 winy + win.ch +

1442 ((winy + win.ch >= borderpx + win.th)? win.h : 0));

1443 }

1444 if (winx + width >= borderpx + win.tw) {

1445 xclear(winx + width, (y == 0)? 0 : winy, win.w,

1446 ((winy + win.ch >= borderpx + win.th)? win.h : (winy + win.ch)));

1447 }

1448 if (y == 0)

1449 xclear(winx, 0, winx + width, borderpx);

1450 if (winy + win.ch >= borderpx + win.th)

1451 xclear(winx, winy + win.ch, winx + width, win.h);

1452

1453 /* Clean up the region we want to draw to. */

1454 XftDrawRect(xw.draw, bg, winx, winy, width, win.ch);

1455

1456 /* Set the clip region because Xft is sometimes dirty. */

1457 r.x = 0;

1458 r.y = 0;

1459 r.height = win.ch;

1460 r.width = width;

1461 XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1);

1462

1463 /* Render the glyphs. */

1464 XftDrawGlyphFontSpec(xw.draw, fg, specs, len);

1465

1466 /* Render underline and strikethrough. */

1467 if (base.mode & ATTR_UNDERLINE) {

1468 XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1,

1469 width, 1);

1470 }

1471

1472 if (base.mode & ATTR_STRUCK) {

1473 XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3,

1474 width, 1);

1475 }

1476

1477 /* Reset clip to none. */

1478 XftDrawSetClip(xw.draw, 0);

1479 }

1480

1481 void

1482 xdrawglyph(Glyph g, int x, int y)

1483 {

1484 int numspecs;

1485 XftGlyphFontSpec spec;

1486

1487 numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y);

1488 xdrawglyphfontspecs(&spec, g, numspecs, x, y);

1489 }

1490

1491 void

1492 xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og)

1493 {

1494 Color drawcol;

1495

1496 /* remove the old cursor */

1497 if (selected(ox, oy))

1498 og.mode ^= ATTR_REVERSE;

1499 xdrawglyph(og, ox, oy);

1500

1501 if (IS_SET(MODE_HIDE))

1502 return;

1503

1504 /*

1505 * Select the right color for the right mode.

1506 */

1507 g.mode &= ATTR_BOLD|ATTR_ITALIC|ATTR_UNDERLINE|ATTR_STRUCK|ATTR_WIDE;

1508

1509 if (IS_SET(MODE_REVERSE)) {

1510 g.mode |= ATTR_REVERSE;

1511 g.bg = defaultfg;

1512 if (selected(cx, cy)) {

1513 drawcol = dc.col[defaultcs];

1514 g.fg = defaultrcs;

1515 } else {

1516 drawcol = dc.col[defaultrcs];

1517 g.fg = defaultcs;

1518 }

1519 } else {

1520 if (selected(cx, cy)) {

1521 g.fg = defaultfg;

1522 g.bg = defaultrcs;

1523 } else {

1524 g.fg = defaultbg;

1525 g.bg = defaultcs;

1526 }

1527 drawcol = dc.col[g.bg];

1528 }

1529

1530 /* draw the new one */

1531 if (IS_SET(MODE_FOCUSED)) {

1532 switch (win.cursor) {

1533 case 7: /* st extension */

1534 g.u = 0x2603; /* snowman (U+2603) */

1535 /* FALLTHROUGH */

1536 case 0: /* Blinking Block */

1537 case 1: /* Blinking Block (Default) */

1538 case 2: /* Steady Block */

1539 xdrawglyph(g, cx, cy);

1540 break;

1541 case 3: /* Blinking Underline */

1542 case 4: /* Steady Underline */

1543 XftDrawRect(xw.draw, &drawcol,

1544 borderpx + cx * win.cw,

1545 borderpx + (cy + 1) * win.ch - \

1546 cursorthickness,

1547 win.cw, cursorthickness);

1548 break;

1549 case 5: /* Blinking bar */

1550 case 6: /* Steady bar */

1551 XftDrawRect(xw.draw, &drawcol,

1552 borderpx + cx * win.cw,

1553 borderpx + cy * win.ch,

1554 cursorthickness, win.ch);

1555 break;

1556 }

1557 } else {

1558 XftDrawRect(xw.draw, &drawcol,

1559 borderpx + cx * win.cw,

1560 borderpx + cy * win.ch,

1561 win.cw - 1, 1);

1562 XftDrawRect(xw.draw, &drawcol,

1563 borderpx + cx * win.cw,

1564 borderpx + cy * win.ch,

1565 1, win.ch - 1);

1566 XftDrawRect(xw.draw, &drawcol,

1567 borderpx + (cx + 1) * win.cw - 1,

1568 borderpx + cy * win.ch,

1569 1, win.ch - 1);

1570 XftDrawRect(xw.draw, &drawcol,

1571 borderpx + cx * win.cw,

1572 borderpx + (cy + 1) * win.ch - 1,

1573 win.cw, 1);

1574 }

1575 }

1576

1577 void

1578 xsetenv(void)

1579 {

1580 char buf[sizeof(long) * 8 + 1];

1581

1582 snprintf(buf, sizeof(buf), "%!l(MISSING)u", xw.win);

1583 setenv("WINDOWID", buf, 1);

1584 }

1585

1586 void

1587 xseticontitle(char *p)

1588 {

1589 XTextProperty prop;

1590 DEFAULT(p, opt_title);

1591

1592 if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,

1593 &prop) != Success)

1594 return;

1595 XSetWMIconName(xw.dpy, xw.win, &prop);

1596 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);

1597 XFree(prop.value);

1598 }

1599

1600 void

1601 xsettitle(char *p)

1602 {

1603 XTextProperty prop;

1604 DEFAULT(p, opt_title);

1605

1606 if (Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,

1607 &prop) != Success)

1608 return;

1609 XSetWMName(xw.dpy, xw.win, &prop);

1610 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);

1611 XFree(prop.value);

1612 }

1613

1614 int

1615 xstartdraw(void)

1616 {

1617 return IS_SET(MODE_VISIBLE);

1618 }

1619

1620 void

1621 xdrawline(Line line, int x1, int y1, int x2)

1622 {

1623 int i, x, ox, numspecs;

1624 Glyph base, new;

1625 XftGlyphFontSpec *specs = xw.specbuf;

1626

1627 numspecs = xmakeglyphfontspecs(specs, &line[x1], x2 - x1, x1, y1);

1628 i = ox = 0;

1629 for (x = x1; x < x2 && i < numspecs; x++) {

1630 new = line[x];

1631 if (new.mode == ATTR_WDUMMY)

1632 continue;

1633 if (selected(x, y1))

1634 new.mode ^= ATTR_REVERSE;

1635 if (i > 0 && ATTRCMP(base, new)) {

1636 xdrawglyphfontspecs(specs, base, i, ox, y1);

1637 specs += i;

1638 numspecs -= i;

1639 i = 0;

1640 }

1641 if (i == 0) {

1642 ox = x;

1643 base = new;

1644 }

1645 i++;

1646 }

1647 if (i > 0)

1648 xdrawglyphfontspecs(specs, base, i, ox, y1);

1649 }

1650

1651 void

1652 xfinishdraw(void)

1653 {

1654 XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w,

1655 win.h, 0, 0);

1656 XSetForeground(xw.dpy, dc.gc,

1657 dc.col[IS_SET(MODE_REVERSE)?

1658 defaultfg : defaultbg].pixel);

1659 }

1660

1661 void

1662 xximspot(int x, int y)

1663 {

1664 if (xw.ime.xic == NULL)

1665 return;

1666

1667 xw.ime.spot.x = borderpx + x * win.cw;

1668 xw.ime.spot.y = borderpx + (y + 1) * win.ch;

1669

1670 XSetICValues(xw.ime.xic, XNPreeditAttributes, xw.ime.spotlist, NULL);

1671 }

1672

1673 void

1674 expose(XEvent *ev)

1675 {

1676 redraw();

1677 }

1678

1679 void

1680 visibility(XEvent *ev)

1681 {

1682 XVisibilityEvent *e = &ev->xvisibility;

1683

1684 MODBIT(win.mode, e->state != VisibilityFullyObscured, MODE_VISIBLE);

1685 }

1686

1687 void

1688 unmap(XEvent *ev)

1689 {

1690 win.mode &= ~MODE_VISIBLE;

1691 }

1692

1693 void

1694 xsetpointermotion(int set)

1695 {

1696 MODBIT(xw.attrs.event_mask, set, PointerMotionMask);

1697 XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs);

1698 }

1699

1700 void

1701 xsetmode(int set, unsigned int flags)

1702 {

1703 int mode = win.mode;

1704 MODBIT(win.mode, set, flags);

1705 if ((win.mode & MODE_REVERSE) != (mode & MODE_REVERSE))

1706 redraw();

1707 }

1708

1709 int

1710 xsetcursor(int cursor)

1711 {

1712 if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */

1713 return 1;

1714 win.cursor = cursor;

1715 return 0;

1716 }

1717

1718 void

1719 xseturgency(int add)

1720 {

1721 XWMHints *h = XGetWMHints(xw.dpy, xw.win);

1722

1723 MODBIT(h->flags, add, XUrgencyHint);

1724 XSetWMHints(xw.dpy, xw.win, h);

1725 XFree(h);

1726 }

1727

1728 void

1729 xbell(void)

1730 {

1731 if (!(IS_SET(MODE_FOCUSED)))

1732 xseturgency(1);

1733 if (bellvolume)

1734 XkbBell(xw.dpy, xw.win, bellvolume, (Atom)NULL);

1735 }

1736

1737 void

1738 focus(XEvent *ev)

1739 {

1740 XFocusChangeEvent *e = &ev->xfocus;

1741

1742 if (e->mode == NotifyGrab)

1743 return;

1744

1745 if (ev->type == FocusIn) {

1746 if (xw.ime.xic)

1747 XSetICFocus(xw.ime.xic);

1748 win.mode |= MODE_FOCUSED;

1749 xseturgency(0);

1750 if (IS_SET(MODE_FOCUS))

1751 ttywrite("\033[I", 3, 0);

1752 } else {

1753 if (xw.ime.xic)

1754 XUnsetICFocus(xw.ime.xic);

1755 win.mode &= ~MODE_FOCUSED;

1756 if (IS_SET(MODE_FOCUS))

1757 ttywrite("\033[O", 3, 0);

1758 }

1759 }

1760

1761 int

1762 match(uint mask, uint state)

1763 {

1764 return mask == XK_ANY_MOD || mask == (state & ~ignoremod);

1765 }

1766

1767 char*

1768 kmap(KeySym k, uint state)

1769 {

1770 Key *kp;

1771 int i;

1772

1773 /* Check for mapped keys out of X11 function keys. */

1774 for (i = 0; i < LEN(mappedkeys); i++) {

1775 if (mappedkeys[i] == k)

1776 break;

1777 }

1778 if (i == LEN(mappedkeys)) {

1779 if ((k & 0xFFFF) < 0xFD00)

1780 return NULL;

1781 }

1782

1783 for (kp = key; kp < key + LEN(key); kp++) {

1784 if (kp->k != k)

1785 continue;

1786

1787 if (!match(kp->mask, state))

1788 continue;

1789

1790 if (IS_SET(MODE_APPKEYPAD) ? kp->appkey < 0 : kp->appkey > 0)

1791 continue;

1792 if (IS_SET(MODE_NUMLOCK) && kp->appkey == 2)

1793 continue;

1794

1795 if (IS_SET(MODE_APPCURSOR) ? kp->appcursor < 0 : kp->appcursor > 0)

1796 continue;

1797

1798 return kp->s;

1799 }

1800

1801 return NULL;

1802 }

1803

1804 void

1805 kpress(XEvent *ev)

1806 {

1807 XKeyEvent *e = &ev->xkey;

1808 KeySym ksym;

1809 char buf[64], *customkey;

1810 int len;

1811 Rune c;

1812 Status status;

1813 Shortcut *bp;

1814

1815 if (IS_SET(MODE_KBDLOCK))

1816 return;

1817

1818 if (xw.ime.xic)

1819 len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status);

1820 else

1821 len = XLookupString(e, buf, sizeof buf, &ksym, NULL);

1822 /* 1. shortcuts */

1823 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {

1824 if (ksym == bp->keysym && match(bp->mod, e->state)) {

1825 bp->func(&(bp->arg));

1826 return;

1827 }

1828 }

1829

1830 /* 2. custom keys from config.h */

1831 if ((customkey = kmap(ksym, e->state))) {

1832 ttywrite(customkey, strlen(customkey), 1);

1833 return;

1834 }

1835

1836 /* 3. composed string from input method */

1837 if (len == 0)

1838 return;

1839 if (len == 1 && e->state & Mod1Mask) {

1840 if (IS_SET(MODE_8BIT)) {

1841 if (*buf < 0177) {

1842 c = *buf | 0x80;

1843 len = utf8encode(c, buf);

1844 }

1845 } else {

1846 buf[1] = buf[0];

1847 buf[0] = '\033';

1848 len = 2;

1849 }

1850 }

1851 ttywrite(buf, len, 1);

1852 }

1853

1854 void

1855 cmessage(XEvent *e)

1856 {

1857 /*

1858 * See xembed specs

1859 * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html

1860 */

1861 if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) {

1862 if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) {

1863 win.mode |= MODE_FOCUSED;

1864 xseturgency(0);

1865 } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) {

1866 win.mode &= ~MODE_FOCUSED;

1867 }

1868 } else if (e->xclient.data.l[0] == xw.wmdeletewin) {

1869 ttyhangup();

1870 exit(0);

1871 }

1872 }

1873

1874 void

1875 resize(XEvent *e)

1876 {

1877 if (e->xconfigure.width == win.w && e->xconfigure.height == win.h)

1878 return;

1879

1880 cresize(e->xconfigure.width, e->xconfigure.height);

1881 }

1882

1883 void

1884 run(void)

1885 {

1886 XEvent ev;

1887 int w = win.w, h = win.h;

1888 fd_set rfd;

1889 int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;

1890 struct timespec seltv, *tv, now, lastblink, trigger;

1891 double timeout;

1892

1893 /* Waiting for window mapping */

1894 do {

1895 XNextEvent(xw.dpy, &ev);

1896 /*

1897 * This XFilterEvent call is required because of XOpenIM. It

1898 * does filter out the key event and some client message for

1899 * the input method too.

1900 */

1901 if (XFilterEvent(&ev, None))

1902 continue;

1903 if (ev.type == ConfigureNotify) {

1904 w = ev.xconfigure.width;

1905 h = ev.xconfigure.height;

1906 }

1907 } while (ev.type != MapNotify);

1908

1909 ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);

1910 cresize(w, h);

1911

1912 for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {

1913 FD_ZERO(&rfd);

1914 FD_SET(ttyfd, &rfd);

1915 FD_SET(xfd, &rfd);

1916

1917 if (XPending(xw.dpy))

1918 timeout = 0; /* existing events might not set xfd */

1919

1920 seltv.tv_sec = timeout / 1E3;

1921 seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);

1922 tv = timeout >= 0 ? &seltv : NULL;

1923

1924 if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {

1925 if (errno == EINTR)

1926 continue;

1927 die("select failed: %!s(MISSING)\n", strerror(errno));

1928 }

1929 clock_gettime(CLOCK_MONOTONIC, &now);

1930

1931 if (FD_ISSET(ttyfd, &rfd))

1932 ttyread();

1933

1934 xev = 0;

1935 while (XPending(xw.dpy)) {

1936 xev = 1;

1937 XNextEvent(xw.dpy, &ev);

1938 if (XFilterEvent(&ev, None))

1939 continue;

1940 if (handler[ev.type])

1941 (handler[ev.type])(&ev);

1942 }

1943

1944 /*

1945 * To reduce flicker and tearing, when new content or event

1946 * triggers drawing, we first wait a bit to ensure we got

1947 * everything, and if nothing new arrives - we draw.

1948 * We start with trying to wait minlatency ms. If more content

1949 * arrives sooner, we retry with shorter and shorter periods,

1950 * and eventually draw even without idle after maxlatency ms.

1951 * Typically this results in low latency while interacting,

1952 * maximum latency intervals during `cat huge.txt`, and perfect

1953 * sync with periodic updates from animations/key-repeats/etc.

1954 */

1955 if (FD_ISSET(ttyfd, &rfd) || xev) {

1956 if (!drawing) {

1957 trigger = now;

1958 drawing = 1;

1959 }

1960 timeout = (maxlatency - TIMEDIFF(now, trigger)) \

1961 / maxlatency * minlatency;

1962 if (timeout > 0)

1963 continue; /* we have time, try to find idle */

1964 }

1965

1966 /* idle detected or maxlatency exhausted -> draw */

1967 timeout = -1;

1968 if (blinktimeout && tattrset(ATTR_BLINK)) {

1969 timeout = blinktimeout - TIMEDIFF(now, lastblink);

1970 if (timeout <= 0) {

1971 if (-timeout > blinktimeout) /* start visible */

1972 win.mode |= MODE_BLINK;

1973 win.mode ^= MODE_BLINK;

1974 tsetdirtattr(ATTR_BLINK);

1975 lastblink = now;

1976 timeout = blinktimeout;

1977 }

1978 }

1979

1980 draw();

1981 XFlush(xw.dpy);

1982 drawing = 0;

1983 }

1984 }

1985

1986 void

1987 usage(void)

1988 {

1989 die("usage: %!s(MISSING) [-aiv] [-c class] [-f font] [-g geometry]"

1990 " [-n name] [-o file]\n"

1991 " [-T title] [-t title] [-w windowid]"

1992 " [[-e] command [args ...]]\n"

1993 " %!s(MISSING) [-aiv] [-c class] [-f font] [-g geometry]"

1994 " [-n name] [-o file]\n"

1995 " [-T title] [-t title] [-w windowid] -l line"

1996 " [stty_args ...]\n", argv0, argv0);

1997 }

1998

1999 int

2000 main(int argc, char *argv[])

2001 {

2002 xw.l = xw.t = 0;

2003 xw.isfixed = False;

2004 xsetcursor(cursorshape);

2005

2006 ARGBEGIN {

2007 case 'a':

2008 allowaltscreen = 0;

2009 break;

2010 case 'c':

2011 opt_class = EARGF(usage());

2012 break;

2013 case 'e':

2014 if (argc > 0)

2015 --argc, ++argv;

2016 goto run;

2017 case 'f':

2018 opt_font = EARGF(usage());

2019 break;

2020 case 'g':

2021 xw.gm = XParseGeometry(EARGF(usage()),

2022 &xw.l, &xw.t, &cols, &rows);

2023 break;

2024 case 'i':

2025 xw.isfixed = 1;

2026 break;

2027 case 'o':

2028 opt_io = EARGF(usage());

2029 break;

2030 case 'l':

2031 opt_line = EARGF(usage());

2032 break;

2033 case 'n':

2034 opt_name = EARGF(usage());

2035 break;

2036 case 't':

2037 case 'T':

2038 opt_title = EARGF(usage());

2039 break;

2040 case 'w':

2041 opt_embed = EARGF(usage());

2042 break;

2043 case 'v':

2044 die("%!s(MISSING) " VERSION "\n", argv0);

2045 break;

2046 default:

2047 usage();

2048 } ARGEND;

2049

2050 run:

2051 if (argc > 0) /* eat all remaining arguments */

2052 opt_cmd = argv;

2053

2054 if (!opt_title)

2055 opt_title = (opt_line || !opt_cmd) ? "st" : opt_cmd[0];

2056

2057 setlocale(LC_CTYPE, "");

2058 XSetLocaleModifiers("");

2059 cols = MAX(cols, 1);

2060 rows = MAX(rows, 1);

2061 tnew(cols, rows);

2062 xinit(cols, rows);

2063 xsetenv();

2064 selinit();

2065 run();

2066

2067 return 0;

2068 }

2069