diff options
| author | Michael Forney <mforney@mforney.org> | 2017-01-20 00:06:39 -0800 |
|---|---|---|
| committer | Michael Forney <mforney@mforney.org> | 2017-01-20 19:42:26 -0800 |
| commit | e2ee5ee6114eb74bb08cb9abe5a3020203e92688 (patch) | |
| tree | 7fa47c27df85b1803f8e35860ad6ed2603138acb /x.c | |
| parent | Move column and row default numbers into config.h (diff) | |
| download | st-e2ee5ee6114eb74bb08cb9abe5a3020203e92688.tar.gz st-e2ee5ee6114eb74bb08cb9abe5a3020203e92688.zip | |
Split X-specific code into x.c
Diffstat (limited to '')
| -rw-r--r-- | x.c | 1766 |
1 files changed, 1766 insertions, 0 deletions
| @@ -0,0 +1,1766 @@ | |||
| 1 | /* See LICENSE for license details. */ | ||
| 2 | #include <errno.h> | ||
| 3 | #include <locale.h> | ||
| 4 | #include <signal.h> | ||
| 5 | #include <stdint.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/Xutil.h> | ||
| 13 | #include <X11/cursorfont.h> | ||
| 14 | #include <X11/keysym.h> | ||
| 15 | #include <X11/Xft/Xft.h> | ||
| 16 | #include <X11/XKBlib.h> | ||
| 17 | |||
| 18 | #include "arg.h" | ||
| 19 | |||
| 20 | #define Glyph Glyph_ | ||
| 21 | #define Font Font_ | ||
| 22 | |||
| 23 | #include "win.h" | ||
| 24 | #include "st.h" | ||
| 25 | |||
| 26 | /* XEMBED messages */ | ||
| 27 | #define XEMBED_FOCUS_IN 4 | ||
| 28 | #define XEMBED_FOCUS_OUT 5 | ||
| 29 | |||
| 30 | /* macros */ | ||
| 31 | #define TRUERED(x) (((x) & 0xff0000) >> 8) | ||
| 32 | #define TRUEGREEN(x) (((x) & 0xff00)) | ||
| 33 | #define TRUEBLUE(x) (((x) & 0xff) << 8) | ||
| 34 | |||
| 35 | typedef XftDraw *Draw; | ||
| 36 | typedef XftColor Color; | ||
| 37 | |||
| 38 | /* Purely graphic info */ | ||
| 39 | typedef struct { | ||
| 40 | Display *dpy; | ||
| 41 | Colormap cmap; | ||
| 42 | Window win; | ||
| 43 | Drawable buf; | ||
| 44 | Atom xembed, wmdeletewin, netwmname, netwmpid; | ||
| 45 | XIM xim; | ||
| 46 | XIC xic; | ||
| 47 | Draw draw; | ||
| 48 | Visual *vis; | ||
| 49 | XSetWindowAttributes attrs; | ||
| 50 | int scr; | ||
| 51 | int isfixed; /* is fixed geometry? */ | ||
| 52 | int l, t; /* left and top offset */ | ||
| 53 | int gm; /* geometry mask */ | ||
| 54 | } XWindow; | ||
| 55 | |||
| 56 | typedef struct { | ||
| 57 | Atom xtarget; | ||
| 58 | } XSelection; | ||
| 59 | |||
| 60 | /* Font structure */ | ||
| 61 | typedef struct { | ||
| 62 | int height; | ||
| 63 | int width; | ||
| 64 | int ascent; | ||
| 65 | int descent; | ||
| 66 | int badslant; | ||
| 67 | int badweight; | ||
| 68 | short lbearing; | ||
| 69 | short rbearing; | ||
| 70 | XftFont *match; | ||
| 71 | FcFontSet *set; | ||
| 72 | FcPattern *pattern; | ||
| 73 | } Font; | ||
| 74 | |||
| 75 | /* Drawing Context */ | ||
| 76 | typedef struct { | ||
| 77 | Color *col; | ||
| 78 | size_t collen; | ||
| 79 | Font font, bfont, ifont, ibfont; | ||
| 80 | GC gc; | ||
| 81 | } DC; | ||
| 82 | |||
| 83 | static inline ushort sixd_to_16bit(int); | ||
| 84 | static int xmakeglyphfontspecs(XftGlyphFontSpec *, const Glyph *, int, int, int); | ||
| 85 | static void xdrawglyphfontspecs(const XftGlyphFontSpec *, Glyph, int, int, int); | ||
| 86 | static void xdrawglyph(Glyph, int, int); | ||
| 87 | static void xclear(int, int, int, int); | ||
| 88 | static void xdrawcursor(void); | ||
| 89 | static int xgeommasktogravity(int); | ||
| 90 | static int xloadfont(Font *, FcPattern *); | ||
| 91 | static void xsetsel(char *, Time); | ||
| 92 | static void xunloadfont(Font *); | ||
| 93 | |||
| 94 | static void expose(XEvent *); | ||
| 95 | static void visibility(XEvent *); | ||
| 96 | static void unmap(XEvent *); | ||
| 97 | static void kpress(XEvent *); | ||
| 98 | static void cmessage(XEvent *); | ||
| 99 | static void resize(XEvent *); | ||
| 100 | static void focus(XEvent *); | ||
| 101 | static void brelease(XEvent *); | ||
| 102 | static void bpress(XEvent *); | ||
| 103 | static void bmotion(XEvent *); | ||
| 104 | static void propnotify(XEvent *); | ||
| 105 | static void selnotify(XEvent *); | ||
| 106 | static void selclear_(XEvent *); | ||
| 107 | static void selrequest(XEvent *); | ||
| 108 | |||
| 109 | static void selcopy(Time); | ||
| 110 | static void getbuttoninfo(XEvent *); | ||
| 111 | static void mousereport(XEvent *); | ||
| 112 | |||
| 113 | static void (*handler[LASTEvent])(XEvent *) = { | ||
| 114 | [KeyPress] = kpress, | ||
| 115 | [ClientMessage] = cmessage, | ||
| 116 | [ConfigureNotify] = resize, | ||
| 117 | [VisibilityNotify] = visibility, | ||
| 118 | [UnmapNotify] = unmap, | ||
| 119 | [Expose] = expose, | ||
| 120 | [FocusIn] = focus, | ||
| 121 | [FocusOut] = focus, | ||
| 122 | [MotionNotify] = bmotion, | ||
| 123 | [ButtonPress] = bpress, | ||
| 124 | [ButtonRelease] = brelease, | ||
| 125 | /* | ||
| 126 | * Uncomment if you want the selection to disappear when you select something | ||
| 127 | * different in another window. | ||
| 128 | */ | ||
| 129 | /* [SelectionClear] = selclear_, */ | ||
| 130 | [SelectionNotify] = selnotify, | ||
| 131 | /* | ||
| 132 | * PropertyNotify is only turned on when there is some INCR transfer happening | ||
| 133 | * for the selection retrieval. | ||
| 134 | */ | ||
| 135 | [PropertyNotify] = propnotify, | ||
| 136 | [SelectionRequest] = selrequest, | ||
| 137 | }; | ||
| 138 | |||
| 139 | /* Globals */ | ||
| 140 | static DC dc; | ||
| 141 | static XWindow xw; | ||
| 142 | static XSelection xsel; | ||
| 143 | |||
| 144 | /* Font Ring Cache */ | ||
| 145 | enum { | ||
| 146 | FRC_NORMAL, | ||
| 147 | FRC_ITALIC, | ||
| 148 | FRC_BOLD, | ||
| 149 | FRC_ITALICBOLD | ||
| 150 | }; | ||
| 151 | |||
| 152 | typedef struct { | ||
| 153 | XftFont *font; | ||
| 154 | int flags; | ||
| 155 | Rune unicodep; | ||
| 156 | } Fontcache; | ||
| 157 | |||
| 158 | /* Fontcache is an array now. A new font will be appended to the array. */ | ||
| 159 | static Fontcache frc[16]; | ||
| 160 | static int frclen = 0; | ||
| 161 | |||
| 162 | void | ||
| 163 | getbuttoninfo(XEvent *e) | ||
| 164 | { | ||
| 165 | int type; | ||
| 166 | uint state = e->xbutton.state & ~(Button1Mask | forceselmod); | ||
| 167 | |||
| 168 | sel.alt = IS_SET(MODE_ALTSCREEN); | ||
| 169 | |||
| 170 | sel.oe.x = x2col(e->xbutton.x); | ||
| 171 | sel.oe.y = y2row(e->xbutton.y); | ||
| 172 | selnormalize(); | ||
| 173 | |||
| 174 | sel.type = SEL_REGULAR; | ||
| 175 | for (type = 1; type < selmaskslen; ++type) { | ||
| 176 | if (match(selmasks[type], state)) { | ||
| 177 | sel.type = type; | ||
| 178 | break; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | void | ||
| 184 | mousereport(XEvent *e) | ||
| 185 | { | ||
| 186 | int x = x2col(e->xbutton.x), y = y2row(e->xbutton.y), | ||
| 187 | button = e->xbutton.button, state = e->xbutton.state, | ||
| 188 | len; | ||
| 189 | char buf[40]; | ||
| 190 | static int ox, oy; | ||
| 191 | |||
| 192 | /* from urxvt */ | ||
| 193 | if (e->xbutton.type == MotionNotify) { | ||
| 194 | if (x == ox && y == oy) | ||
| 195 | return; | ||
| 196 | if (!IS_SET(MODE_MOUSEMOTION) && !IS_SET(MODE_MOUSEMANY)) | ||
| 197 | return; | ||
| 198 | /* MOUSE_MOTION: no reporting if no button is pressed */ | ||
| 199 | if (IS_SET(MODE_MOUSEMOTION) && oldbutton == 3) | ||
| 200 | return; | ||
| 201 | |||
| 202 | button = oldbutton + 32; | ||
| 203 | ox = x; | ||
| 204 | oy = y; | ||
| 205 | } else { | ||
| 206 | if (!IS_SET(MODE_MOUSESGR) && e->xbutton.type == ButtonRelease) { | ||
| 207 | button = 3; | ||
| 208 | } else { | ||
| 209 | button -= Button1; | ||
| 210 | if (button >= 3) | ||
| 211 | button += 64 - 3; | ||
| 212 | } | ||
| 213 | if (e->xbutton.type == ButtonPress) { | ||
| 214 | oldbutton = button; | ||
| 215 | ox = x; | ||
| 216 | oy = y; | ||
| 217 | } else if (e->xbutton.type == ButtonRelease) { | ||
| 218 | oldbutton = 3; | ||
| 219 | /* MODE_MOUSEX10: no button release reporting */ | ||
| 220 | if (IS_SET(MODE_MOUSEX10)) | ||
| 221 | return; | ||
| 222 | if (button == 64 || button == 65) | ||
| 223 | return; | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | if (!IS_SET(MODE_MOUSEX10)) { | ||
| 228 | button += ((state & ShiftMask ) ? 4 : 0) | ||
| 229 | + ((state & Mod4Mask ) ? 8 : 0) | ||
| 230 | + ((state & ControlMask) ? 16 : 0); | ||
| 231 | } | ||
| 232 | |||
| 233 | if (IS_SET(MODE_MOUSESGR)) { | ||
| 234 | len = snprintf(buf, sizeof(buf), "\033[<%d;%d;%d%c", | ||
| 235 | button, x+1, y+1, | ||
| 236 | e->xbutton.type == ButtonRelease ? 'm' : 'M'); | ||
| 237 | } else if (x < 223 && y < 223) { | ||
| 238 | len = snprintf(buf, sizeof(buf), "\033[M%c%c%c", | ||
| 239 | 32+button, 32+x+1, 32+y+1); | ||
| 240 | } else { | ||
| 241 | return; | ||
| 242 | } | ||
| 243 | |||
| 244 | ttywrite(buf, len); | ||
| 245 | } | ||
| 246 | |||
| 247 | void | ||
| 248 | bpress(XEvent *e) | ||
| 249 | { | ||
| 250 | struct timespec now; | ||
| 251 | MouseShortcut *ms; | ||
| 252 | |||
| 253 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { | ||
| 254 | mousereport(e); | ||
| 255 | return; | ||
| 256 | } | ||
| 257 | |||
| 258 | for (ms = mshortcuts; ms < mshortcuts + mshortcutslen; ms++) { | ||
| 259 | if (e->xbutton.button == ms->b | ||
| 260 | && match(ms->mask, e->xbutton.state)) { | ||
| 261 | ttysend(ms->s, strlen(ms->s)); | ||
| 262 | return; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | if (e->xbutton.button == Button1) { | ||
| 267 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
| 268 | |||
| 269 | /* Clear previous selection, logically and visually. */ | ||
| 270 | selclear_(NULL); | ||
| 271 | sel.mode = SEL_EMPTY; | ||
| 272 | sel.type = SEL_REGULAR; | ||
| 273 | sel.oe.x = sel.ob.x = x2col(e->xbutton.x); | ||
| 274 | sel.oe.y = sel.ob.y = y2row(e->xbutton.y); | ||
| 275 | |||
| 276 | /* | ||
| 277 | * If the user clicks below predefined timeouts specific | ||
| 278 | * snapping behaviour is exposed. | ||
| 279 | */ | ||
| 280 | if (TIMEDIFF(now, sel.tclick2) <= tripleclicktimeout) { | ||
| 281 | sel.snap = SNAP_LINE; | ||
| 282 | } else if (TIMEDIFF(now, sel.tclick1) <= doubleclicktimeout) { | ||
| 283 | sel.snap = SNAP_WORD; | ||
| 284 | } else { | ||
| 285 | sel.snap = 0; | ||
| 286 | } | ||
| 287 | selnormalize(); | ||
| 288 | |||
| 289 | if (sel.snap != 0) | ||
| 290 | sel.mode = SEL_READY; | ||
| 291 | tsetdirt(sel.nb.y, sel.ne.y); | ||
| 292 | sel.tclick2 = sel.tclick1; | ||
| 293 | sel.tclick1 = now; | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | void | ||
| 298 | selcopy(Time t) | ||
| 299 | { | ||
| 300 | xsetsel(getsel(), t); | ||
| 301 | } | ||
| 302 | |||
| 303 | void | ||
| 304 | propnotify(XEvent *e) | ||
| 305 | { | ||
| 306 | XPropertyEvent *xpev; | ||
| 307 | Atom clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); | ||
| 308 | |||
| 309 | xpev = &e->xproperty; | ||
| 310 | if (xpev->state == PropertyNewValue && | ||
| 311 | (xpev->atom == XA_PRIMARY || | ||
| 312 | xpev->atom == clipboard)) { | ||
| 313 | selnotify(e); | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | void | ||
| 318 | selnotify(XEvent *e) | ||
| 319 | { | ||
| 320 | ulong nitems, ofs, rem; | ||
| 321 | int format; | ||
| 322 | uchar *data, *last, *repl; | ||
| 323 | Atom type, incratom, property; | ||
| 324 | |||
| 325 | incratom = XInternAtom(xw.dpy, "INCR", 0); | ||
| 326 | |||
| 327 | ofs = 0; | ||
| 328 | if (e->type == SelectionNotify) { | ||
| 329 | property = e->xselection.property; | ||
| 330 | } else if(e->type == PropertyNotify) { | ||
| 331 | property = e->xproperty.atom; | ||
| 332 | } else { | ||
| 333 | return; | ||
| 334 | } | ||
| 335 | if (property == None) | ||
| 336 | return; | ||
| 337 | |||
| 338 | do { | ||
| 339 | if (XGetWindowProperty(xw.dpy, xw.win, property, ofs, | ||
| 340 | BUFSIZ/4, False, AnyPropertyType, | ||
| 341 | &type, &format, &nitems, &rem, | ||
| 342 | &data)) { | ||
| 343 | fprintf(stderr, "Clipboard allocation failed\n"); | ||
| 344 | return; | ||
| 345 | } | ||
| 346 | |||
| 347 | if (e->type == PropertyNotify && nitems == 0 && rem == 0) { | ||
| 348 | /* | ||
| 349 | * If there is some PropertyNotify with no data, then | ||
| 350 | * this is the signal of the selection owner that all | ||
| 351 | * data has been transferred. We won't need to receive | ||
| 352 | * PropertyNotify events anymore. | ||
| 353 | */ | ||
| 354 | MODBIT(xw.attrs.event_mask, 0, PropertyChangeMask); | ||
| 355 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, | ||
| 356 | &xw.attrs); | ||
| 357 | } | ||
| 358 | |||
| 359 | if (type == incratom) { | ||
| 360 | /* | ||
| 361 | * Activate the PropertyNotify events so we receive | ||
| 362 | * when the selection owner does send us the next | ||
| 363 | * chunk of data. | ||
| 364 | */ | ||
| 365 | MODBIT(xw.attrs.event_mask, 1, PropertyChangeMask); | ||
| 366 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, | ||
| 367 | &xw.attrs); | ||
| 368 | |||
| 369 | /* | ||
| 370 | * Deleting the property is the transfer start signal. | ||
| 371 | */ | ||
| 372 | XDeleteProperty(xw.dpy, xw.win, (int)property); | ||
| 373 | continue; | ||
| 374 | } | ||
| 375 | |||
| 376 | /* | ||
| 377 | * As seen in getsel: | ||
| 378 | * Line endings are inconsistent in the terminal and GUI world | ||
| 379 | * copy and pasting. When receiving some selection data, | ||
| 380 | * replace all '\n' with '\r'. | ||
| 381 | * FIXME: Fix the computer world. | ||
| 382 | */ | ||
| 383 | repl = data; | ||
| 384 | last = data + nitems * format / 8; | ||
| 385 | while ((repl = memchr(repl, '\n', last - repl))) { | ||
| 386 | *repl++ = '\r'; | ||
| 387 | } | ||
| 388 | |||
| 389 | if (IS_SET(MODE_BRCKTPASTE) && ofs == 0) | ||
| 390 | ttywrite("\033[200~", 6); | ||
| 391 | ttysend((char *)data, nitems * format / 8); | ||
| 392 | if (IS_SET(MODE_BRCKTPASTE) && rem == 0) | ||
| 393 | ttywrite("\033[201~", 6); | ||
| 394 | XFree(data); | ||
| 395 | /* number of 32-bit chunks returned */ | ||
| 396 | ofs += nitems * format / 32; | ||
| 397 | } while (rem > 0); | ||
| 398 | |||
| 399 | /* | ||
| 400 | * Deleting the property again tells the selection owner to send the | ||
| 401 | * next data chunk in the property. | ||
| 402 | */ | ||
| 403 | XDeleteProperty(xw.dpy, xw.win, (int)property); | ||
| 404 | } | ||
| 405 | |||
| 406 | void | ||
| 407 | xselpaste(void) | ||
| 408 | { | ||
| 409 | XConvertSelection(xw.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY, | ||
| 410 | xw.win, CurrentTime); | ||
| 411 | } | ||
| 412 | |||
| 413 | void | ||
| 414 | xclipcopy(void) | ||
| 415 | { | ||
| 416 | Atom clipboard; | ||
| 417 | |||
| 418 | if (sel.clipboard != NULL) | ||
| 419 | free(sel.clipboard); | ||
| 420 | |||
| 421 | if (sel.primary != NULL) { | ||
| 422 | sel.clipboard = xstrdup(sel.primary); | ||
| 423 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); | ||
| 424 | XSetSelectionOwner(xw.dpy, clipboard, xw.win, CurrentTime); | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | void | ||
| 429 | xclippaste(void) | ||
| 430 | { | ||
| 431 | Atom clipboard; | ||
| 432 | |||
| 433 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); | ||
| 434 | XConvertSelection(xw.dpy, clipboard, xsel.xtarget, clipboard, | ||
| 435 | xw.win, CurrentTime); | ||
| 436 | } | ||
| 437 | |||
| 438 | void | ||
| 439 | selclear_(XEvent *e) | ||
| 440 | { | ||
| 441 | selclear(); | ||
| 442 | } | ||
| 443 | |||
| 444 | void | ||
| 445 | selrequest(XEvent *e) | ||
| 446 | { | ||
| 447 | XSelectionRequestEvent *xsre; | ||
| 448 | XSelectionEvent xev; | ||
| 449 | Atom xa_targets, string, clipboard; | ||
| 450 | char *seltext; | ||
| 451 | |||
| 452 | xsre = (XSelectionRequestEvent *) e; | ||
| 453 | xev.type = SelectionNotify; | ||
| 454 | xev.requestor = xsre->requestor; | ||
| 455 | xev.selection = xsre->selection; | ||
| 456 | xev.target = xsre->target; | ||
| 457 | xev.time = xsre->time; | ||
| 458 | if (xsre->property == None) | ||
| 459 | xsre->property = xsre->target; | ||
| 460 | |||
| 461 | /* reject */ | ||
| 462 | xev.property = None; | ||
| 463 | |||
| 464 | xa_targets = XInternAtom(xw.dpy, "TARGETS", 0); | ||
| 465 | if (xsre->target == xa_targets) { | ||
| 466 | /* respond with the supported type */ | ||
| 467 | string = xsel.xtarget; | ||
| 468 | XChangeProperty(xsre->display, xsre->requestor, xsre->property, | ||
| 469 | XA_ATOM, 32, PropModeReplace, | ||
| 470 | (uchar *) &string, 1); | ||
| 471 | xev.property = xsre->property; | ||
| 472 | } else if (xsre->target == xsel.xtarget || xsre->target == XA_STRING) { | ||
| 473 | /* | ||
| 474 | * xith XA_STRING non ascii characters may be incorrect in the | ||
| 475 | * requestor. It is not our problem, use utf8. | ||
| 476 | */ | ||
| 477 | clipboard = XInternAtom(xw.dpy, "CLIPBOARD", 0); | ||
| 478 | if (xsre->selection == XA_PRIMARY) { | ||
| 479 | seltext = sel.primary; | ||
| 480 | } else if (xsre->selection == clipboard) { | ||
| 481 | seltext = sel.clipboard; | ||
| 482 | } else { | ||
| 483 | fprintf(stderr, | ||
| 484 | "Unhandled clipboard selection 0x%lx\n", | ||
| 485 | xsre->selection); | ||
| 486 | return; | ||
| 487 | } | ||
| 488 | if (seltext != NULL) { | ||
| 489 | XChangeProperty(xsre->display, xsre->requestor, | ||
| 490 | xsre->property, xsre->target, | ||
| 491 | 8, PropModeReplace, | ||
| 492 | (uchar *)seltext, strlen(seltext)); | ||
| 493 | xev.property = xsre->property; | ||
| 494 | } | ||
| 495 | } | ||
| 496 | |||
| 497 | /* all done, send a notification to the listener */ | ||
| 498 | if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) | ||
| 499 | fprintf(stderr, "Error sending SelectionNotify event\n"); | ||
| 500 | } | ||
| 501 | |||
| 502 | void | ||
| 503 | xsetsel(char *str, Time t) | ||
| 504 | { | ||
| 505 | free(sel.primary); | ||
| 506 | sel.primary = str; | ||
| 507 | |||
| 508 | XSetSelectionOwner(xw.dpy, XA_PRIMARY, xw.win, t); | ||
| 509 | if (XGetSelectionOwner(xw.dpy, XA_PRIMARY) != xw.win) | ||
| 510 | selclear_(NULL); | ||
| 511 | } | ||
| 512 | |||
| 513 | void | ||
| 514 | brelease(XEvent *e) | ||
| 515 | { | ||
| 516 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { | ||
| 517 | mousereport(e); | ||
| 518 | return; | ||
| 519 | } | ||
| 520 | |||
| 521 | if (e->xbutton.button == Button2) { | ||
| 522 | xselpaste(); | ||
| 523 | } else if (e->xbutton.button == Button1) { | ||
| 524 | if (sel.mode == SEL_READY) { | ||
| 525 | getbuttoninfo(e); | ||
| 526 | selcopy(e->xbutton.time); | ||
| 527 | } else | ||
| 528 | selclear_(NULL); | ||
| 529 | sel.mode = SEL_IDLE; | ||
| 530 | tsetdirt(sel.nb.y, sel.ne.y); | ||
| 531 | } | ||
| 532 | } | ||
| 533 | |||
| 534 | void | ||
| 535 | bmotion(XEvent *e) | ||
| 536 | { | ||
| 537 | int oldey, oldex, oldsby, oldsey; | ||
| 538 | |||
| 539 | if (IS_SET(MODE_MOUSE) && !(e->xbutton.state & forceselmod)) { | ||
| 540 | mousereport(e); | ||
| 541 | return; | ||
| 542 | } | ||
| 543 | |||
| 544 | if (!sel.mode) | ||
| 545 | return; | ||
| 546 | |||
| 547 | sel.mode = SEL_READY; | ||
| 548 | oldey = sel.oe.y; | ||
| 549 | oldex = sel.oe.x; | ||
| 550 | oldsby = sel.nb.y; | ||
| 551 | oldsey = sel.ne.y; | ||
| 552 | getbuttoninfo(e); | ||
| 553 | |||
| 554 | if (oldey != sel.oe.y || oldex != sel.oe.x) | ||
| 555 | tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey)); | ||
| 556 | } | ||
| 557 | |||
| 558 | void | ||
| 559 | xresize(int col, int row) | ||
| 560 | { | ||
| 561 | win.tw = MAX(1, col * win.cw); | ||
| 562 | win.th = MAX(1, row * win.ch); | ||
| 563 | |||
| 564 | XFreePixmap(xw.dpy, xw.buf); | ||
| 565 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, | ||
| 566 | DefaultDepth(xw.dpy, xw.scr)); | ||
| 567 | XftDrawChange(xw.draw, xw.buf); | ||
| 568 | xclear(0, 0, win.w, win.h); | ||
| 569 | } | ||
| 570 | |||
| 571 | ushort | ||
| 572 | sixd_to_16bit(int x) | ||
| 573 | { | ||
| 574 | return x == 0 ? 0 : 0x3737 + 0x2828 * x; | ||
| 575 | } | ||
| 576 | |||
| 577 | int | ||
| 578 | xloadcolor(int i, const char *name, Color *ncolor) | ||
| 579 | { | ||
| 580 | XRenderColor color = { .alpha = 0xffff }; | ||
| 581 | |||
| 582 | if (!name) { | ||
| 583 | if (BETWEEN(i, 16, 255)) { /* 256 color */ | ||
| 584 | if (i < 6*6*6+16) { /* same colors as xterm */ | ||
| 585 | color.red = sixd_to_16bit( ((i-16)/36)%6 ); | ||
| 586 | color.green = sixd_to_16bit( ((i-16)/6) %6 ); | ||
| 587 | color.blue = sixd_to_16bit( ((i-16)/1) %6 ); | ||
| 588 | } else { /* greyscale */ | ||
| 589 | color.red = 0x0808 + 0x0a0a * (i - (6*6*6+16)); | ||
| 590 | color.green = color.blue = color.red; | ||
| 591 | } | ||
| 592 | return XftColorAllocValue(xw.dpy, xw.vis, | ||
| 593 | xw.cmap, &color, ncolor); | ||
| 594 | } else | ||
| 595 | name = colorname[i]; | ||
| 596 | } | ||
| 597 | |||
| 598 | return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); | ||
| 599 | } | ||
| 600 | |||
| 601 | void | ||
| 602 | xloadcols(void) | ||
| 603 | { | ||
| 604 | int i; | ||
| 605 | static int loaded; | ||
| 606 | Color *cp; | ||
| 607 | |||
| 608 | dc.collen = MAX(colornamelen, 256); | ||
| 609 | dc.col = xmalloc(dc.collen * sizeof(Color)); | ||
| 610 | |||
| 611 | if (loaded) { | ||
| 612 | for (cp = dc.col; cp < &dc.col[dc.collen]; ++cp) | ||
| 613 | XftColorFree(xw.dpy, xw.vis, xw.cmap, cp); | ||
| 614 | } | ||
| 615 | |||
| 616 | for (i = 0; i < dc.collen; i++) | ||
| 617 | if (!xloadcolor(i, NULL, &dc.col[i])) { | ||
| 618 | if (colorname[i]) | ||
| 619 | die("Could not allocate color '%s'\n", colorname[i]); | ||
| 620 | else | ||
| 621 | die("Could not allocate color %d\n", i); | ||
| 622 | } | ||
| 623 | loaded = 1; | ||
| 624 | } | ||
| 625 | |||
| 626 | int | ||
| 627 | xsetcolorname(int x, const char *name) | ||
| 628 | { | ||
| 629 | Color ncolor; | ||
| 630 | |||
| 631 | if (!BETWEEN(x, 0, dc.collen)) | ||
| 632 | return 1; | ||
| 633 | |||
| 634 | |||
| 635 | if (!xloadcolor(x, name, &ncolor)) | ||
| 636 | return 1; | ||
| 637 | |||
| 638 | XftColorFree(xw.dpy, xw.vis, xw.cmap, &dc.col[x]); | ||
| 639 | dc.col[x] = ncolor; | ||
| 640 | |||
| 641 | return 0; | ||
| 642 | } | ||
| 643 | |||
| 644 | /* | ||
| 645 | * Absolute coordinates. | ||
| 646 | */ | ||
| 647 | void | ||
| 648 | xclear(int x1, int y1, int x2, int y2) | ||
| 649 | { | ||
| 650 | XftDrawRect(xw.draw, | ||
| 651 | &dc.col[IS_SET(MODE_REVERSE)? defaultfg : defaultbg], | ||
| 652 | x1, y1, x2-x1, y2-y1); | ||
| 653 | } | ||
| 654 | |||
| 655 | void | ||
| 656 | xhints(void) | ||
| 657 | { | ||
| 658 | XClassHint class = {opt_name ? opt_name : termname, | ||
| 659 | opt_class ? opt_class : termname}; | ||
| 660 | XWMHints wm = {.flags = InputHint, .input = 1}; | ||
| 661 | XSizeHints *sizeh = NULL; | ||
| 662 | |||
| 663 | sizeh = XAllocSizeHints(); | ||
| 664 | |||
| 665 | sizeh->flags = PSize | PResizeInc | PBaseSize; | ||
| 666 | sizeh->height = win.h; | ||
| 667 | sizeh->width = win.w; | ||
| 668 | sizeh->height_inc = win.ch; | ||
| 669 | sizeh->width_inc = win.cw; | ||
| 670 | sizeh->base_height = 2 * borderpx; | ||
| 671 | sizeh->base_width = 2 * borderpx; | ||
| 672 | if (xw.isfixed) { | ||
| 673 | sizeh->flags |= PMaxSize | PMinSize; | ||
| 674 | sizeh->min_width = sizeh->max_width = win.w; | ||
| 675 | sizeh->min_height = sizeh->max_height = win.h; | ||
| 676 | } | ||
| 677 | if (xw.gm & (XValue|YValue)) { | ||
| 678 | sizeh->flags |= USPosition | PWinGravity; | ||
| 679 | sizeh->x = xw.l; | ||
| 680 | sizeh->y = xw.t; | ||
| 681 | sizeh->win_gravity = xgeommasktogravity(xw.gm); | ||
| 682 | } | ||
| 683 | |||
| 684 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, | ||
| 685 | &class); | ||
| 686 | XFree(sizeh); | ||
| 687 | } | ||
| 688 | |||
| 689 | int | ||
| 690 | xgeommasktogravity(int mask) | ||
| 691 | { | ||
| 692 | switch (mask & (XNegative|YNegative)) { | ||
| 693 | case 0: | ||
| 694 | return NorthWestGravity; | ||
| 695 | case XNegative: | ||
| 696 | return NorthEastGravity; | ||
| 697 | case YNegative: | ||
| 698 | return SouthWestGravity; | ||
| 699 | } | ||
| 700 | |||
| 701 | return SouthEastGravity; | ||
| 702 | } | ||
| 703 | |||
| 704 | int | ||
| 705 | xloadfont(Font *f, FcPattern *pattern) | ||
| 706 | { | ||
| 707 | FcPattern *configured; | ||
| 708 | FcPattern *match; | ||
| 709 | FcResult result; | ||
| 710 | XGlyphInfo extents; | ||
| 711 | int wantattr, haveattr; | ||
| 712 | |||
| 713 | /* | ||
| 714 | * Manually configure instead of calling XftMatchFont | ||
| 715 | * so that we can use the configured pattern for | ||
| 716 | * "missing glyph" lookups. | ||
| 717 | */ | ||
| 718 | configured = FcPatternDuplicate(pattern); | ||
| 719 | if (!configured) | ||
| 720 | return 1; | ||
| 721 | |||
| 722 | FcConfigSubstitute(NULL, configured, FcMatchPattern); | ||
| 723 | XftDefaultSubstitute(xw.dpy, xw.scr, configured); | ||
| 724 | |||
| 725 | match = FcFontMatch(NULL, configured, &result); | ||
| 726 | if (!match) { | ||
| 727 | FcPatternDestroy(configured); | ||
| 728 | return 1; | ||
| 729 | } | ||
| 730 | |||
| 731 | if (!(f->match = XftFontOpenPattern(xw.dpy, match))) { | ||
| 732 | FcPatternDestroy(configured); | ||
| 733 | FcPatternDestroy(match); | ||
| 734 | return 1; | ||
| 735 | } | ||
| 736 | |||
| 737 | if ((XftPatternGetInteger(pattern, "slant", 0, &wantattr) == | ||
| 738 | XftResultMatch)) { | ||
| 739 | /* | ||
| 740 | * Check if xft was unable to find a font with the appropriate | ||
| 741 | * slant but gave us one anyway. Try to mitigate. | ||
| 742 | */ | ||
| 743 | if ((XftPatternGetInteger(f->match->pattern, "slant", 0, | ||
| 744 | &haveattr) != XftResultMatch) || haveattr < wantattr) { | ||
| 745 | f->badslant = 1; | ||
| 746 | fputs("st: font slant does not match\n", stderr); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | |||
| 750 | if ((XftPatternGetInteger(pattern, "weight", 0, &wantattr) == | ||
| 751 | XftResultMatch)) { | ||
| 752 | if ((XftPatternGetInteger(f->match->pattern, "weight", 0, | ||
| 753 | &haveattr) != XftResultMatch) || haveattr != wantattr) { | ||
| 754 | f->badweight = 1; | ||
| 755 | fputs("st: font weight does not match\n", stderr); | ||
| 756 | } | ||
| 757 | } | ||
| 758 | |||
| 759 | XftTextExtentsUtf8(xw.dpy, f->match, | ||
| 760 | (const FcChar8 *) ascii_printable, | ||
| 761 | strlen(ascii_printable), &extents); | ||
| 762 | |||
| 763 | f->set = NULL; | ||
| 764 | f->pattern = configured; | ||
| 765 | |||
| 766 | f->ascent = f->match->ascent; | ||
| 767 | f->descent = f->match->descent; | ||
| 768 | f->lbearing = 0; | ||
| 769 | f->rbearing = f->match->max_advance_width; | ||
| 770 | |||
| 771 | f->height = f->ascent + f->descent; | ||
| 772 | f->width = DIVCEIL(extents.xOff, strlen(ascii_printable)); | ||
| 773 | |||
| 774 | return 0; | ||
| 775 | } | ||
| 776 | |||
| 777 | void | ||
| 778 | xloadfonts(char *fontstr, double fontsize) | ||
| 779 | { | ||
| 780 | FcPattern *pattern; | ||
| 781 | double fontval; | ||
| 782 | float ceilf(float); | ||
| 783 | |||
| 784 | if (fontstr[0] == '-') { | ||
| 785 | pattern = XftXlfdParse(fontstr, False, False); | ||
| 786 | } else { | ||
| 787 | pattern = FcNameParse((FcChar8 *)fontstr); | ||
| 788 | } | ||
| 789 | |||
| 790 | if (!pattern) | ||
| 791 | die("st: can't open font %s\n", fontstr); | ||
| 792 | |||
| 793 | if (fontsize > 1) { | ||
| 794 | FcPatternDel(pattern, FC_PIXEL_SIZE); | ||
| 795 | FcPatternDel(pattern, FC_SIZE); | ||
| 796 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, (double)fontsize); | ||
| 797 | usedfontsize = fontsize; | ||
| 798 | } else { | ||
| 799 | if (FcPatternGetDouble(pattern, FC_PIXEL_SIZE, 0, &fontval) == | ||
| 800 | FcResultMatch) { | ||
| 801 | usedfontsize = fontval; | ||
| 802 | } else if (FcPatternGetDouble(pattern, FC_SIZE, 0, &fontval) == | ||
| 803 | FcResultMatch) { | ||
| 804 | usedfontsize = -1; | ||
| 805 | } else { | ||
| 806 | /* | ||
| 807 | * Default font size is 12, if none given. This is to | ||
| 808 | * have a known usedfontsize value. | ||
| 809 | */ | ||
| 810 | FcPatternAddDouble(pattern, FC_PIXEL_SIZE, 12); | ||
| 811 | usedfontsize = 12; | ||
| 812 | } | ||
| 813 | defaultfontsize = usedfontsize; | ||
| 814 | } | ||
| 815 | |||
| 816 | if (xloadfont(&dc.font, pattern)) | ||
| 817 | die("st: can't open font %s\n", fontstr); | ||
| 818 | |||
| 819 | if (usedfontsize < 0) { | ||
| 820 | FcPatternGetDouble(dc.font.match->pattern, | ||
| 821 | FC_PIXEL_SIZE, 0, &fontval); | ||
| 822 | usedfontsize = fontval; | ||
| 823 | if (fontsize == 0) | ||
| 824 | defaultfontsize = fontval; | ||
| 825 | } | ||
| 826 | |||
| 827 | /* Setting character width and height. */ | ||
| 828 | win.cw = ceilf(dc.font.width * cwscale); | ||
| 829 | win.ch = ceilf(dc.font.height * chscale); | ||
| 830 | |||
| 831 | FcPatternDel(pattern, FC_SLANT); | ||
| 832 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ITALIC); | ||
| 833 | if (xloadfont(&dc.ifont, pattern)) | ||
| 834 | die("st: can't open font %s\n", fontstr); | ||
| 835 | |||
| 836 | FcPatternDel(pattern, FC_WEIGHT); | ||
| 837 | FcPatternAddInteger(pattern, FC_WEIGHT, FC_WEIGHT_BOLD); | ||
| 838 | if (xloadfont(&dc.ibfont, pattern)) | ||
| 839 | die("st: can't open font %s\n", fontstr); | ||
| 840 | |||
| 841 | FcPatternDel(pattern, FC_SLANT); | ||
| 842 | FcPatternAddInteger(pattern, FC_SLANT, FC_SLANT_ROMAN); | ||
| 843 | if (xloadfont(&dc.bfont, pattern)) | ||
| 844 | die("st: can't open font %s\n", fontstr); | ||
| 845 | |||
| 846 | FcPatternDestroy(pattern); | ||
| 847 | } | ||
| 848 | |||
| 849 | void | ||
| 850 | xunloadfont(Font *f) | ||
| 851 | { | ||
| 852 | XftFontClose(xw.dpy, f->match); | ||
| 853 | FcPatternDestroy(f->pattern); | ||
| 854 | if (f->set) | ||
| 855 | FcFontSetDestroy(f->set); | ||
| 856 | } | ||
| 857 | |||
| 858 | void | ||
| 859 | xunloadfonts(void) | ||
| 860 | { | ||
| 861 | /* Free the loaded fonts in the font cache. */ | ||
| 862 | while (frclen > 0) | ||
| 863 | XftFontClose(xw.dpy, frc[--frclen].font); | ||
| 864 | |||
| 865 | xunloadfont(&dc.font); | ||
| 866 | xunloadfont(&dc.bfont); | ||
| 867 | xunloadfont(&dc.ifont); | ||
| 868 | xunloadfont(&dc.ibfont); | ||
| 869 | } | ||
| 870 | |||
| 871 | void | ||
| 872 | xinit(void) | ||
| 873 | { | ||
| 874 | XGCValues gcvalues; | ||
| 875 | Cursor cursor; | ||
| 876 | Window parent; | ||
| 877 | pid_t thispid = getpid(); | ||
| 878 | XColor xmousefg, xmousebg; | ||
| 879 | |||
| 880 | if (!(xw.dpy = XOpenDisplay(NULL))) | ||
| 881 | die("Can't open display\n"); | ||
| 882 | xw.scr = XDefaultScreen(xw.dpy); | ||
| 883 | xw.vis = XDefaultVisual(xw.dpy, xw.scr); | ||
| 884 | |||
| 885 | /* font */ | ||
| 886 | if (!FcInit()) | ||
| 887 | die("Could not init fontconfig.\n"); | ||
| 888 | |||
| 889 | usedfont = (opt_font == NULL)? font : opt_font; | ||
| 890 | xloadfonts(usedfont, 0); | ||
| 891 | |||
| 892 | /* colors */ | ||
| 893 | xw.cmap = XDefaultColormap(xw.dpy, xw.scr); | ||
| 894 | xloadcols(); | ||
| 895 | |||
| 896 | /* adjust fixed window geometry */ | ||
| 897 | win.w = 2 * borderpx + term.col * win.cw; | ||
| 898 | win.h = 2 * borderpx + term.row * win.ch; | ||
| 899 | if (xw.gm & XNegative) | ||
| 900 | xw.l += DisplayWidth(xw.dpy, xw.scr) - win.w - 2; | ||
| 901 | if (xw.gm & YNegative) | ||
| 902 | xw.t += DisplayHeight(xw.dpy, xw.scr) - win.h - 2; | ||
| 903 | |||
| 904 | /* Events */ | ||
| 905 | xw.attrs.background_pixel = dc.col[defaultbg].pixel; | ||
| 906 | xw.attrs.border_pixel = dc.col[defaultbg].pixel; | ||
| 907 | xw.attrs.bit_gravity = NorthWestGravity; | ||
| 908 | xw.attrs.event_mask = FocusChangeMask | KeyPressMask | ||
| 909 | | ExposureMask | VisibilityChangeMask | StructureNotifyMask | ||
| 910 | | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; | ||
| 911 | xw.attrs.colormap = xw.cmap; | ||
| 912 | |||
| 913 | if (!(opt_embed && (parent = strtol(opt_embed, NULL, 0)))) | ||
| 914 | parent = XRootWindow(xw.dpy, xw.scr); | ||
| 915 | xw.win = XCreateWindow(xw.dpy, parent, xw.l, xw.t, | ||
| 916 | win.w, win.h, 0, XDefaultDepth(xw.dpy, xw.scr), InputOutput, | ||
| 917 | xw.vis, CWBackPixel | CWBorderPixel | CWBitGravity | ||
| 918 | | CWEventMask | CWColormap, &xw.attrs); | ||
| 919 | |||
| 920 | memset(&gcvalues, 0, sizeof(gcvalues)); | ||
| 921 | gcvalues.graphics_exposures = False; | ||
| 922 | dc.gc = XCreateGC(xw.dpy, parent, GCGraphicsExposures, | ||
| 923 | &gcvalues); | ||
| 924 | xw.buf = XCreatePixmap(xw.dpy, xw.win, win.w, win.h, | ||
| 925 | DefaultDepth(xw.dpy, xw.scr)); | ||
| 926 | XSetForeground(xw.dpy, dc.gc, dc.col[defaultbg].pixel); | ||
| 927 | XFillRectangle(xw.dpy, xw.buf, dc.gc, 0, 0, win.w, win.h); | ||
| 928 | |||
| 929 | /* Xft rendering context */ | ||
| 930 | xw.draw = XftDrawCreate(xw.dpy, xw.buf, xw.vis, xw.cmap); | ||
| 931 | |||
| 932 | /* input methods */ | ||
| 933 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { | ||
| 934 | XSetLocaleModifiers("@im=local"); | ||
| 935 | if ((xw.xim = XOpenIM(xw.dpy, NULL, NULL, NULL)) == NULL) { | ||
| 936 | XSetLocaleModifiers("@im="); | ||
| 937 | if ((xw.xim = XOpenIM(xw.dpy, | ||
| 938 | NULL, NULL, NULL)) == NULL) { | ||
| 939 | die("XOpenIM failed. Could not open input" | ||
| 940 | " device.\n"); | ||
| 941 | } | ||
| 942 | } | ||
| 943 | } | ||
| 944 | xw.xic = XCreateIC(xw.xim, XNInputStyle, XIMPreeditNothing | ||
| 945 | | XIMStatusNothing, XNClientWindow, xw.win, | ||
| 946 | XNFocusWindow, xw.win, NULL); | ||
| 947 | if (xw.xic == NULL) | ||
| 948 | die("XCreateIC failed. Could not obtain input method.\n"); | ||
| 949 | |||
| 950 | /* white cursor, black outline */ | ||
| 951 | cursor = XCreateFontCursor(xw.dpy, mouseshape); | ||
| 952 | XDefineCursor(xw.dpy, xw.win, cursor); | ||
| 953 | |||
| 954 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousefg], &xmousefg) == 0) { | ||
| 955 | xmousefg.red = 0xffff; | ||
| 956 | xmousefg.green = 0xffff; | ||
| 957 | xmousefg.blue = 0xffff; | ||
| 958 | } | ||
| 959 | |||
| 960 | if (XParseColor(xw.dpy, xw.cmap, colorname[mousebg], &xmousebg) == 0) { | ||
| 961 | xmousebg.red = 0x0000; | ||
| 962 | xmousebg.green = 0x0000; | ||
| 963 | xmousebg.blue = 0x0000; | ||
| 964 | } | ||
| 965 | |||
| 966 | XRecolorCursor(xw.dpy, cursor, &xmousefg, &xmousebg); | ||
| 967 | |||
| 968 | xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False); | ||
| 969 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); | ||
| 970 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); | ||
| 971 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); | ||
| 972 | |||
| 973 | xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False); | ||
| 974 | XChangeProperty(xw.dpy, xw.win, xw.netwmpid, XA_CARDINAL, 32, | ||
| 975 | PropModeReplace, (uchar *)&thispid, 1); | ||
| 976 | |||
| 977 | resettitle(); | ||
| 978 | XMapWindow(xw.dpy, xw.win); | ||
| 979 | xhints(); | ||
| 980 | XSync(xw.dpy, False); | ||
| 981 | |||
| 982 | xsel.xtarget = XInternAtom(xw.dpy, "UTF8_STRING", 0); | ||
| 983 | if (xsel.xtarget == None) | ||
| 984 | xsel.xtarget = XA_STRING; | ||
| 985 | } | ||
| 986 | |||
| 987 | int | ||
| 988 | xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x, int y) | ||
| 989 | { | ||
| 990 | float winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, xp, yp; | ||
| 991 | ushort mode, prevmode = USHRT_MAX; | ||
| 992 | Font *font = &dc.font; | ||
| 993 | int frcflags = FRC_NORMAL; | ||
| 994 | float runewidth = win.cw; | ||
| 995 | Rune rune; | ||
| 996 | FT_UInt glyphidx; | ||
| 997 | FcResult fcres; | ||
| 998 | FcPattern *fcpattern, *fontpattern; | ||
| 999 | FcFontSet *fcsets[] = { NULL }; | ||
| 1000 | FcCharSet *fccharset; | ||
| 1001 | int i, f, numspecs = 0; | ||
| 1002 | |||
| 1003 | for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { | ||
| 1004 | /* Fetch rune and mode for current glyph. */ | ||
| 1005 | rune = glyphs[i].u; | ||
| 1006 | mode = glyphs[i].mode; | ||
| 1007 | |||
| 1008 | /* Skip dummy wide-character spacing. */ | ||
| 1009 | if (mode == ATTR_WDUMMY) | ||
| 1010 | continue; | ||
| 1011 | |||
| 1012 | /* Determine font for glyph if different from previous glyph. */ | ||
| 1013 | if (prevmode != mode) { | ||
| 1014 | prevmode = mode; | ||
| 1015 | font = &dc.font; | ||
| 1016 | frcflags = FRC_NORMAL; | ||
| 1017 | runewidth = win.cw * ((mode & ATTR_WIDE) ? 2.0f : 1.0f); | ||
| 1018 | if ((mode & ATTR_ITALIC) && (mode & ATTR_BOLD)) { | ||
| 1019 | font = &dc.ibfont; | ||
| 1020 | frcflags = FRC_ITALICBOLD; | ||
| 1021 | } else if (mode & ATTR_ITALIC) { | ||
| 1022 | font = &dc.ifont; | ||
| 1023 | frcflags = FRC_ITALIC; | ||
| 1024 | } else if (mode & ATTR_BOLD) { | ||
| 1025 | font = &dc.bfont; | ||
| 1026 | frcflags = FRC_BOLD; | ||
| 1027 | } | ||
| 1028 | yp = winy + font->ascent; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | /* Lookup character index with default font. */ | ||
| 1032 | glyphidx = XftCharIndex(xw.dpy, font->match, rune); | ||
| 1033 | if (glyphidx) { | ||
| 1034 | specs[numspecs].font = font->match; | ||
| 1035 | specs[numspecs].glyph = glyphidx; | ||
| 1036 | specs[numspecs].x = (short)xp; | ||
| 1037 | specs[numspecs].y = (short)yp; | ||
| 1038 | xp += runewidth; | ||
| 1039 | numspecs++; | ||
| 1040 | continue; | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | /* Fallback on font cache, search the font cache for match. */ | ||
| 1044 | for (f = 0; f < frclen; f++) { | ||
| 1045 | glyphidx = XftCharIndex(xw.dpy, frc[f].font, rune); | ||
| 1046 | /* Everything correct. */ | ||
| 1047 | if (glyphidx && frc[f].flags == frcflags) | ||
| 1048 | break; | ||
| 1049 | /* We got a default font for a not found glyph. */ | ||
| 1050 | if (!glyphidx && frc[f].flags == frcflags | ||
| 1051 | && frc[f].unicodep == rune) { | ||
| 1052 | break; | ||
| 1053 | } | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | /* Nothing was found. Use fontconfig to find matching font. */ | ||
| 1057 | if (f >= frclen) { | ||
| 1058 | if (!font->set) | ||
| 1059 | font->set = FcFontSort(0, font->pattern, | ||
| 1060 | 1, 0, &fcres); | ||
| 1061 | fcsets[0] = font->set; | ||
| 1062 | |||
| 1063 | /* | ||
| 1064 | * Nothing was found in the cache. Now use | ||
| 1065 | * some dozen of Fontconfig calls to get the | ||
| 1066 | * font for one single character. | ||
| 1067 | * | ||
| 1068 | * Xft and fontconfig are design failures. | ||
| 1069 | */ | ||
| 1070 | fcpattern = FcPatternDuplicate(font->pattern); | ||
| 1071 | fccharset = FcCharSetCreate(); | ||
| 1072 | |||
| 1073 | FcCharSetAddChar(fccharset, rune); | ||
| 1074 | FcPatternAddCharSet(fcpattern, FC_CHARSET, | ||
| 1075 | fccharset); | ||
| 1076 | FcPatternAddBool(fcpattern, FC_SCALABLE, 1); | ||
| 1077 | |||
| 1078 | FcConfigSubstitute(0, fcpattern, | ||
| 1079 | FcMatchPattern); | ||
| 1080 | FcDefaultSubstitute(fcpattern); | ||
| 1081 | |||
| 1082 | fontpattern = FcFontSetMatch(0, fcsets, 1, | ||
| 1083 | fcpattern, &fcres); | ||
| 1084 | |||
| 1085 | /* | ||
| 1086 | * Overwrite or create the new cache entry. | ||
| 1087 | */ | ||
| 1088 | if (frclen >= LEN(frc)) { | ||
| 1089 | frclen = LEN(frc) - 1; | ||
| 1090 | XftFontClose(xw.dpy, frc[frclen].font); | ||
| 1091 | frc[frclen].unicodep = 0; | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | frc[frclen].font = XftFontOpenPattern(xw.dpy, | ||
| 1095 | fontpattern); | ||
| 1096 | frc[frclen].flags = frcflags; | ||
| 1097 | frc[frclen].unicodep = rune; | ||
| 1098 | |||
| 1099 | glyphidx = XftCharIndex(xw.dpy, frc[frclen].font, rune); | ||
| 1100 | |||
| 1101 | f = frclen; | ||
| 1102 | frclen++; | ||
| 1103 | |||
| 1104 | FcPatternDestroy(fcpattern); | ||
| 1105 | FcCharSetDestroy(fccharset); | ||
| 1106 | } | ||
| 1107 | |||
| 1108 | specs[numspecs].font = frc[f].font; | ||
| 1109 | specs[numspecs].glyph = glyphidx; | ||
| 1110 | specs[numspecs].x = (short)xp; | ||
| 1111 | specs[numspecs].y = (short)yp; | ||
| 1112 | xp += runewidth; | ||
| 1113 | numspecs++; | ||
| 1114 | } | ||
| 1115 | |||
| 1116 | return numspecs; | ||
| 1117 | } | ||
| 1118 | |||
| 1119 | void | ||
| 1120 | xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, int y) | ||
| 1121 | { | ||
| 1122 | int charlen = len * ((base.mode & ATTR_WIDE) ? 2 : 1); | ||
| 1123 | int winx = borderpx + x * win.cw, winy = borderpx + y * win.ch, | ||
| 1124 | width = charlen * win.cw; | ||
| 1125 | Color *fg, *bg, *temp, revfg, revbg, truefg, truebg; | ||
| 1126 | XRenderColor colfg, colbg; | ||
| 1127 | XRectangle r; | ||
| 1128 | |||
| 1129 | /* Fallback on color display for attributes not supported by the font */ | ||
| 1130 | if (base.mode & ATTR_ITALIC && base.mode & ATTR_BOLD) { | ||
| 1131 | if (dc.ibfont.badslant || dc.ibfont.badweight) | ||
| 1132 | base.fg = defaultattr; | ||
| 1133 | } else if ((base.mode & ATTR_ITALIC && dc.ifont.badslant) || | ||
| 1134 | (base.mode & ATTR_BOLD && dc.bfont.badweight)) { | ||
| 1135 | base.fg = defaultattr; | ||
| 1136 | } | ||
| 1137 | |||
| 1138 | if (IS_TRUECOL(base.fg)) { | ||
| 1139 | colfg.alpha = 0xffff; | ||
| 1140 | colfg.red = TRUERED(base.fg); | ||
| 1141 | colfg.green = TRUEGREEN(base.fg); | ||
| 1142 | colfg.blue = TRUEBLUE(base.fg); | ||
| 1143 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &truefg); | ||
| 1144 | fg = &truefg; | ||
| 1145 | } else { | ||
| 1146 | fg = &dc.col[base.fg]; | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | if (IS_TRUECOL(base.bg)) { | ||
| 1150 | colbg.alpha = 0xffff; | ||
| 1151 | colbg.green = TRUEGREEN(base.bg); | ||
| 1152 | colbg.red = TRUERED(base.bg); | ||
| 1153 | colbg.blue = TRUEBLUE(base.bg); | ||
| 1154 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, &truebg); | ||
| 1155 | bg = &truebg; | ||
| 1156 | } else { | ||
| 1157 | bg = &dc.col[base.bg]; | ||
| 1158 | } | ||
| 1159 | |||
| 1160 | /* Change basic system colors [0-7] to bright system colors [8-15] */ | ||
| 1161 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_BOLD && BETWEEN(base.fg, 0, 7)) | ||
| 1162 | fg = &dc.col[base.fg + 8]; | ||
| 1163 | |||
| 1164 | if (IS_SET(MODE_REVERSE)) { | ||
| 1165 | if (fg == &dc.col[defaultfg]) { | ||
| 1166 | fg = &dc.col[defaultbg]; | ||
| 1167 | } else { | ||
| 1168 | colfg.red = ~fg->color.red; | ||
| 1169 | colfg.green = ~fg->color.green; | ||
| 1170 | colfg.blue = ~fg->color.blue; | ||
| 1171 | colfg.alpha = fg->color.alpha; | ||
| 1172 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, | ||
| 1173 | &revfg); | ||
| 1174 | fg = &revfg; | ||
| 1175 | } | ||
| 1176 | |||
| 1177 | if (bg == &dc.col[defaultbg]) { | ||
| 1178 | bg = &dc.col[defaultfg]; | ||
| 1179 | } else { | ||
| 1180 | colbg.red = ~bg->color.red; | ||
| 1181 | colbg.green = ~bg->color.green; | ||
| 1182 | colbg.blue = ~bg->color.blue; | ||
| 1183 | colbg.alpha = bg->color.alpha; | ||
| 1184 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colbg, | ||
| 1185 | &revbg); | ||
| 1186 | bg = &revbg; | ||
| 1187 | } | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | if (base.mode & ATTR_REVERSE) { | ||
| 1191 | temp = fg; | ||
| 1192 | fg = bg; | ||
| 1193 | bg = temp; | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | if ((base.mode & ATTR_BOLD_FAINT) == ATTR_FAINT) { | ||
| 1197 | colfg.red = fg->color.red / 2; | ||
| 1198 | colfg.green = fg->color.green / 2; | ||
| 1199 | colfg.blue = fg->color.blue / 2; | ||
| 1200 | XftColorAllocValue(xw.dpy, xw.vis, xw.cmap, &colfg, &revfg); | ||
| 1201 | fg = &revfg; | ||
| 1202 | } | ||
| 1203 | |||
| 1204 | if (base.mode & ATTR_BLINK && term.mode & MODE_BLINK) | ||
| 1205 | fg = bg; | ||
| 1206 | |||
| 1207 | if (base.mode & ATTR_INVISIBLE) | ||
| 1208 | fg = bg; | ||
| 1209 | |||
| 1210 | /* Intelligent cleaning up of the borders. */ | ||
| 1211 | if (x == 0) { | ||
| 1212 | xclear(0, (y == 0)? 0 : winy, borderpx, | ||
| 1213 | winy + win.ch + ((y >= term.row-1)? win.h : 0)); | ||
| 1214 | } | ||
| 1215 | if (x + charlen >= term.col) { | ||
| 1216 | xclear(winx + width, (y == 0)? 0 : winy, win.w, | ||
| 1217 | ((y >= term.row-1)? win.h : (winy + win.ch))); | ||
| 1218 | } | ||
| 1219 | if (y == 0) | ||
| 1220 | xclear(winx, 0, winx + width, borderpx); | ||
| 1221 | if (y == term.row-1) | ||
| 1222 | xclear(winx, winy + win.ch, winx + width, win.h); | ||
| 1223 | |||
| 1224 | /* Clean up the region we want to draw to. */ | ||
| 1225 | XftDrawRect(xw.draw, bg, winx, winy, width, win.ch); | ||
| 1226 | |||
| 1227 | /* Set the clip region because Xft is sometimes dirty. */ | ||
| 1228 | r.x = 0; | ||
| 1229 | r.y = 0; | ||
| 1230 | r.height = win.ch; | ||
| 1231 | r.width = width; | ||
| 1232 | XftDrawSetClipRectangles(xw.draw, winx, winy, &r, 1); | ||
| 1233 | |||
| 1234 | /* Render the glyphs. */ | ||
| 1235 | XftDrawGlyphFontSpec(xw.draw, fg, specs, len); | ||
| 1236 | |||
| 1237 | /* Render underline and strikethrough. */ | ||
| 1238 | if (base.mode & ATTR_UNDERLINE) { | ||
| 1239 | XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent + 1, | ||
| 1240 | width, 1); | ||
| 1241 | } | ||
| 1242 | |||
| 1243 | if (base.mode & ATTR_STRUCK) { | ||
| 1244 | XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent / 3, | ||
| 1245 | width, 1); | ||
| 1246 | } | ||
| 1247 | |||
| 1248 | /* Reset clip to none. */ | ||
| 1249 | XftDrawSetClip(xw.draw, 0); | ||
| 1250 | } | ||
| 1251 | |||
| 1252 | void | ||
| 1253 | xdrawglyph(Glyph g, int x, int y) | ||
| 1254 | { | ||
| 1255 | int numspecs; | ||
| 1256 | XftGlyphFontSpec spec; | ||
| 1257 | |||
| 1258 | numspecs = xmakeglyphfontspecs(&spec, &g, 1, x, y); | ||
| 1259 | xdrawglyphfontspecs(&spec, g, numspecs, x, y); | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | void | ||
| 1263 | xdrawcursor(void) | ||
| 1264 | { | ||
| 1265 | static int oldx = 0, oldy = 0; | ||
| 1266 | int curx; | ||
| 1267 | Glyph g = {' ', ATTR_NULL, defaultbg, defaultcs}, og; | ||
| 1268 | int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); | ||
| 1269 | Color drawcol; | ||
| 1270 | |||
| 1271 | LIMIT(oldx, 0, term.col-1); | ||
| 1272 | LIMIT(oldy, 0, term.row-1); | ||
| 1273 | |||
| 1274 | curx = term.c.x; | ||
| 1275 | |||
| 1276 | /* adjust position if in dummy */ | ||
| 1277 | if (term.line[oldy][oldx].mode & ATTR_WDUMMY) | ||
| 1278 | oldx--; | ||
| 1279 | if (term.line[term.c.y][curx].mode & ATTR_WDUMMY) | ||
| 1280 | curx--; | ||
| 1281 | |||
| 1282 | /* remove the old cursor */ | ||
| 1283 | og = term.line[oldy][oldx]; | ||
| 1284 | if (ena_sel && selected(oldx, oldy)) | ||
| 1285 | og.mode ^= ATTR_REVERSE; | ||
| 1286 | xdrawglyph(og, oldx, oldy); | ||
| 1287 | |||
| 1288 | g.u = term.line[term.c.y][term.c.x].u; | ||
| 1289 | |||
| 1290 | /* | ||
| 1291 | * Select the right color for the right mode. | ||
| 1292 | */ | ||
| 1293 | if (IS_SET(MODE_REVERSE)) { | ||
| 1294 | g.mode |= ATTR_REVERSE; | ||
| 1295 | g.bg = defaultfg; | ||
| 1296 | if (ena_sel && selected(term.c.x, term.c.y)) { | ||
| 1297 | drawcol = dc.col[defaultcs]; | ||
| 1298 | g.fg = defaultrcs; | ||
| 1299 | } else { | ||
| 1300 | drawcol = dc.col[defaultrcs]; | ||
| 1301 | g.fg = defaultcs; | ||
| 1302 | } | ||
| 1303 | } else { | ||
| 1304 | if (ena_sel && selected(term.c.x, term.c.y)) { | ||
| 1305 | drawcol = dc.col[defaultrcs]; | ||
| 1306 | g.fg = defaultfg; | ||
| 1307 | g.bg = defaultrcs; | ||
| 1308 | } else { | ||
| 1309 | drawcol = dc.col[defaultcs]; | ||
| 1310 | } | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | if (IS_SET(MODE_HIDE)) | ||
| 1314 | return; | ||
| 1315 | |||
| 1316 | /* draw the new one */ | ||
| 1317 | if (win.state & WIN_FOCUSED) { | ||
| 1318 | switch (win.cursor) { | ||
| 1319 | case 7: /* st extension: snowman */ | ||
| 1320 | utf8decode("☃", &g.u, UTF_SIZ); | ||
| 1321 | case 0: /* Blinking Block */ | ||
| 1322 | case 1: /* Blinking Block (Default) */ | ||
| 1323 | case 2: /* Steady Block */ | ||
| 1324 | g.mode |= term.line[term.c.y][curx].mode & ATTR_WIDE; | ||
| 1325 | xdrawglyph(g, term.c.x, term.c.y); | ||
| 1326 | break; | ||
| 1327 | case 3: /* Blinking Underline */ | ||
| 1328 | case 4: /* Steady Underline */ | ||
| 1329 | XftDrawRect(xw.draw, &drawcol, | ||
| 1330 | borderpx + curx * win.cw, | ||
| 1331 | borderpx + (term.c.y + 1) * win.ch - \ | ||
| 1332 | cursorthickness, | ||
| 1333 | win.cw, cursorthickness); | ||
| 1334 | break; | ||
| 1335 | case 5: /* Blinking bar */ | ||
| 1336 | case 6: /* Steady bar */ | ||
| 1337 | XftDrawRect(xw.draw, &drawcol, | ||
| 1338 | borderpx + curx * win.cw, | ||
| 1339 | borderpx + term.c.y * win.ch, | ||
| 1340 | cursorthickness, win.ch); | ||
| 1341 | break; | ||
| 1342 | } | ||
| 1343 | } else { | ||
| 1344 | XftDrawRect(xw.draw, &drawcol, | ||
| 1345 | borderpx + curx * win.cw, | ||
| 1346 | borderpx + term.c.y * win.ch, | ||
| 1347 | win.cw - 1, 1); | ||
| 1348 | XftDrawRect(xw.draw, &drawcol, | ||
| 1349 | borderpx + curx * win.cw, | ||
| 1350 | borderpx + term.c.y * win.ch, | ||
| 1351 | 1, win.ch - 1); | ||
| 1352 | XftDrawRect(xw.draw, &drawcol, | ||
| 1353 | borderpx + (curx + 1) * win.cw - 1, | ||
| 1354 | borderpx + term.c.y * win.ch, | ||
| 1355 | 1, win.ch - 1); | ||
| 1356 | XftDrawRect(xw.draw, &drawcol, | ||
| 1357 | borderpx + curx * win.cw, | ||
| 1358 | borderpx + (term.c.y + 1) * win.ch - 1, | ||
| 1359 | win.cw, 1); | ||
| 1360 | } | ||
| 1361 | oldx = curx, oldy = term.c.y; | ||
| 1362 | } | ||
| 1363 | |||
| 1364 | void | ||
| 1365 | xsetenv(void) | ||
| 1366 | { | ||
| 1367 | char buf[sizeof(long) * 8 + 1]; | ||
| 1368 | |||
| 1369 | snprintf(buf, sizeof(buf), "%lu", xw.win); | ||
| 1370 | setenv("WINDOWID", buf, 1); | ||
| 1371 | } | ||
| 1372 | |||
| 1373 | void | ||
| 1374 | xsettitle(char *p) | ||
| 1375 | { | ||
| 1376 | XTextProperty prop; | ||
| 1377 | |||
| 1378 | Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle, | ||
| 1379 | &prop); | ||
| 1380 | XSetWMName(xw.dpy, xw.win, &prop); | ||
| 1381 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); | ||
| 1382 | XFree(prop.value); | ||
| 1383 | } | ||
| 1384 | |||
| 1385 | void | ||
| 1386 | draw(void) | ||
| 1387 | { | ||
| 1388 | drawregion(0, 0, term.col, term.row); | ||
| 1389 | XCopyArea(xw.dpy, xw.buf, xw.win, dc.gc, 0, 0, win.w, | ||
| 1390 | win.h, 0, 0); | ||
| 1391 | XSetForeground(xw.dpy, dc.gc, | ||
| 1392 | dc.col[IS_SET(MODE_REVERSE)? | ||
| 1393 | defaultfg : defaultbg].pixel); | ||
| 1394 | } | ||
| 1395 | |||
| 1396 | void | ||
| 1397 | drawregion(int x1, int y1, int x2, int y2) | ||
| 1398 | { | ||
| 1399 | int i, x, y, ox, numspecs; | ||
| 1400 | Glyph base, new; | ||
| 1401 | XftGlyphFontSpec *specs; | ||
| 1402 | int ena_sel = sel.ob.x != -1 && sel.alt == IS_SET(MODE_ALTSCREEN); | ||
| 1403 | |||
| 1404 | if (!(win.state & WIN_VISIBLE)) | ||
| 1405 | return; | ||
| 1406 | |||
| 1407 | for (y = y1; y < y2; y++) { | ||
| 1408 | if (!term.dirty[y]) | ||
| 1409 | continue; | ||
| 1410 | |||
| 1411 | term.dirty[y] = 0; | ||
| 1412 | |||
| 1413 | specs = term.specbuf; | ||
| 1414 | numspecs = xmakeglyphfontspecs(specs, &term.line[y][x1], x2 - x1, x1, y); | ||
| 1415 | |||
| 1416 | i = ox = 0; | ||
| 1417 | for (x = x1; x < x2 && i < numspecs; x++) { | ||
| 1418 | new = term.line[y][x]; | ||
| 1419 | if (new.mode == ATTR_WDUMMY) | ||
| 1420 | continue; | ||
| 1421 | if (ena_sel && selected(x, y)) | ||
| 1422 | new.mode ^= ATTR_REVERSE; | ||
| 1423 | if (i > 0 && ATTRCMP(base, new)) { | ||
| 1424 | xdrawglyphfontspecs(specs, base, i, ox, y); | ||
| 1425 | specs += i; | ||
| 1426 | numspecs -= i; | ||
| 1427 | i = 0; | ||
| 1428 | } | ||
| 1429 | if (i == 0) { | ||
| 1430 | ox = x; | ||
| 1431 | base = new; | ||
| 1432 | } | ||
| 1433 | i++; | ||
| 1434 | } | ||
| 1435 | if (i > 0) | ||
| 1436 | xdrawglyphfontspecs(specs, base, i, ox, y); | ||
| 1437 | } | ||
| 1438 | xdrawcursor(); | ||
| 1439 | } | ||
| 1440 | |||
| 1441 | void | ||
| 1442 | expose(XEvent *ev) | ||
| 1443 | { | ||
| 1444 | redraw(); | ||
| 1445 | } | ||
| 1446 | |||
| 1447 | void | ||
| 1448 | visibility(XEvent *ev) | ||
| 1449 | { | ||
| 1450 | XVisibilityEvent *e = &ev->xvisibility; | ||
| 1451 | |||
| 1452 | MODBIT(win.state, e->state != VisibilityFullyObscured, WIN_VISIBLE); | ||
| 1453 | } | ||
| 1454 | |||
| 1455 | void | ||
| 1456 | unmap(XEvent *ev) | ||
| 1457 | { | ||
| 1458 | win.state &= ~WIN_VISIBLE; | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | void | ||
| 1462 | xsetpointermotion(int set) | ||
| 1463 | { | ||
| 1464 | MODBIT(xw.attrs.event_mask, set, PointerMotionMask); | ||
| 1465 | XChangeWindowAttributes(xw.dpy, xw.win, CWEventMask, &xw.attrs); | ||
| 1466 | } | ||
| 1467 | |||
| 1468 | void | ||
| 1469 | xseturgency(int add) | ||
| 1470 | { | ||
| 1471 | XWMHints *h = XGetWMHints(xw.dpy, xw.win); | ||
| 1472 | |||
| 1473 | MODBIT(h->flags, add, XUrgencyHint); | ||
| 1474 | XSetWMHints(xw.dpy, xw.win, h); | ||
| 1475 | XFree(h); | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | void | ||
| 1479 | xbell(int vol) | ||
| 1480 | { | ||
| 1481 | XkbBell(xw.dpy, xw.win, vol, (Atom)NULL); | ||
| 1482 | } | ||
| 1483 | |||
| 1484 | unsigned long | ||
| 1485 | xwinid(void) | ||
| 1486 | { | ||
| 1487 | return xw.win; | ||
| 1488 | } | ||
| 1489 | |||
| 1490 | void | ||
| 1491 | focus(XEvent *ev) | ||
| 1492 | { | ||
| 1493 | XFocusChangeEvent *e = &ev->xfocus; | ||
| 1494 | |||
| 1495 | if (e->mode == NotifyGrab) | ||
| 1496 | return; | ||
| 1497 | |||
| 1498 | if (ev->type == FocusIn) { | ||
| 1499 | XSetICFocus(xw.xic); | ||
| 1500 | win.state |= WIN_FOCUSED; | ||
| 1501 | xseturgency(0); | ||
| 1502 | if (IS_SET(MODE_FOCUS)) | ||
| 1503 | ttywrite("\033[I", 3); | ||
| 1504 | } else { | ||
| 1505 | XUnsetICFocus(xw.xic); | ||
| 1506 | win.state &= ~WIN_FOCUSED; | ||
| 1507 | if (IS_SET(MODE_FOCUS)) | ||
| 1508 | ttywrite("\033[O", 3); | ||
| 1509 | } | ||
| 1510 | } | ||
| 1511 | |||
| 1512 | void | ||
| 1513 | kpress(XEvent *ev) | ||
| 1514 | { | ||
| 1515 | XKeyEvent *e = &ev->xkey; | ||
| 1516 | KeySym ksym; | ||
| 1517 | char buf[32], *customkey; | ||
| 1518 | int len; | ||
| 1519 | Rune c; | ||
| 1520 | Status status; | ||
| 1521 | Shortcut *bp; | ||
| 1522 | |||
| 1523 | if (IS_SET(MODE_KBDLOCK)) | ||
| 1524 | return; | ||
| 1525 | |||
| 1526 | len = XmbLookupString(xw.xic, e, buf, sizeof buf, &ksym, &status); | ||
| 1527 | /* 1. shortcuts */ | ||
| 1528 | for (bp = shortcuts; bp < shortcuts + shortcutslen; bp++) { | ||
| 1529 | if (ksym == bp->keysym && match(bp->mod, e->state)) { | ||
| 1530 | bp->func(&(bp->arg)); | ||
| 1531 | return; | ||
| 1532 | } | ||
| 1533 | } | ||
| 1534 | |||
| 1535 | /* 2. custom keys from config.h */ | ||
| 1536 | if ((customkey = kmap(ksym, e->state))) { | ||
| 1537 | ttysend(customkey, strlen(customkey)); | ||
| 1538 | return; | ||
| 1539 | } | ||
| 1540 | |||
| 1541 | /* 3. composed string from input method */ | ||
| 1542 | if (len == 0) | ||
| 1543 | return; | ||
| 1544 | if (len == 1 && e->state & Mod1Mask) { | ||
| 1545 | if (IS_SET(MODE_8BIT)) { | ||
| 1546 | if (*buf < 0177) { | ||
| 1547 | c = *buf | 0x80; | ||
| 1548 | len = utf8encode(c, buf); | ||
| 1549 | } | ||
| 1550 | } else { | ||
| 1551 | buf[1] = buf[0]; | ||
| 1552 | buf[0] = '\033'; | ||
| 1553 | len = 2; | ||
| 1554 | } | ||
| 1555 | } | ||
| 1556 | ttysend(buf, len); | ||
| 1557 | } | ||
| 1558 | |||
| 1559 | |||
| 1560 | void | ||
| 1561 | cmessage(XEvent *e) | ||
| 1562 | { | ||
| 1563 | /* | ||
| 1564 | * See xembed specs | ||
| 1565 | * http://standards.freedesktop.org/xembed-spec/xembed-spec-latest.html | ||
| 1566 | */ | ||
| 1567 | if (e->xclient.message_type == xw.xembed && e->xclient.format == 32) { | ||
| 1568 | if (e->xclient.data.l[1] == XEMBED_FOCUS_IN) { | ||
| 1569 | win.state |= WIN_FOCUSED; | ||
| 1570 | xseturgency(0); | ||
| 1571 | } else if (e->xclient.data.l[1] == XEMBED_FOCUS_OUT) { | ||
| 1572 | win.state &= ~WIN_FOCUSED; | ||
| 1573 | } | ||
| 1574 | } else if (e->xclient.data.l[0] == xw.wmdeletewin) { | ||
| 1575 | /* Send SIGHUP to shell */ | ||
| 1576 | kill(pid, SIGHUP); | ||
| 1577 | exit(0); | ||
| 1578 | } | ||
| 1579 | } | ||
| 1580 | |||
| 1581 | void | ||
| 1582 | resize(XEvent *e) | ||
| 1583 | { | ||
| 1584 | if (e->xconfigure.width == win.w && e->xconfigure.height == win.h) | ||
| 1585 | return; | ||
| 1586 | |||
| 1587 | cresize(e->xconfigure.width, e->xconfigure.height); | ||
| 1588 | ttyresize(); | ||
| 1589 | } | ||
| 1590 | |||
| 1591 | void | ||
| 1592 | run(void) | ||
| 1593 | { | ||
| 1594 | XEvent ev; | ||
| 1595 | int w = win.w, h = win.h; | ||
| 1596 | fd_set rfd; | ||
| 1597 | int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; | ||
| 1598 | struct timespec drawtimeout, *tv = NULL, now, last, lastblink; | ||
| 1599 | long deltatime; | ||
| 1600 | |||
| 1601 | /* Waiting for window mapping */ | ||
| 1602 | do { | ||
| 1603 | XNextEvent(xw.dpy, &ev); | ||
| 1604 | /* | ||
| 1605 | * This XFilterEvent call is required because of XOpenIM. It | ||
| 1606 | * does filter out the key event and some client message for | ||
| 1607 | * the input method too. | ||
| 1608 | */ | ||
| 1609 | if (XFilterEvent(&ev, None)) | ||
| 1610 | continue; | ||
| 1611 | if (ev.type == ConfigureNotify) { | ||
| 1612 | w = ev.xconfigure.width; | ||
| 1613 | h = ev.xconfigure.height; | ||
| 1614 | } | ||
| 1615 | } while (ev.type != MapNotify); | ||
| 1616 | |||
| 1617 | cresize(w, h); | ||
| 1618 | ttynew(); | ||
| 1619 | ttyresize(); | ||
| 1620 | |||
| 1621 | clock_gettime(CLOCK_MONOTONIC, &last); | ||
| 1622 | lastblink = last; | ||
| 1623 | |||
| 1624 | for (xev = actionfps;;) { | ||
| 1625 | FD_ZERO(&rfd); | ||
| 1626 | FD_SET(cmdfd, &rfd); | ||
| 1627 | FD_SET(xfd, &rfd); | ||
| 1628 | |||
| 1629 | if (pselect(MAX(xfd, cmdfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { | ||
| 1630 | if (errno == EINTR) | ||
| 1631 | continue; | ||
| 1632 | die("select failed: %s\n", strerror(errno)); | ||
| 1633 | } | ||
| 1634 | if (FD_ISSET(cmdfd, &rfd)) { | ||
| 1635 | ttyread(); | ||
| 1636 | if (blinktimeout) { | ||
| 1637 | blinkset = tattrset(ATTR_BLINK); | ||
| 1638 | if (!blinkset) | ||
| 1639 | MODBIT(term.mode, 0, MODE_BLINK); | ||
| 1640 | } | ||
| 1641 | } | ||
| 1642 | |||
| 1643 | if (FD_ISSET(xfd, &rfd)) | ||
| 1644 | xev = actionfps; | ||
| 1645 | |||
| 1646 | clock_gettime(CLOCK_MONOTONIC, &now); | ||
| 1647 | drawtimeout.tv_sec = 0; | ||
| 1648 | drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; | ||
| 1649 | tv = &drawtimeout; | ||
| 1650 | |||
| 1651 | dodraw = 0; | ||
| 1652 | if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { | ||
| 1653 | tsetdirtattr(ATTR_BLINK); | ||
| 1654 | term.mode ^= MODE_BLINK; | ||
| 1655 | lastblink = now; | ||
| 1656 | dodraw = 1; | ||
| 1657 | } | ||
| 1658 | deltatime = TIMEDIFF(now, last); | ||
| 1659 | if (deltatime > 1000 / (xev ? xfps : actionfps)) { | ||
| 1660 | dodraw = 1; | ||
| 1661 | last = now; | ||
| 1662 | } | ||
| 1663 | |||
| 1664 | if (dodraw) { | ||
| 1665 | while (XPending(xw.dpy)) { | ||
| 1666 | XNextEvent(xw.dpy, &ev); | ||
| 1667 | if (XFilterEvent(&ev, None)) | ||
| 1668 | continue; | ||
| 1669 | if (handler[ev.type]) | ||
| 1670 | (handler[ev.type])(&ev); | ||
| 1671 | } | ||
| 1672 | |||
| 1673 | draw(); | ||
| 1674 | XFlush(xw.dpy); | ||
| 1675 | |||
| 1676 | if (xev && !FD_ISSET(xfd, &rfd)) | ||
| 1677 | xev--; | ||
| 1678 | if (!FD_ISSET(cmdfd, &rfd) && !FD_ISSET(xfd, &rfd)) { | ||
| 1679 | if (blinkset) { | ||
| 1680 | if (TIMEDIFF(now, lastblink) \ | ||
| 1681 | > blinktimeout) { | ||
| 1682 | drawtimeout.tv_nsec = 1000; | ||
| 1683 | } else { | ||
| 1684 | drawtimeout.tv_nsec = (1E6 * \ | ||
| 1685 | (blinktimeout - \ | ||
| 1686 | TIMEDIFF(now, | ||
| 1687 | lastblink))); | ||
| 1688 | } | ||
| 1689 | drawtimeout.tv_sec = \ | ||
| 1690 | drawtimeout.tv_nsec / 1E9; | ||
| 1691 | drawtimeout.tv_nsec %= (long)1E9; | ||
| 1692 | } else { | ||
| 1693 | tv = NULL; | ||
| 1694 | } | ||
| 1695 | } | ||
| 1696 | } | ||
| 1697 | } | ||
| 1698 | } | ||
| 1699 | |||
| 1700 | int | ||
| 1701 | main(int argc, char *argv[]) | ||
| 1702 | { | ||
| 1703 | xw.l = xw.t = 0; | ||
| 1704 | xw.isfixed = False; | ||
| 1705 | win.cursor = cursorshape; | ||
| 1706 | |||
| 1707 | ARGBEGIN { | ||
| 1708 | case 'a': | ||
| 1709 | allowaltscreen = 0; | ||
| 1710 | break; | ||
| 1711 | case 'c': | ||
| 1712 | opt_class = EARGF(usage()); | ||
| 1713 | break; | ||
| 1714 | case 'e': | ||
| 1715 | if (argc > 0) | ||
| 1716 | --argc, ++argv; | ||
| 1717 | goto run; | ||
| 1718 | case 'f': | ||
| 1719 | opt_font = EARGF(usage()); | ||
| 1720 | break; | ||
| 1721 | case 'g': | ||
| 1722 | xw.gm = XParseGeometry(EARGF(usage()), | ||
| 1723 | &xw.l, &xw.t, &cols, &rows); | ||
| 1724 | break; | ||
| 1725 | case 'i': | ||
| 1726 | xw.isfixed = 1; | ||
| 1727 | break; | ||
| 1728 | case 'o': | ||
| 1729 | opt_io = EARGF(usage()); | ||
| 1730 | break; | ||
| 1731 | case 'l': | ||
| 1732 | opt_line = EARGF(usage()); | ||
| 1733 | break; | ||
| 1734 | case 'n': | ||
| 1735 | opt_name = EARGF(usage()); | ||
| 1736 | break; | ||
| 1737 | case 't': | ||
| 1738 | case 'T': | ||
| 1739 | opt_title = EARGF(usage()); | ||
| 1740 | break; | ||
| 1741 | case 'w': | ||
| 1742 | opt_embed = EARGF(usage()); | ||
| 1743 | break; | ||
| 1744 | case 'v': | ||
| 1745 | die("%s " VERSION " (c) 2010-2016 st engineers\n", argv0); | ||
| 1746 | break; | ||
| 1747 | default: | ||
| 1748 | usage(); | ||
| 1749 | } ARGEND; | ||
| 1750 | |||
| 1751 | run: | ||
| 1752 | if (argc > 0) { | ||
| 1753 | /* eat all remaining arguments */ | ||
| 1754 | opt_cmd = argv; | ||
| 1755 | if (!opt_title && !opt_line) | ||
| 1756 | opt_title = basename(xstrdup(argv[0])); | ||
| 1757 | } | ||
| 1758 | setlocale(LC_CTYPE, ""); | ||
| 1759 | XSetLocaleModifiers(""); | ||
| 1760 | tnew(MAX(cols, 1), MAX(rows, 1)); | ||
| 1761 | xinit(); | ||
| 1762 | selinit(); | ||
| 1763 | run(); | ||
| 1764 | |||
| 1765 | return 0; | ||
| 1766 | } | ||
