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.

driver_x11.c 18 KiB

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