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.
 
 
 
 
 
 

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