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