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.
 
 
 
 
 
 

461 lines
14 KiB

  1. /*
  2. * libcaca 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 <string.h>
  28. #include <stdlib.h>
  29. #if defined(HAVE_UNISTD_H)
  30. # include <unistd.h>
  31. #endif
  32. #include <stdarg.h>
  33. #include "caca.h"
  34. #include "caca_internals.h"
  35. #include "cucul.h"
  36. #include "cucul_internals.h"
  37. /*
  38. * Local functions
  39. */
  40. static int x11_error_handler(Display *, XErrorEvent *);
  41. static int x11_init_graphics(caca_t *kk)
  42. {
  43. static int const x11_palette[] =
  44. {
  45. /* Standard curses colours */
  46. 0x0, 0x0, 0x0,
  47. 0x0, 0x0, 0x8000,
  48. 0x0, 0x8000, 0x0,
  49. 0x0, 0x8000, 0x8000,
  50. 0x8000, 0x0, 0x0,
  51. 0x8000, 0x0, 0x8000,
  52. 0x8000, 0x8000, 0x0,
  53. 0x8000, 0x8000, 0x8000,
  54. /* Extra values for xterm-16color */
  55. 0x4000, 0x4000, 0x4000,
  56. 0x4000, 0x4000, 0xffff,
  57. 0x4000, 0xffff, 0x4000,
  58. 0x4000, 0xffff, 0xffff,
  59. 0xffff, 0x4000, 0x4000,
  60. 0xffff, 0x4000, 0xffff,
  61. 0xffff, 0xffff, 0x4000,
  62. 0xffff, 0xffff, 0xffff,
  63. };
  64. Colormap colormap;
  65. XSetWindowAttributes x11_winattr;
  66. int (*old_error_handler)(Display *, XErrorEvent *);
  67. char const *fonts[] = { NULL, "8x13bold", "fixed" }, **parser;
  68. char const *geometry;
  69. unsigned int width = 0, height = 0;
  70. int i;
  71. geometry = getenv("CACA_GEOMETRY");
  72. if(geometry && *(geometry))
  73. sscanf(geometry, "%ux%u", &width, &height);
  74. if(width && height)
  75. cucul_set_size(kk->qq, width, height);
  76. kk->x11.dpy = XOpenDisplay(NULL);
  77. if(kk->x11.dpy == NULL)
  78. return -1;
  79. fonts[0] = getenv("CACA_FONT");
  80. if(fonts[0] && *fonts[0])
  81. parser = fonts;
  82. else
  83. parser = fonts + 1;
  84. /* Ignore font errors */
  85. old_error_handler = XSetErrorHandler(x11_error_handler);
  86. /* Parse our font list */
  87. for( ; ; parser++)
  88. {
  89. if(!*parser)
  90. {
  91. XSetErrorHandler(old_error_handler);
  92. XCloseDisplay(kk->x11.dpy);
  93. return -1;
  94. }
  95. kk->x11.font = XLoadFont(kk->x11.dpy, *parser);
  96. if(!kk->x11.font)
  97. continue;
  98. kk->x11.font_struct = XQueryFont(kk->x11.dpy, kk->x11.font);
  99. if(!kk->x11.font_struct)
  100. {
  101. XUnloadFont(kk->x11.dpy, kk->x11.font);
  102. continue;
  103. }
  104. break;
  105. }
  106. /* Reset the default X11 error handler */
  107. XSetErrorHandler(old_error_handler);
  108. kk->x11.font_width = kk->x11.font_struct->max_bounds.width;
  109. kk->x11.font_height = kk->x11.font_struct->max_bounds.ascent
  110. + kk->x11.font_struct->max_bounds.descent;
  111. kk->x11.font_offset = kk->x11.font_struct->max_bounds.descent;
  112. colormap = DefaultColormap(kk->x11.dpy, DefaultScreen(kk->x11.dpy));
  113. for(i = 0; i < 16; i++)
  114. {
  115. XColor color;
  116. color.red = x11_palette[i * 3];
  117. color.green = x11_palette[i * 3 + 1];
  118. color.blue = x11_palette[i * 3 + 2];
  119. XAllocColor(kk->x11.dpy, colormap, &color);
  120. kk->x11.colors[i] = color.pixel;
  121. }
  122. x11_winattr.backing_store = Always;
  123. x11_winattr.background_pixel = kk->x11.colors[0];
  124. x11_winattr.event_mask = ExposureMask | StructureNotifyMask;
  125. kk->x11.window =
  126. XCreateWindow(kk->x11.dpy, DefaultRootWindow(kk->x11.dpy), 0, 0,
  127. kk->qq->width * kk->x11.font_width,
  128. kk->qq->height * kk->x11.font_height,
  129. 0, 0, InputOutput, 0,
  130. CWBackingStore | CWBackPixel | CWEventMask,
  131. &x11_winattr);
  132. XStoreName(kk->x11.dpy, kk->x11.window, "caca for X");
  133. XSelectInput(kk->x11.dpy, kk->x11.window, StructureNotifyMask);
  134. XMapWindow(kk->x11.dpy, kk->x11.window);
  135. kk->x11.gc = XCreateGC(kk->x11.dpy, kk->x11.window, 0, NULL);
  136. XSetForeground(kk->x11.dpy, kk->x11.gc, kk->x11.colors[15]);
  137. XSetFont(kk->x11.dpy, kk->x11.gc, kk->x11.font);
  138. for(;;)
  139. {
  140. XEvent xevent;
  141. XNextEvent(kk->x11.dpy, &xevent);
  142. if (xevent.type == MapNotify)
  143. break;
  144. }
  145. #if defined(HAVE_X11_XKBLIB_H)
  146. /* Disable autorepeat */
  147. XkbSetDetectableAutoRepeat(kk->x11.dpy, True, &kk->x11.autorepeat);
  148. if(!kk->x11.autorepeat)
  149. XAutoRepeatOff(kk->x11.dpy);
  150. #endif
  151. kk->x11.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask
  152. | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask
  153. | ExposureMask;
  154. XSelectInput(kk->x11.dpy, kk->x11.window, kk->x11.event_mask);
  155. XSync(kk->x11.dpy, False);
  156. kk->x11.pixmap = XCreatePixmap(kk->x11.dpy, kk->x11.window,
  157. kk->qq->width * kk->x11.font_width,
  158. kk->qq->height * kk->x11.font_height,
  159. DefaultDepth(kk->x11.dpy,
  160. DefaultScreen(kk->x11.dpy)));
  161. kk->x11.new_width = kk->x11.new_height = 0;
  162. return 0;
  163. }
  164. static int x11_end_graphics(caca_t *kk)
  165. {
  166. XSync(kk->x11.dpy, False);
  167. #if defined(HAVE_X11_XKBLIB_H)
  168. if(!kk->x11.autorepeat)
  169. XAutoRepeatOn(kk->x11.dpy);
  170. #endif
  171. XFreePixmap(kk->x11.dpy, kk->x11.pixmap);
  172. XFreeFont(kk->x11.dpy, kk->x11.font_struct);
  173. XFreeGC(kk->x11.dpy, kk->x11.gc);
  174. XUnmapWindow(kk->x11.dpy, kk->x11.window);
  175. XDestroyWindow(kk->x11.dpy, kk->x11.window);
  176. XCloseDisplay(kk->x11.dpy);
  177. return 0;
  178. }
  179. static int x11_set_window_title(caca_t *kk, char const *title)
  180. {
  181. XStoreName(kk->x11.dpy, kk->x11.window, title);
  182. return 0;
  183. }
  184. static unsigned int x11_get_window_width(caca_t *kk)
  185. {
  186. return kk->qq->width * kk->x11.font_width;
  187. }
  188. static unsigned int x11_get_window_height(caca_t *kk)
  189. {
  190. return kk->qq->height * kk->x11.font_height;
  191. }
  192. static void x11_display(caca_t *kk)
  193. {
  194. unsigned int x, y, len;
  195. /* First draw the background colours. Splitting the process in two
  196. * loops like this is actually slightly faster. */
  197. for(y = 0; y < kk->qq->height; y++)
  198. {
  199. for(x = 0; x < kk->qq->width; x += len)
  200. {
  201. uint8_t *attr = kk->qq->attr + x + y * kk->qq->width;
  202. len = 1;
  203. while(x + len < kk->qq->width
  204. && (attr[len] >> 4) == (attr[0] >> 4))
  205. len++;
  206. XSetForeground(kk->x11.dpy, kk->x11.gc,
  207. kk->x11.colors[attr[0] >> 4]);
  208. XFillRectangle(kk->x11.dpy, kk->x11.pixmap, kk->x11.gc,
  209. x * kk->x11.font_width, y * kk->x11.font_height,
  210. len * kk->x11.font_width, kk->x11.font_height);
  211. }
  212. }
  213. /* Then print the foreground characters */
  214. for(y = 0; y < kk->qq->height; y++)
  215. {
  216. for(x = 0; x < kk->qq->width; x += len)
  217. {
  218. char buffer[BUFSIZ]; /* FIXME: use a smaller buffer */
  219. uint32_t *chars = kk->qq->chars + x + y * kk->qq->width;
  220. uint8_t *attr = kk->qq->attr + x + y * kk->qq->width;
  221. len = 1;
  222. /* Skip spaces */
  223. if(chars[0] == ' ')
  224. continue;
  225. buffer[0] = chars[0] & 0x7f;
  226. while(x + len < kk->qq->width
  227. && (attr[len] & 0xf) == (attr[0] & 0xf))
  228. {
  229. buffer[len] = chars[len] & 0x7f;
  230. len++;
  231. }
  232. XSetForeground(kk->x11.dpy, kk->x11.gc, kk->x11.colors[attr[0] & 0xf]);
  233. XDrawString(kk->x11.dpy, kk->x11.pixmap, kk->x11.gc,
  234. x * kk->x11.font_width,
  235. (y + 1) * kk->x11.font_height - kk->x11.font_offset,
  236. buffer, len);
  237. }
  238. }
  239. XCopyArea(kk->x11.dpy, kk->x11.pixmap, kk->x11.window, kk->x11.gc, 0, 0,
  240. kk->qq->width * kk->x11.font_width,
  241. kk->qq->height * kk->x11.font_height,
  242. 0, 0);
  243. XFlush(kk->x11.dpy);
  244. }
  245. static void x11_handle_resize(caca_t *kk, unsigned int *new_width,
  246. unsigned int *new_height)
  247. {
  248. Pixmap new_pixmap;
  249. *new_width = kk->x11.new_width;
  250. *new_height = kk->x11.new_height;
  251. new_pixmap = XCreatePixmap(kk->x11.dpy, kk->x11.window,
  252. kk->qq->width * kk->x11.font_width,
  253. kk->qq->height * kk->x11.font_height,
  254. DefaultDepth(kk->x11.dpy,
  255. DefaultScreen(kk->x11.dpy)));
  256. XCopyArea(kk->x11.dpy, kk->x11.pixmap, new_pixmap, kk->x11.gc, 0, 0,
  257. kk->qq->width * kk->x11.font_width,
  258. kk->qq->height * kk->x11.font_height, 0, 0);
  259. XFreePixmap(kk->x11.dpy, kk->x11.pixmap);
  260. kk->x11.pixmap = new_pixmap;
  261. }
  262. static unsigned int x11_get_event(caca_t *kk)
  263. {
  264. unsigned int event = 0;
  265. XEvent xevent;
  266. char key;
  267. while(XCheckWindowEvent(kk->x11.dpy, kk->x11.window,
  268. kk->x11.event_mask, &xevent) == True)
  269. {
  270. KeySym keysym;
  271. /* Expose event */
  272. if(xevent.type == Expose)
  273. {
  274. XCopyArea(kk->x11.dpy, kk->x11.pixmap,
  275. kk->x11.window, kk->x11.gc, 0, 0,
  276. kk->qq->width * kk->x11.font_width,
  277. kk->qq->height * kk->x11.font_height, 0, 0);
  278. continue;
  279. }
  280. /* Resize event */
  281. if(xevent.type == ConfigureNotify)
  282. {
  283. unsigned int w, h;
  284. w = (xevent.xconfigure.width + kk->x11.font_width / 3)
  285. / kk->x11.font_width;
  286. h = (xevent.xconfigure.height + kk->x11.font_height / 3)
  287. / kk->x11.font_height;
  288. if(!w || !h || (w == kk->qq->width && h == kk->qq->height))
  289. continue;
  290. kk->x11.new_width = w;
  291. kk->x11.new_height = h;
  292. /* If we are already resizing, ignore the new signal */
  293. if(kk->resize)
  294. continue;
  295. kk->resize = 1;
  296. return CACA_EVENT_RESIZE;
  297. }
  298. /* Check for mouse motion events */
  299. if(xevent.type == MotionNotify)
  300. {
  301. unsigned int newx = xevent.xmotion.x / kk->x11.font_width;
  302. unsigned int newy = xevent.xmotion.y / kk->x11.font_height;
  303. if(newx >= kk->qq->width)
  304. newx = kk->qq->width - 1;
  305. if(newy >= kk->qq->height)
  306. newy = kk->qq->height - 1;
  307. if(kk->mouse_x == newx && kk->mouse_y == newy)
  308. continue;
  309. kk->mouse_x = newx;
  310. kk->mouse_y = newy;
  311. return CACA_EVENT_MOUSE_MOTION | (kk->mouse_x << 12) | kk->mouse_y;
  312. }
  313. /* Check for mouse press and release events */
  314. if(xevent.type == ButtonPress)
  315. return CACA_EVENT_MOUSE_PRESS
  316. | ((XButtonEvent *)&xevent)->button;
  317. if(xevent.type == ButtonRelease)
  318. return CACA_EVENT_MOUSE_RELEASE
  319. | ((XButtonEvent *)&xevent)->button;
  320. /* Check for key press and release events */
  321. if(xevent.type == KeyPress)
  322. event |= CACA_EVENT_KEY_PRESS;
  323. else if(xevent.type == KeyRelease)
  324. event |= CACA_EVENT_KEY_RELEASE;
  325. else
  326. continue;
  327. if(XLookupString(&xevent.xkey, &key, 1, NULL, NULL))
  328. return event | key;
  329. keysym = XKeycodeToKeysym(kk->x11.dpy, xevent.xkey.keycode, 0);
  330. switch(keysym)
  331. {
  332. case XK_F1: return event | CACA_KEY_F1;
  333. case XK_F2: return event | CACA_KEY_F2;
  334. case XK_F3: return event | CACA_KEY_F3;
  335. case XK_F4: return event | CACA_KEY_F4;
  336. case XK_F5: return event | CACA_KEY_F5;
  337. case XK_F6: return event | CACA_KEY_F6;
  338. case XK_F7: return event | CACA_KEY_F7;
  339. case XK_F8: return event | CACA_KEY_F8;
  340. case XK_F9: return event | CACA_KEY_F9;
  341. case XK_F10: return event | CACA_KEY_F10;
  342. case XK_F11: return event | CACA_KEY_F11;
  343. case XK_F12: return event | CACA_KEY_F12;
  344. case XK_F13: return event | CACA_KEY_F13;
  345. case XK_F14: return event | CACA_KEY_F14;
  346. case XK_F15: return event | CACA_KEY_F15;
  347. case XK_Left: return event | CACA_KEY_LEFT;
  348. case XK_Right: return event | CACA_KEY_RIGHT;
  349. case XK_Up: return event | CACA_KEY_UP;
  350. case XK_Down: return event | CACA_KEY_DOWN;
  351. default: return CACA_EVENT_NONE;
  352. }
  353. }
  354. return CACA_EVENT_NONE;
  355. }
  356. /*
  357. * XXX: following functions are local
  358. */
  359. static int x11_error_handler(Display *dpy, XErrorEvent *xevent)
  360. {
  361. /* Ignore the error */
  362. return 0;
  363. }
  364. /*
  365. * Driver initialisation
  366. */
  367. void x11_init_driver(caca_t *kk)
  368. {
  369. kk->driver.driver = CACA_DRIVER_X11;
  370. kk->driver.init_graphics = x11_init_graphics;
  371. kk->driver.end_graphics = x11_end_graphics;
  372. kk->driver.set_window_title = x11_set_window_title;
  373. kk->driver.get_window_width = x11_get_window_width;
  374. kk->driver.get_window_height = x11_get_window_height;
  375. kk->driver.display = x11_display;
  376. kk->driver.handle_resize = x11_handle_resize;
  377. kk->driver.get_event = x11_get_event;
  378. }
  379. #endif /* USE_X11 */