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

585 line
19 KiB

  1. /*
  2. * libcaca Colour ASCII-Art library
  3. * Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
  4. * All Rights Reserved
  5. *
  6. * $Id$
  7. *
  8. * This library is free software; you can redistribute it and/or
  9. * modify it under the terms of the Do What The Fuck You Want To
  10. * Public License, Version 2, as published by Sam Hocevar. See
  11. * http://sam.zoy.org/wtfpl/COPYING for more details.
  12. */
  13. /*
  14. * This file contains the libcaca X11 input and output driver
  15. */
  16. #include "config.h"
  17. #if defined(USE_X11)
  18. #include <X11/Xlib.h>
  19. #include <X11/Xutil.h>
  20. #include <X11/keysym.h>
  21. #if defined(HAVE_X11_XKBLIB_H)
  22. # include <X11/XKBlib.h>
  23. #endif
  24. #include <stdio.h> /* BUFSIZ */
  25. #include <stdlib.h>
  26. #include "caca.h"
  27. #include "caca_internals.h"
  28. #include "cucul.h"
  29. #include "cucul_internals.h"
  30. /*
  31. * Local functions
  32. */
  33. static int x11_error_handler(Display *, XErrorEvent *);
  34. struct driver_private
  35. {
  36. Display *dpy;
  37. Window window;
  38. Pixmap pixmap;
  39. GC gc;
  40. long int event_mask;
  41. int font_width, font_height;
  42. int colors[4096];
  43. Font font;
  44. XFontStruct *font_struct;
  45. int font_offset;
  46. Cursor pointer;
  47. #if defined(HAVE_X11_XKBLIB_H)
  48. Bool autorepeat;
  49. #endif
  50. };
  51. static int x11_init_graphics(caca_t *kk)
  52. {
  53. Colormap colormap;
  54. XSetWindowAttributes x11_winattr;
  55. int (*old_error_handler)(Display *, XErrorEvent *);
  56. char const *fonts[] = { NULL, "8x13bold", "fixed" }, **parser;
  57. char const *geometry;
  58. unsigned int width = 0, height = 0;
  59. int i;
  60. kk->drv.p = malloc(sizeof(struct driver_private));
  61. #if defined(HAVE_GETENV)
  62. geometry = getenv("CACA_GEOMETRY");
  63. if(geometry && *geometry)
  64. sscanf(geometry, "%ux%u", &width, &height);
  65. #endif
  66. if(width && height)
  67. _cucul_set_size(kk->qq, width, height);
  68. kk->drv.p->dpy = XOpenDisplay(NULL);
  69. if(kk->drv.p->dpy == NULL)
  70. return -1;
  71. #if defined(HAVE_GETENV)
  72. fonts[0] = getenv("CACA_FONT");
  73. if(fonts[0] && *fonts[0])
  74. parser = fonts;
  75. else
  76. #endif
  77. parser = fonts + 1;
  78. /* Ignore font errors */
  79. old_error_handler = XSetErrorHandler(x11_error_handler);
  80. /* Parse our font list */
  81. for( ; ; parser++)
  82. {
  83. if(!*parser)
  84. {
  85. XSetErrorHandler(old_error_handler);
  86. XCloseDisplay(kk->drv.p->dpy);
  87. return -1;
  88. }
  89. kk->drv.p->font = XLoadFont(kk->drv.p->dpy, *parser);
  90. if(!kk->drv.p->font)
  91. continue;
  92. kk->drv.p->font_struct = XQueryFont(kk->drv.p->dpy, kk->drv.p->font);
  93. if(!kk->drv.p->font_struct)
  94. {
  95. XUnloadFont(kk->drv.p->dpy, kk->drv.p->font);
  96. continue;
  97. }
  98. break;
  99. }
  100. /* Reset the default X11 error handler */
  101. XSetErrorHandler(old_error_handler);
  102. kk->drv.p->font_width = kk->drv.p->font_struct->max_bounds.width;
  103. kk->drv.p->font_height = kk->drv.p->font_struct->max_bounds.ascent
  104. + kk->drv.p->font_struct->max_bounds.descent;
  105. kk->drv.p->font_offset = kk->drv.p->font_struct->max_bounds.descent;
  106. colormap = DefaultColormap(kk->drv.p->dpy, DefaultScreen(kk->drv.p->dpy));
  107. for(i = 0x000; i < 0x1000; i++)
  108. {
  109. XColor color;
  110. color.red = ((i & 0xf00) >> 8) * 0x1111;
  111. color.green = ((i & 0x0f0) >> 4) * 0x1111;
  112. color.blue = (i & 0x00f) * 0x1111;
  113. XAllocColor(kk->drv.p->dpy, colormap, &color);
  114. kk->drv.p->colors[i] = color.pixel;
  115. }
  116. x11_winattr.backing_store = Always;
  117. x11_winattr.background_pixel = kk->drv.p->colors[0x000];
  118. x11_winattr.event_mask = ExposureMask | StructureNotifyMask;
  119. kk->drv.p->window =
  120. XCreateWindow(kk->drv.p->dpy, DefaultRootWindow(kk->drv.p->dpy), 0, 0,
  121. kk->qq->width * kk->drv.p->font_width,
  122. kk->qq->height * kk->drv.p->font_height,
  123. 0, 0, InputOutput, 0,
  124. CWBackingStore | CWBackPixel | CWEventMask,
  125. &x11_winattr);
  126. XStoreName(kk->drv.p->dpy, kk->drv.p->window, "caca for X");
  127. XSelectInput(kk->drv.p->dpy, kk->drv.p->window, StructureNotifyMask);
  128. XMapWindow(kk->drv.p->dpy, kk->drv.p->window);
  129. kk->drv.p->gc = XCreateGC(kk->drv.p->dpy, kk->drv.p->window, 0, NULL);
  130. XSetForeground(kk->drv.p->dpy, kk->drv.p->gc, kk->drv.p->colors[0x888]);
  131. XSetFont(kk->drv.p->dpy, kk->drv.p->gc, kk->drv.p->font);
  132. for(;;)
  133. {
  134. XEvent xevent;
  135. XNextEvent(kk->drv.p->dpy, &xevent);
  136. if (xevent.type == MapNotify)
  137. break;
  138. }
  139. #if defined(HAVE_X11_XKBLIB_H)
  140. /* Disable autorepeat */
  141. XkbSetDetectableAutoRepeat(kk->drv.p->dpy, True, &kk->drv.p->autorepeat);
  142. if(!kk->drv.p->autorepeat)
  143. XAutoRepeatOff(kk->drv.p->dpy);
  144. #endif
  145. kk->drv.p->event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask
  146. | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask
  147. | ExposureMask;
  148. XSelectInput(kk->drv.p->dpy, kk->drv.p->window, kk->drv.p->event_mask);
  149. XSync(kk->drv.p->dpy, False);
  150. kk->drv.p->pixmap = XCreatePixmap(kk->drv.p->dpy, kk->drv.p->window,
  151. kk->qq->width * kk->drv.p->font_width,
  152. kk->qq->height * kk->drv.p->font_height,
  153. DefaultDepth(kk->drv.p->dpy,
  154. DefaultScreen(kk->drv.p->dpy)));
  155. kk->drv.p->pointer = None;
  156. return 0;
  157. }
  158. static int x11_end_graphics(caca_t *kk)
  159. {
  160. XSync(kk->drv.p->dpy, False);
  161. #if defined(HAVE_X11_XKBLIB_H)
  162. if(!kk->drv.p->autorepeat)
  163. XAutoRepeatOn(kk->drv.p->dpy);
  164. #endif
  165. XFreePixmap(kk->drv.p->dpy, kk->drv.p->pixmap);
  166. XFreeFont(kk->drv.p->dpy, kk->drv.p->font_struct);
  167. XFreeGC(kk->drv.p->dpy, kk->drv.p->gc);
  168. XUnmapWindow(kk->drv.p->dpy, kk->drv.p->window);
  169. XDestroyWindow(kk->drv.p->dpy, kk->drv.p->window);
  170. XCloseDisplay(kk->drv.p->dpy);
  171. free(kk->drv.p);
  172. return 0;
  173. }
  174. static int x11_set_window_title(caca_t *kk, char const *title)
  175. {
  176. XStoreName(kk->drv.p->dpy, kk->drv.p->window, title);
  177. return 0;
  178. }
  179. static unsigned int x11_get_window_width(caca_t *kk)
  180. {
  181. return kk->qq->width * kk->drv.p->font_width;
  182. }
  183. static unsigned int x11_get_window_height(caca_t *kk)
  184. {
  185. return kk->qq->height * kk->drv.p->font_height;
  186. }
  187. static void x11_display(caca_t *kk)
  188. {
  189. unsigned int x, y, len;
  190. /* First draw the background colours. Splitting the process in two
  191. * loops like this is actually slightly faster. */
  192. for(y = 0; y < kk->qq->height; y++)
  193. {
  194. for(x = 0; x < kk->qq->width; x += len)
  195. {
  196. uint32_t *attr = kk->qq->attr + x + y * kk->qq->width;
  197. uint16_t bg = _cucul_argb32_to_rgb12bg(*attr);
  198. len = 1;
  199. while(x + len < kk->qq->width
  200. && _cucul_argb32_to_rgb12bg(attr[len]) == bg)
  201. len++;
  202. XSetForeground(kk->drv.p->dpy, kk->drv.p->gc,
  203. kk->drv.p->colors[bg]);
  204. XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap, kk->drv.p->gc,
  205. x * kk->drv.p->font_width, y * kk->drv.p->font_height,
  206. len * kk->drv.p->font_width, kk->drv.p->font_height);
  207. }
  208. }
  209. /* Then print the foreground characters */
  210. for(y = 0; y < kk->qq->height; y++)
  211. {
  212. unsigned int yoff = (y + 1) * kk->drv.p->font_height
  213. - kk->drv.p->font_offset;
  214. uint32_t *chars = kk->qq->chars + y * kk->qq->width;
  215. for(x = 0; x < kk->qq->width; x++, chars++)
  216. {
  217. uint32_t *attr = kk->qq->attr + x + y * kk->qq->width;
  218. /* Skip spaces */
  219. if(*chars == 0x00000020)
  220. continue;
  221. XSetForeground(kk->drv.p->dpy, kk->drv.p->gc,
  222. kk->drv.p->colors[_cucul_argb32_to_rgb12fg(*attr)]);
  223. /* Plain ASCII, no problem. */
  224. if(*chars > 0x00000020 && *chars < 0x00000080)
  225. {
  226. char c = (uint8_t)*chars;
  227. XDrawString(kk->drv.p->dpy, kk->drv.p->pixmap, kk->drv.p->gc,
  228. x * kk->drv.p->font_width, yoff, &c, 1);
  229. continue;
  230. }
  231. /* We want to be able to print a few special Unicode characters
  232. * such as the CP437 gradients and half blocks. For unknown
  233. * characters, just print '?'. */
  234. switch(*chars)
  235. {
  236. case 0x00002580: /* ▀ */
  237. XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
  238. kk->drv.p->gc,
  239. x * kk->drv.p->font_width,
  240. y * kk->drv.p->font_height,
  241. kk->drv.p->font_width,
  242. kk->drv.p->font_height / 2);
  243. break;
  244. case 0x00002584: /* ▄ */
  245. XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
  246. kk->drv.p->gc,
  247. x * kk->drv.p->font_width,
  248. (y + 1) * kk->drv.p->font_height
  249. - kk->drv.p->font_height / 2,
  250. kk->drv.p->font_width,
  251. kk->drv.p->font_height / 2);
  252. break;
  253. case 0x00002588: /* █ */
  254. XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
  255. kk->drv.p->gc,
  256. x * kk->drv.p->font_width,
  257. y * kk->drv.p->font_height,
  258. kk->drv.p->font_width,
  259. kk->drv.p->font_height);
  260. break;
  261. case 0x0000258c: /* ▌ */
  262. XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
  263. kk->drv.p->gc,
  264. x * kk->drv.p->font_width,
  265. y * kk->drv.p->font_height,
  266. kk->drv.p->font_width / 2,
  267. kk->drv.p->font_height);
  268. break;
  269. case 0x00002590: /* ▐ */
  270. XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
  271. kk->drv.p->gc,
  272. (x + 1) * kk->drv.p->font_width
  273. - kk->drv.p->font_width / 2,
  274. y * kk->drv.p->font_height,
  275. kk->drv.p->font_width / 2,
  276. kk->drv.p->font_height);
  277. break;
  278. case 0x00002593: /* ▓ */
  279. case 0x00002592: /* ▒ */
  280. case 0x00002591: /* ░ */
  281. {
  282. /* FIXME: this sucks utterly */
  283. int i, j, k = *chars - 0x00002591;
  284. for(j = kk->drv.p->font_height; j--; )
  285. for(i = kk->drv.p->font_width; i--; )
  286. {
  287. if(((i + 2 * (j & 1)) & 3) > k)
  288. continue;
  289. XDrawPoint(kk->drv.p->dpy, kk->drv.p->pixmap,
  290. kk->drv.p->gc,
  291. x * kk->drv.p->font_width + i,
  292. y * kk->drv.p->font_height + j);
  293. }
  294. break;
  295. }
  296. default:
  297. {
  298. char c;
  299. c = '?';
  300. XDrawString(kk->drv.p->dpy, kk->drv.p->pixmap,
  301. kk->drv.p->gc,
  302. x * kk->drv.p->font_width, yoff, &c, 1);
  303. break;
  304. }
  305. }
  306. }
  307. }
  308. XCopyArea(kk->drv.p->dpy, kk->drv.p->pixmap, kk->drv.p->window,
  309. kk->drv.p->gc, 0, 0,
  310. kk->qq->width * kk->drv.p->font_width,
  311. kk->qq->height * kk->drv.p->font_height,
  312. 0, 0);
  313. XFlush(kk->drv.p->dpy);
  314. }
  315. static void x11_handle_resize(caca_t *kk)
  316. {
  317. Pixmap new_pixmap;
  318. new_pixmap = XCreatePixmap(kk->drv.p->dpy, kk->drv.p->window,
  319. kk->resize.w * kk->drv.p->font_width,
  320. kk->resize.h * kk->drv.p->font_height,
  321. DefaultDepth(kk->drv.p->dpy,
  322. DefaultScreen(kk->drv.p->dpy)));
  323. XCopyArea(kk->drv.p->dpy, kk->drv.p->pixmap, new_pixmap,
  324. kk->drv.p->gc, 0, 0,
  325. kk->resize.w * kk->drv.p->font_width,
  326. kk->resize.h * kk->drv.p->font_height, 0, 0);
  327. XFreePixmap(kk->drv.p->dpy, kk->drv.p->pixmap);
  328. kk->drv.p->pixmap = new_pixmap;
  329. }
  330. static int x11_get_event(caca_t *kk, caca_event_t *ev)
  331. {
  332. XEvent xevent;
  333. char key;
  334. while(XCheckWindowEvent(kk->drv.p->dpy, kk->drv.p->window,
  335. kk->drv.p->event_mask, &xevent) == True)
  336. {
  337. KeySym keysym;
  338. /* Expose event */
  339. if(xevent.type == Expose)
  340. {
  341. XCopyArea(kk->drv.p->dpy, kk->drv.p->pixmap,
  342. kk->drv.p->window, kk->drv.p->gc, 0, 0,
  343. kk->qq->width * kk->drv.p->font_width,
  344. kk->qq->height * kk->drv.p->font_height, 0, 0);
  345. continue;
  346. }
  347. /* Resize event */
  348. if(xevent.type == ConfigureNotify)
  349. {
  350. unsigned int w, h;
  351. w = (xevent.xconfigure.width + kk->drv.p->font_width / 3)
  352. / kk->drv.p->font_width;
  353. h = (xevent.xconfigure.height + kk->drv.p->font_height / 3)
  354. / kk->drv.p->font_height;
  355. if(!w || !h || (w == kk->qq->width && h == kk->qq->height))
  356. continue;
  357. kk->resize.w = w;
  358. kk->resize.h = h;
  359. kk->resize.resized = 1;
  360. continue;
  361. }
  362. /* Check for mouse motion events */
  363. if(xevent.type == MotionNotify)
  364. {
  365. unsigned int newx = xevent.xmotion.x / kk->drv.p->font_width;
  366. unsigned int newy = xevent.xmotion.y / kk->drv.p->font_height;
  367. if(newx >= kk->qq->width)
  368. newx = kk->qq->width - 1;
  369. if(newy >= kk->qq->height)
  370. newy = kk->qq->height - 1;
  371. if(kk->mouse.x == newx && kk->mouse.y == newy)
  372. continue;
  373. kk->mouse.x = newx;
  374. kk->mouse.y = newy;
  375. ev->type = CACA_EVENT_MOUSE_MOTION;
  376. ev->data.mouse.x = kk->mouse.x;
  377. ev->data.mouse.y = kk->mouse.y;
  378. return 1;
  379. }
  380. /* Check for mouse press and release events */
  381. if(xevent.type == ButtonPress)
  382. {
  383. ev->type = CACA_EVENT_MOUSE_PRESS;
  384. ev->data.mouse.button = ((XButtonEvent *)&xevent)->button;
  385. return 1;
  386. }
  387. if(xevent.type == ButtonRelease)
  388. {
  389. ev->type = CACA_EVENT_MOUSE_RELEASE;
  390. ev->data.mouse.button = ((XButtonEvent *)&xevent)->button;
  391. return 1;
  392. }
  393. /* Check for key press and release events */
  394. if(xevent.type == KeyPress)
  395. ev->type = CACA_EVENT_KEY_PRESS;
  396. else if(xevent.type == KeyRelease)
  397. ev->type = CACA_EVENT_KEY_RELEASE;
  398. else
  399. continue;
  400. if(XLookupString(&xevent.xkey, &key, 1, NULL, NULL))
  401. {
  402. ev->data.key.c = key;
  403. ev->data.key.ucs4 = key;
  404. ev->data.key.utf8[0] = key;
  405. ev->data.key.utf8[1] = '\0';
  406. return 1;
  407. }
  408. keysym = XKeycodeToKeysym(kk->drv.p->dpy, xevent.xkey.keycode, 0);
  409. switch(keysym)
  410. {
  411. case XK_F1: ev->data.key.c = CACA_KEY_F1; break;
  412. case XK_F2: ev->data.key.c = CACA_KEY_F2; break;
  413. case XK_F3: ev->data.key.c = CACA_KEY_F3; break;
  414. case XK_F4: ev->data.key.c = CACA_KEY_F4; break;
  415. case XK_F5: ev->data.key.c = CACA_KEY_F5; break;
  416. case XK_F6: ev->data.key.c = CACA_KEY_F6; break;
  417. case XK_F7: ev->data.key.c = CACA_KEY_F7; break;
  418. case XK_F8: ev->data.key.c = CACA_KEY_F8; break;
  419. case XK_F9: ev->data.key.c = CACA_KEY_F9; break;
  420. case XK_F10: ev->data.key.c = CACA_KEY_F10; break;
  421. case XK_F11: ev->data.key.c = CACA_KEY_F11; break;
  422. case XK_F12: ev->data.key.c = CACA_KEY_F12; break;
  423. case XK_F13: ev->data.key.c = CACA_KEY_F13; break;
  424. case XK_F14: ev->data.key.c = CACA_KEY_F14; break;
  425. case XK_F15: ev->data.key.c = CACA_KEY_F15; break;
  426. case XK_Left: ev->data.key.c = CACA_KEY_LEFT; break;
  427. case XK_Right: ev->data.key.c = CACA_KEY_RIGHT; break;
  428. case XK_Up: ev->data.key.c = CACA_KEY_UP; break;
  429. case XK_Down: ev->data.key.c = CACA_KEY_DOWN; break;
  430. default: ev->type = CACA_EVENT_NONE; return 0;
  431. }
  432. ev->data.key.ucs4 = 0;
  433. ev->data.key.utf8[0] = '\0';
  434. return 1;
  435. }
  436. ev->type = CACA_EVENT_NONE;
  437. return 0;
  438. }
  439. static void x11_set_mouse(caca_t *kk, int flags)
  440. {
  441. Cursor no_ptr;
  442. Pixmap bm_no;
  443. XColor black, dummy;
  444. Colormap colormap;
  445. static char const empty[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
  446. if(flags)
  447. {
  448. XDefineCursor(kk->drv.p->dpy,kk->drv.p->window, 0);
  449. return;
  450. }
  451. colormap = DefaultColormap(kk->drv.p->dpy, DefaultScreen(kk->drv.p->dpy));
  452. if(!XAllocNamedColor(kk->drv.p->dpy, colormap, "black", &black, &dummy))
  453. {
  454. return;
  455. }
  456. bm_no = XCreateBitmapFromData(kk->drv.p->dpy, kk->drv.p->window,
  457. empty, 8, 8);
  458. no_ptr = XCreatePixmapCursor(kk->drv.p->dpy, bm_no, bm_no,
  459. &black, &black, 0, 0);
  460. XDefineCursor(kk->drv.p->dpy, kk->drv.p->window, no_ptr);
  461. XFreeCursor(kk->drv.p->dpy, no_ptr);
  462. if(bm_no != None)
  463. XFreePixmap(kk->drv.p->dpy, bm_no);
  464. XFreeColors(kk->drv.p->dpy, colormap, &black.pixel, 1, 0);
  465. XSync(kk->drv.p->dpy, False);
  466. }
  467. /*
  468. * XXX: following functions are local
  469. */
  470. static int x11_error_handler(Display *dpy, XErrorEvent *xevent)
  471. {
  472. /* Ignore the error */
  473. return 0;
  474. }
  475. /*
  476. * Driver initialisation
  477. */
  478. int x11_install(caca_t *kk)
  479. {
  480. #if defined(HAVE_GETENV)
  481. if(!getenv("DISPLAY") || !*(getenv("DISPLAY")))
  482. return -1;
  483. #endif
  484. kk->drv.driver = CACA_DRIVER_X11;
  485. kk->drv.init_graphics = x11_init_graphics;
  486. kk->drv.end_graphics = x11_end_graphics;
  487. kk->drv.set_window_title = x11_set_window_title;
  488. kk->drv.get_window_width = x11_get_window_width;
  489. kk->drv.get_window_height = x11_get_window_height;
  490. kk->drv.display = x11_display;
  491. kk->drv.handle_resize = x11_handle_resize;
  492. kk->drv.get_event = x11_get_event;
  493. kk->drv.set_mouse = x11_set_mouse;
  494. return 0;
  495. }
  496. #endif /* USE_X11 */