25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

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