|
- /*
- * libcaca ASCII-Art library
- * Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
- * All Rights Reserved
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the Do What The Fuck You Want To
- * Public License, Version 2, as published by Sam Hocevar. See
- * http://sam.zoy.org/wtfpl/COPYING for more details.
- */
-
- /** \file graphics.c
- * \version \$Id$
- * \author Sam Hocevar <sam@zoy.org>
- * \brief Character drawing
- *
- * This file contains character and string drawing functions.
- */
-
- #include "config.h"
-
- #if defined(USE_SLANG)
- # if defined(HAVE_SLANG_SLANG_H)
- # include <slang/slang.h>
- # else
- # include <slang.h>
- # endif
- #endif
- #if defined(USE_NCURSES)
- # if defined(HAVE_NCURSES_H)
- # include <ncurses.h>
- # else
- # include <curses.h>
- # endif
- #endif
- #if defined(USE_CONIO)
- # include <conio.h>
- # if defined(SCREENUPDATE_IN_PC_H)
- # include <pc.h>
- # endif
- #endif
- #if defined(USE_X11)
- # include <X11/Xlib.h>
- # if defined(HAVE_X11_XKBLIB_H)
- # include <X11/XKBlib.h>
- # endif
- #endif
- #if defined(USE_WIN32)
- # include <windows.h>
- #endif
- #if defined(USE_GL)
- # include <GL/gl.h>
- # include <GL/glut.h>
- # include <GL/freeglut_ext.h>
- #endif
- #if defined(HAVE_INTTYPES_H) || defined(_DOXYGEN_SKIP_ME)
- # include <inttypes.h>
- #else
- typedef unsigned int uint32_t;
- typedef unsigned char uint8_t;
- #endif
-
- #include <stdio.h> /* BUFSIZ */
- #include <string.h>
- #include <stdlib.h>
- #if defined(HAVE_UNISTD_H)
- # include <unistd.h>
- #endif
- #include <stdarg.h>
-
- #if defined(HAVE_SIGNAL_H)
- # include <signal.h>
- #endif
- #if defined(HAVE_SYS_IOCTL_H)
- # include <sys/ioctl.h>
- #endif
-
- #include "caca.h"
- #include "caca_internals.h"
- #include "cucul.h"
- #include "cucul_internals.h"
-
- /*
- * Global variables
- */
-
- #if defined(USE_SLANG)
- /* Tables generated by test/optipal.c */
- static int const slang_palette[2*16*16] =
- {
- 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0,
- 9, 0, 10, 0, 11, 0, 12, 0, 13, 0, 14, 0, 15, 0, 0, 8,
- 8, 7, 7, 8, 15, 7, 7, 15, 15, 9, 9, 15, 1, 9, 9, 1,
- 7, 9, 9, 7, 8, 1, 1, 8, 0, 1, 15, 10, 10, 15, 2, 10,
- 10, 2, 7, 10, 10, 7, 8, 2, 2, 8, 0, 2, 15, 11, 11, 15,
- 3, 11, 11, 3, 7, 11, 11, 7, 8, 3, 3, 8, 0, 3, 15, 12,
- 12, 15, 4, 12, 12, 4, 7, 12, 12, 7, 8, 4, 4, 8, 0, 4,
- 15, 13, 13, 15, 5, 13, 13, 5, 7, 13, 13, 7, 8, 5, 5, 8,
- 0, 5, 15, 14, 14, 15, 6, 14, 14, 6, 7, 14, 14, 7, 8, 6,
- 6, 8, 0, 6, 4, 6, 6, 4, 12, 14, 14, 12, 6, 2, 2, 6,
- 14, 10, 10, 14, 2, 3, 3, 2, 10, 11, 11, 10, 3, 1, 1, 3,
- 11, 9, 9, 11, 1, 5, 5, 1, 9, 13, 13, 9, 5, 4, 4, 5,
- 13, 12, 12, 13, 4, 14, 6, 12, 12, 6, 14, 4, 6, 10, 2, 14,
- 14, 2, 10, 6, 2, 11, 3, 10, 10, 3, 11, 2, 3, 9, 1, 11,
- 11, 1, 9, 3, 1, 13, 5, 9, 9, 5, 13, 1, 5, 12, 4, 13,
- 13, 4, 12, 5, 0, 7, 0, 15, 15, 8, 8, 15, 15, 1, 7, 1,
- 1, 6, 2, 5, 3, 4, 4, 3, 5, 2, 6, 1, 0, 0, 1, 1,
- 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 2, 2, 3, 3,
- 4, 4, 5, 5, 6, 6, 7, 7, 14, 9, 1, 15, 8, 9, 8, 8,
- 9, 9, 1, 7, 0, 9, 9, 8, 6, 9, 13, 10, 2, 15, 8, 10,
- 7, 2, 15, 2, 2, 7, 0, 10, 10, 8, 5, 10, 12, 11, 3, 15,
- 8, 11, 7, 3, 15, 3, 3, 7, 0, 11, 11, 8, 4, 11, 11, 12,
- 4, 15, 8, 12, 7, 4, 15, 4, 4, 7, 0, 12, 12, 8, 3, 12,
- 10, 13, 5, 15, 8, 13, 7, 5, 15, 5, 5, 7, 0, 13, 13, 8,
- 2, 13, 9, 14, 6, 15, 8, 14, 7, 6, 15, 6, 6, 7, 0, 14,
- 14, 8, 1, 14, 5, 6, 2, 4, 13, 14, 10, 12, 4, 2, 3, 6,
- 12, 10, 11, 14, 6, 3, 1, 2, 14, 11, 9, 10, 2, 1, 5, 3,
- 10, 9, 13, 11, 3, 5, 4, 1, 11, 13, 12, 9, 1, 4, 6, 5,
- 9, 12, 14, 13, 5, 14, 2, 12, 13, 6, 10, 4, 4, 10, 3, 14,
- 12, 2, 11, 6, 6, 11, 1, 10, 14, 3, 9, 2, 2, 9, 5, 11,
- 10, 1, 13, 3, 3, 13, 4, 9, 11, 5, 12, 1, 1, 12, 6, 13,
- 9, 4, 14, 5, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15,
- };
-
- static int const slang_assoc[16*16] =
- {
- 134, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
- 28, 135, 214, 86, 219, 91, 133, 127, 26, 23, 240, 112, 245, 117, 141, 126,
- 37, 211, 142, 83, 206, 132, 78, 160, 35, 237, 32, 109, 232, 140, 104, 161,
- 46, 87, 82, 143, 131, 215, 210, 169, 44, 113, 108, 41, 139, 241, 236, 170,
- 55, 222, 203, 130, 144, 94, 75, 178, 53, 248, 229, 138, 50, 120, 101, 179,
- 64, 90, 129, 218, 95, 145, 223, 187, 62, 116, 137, 244, 121, 59, 249, 188,
- 73, 128, 79, 207, 74, 202, 146, 196, 71, 136, 105, 233, 100, 228, 68, 197,
- 122, 153, 162, 171, 180, 189, 198, 147, 16, 25, 34, 43, 52, 61, 70, 18,
- 15, 27, 36, 45, 54, 63, 72, 17, 151, 155, 164, 173, 182, 191, 200, 124,
- 154, 22, 238, 110, 243, 115, 156, 24, 150, 152, 216, 88, 221, 93, 148, 20,
- 163, 235, 31, 107, 230, 165, 102, 33, 159, 213, 250, 85, 208, 157, 80, 29,
- 172, 111, 106, 40, 174, 239, 234, 42, 168, 89, 84, 251, 166, 217, 212, 38,
- 181, 246, 227, 183, 49, 118, 99, 51, 177, 224, 205, 175, 252, 96, 77, 47,
- 190, 114, 192, 242, 119, 58, 247, 60, 186, 92, 184, 220, 97, 253, 225, 56,
- 199, 201, 103, 231, 98, 226, 67, 69, 195, 193, 81, 209, 76, 204, 254, 65,
- 123, 149, 158, 167, 176, 185, 194, 19, 125, 21, 30, 39, 48, 57, 66, 255,
- };
- #endif
-
- #if defined(USE_WIN32)
- static int const win32_fg_palette[] =
- {
- 0,
- FOREGROUND_BLUE,
- FOREGROUND_GREEN,
- FOREGROUND_GREEN | FOREGROUND_BLUE,
- FOREGROUND_RED,
- FOREGROUND_RED | FOREGROUND_BLUE,
- FOREGROUND_RED | FOREGROUND_GREEN,
- FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
- FOREGROUND_INTENSITY,
- FOREGROUND_INTENSITY | FOREGROUND_BLUE,
- FOREGROUND_INTENSITY | FOREGROUND_GREEN,
- FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE,
- FOREGROUND_INTENSITY | FOREGROUND_RED,
- FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE,
- FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN,
- FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
- };
-
- static int const win32_bg_palette[] =
- {
- 0,
- BACKGROUND_BLUE,
- BACKGROUND_GREEN,
- BACKGROUND_GREEN | BACKGROUND_BLUE,
- BACKGROUND_RED,
- BACKGROUND_RED | BACKGROUND_BLUE,
- BACKGROUND_RED | BACKGROUND_GREEN,
- BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
- BACKGROUND_INTENSITY,
- BACKGROUND_INTENSITY | BACKGROUND_BLUE,
- BACKGROUND_INTENSITY | BACKGROUND_GREEN,
- BACKGROUND_INTENSITY | BACKGROUND_GREEN | BACKGROUND_BLUE,
- BACKGROUND_INTENSITY | BACKGROUND_RED,
- BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_BLUE,
- BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN,
- BACKGROUND_INTENSITY | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
- };
- #endif
-
- #if defined(USE_GL)
- /* Ok, I just suck. */
- static GLbyte const gl_bgpal[][4] =
- {
- { 0x00, 0x00, 0x00, 0x7f },
- { 0x00, 0x00, 0x3f, 0x7f },
- { 0x00, 0x3f, 0x00, 0x7f },
- { 0x00, 0x3f, 0x3f, 0x7f },
- { 0x3f, 0x00, 0x00, 0x7f },
- { 0x3f, 0x00, 0x3f, 0x7f },
- { 0x3f, 0x3f, 0x00, 0x7f },
- { 0x3f, 0x3f, 0x3f, 0x7f },
- // + intensity
- { 0x00, 0x00, 0x00, 0x7f },
- { 0x00, 0x00, 0x7f, 0x7f },
- { 0x00, 0x7f, 0x00, 0x7f },
- { 0x00, 0x7f, 0x7f, 0x7f },
- { 0x7f, 0x00, 0x00, 0x7f },
- { 0x7f, 0x00, 0x7f, 0x7f },
- { 0x7f, 0x7f, 0x00, 0x7f },
- { 0x7f, 0x7f, 0x7f, 0x7f }
- };
-
- static caca_t *gl_kk; /* FIXME: we ought to get rid of this */
- #endif
-
- /*
- * Local functions
- */
- static void caca_handle_resize(caca_t *kk);
-
- #if defined(USE_SLANG)
- static void slang_init_palette(void);
- #endif
-
- #if defined(HAVE_SIGNAL) && (defined(USE_NCURSES) || defined(USE_SLANG))
- static RETSIGTYPE sigwinch_handler(int);
- static caca_t *sigwinch_kk; /* FIXME: we ought to get rid of this */
- #endif
-
- #if defined(USE_X11)
- static int x11_error_handler(Display *, XErrorEvent *);
- #endif
-
- #if defined(USE_GL)
- static void gl_handle_keyboard(unsigned char, int, int);
- static void gl_handle_special_key(int, int, int);
- static void gl_handle_reshape(int, int);
- static void gl_handle_mouse(int, int, int, int);
- static void gl_handle_mouse_motion(int, int);
- #endif
-
- #if !defined(_DOXYGEN_SKIP_ME)
- int _caca_init_graphics(caca_t *kk)
- {
- #if defined(HAVE_SIGNAL) && (defined(USE_NCURSES) || defined(USE_SLANG))
- sigwinch_kk = kk;
- signal(SIGWINCH, sigwinch_handler);
- #endif
-
- #if defined(USE_SLANG)
- if(kk->driver == CACA_DRIVER_SLANG)
- {
- slang_init_palette();
-
- /* Disable alt charset support so that we get a chance to have all
- * 256 colour pairs */
- SLtt_Has_Alt_Charset = 0;
-
- cucul_set_size(kk->qq, SLtt_Screen_Cols, SLtt_Screen_Rows);
- }
- else
- #endif
- #if defined(USE_NCURSES)
- if(kk->driver == CACA_DRIVER_NCURSES)
- {
- static int curses_colors[] =
- {
- /* Standard curses colours */
- COLOR_BLACK,
- COLOR_BLUE,
- COLOR_GREEN,
- COLOR_CYAN,
- COLOR_RED,
- COLOR_MAGENTA,
- COLOR_YELLOW,
- COLOR_WHITE,
- /* Extra values for xterm-16color */
- COLOR_BLACK + 8,
- COLOR_BLUE + 8,
- COLOR_GREEN + 8,
- COLOR_CYAN + 8,
- COLOR_RED + 8,
- COLOR_MAGENTA + 8,
- COLOR_YELLOW + 8,
- COLOR_WHITE + 8
- };
-
- int fg, bg, max;
-
- /* Activate colour */
- start_color();
-
- /* If COLORS == 16, it means the terminal supports full bright colours
- * using setab and setaf (will use \e[90m \e[91m etc. for colours >= 8),
- * we can build 16*16 colour pairs.
- * If COLORS == 8, it means the terminal does not know about bright
- * colours and we need to get them through A_BOLD and A_BLINK (\e[1m
- * and \e[5m). We can only build 8*8 colour pairs. */
- max = COLORS >= 16 ? 16 : 8;
-
- for(bg = 0; bg < max; bg++)
- for(fg = 0; fg < max; fg++)
- {
- /* Use ((max + 7 - fg) % max) instead of fg so that colour 0
- * is light gray on black. Some terminals don't like this
- * colour pair to be redefined. */
- int col = ((max + 7 - fg) % max) + max * bg;
- init_pair(col, curses_colors[fg], curses_colors[bg]);
- kk->ncurses.attr[fg + 16 * bg] = COLOR_PAIR(col);
-
- if(max == 8)
- {
- /* Bright fg on simple bg */
- kk->ncurses.attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
- /* Simple fg on bright bg */
- kk->ncurses.attr[fg + 16 * (bg + 8)] = A_BLINK
- | COLOR_PAIR(col);
- /* Bright fg on bright bg */
- kk->ncurses.attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
- | COLOR_PAIR(col);
- }
- }
-
- cucul_set_size(kk->qq, COLS, LINES);
- }
- else
- #endif
- #if defined(USE_CONIO)
- if(kk->driver == CACA_DRIVER_CONIO)
- {
- gettextinfo(&kk->conio.ti);
- kk->conio.screen = malloc(2 * kk->conio.ti.screenwidth
- * kk->conio.ti.screenheight * sizeof(char));
- if(kk->conio.screen == NULL)
- return -1;
- # if defined(SCREENUPDATE_IN_PC_H)
- ScreenRetrieve(kk->conio.screen);
- # else
- /* FIXME */
- # endif
- cucul_set_size(kk->qq, kk->conio.ti.screenwidth,
- kk->conio.ti.screenheight);
- }
- else
- #endif
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- static int const x11_palette[] =
- {
- /* Standard curses colours */
- 0x0, 0x0, 0x0,
- 0x0, 0x0, 0x8000,
- 0x0, 0x8000, 0x0,
- 0x0, 0x8000, 0x8000,
- 0x8000, 0x0, 0x0,
- 0x8000, 0x0, 0x8000,
- 0x8000, 0x8000, 0x0,
- 0x8000, 0x8000, 0x8000,
- /* Extra values for xterm-16color */
- 0x4000, 0x4000, 0x4000,
- 0x4000, 0x4000, 0xffff,
- 0x4000, 0xffff, 0x4000,
- 0x4000, 0xffff, 0xffff,
- 0xffff, 0x4000, 0x4000,
- 0xffff, 0x4000, 0xffff,
- 0xffff, 0xffff, 0x4000,
- 0xffff, 0xffff, 0xffff,
- };
-
- Colormap colormap;
- XSetWindowAttributes x11_winattr;
- int (*old_error_handler)(Display *, XErrorEvent *);
- char const *fonts[] = { NULL, "8x13bold", "fixed" }, **parser;
- char const *geometry;
- unsigned int width = 0, height = 0;
- int i;
-
- geometry = getenv("CACA_GEOMETRY");
- if(geometry && *(geometry))
- sscanf(geometry, "%ux%u", &width, &height);
-
- if(width && height)
- cucul_set_size(kk->qq, width, height);
-
- kk->x11.dpy = XOpenDisplay(NULL);
- if(kk->x11.dpy == NULL)
- return -1;
-
- fonts[0] = getenv("CACA_FONT");
- if(fonts[0] && *fonts[0])
- parser = fonts;
- else
- parser = fonts + 1;
-
- /* Ignore font errors */
- old_error_handler = XSetErrorHandler(x11_error_handler);
-
- /* Parse our font list */
- for( ; ; parser++)
- {
- if(!*parser)
- {
- XSetErrorHandler(old_error_handler);
- XCloseDisplay(kk->x11.dpy);
- return -1;
- }
-
- kk->x11.font = XLoadFont(kk->x11.dpy, *parser);
- if(!kk->x11.font)
- continue;
-
- kk->x11.font_struct = XQueryFont(kk->x11.dpy, kk->x11.font);
- if(!kk->x11.font_struct)
- {
- XUnloadFont(kk->x11.dpy, kk->x11.font);
- continue;
- }
-
- break;
- }
-
- /* Reset the default X11 error handler */
- XSetErrorHandler(old_error_handler);
-
- kk->x11.font_width = kk->x11.font_struct->max_bounds.width;
- kk->x11.font_height = kk->x11.font_struct->max_bounds.ascent
- + kk->x11.font_struct->max_bounds.descent;
- kk->x11.font_offset = kk->x11.font_struct->max_bounds.descent;
-
- colormap = DefaultColormap(kk->x11.dpy, DefaultScreen(kk->x11.dpy));
- for(i = 0; i < 16; i++)
- {
- XColor color;
- color.red = x11_palette[i * 3];
- color.green = x11_palette[i * 3 + 1];
- color.blue = x11_palette[i * 3 + 2];
- XAllocColor(kk->x11.dpy, colormap, &color);
- kk->x11.colors[i] = color.pixel;
- }
-
- x11_winattr.backing_store = Always;
- x11_winattr.background_pixel = kk->x11.colors[0];
- x11_winattr.event_mask = ExposureMask | StructureNotifyMask;
-
- kk->x11.window =
- XCreateWindow(kk->x11.dpy, DefaultRootWindow(kk->x11.dpy), 0, 0,
- kk->qq->width * kk->x11.font_width,
- kk->qq->height * kk->x11.font_height,
- 0, 0, InputOutput, 0,
- CWBackingStore | CWBackPixel | CWEventMask,
- &x11_winattr);
-
- XStoreName(kk->x11.dpy, kk->x11.window, "caca for X");
-
- XSelectInput(kk->x11.dpy, kk->x11.window, StructureNotifyMask);
- XMapWindow(kk->x11.dpy, kk->x11.window);
-
- kk->x11.gc = XCreateGC(kk->x11.dpy, kk->x11.window, 0, NULL);
- XSetForeground(kk->x11.dpy, kk->x11.gc, kk->x11.colors[15]);
- XSetFont(kk->x11.dpy, kk->x11.gc, kk->x11.font);
-
- for(;;)
- {
- XEvent event;
- XNextEvent(kk->x11.dpy, &event);
- if (event.type == MapNotify)
- break;
- }
-
- #if defined(HAVE_X11_XKBLIB_H)
- /* Disable autorepeat */
- XkbSetDetectableAutoRepeat(kk->x11.dpy, True, &kk->x11.autorepeat);
- if(!kk->x11.autorepeat)
- XAutoRepeatOff(kk->x11.dpy);
- #endif
-
- XSelectInput(kk->x11.dpy, kk->x11.window, kk->x11.event_mask);
-
- XSync(kk->x11.dpy, False);
-
- kk->x11.pixmap = XCreatePixmap(kk->x11.dpy, kk->x11.window,
- kk->qq->width * kk->x11.font_width,
- kk->qq->height * kk->x11.font_height,
- DefaultDepth(kk->x11.dpy,
- DefaultScreen(kk->x11.dpy)));
-
- kk->x11.new_width = kk->x11.new_height = 0;
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- CONSOLE_CURSOR_INFO cci;
- CONSOLE_SCREEN_BUFFER_INFO csbi;
- COORD size;
-
- kk->win32.front =
- CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
- 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
- if(!kk->win32.front || kk->win32.front == INVALID_HANDLE_VALUE)
- return -1;
-
- kk->win32.back =
- CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
- 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
- if(!kk->win32.back || kk->win32.back == INVALID_HANDLE_VALUE)
- return -1;
-
- if(!GetConsoleScreenBufferInfo(kk->win32.hout, &csbi))
- return -1;
-
- /* Sample code to get the biggest possible window */
- //size = GetLargestConsoleWindowSize(kk->win32.hout);
- cucul_set_size(kk->qq, csbi.srWindow.Right - csbi.srWindow.Left + 1,
- csbi.srWindow.Bottom - csbi.srWindow.Top + 1);
- size.X = kk->qq->width;
- size.Y = kk->qq->height;
- SetConsoleScreenBufferSize(kk->win32.front, size);
- SetConsoleScreenBufferSize(kk->win32.back, size);
-
- SetConsoleMode(kk->win32.front, 0);
- SetConsoleMode(kk->win32.back, 0);
-
- GetConsoleCursorInfo(kk->win32.front, &cci);
- cci.dwSize = 0;
- cci.bVisible = FALSE;
- SetConsoleCursorInfo(kk->win32.front, &cci);
- SetConsoleCursorInfo(kk->win32.back, &cci);
-
- SetConsoleActiveScreenBuffer(kk->win32.front);
-
- kk->win32.buffer = malloc(kk->qq->width * kk->qq->height
- * sizeof(CHAR_INFO));
- if(kk->win32.buffer == NULL)
- return -1;
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- char *empty_texture;
- char const *geometry;
- char *argv[2] = { "", NULL };
- unsigned int width = 0, height = 0;
- int argc = 1;
- int i;
-
- gl_kk = kk;
-
- geometry = getenv("CACA_GEOMETRY");
- if(geometry && *(geometry))
- sscanf(geometry, "%ux%u", &width, &height);
-
- if(width && height)
- cucul_set_size(kk->qq, width, height);
-
- kk->gl.font_width = 9;
- kk->gl.font_height = 15;
-
- kk->gl.width = kk->qq->width * kk->gl.font_width;
- kk->gl.height = kk->qq->height * kk->gl.font_height;
-
- kk->gl.resized = 0;
- kk->gl.bit = 0;
-
- kk->gl.mouse_changed = kk->gl.mouse_clicked = 0;
- kk->gl.mouse_button = kk->gl.mouse_state = 0;
-
- kk->gl.key = 0;
- kk->gl.special_key = 0;
-
- kk->gl.sw = 9.0f / 16.0f;
- kk->gl.sh = 15.0f / 16.0f;
-
- glutInit(&argc, argv);
-
- glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
- glutInitWindowSize(kk->gl.width, kk->gl.height);
- kk->gl.window = glutCreateWindow("caca for GL");
-
- gluOrtho2D(0, kk->gl.width, kk->gl.height, 0);
-
- glDisable(GL_CULL_FACE);
- glDisable(GL_DEPTH_TEST);
-
- glutKeyboardFunc(gl_handle_keyboard);
- glutSpecialFunc(gl_handle_special_key);
- glutReshapeFunc(gl_handle_reshape);
-
- glutMouseFunc(gl_handle_mouse);
- glutMotionFunc(gl_handle_mouse_motion);
- glutPassiveMotionFunc(gl_handle_mouse_motion);
-
- glLoadIdentity();
-
- glMatrixMode(GL_PROJECTION);
- glPushMatrix();
- glLoadIdentity();
- gluOrtho2D(0, kk->gl.width, kk->gl.height, 0);
-
- glMatrixMode(GL_MODELVIEW);
-
- glClear(GL_COLOR_BUFFER_BIT);
-
- empty_texture = malloc(16 * 16 * 4);
- if(empty_texture == NULL)
- return -1;
-
- memset(empty_texture, 0xff, 16 * 16 * 4);
- glEnable(GL_TEXTURE_2D);
-
- for(i = 0; i < 94; i++)
- {
- glGenTextures(1, (GLuint*)&kk->gl.id[i]);
- glBindTexture(GL_TEXTURE_2D, kk->gl.id[i]);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8,
- 16, 16, 0, GL_RGB, GL_UNSIGNED_BYTE, empty_texture);
- }
-
- for(i = 0; i < 94; i++)
- {
- glDisable(GL_TEXTURE_2D);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glColor3f(1, 1, 1);
- glRasterPos2f(0, 15);
- glutBitmapCharacter(GLUT_BITMAP_9_BY_15, i + 32);
-
- glEnable(GL_TEXTURE_2D);
- glBindTexture(GL_TEXTURE_2D, kk->gl.id[i]);
- glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
- 0, kk->gl.height - 16, 16, 16, 0);
-
- glutMainLoopEvent();
- glutPostRedisplay();
- }
- }
- else
- #endif
- {
- /* Dummy */
- }
-
- kk->delay = 0;
- kk->rendertime = 0;
-
- return 0;
- }
-
- int _caca_end_graphics(caca_t *kk)
- {
- #if defined(USE_SLANG)
- /* Nothing to do */
- #endif
- #if defined(USE_NCURSES)
- /* Nothing to do */
- #endif
- #if defined(USE_CONIO)
- if(kk->driver == CACA_DRIVER_CONIO)
- {
- free(kk->conio.screen);
- }
- else
- #endif
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- XSync(kk->x11.dpy, False);
- #if defined(HAVE_X11_XKBLIB_H)
- if(!kk->x11.autorepeat)
- XAutoRepeatOn(kk->x11.dpy);
- #endif
- XFreePixmap(kk->x11.dpy, kk->x11.pixmap);
- XFreeFont(kk->x11.dpy, kk->x11.font_struct);
- XFreeGC(kk->x11.dpy, kk->x11.gc);
- XUnmapWindow(kk->x11.dpy, kk->x11.window);
- XDestroyWindow(kk->x11.dpy, kk->x11.window);
- XCloseDisplay(kk->x11.dpy);
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- SetConsoleActiveScreenBuffer(kk->win32.hout);
- CloseHandle(kk->win32.back);
- CloseHandle(kk->win32.front);
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- glutDestroyWindow(kk->gl.window);
- }
- else
- #endif
- {
- /* Dummy */
- }
-
- return 0;
- }
- #endif /* _DOXYGEN_SKIP_ME */
-
- /** \brief Set the window title.
- *
- * If libcaca runs in a window, try to change its title. This works with
- * the X11 and Win32 drivers.
- *
- * \param title The desired window title.
- * \return 0 upon success, a non-zero value if an error occurs.
- */
- int caca_set_window_title(caca_t *kk, char const *title)
- {
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- XStoreName(kk->x11.dpy, kk->x11.window, title);
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- SetConsoleTitle(title);
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- glutSetWindowTitle(title);
- }
- else
- #endif
- {
- /* Not supported */
- return -1;
- }
-
- return 0;
- }
-
- /** \brief Get the window width.
- *
- * If libcaca runs in a window, get the usable window width. This value can
- * be used for aspect ratio calculation. If libcaca does not run in a window
- * or if there is no way to know the font size, assume a 6x10 font is being
- * used. Note that the units are not necessarily pixels.
- *
- * \return The window width.
- */
- unsigned int caca_get_window_width(caca_t *kk)
- {
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- return kk->qq->width * kk->x11.font_width;
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- /* FIXME */
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- return kk->gl.width;
- }
- else
- #endif
- {
- /* Dummy */
- }
-
- /* Fallback to a 6x10 font */
- return kk->qq->width * 6;
- }
-
- /** \brief Get the window height.
- *
- * If libcaca runs in a window, get the usable window height. This value can
- * be used for aspect ratio calculation. If libcaca does not run in a window
- * or if there is no way to know the font size, assume a 6x10 font is being
- * used. Note that the units are not necessarily pixels.
- *
- * \return The window height.
- */
- unsigned int caca_get_window_height(caca_t *kk)
- {
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- return kk->qq->height * kk->x11.font_height;
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- /* FIXME */
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- return kk->gl.height;
- }
- else
- #endif
- {
- /* Dummy */
- }
-
- /* Fallback to a 6x10 font */
- return kk->qq->height * 10;
- }
-
- /** \brief Set the refresh delay.
- *
- * This function sets the refresh delay in microseconds. The refresh delay
- * is used by caca_display() to achieve constant framerate. See the
- * caca_display() documentation for more details.
- *
- * If the argument is zero, constant framerate is disabled. This is the
- * default behaviour.
- *
- * \param usec The refresh delay in microseconds.
- */
- void caca_set_delay(caca_t *kk, unsigned int usec)
- {
- kk->delay = usec;
- }
-
- /** \brief Get the average rendering time.
- *
- * This function returns the average rendering time, which is the average
- * measured time between two caca_display() calls, in microseconds. If
- * constant framerate was activated by calling caca_set_delay(), the average
- * rendering time will not be considerably shorter than the requested delay
- * even if the real rendering time was shorter.
- *
- * \return The render time in microseconds.
- */
- unsigned int caca_get_rendertime(caca_t *kk)
- {
- return kk->rendertime;
- }
-
- /** \brief Flush pending changes and redraw the screen.
- *
- * This function flushes all graphical operations and prints them to the
- * screen. Nothing will show on the screen until caca_display() is
- * called.
- *
- * If caca_set_delay() was called with a non-zero value, caca_display()
- * will use that value to achieve constant framerate: if two consecutive
- * calls to caca_display() are within a time range shorter than the value
- * set with caca_set_delay(), the second call will wait a bit before
- * performing the screen refresh.
- */
- void caca_display(caca_t *kk)
- {
- #if !defined(_DOXYGEN_SKIP_ME)
- #define IDLE_USEC 10000
- #endif
- int ticks = kk->lastticks + _caca_getticks(&kk->timer);
-
- #if defined(USE_SLANG)
- if(kk->driver == CACA_DRIVER_SLANG)
- {
- int x, y;
- uint8_t *attr = kk->qq->attr;
- uint32_t *chars = kk->qq->chars;
- for(y = 0; y < (int)kk->qq->height; y++)
- {
- SLsmg_gotorc(y, 0);
- for(x = kk->qq->width; x--; )
- {
- #if defined(OPTIMISE_SLANG_PALETTE)
- /* If foreground == background, just don't use this colour
- * pair, and print a space instead of the real character. */
- uint8_t fgcolor = *attr & 0xf;
- uint8_t bgcolor = *attr >> 4;
- if(fgcolor != bgcolor)
- {
- SLsmg_set_color(slang_assoc[*attr++]);
- SLsmg_write_char(*chars++ & 0x7f);
- }
- else
- {
- if(fgcolor == CUCUL_COLOR_BLACK)
- fgcolor = CUCUL_COLOR_WHITE;
- else if(fgcolor == CUCUL_COLOR_WHITE
- || fgcolor <= CUCUL_COLOR_LIGHTGRAY)
- fgcolor = CUCUL_COLOR_BLACK;
- else
- fgcolor = CUCUL_COLOR_WHITE;
- SLsmg_set_color(slang_assoc[fgcolor + 16 * bgcolor]);
- SLsmg_write_char(' ');
- chars++;
- attr++;
- }
- #else
- SLsmg_set_color(*attr++);
- SLsmg_write_char(*chars++ & 0x7f);
- #endif
- }
- }
- SLsmg_refresh();
- }
- else
- #endif
- #if defined(USE_NCURSES)
- if(kk->driver == CACA_DRIVER_NCURSES)
- {
- int x, y;
- uint8_t *attr = kk->qq->attr;
- uint32_t *chars = kk->qq->chars;
- for(y = 0; y < (int)kk->qq->height; y++)
- {
- move(y, 0);
- for(x = kk->qq->width; x--; )
- {
- attrset(kk->ncurses.attr[*attr++]);
- addch(*chars++ & 0x7f);
- }
- }
- refresh();
- }
- else
- #endif
- #if defined(USE_CONIO)
- if(kk->driver == CACA_DRIVER_CONIO)
- {
- int n;
- char *screen = kk->conio.screen;
- uint8_t *attr = kk->qq->attr;
- uint32_t *chars = kk->qq->chars;
- for(n = kk->qq->height * kk->qq->width; n--; )
- {
- *screen++ = *chars++ & 0x7f;
- *screen++ = *attr++;
- }
- # if defined(SCREENUPDATE_IN_PC_H)
- ScreenUpdate(kk->conio.screen);
- # else
- /* FIXME */
- # endif
- }
- else
- #endif
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- unsigned int x, y, len;
-
- /* First draw the background colours. Splitting the process in two
- * loops like this is actually slightly faster. */
- for(y = 0; y < kk->qq->height; y++)
- {
- for(x = 0; x < kk->qq->width; x += len)
- {
- uint8_t *attr = kk->qq->attr + x + y * kk->qq->width;
-
- len = 1;
- while(x + len < kk->qq->width
- && (attr[len] >> 4) == (attr[0] >> 4))
- len++;
-
- XSetForeground(kk->x11.dpy, kk->x11.gc,
- kk->x11.colors[attr[0] >> 4]);
- XFillRectangle(kk->x11.dpy, kk->x11.pixmap, kk->x11.gc,
- x * kk->x11.font_width, y * kk->x11.font_height,
- len * kk->x11.font_width, kk->x11.font_height);
- }
- }
-
- /* Then print the foreground characters */
- for(y = 0; y < kk->qq->height; y++)
- {
- for(x = 0; x < kk->qq->width; x += len)
- {
- char buffer[BUFSIZ]; /* FIXME: use a smaller buffer */
- uint32_t *chars = kk->qq->chars + x + y * kk->qq->width;
- uint8_t *attr = kk->qq->attr + x + y * kk->qq->width;
-
- len = 1;
-
- /* Skip spaces */
- if(chars[0] == ' ')
- continue;
-
- buffer[0] = chars[0] & 0x7f;
-
- while(x + len < kk->qq->width
- && (attr[len] & 0xf) == (attr[0] & 0xf))
- {
- buffer[len] = chars[len] & 0x7f;
- len++;
- }
-
- XSetForeground(kk->x11.dpy, kk->x11.gc, kk->x11.colors[attr[0] & 0xf]);
- XDrawString(kk->x11.dpy, kk->x11.pixmap, kk->x11.gc,
- x * kk->x11.font_width,
- (y + 1) * kk->x11.font_height - kk->x11.font_offset,
- buffer, len);
- }
- }
-
- XCopyArea(kk->x11.dpy, kk->x11.pixmap, kk->x11.window, kk->x11.gc, 0, 0,
- kk->qq->width * kk->x11.font_width, kk->qq->height * kk->x11.font_height,
- 0, 0);
- XFlush(kk->x11.dpy);
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- COORD size, pos;
- SMALL_RECT rect;
- unsigned int i;
-
- /* Render everything to our back buffer */
- for(i = 0; i < kk->qq->width * kk->qq->height; i++)
- {
- kk->win32.buffer[i].Char.AsciiChar = kk->qq->chars[i] & 0x7f;
- kk->win32.buffer[i].Attributes =
- win32_fg_palette[kk->qq->attr[i] & 0xf]
- | win32_bg_palette[kk->qq->attr[i] >> 4];
- }
-
- /* Blit the back buffer to the front buffer */
- size.X = kk->qq->width;
- size.Y = kk->qq->height;
- pos.X = pos.Y = 0;
- rect.Left = rect.Top = 0;
- rect.Right = kk->qq->width - 1;
- rect.Bottom = kk->qq->height - 1;
- WriteConsoleOutput(kk->win32.front, kk->win32.buffer, size, pos, &rect);
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- unsigned int x, y, line;
-
- glClear(GL_COLOR_BUFFER_BIT);
-
- line = 0;
- for(y = 0; y < kk->gl.height; y += kk->gl.font_height)
- {
- uint8_t *attr = kk->qq->attr + line * kk->qq->width;
-
- for(x = 0; x < kk->gl.width; x += kk->gl.font_width)
- {
- glDisable(GL_TEXTURE_2D);
- glColor4bv(gl_bgpal[attr[0] >> 4]);
- glBegin(GL_QUADS);
- glVertex2f(x, y);
- glVertex2f(x + kk->gl.font_width, y);
- glVertex2f(x + kk->gl.font_width, y + kk->gl.font_height);
- glVertex2f(x, y + kk->gl.font_height);
- glEnd();
-
- attr++;
- }
-
- line++;
- }
-
- /* 2nd pass, avoids changing render state too much */
- glEnable(GL_BLEND);
- glEnable(GL_TEXTURE_2D);
- glBlendFunc(GL_ONE, GL_ONE);
-
- line = 0;
- for(y = 0; y < kk->gl.height; y += kk->gl.font_height)
- {
- uint8_t *attr = kk->qq->attr + line * kk->qq->width;
- uint32_t *chars = kk->qq->chars + line * kk->qq->width;
-
- for(x = 0; x < kk->gl.width; x += kk->gl.font_width)
- {
- if(*chars != (uint32_t)' ')
- {
- char ch = *chars & 0x7f;
-
- /* FIXME: check ch bounds */
- glBindTexture(GL_TEXTURE_2D, kk->gl.id[ch - 32]);
- glColor4bv(gl_bgpal[attr[0] & 0xf]);
- glBegin(GL_QUADS);
- glTexCoord2f(0, kk->gl.sh);
- glVertex2f(x, y);
- glTexCoord2f(kk->gl.sw, kk->gl.sh);
- glVertex2f(x + kk->gl.font_width, y);
- glTexCoord2f(kk->gl.sw, 0);
- glVertex2f(x + kk->gl.font_width, y + kk->gl.font_height);
- glTexCoord2f(0, 0);
- glVertex2f(x, y + kk->gl.font_height);
- glEnd();
- }
-
- attr++;
- chars++;
- }
- line++;
- }
- glDisable(GL_BLEND);
- glDisable(GL_TEXTURE_2D);
-
- glutMainLoopEvent();
- glutSwapBuffers();
- glutPostRedisplay();
- }
- else
- #endif
- {
- /* Dummy */
- }
-
- /* FIXME handle this somewhere else */
- if(kk->resize)
- {
- kk->resize = 0;
- caca_handle_resize(kk);
- }
-
- /* Wait until kk->delay + time of last call */
- ticks += _caca_getticks(&kk->timer);
- for(ticks += _caca_getticks(&kk->timer);
- ticks + IDLE_USEC < (int)kk->delay;
- ticks += _caca_getticks(&kk->timer))
- {
- _caca_sleep(IDLE_USEC);
- }
-
- /* Update the sliding mean of the render time */
- kk->rendertime = (7 * kk->rendertime + ticks) / 8;
-
- kk->lastticks = ticks - kk->delay;
-
- /* If we drifted too much, it's bad, bad, bad. */
- if(kk->lastticks > (int)kk->delay)
- kk->lastticks = 0;
- }
-
- /*
- * XXX: following functions are local
- */
-
- static void caca_handle_resize(caca_t *kk)
- {
- unsigned int new_width, new_height;
-
- new_width = kk->qq->width;
- new_height = kk->qq->height;
-
- #if defined(USE_SLANG)
- if(kk->driver == CACA_DRIVER_SLANG)
- {
- SLtt_get_screen_size();
- new_width = SLtt_Screen_Cols;
- new_height = SLtt_Screen_Rows;
-
- if(new_width != kk->qq->width || new_height != kk->qq->height)
- SLsmg_reinit_smg();
- }
- else
- #endif
- #if defined(USE_NCURSES)
- if(kk->driver == CACA_DRIVER_NCURSES)
- {
- struct winsize size;
-
- if(ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0)
- {
- new_width = size.ws_col;
- new_height = size.ws_row;
- #if defined(HAVE_RESIZE_TERM)
- resize_term(new_height, new_width);
- #else
- resizeterm(new_height, new_width);
- #endif
- wrefresh(curscr);
- }
- }
- else
- #endif
- #if defined(USE_CONIO)
- if(kk->driver == CACA_DRIVER_CONIO)
- {
- /* Nothing to do here. */
- }
- else
- #endif
- #if defined(USE_X11)
- if(kk->driver == CACA_DRIVER_X11)
- {
- Pixmap new_pixmap;
-
- new_width = kk->x11.new_width;
- new_height = kk->x11.new_height;
-
- new_pixmap = XCreatePixmap(kk->x11.dpy, kk->x11.window,
- kk->qq->width * kk->x11.font_width,
- kk->qq->height * kk->x11.font_height,
- DefaultDepth(kk->x11.dpy,
- DefaultScreen(kk->x11.dpy)));
- XCopyArea(kk->x11.dpy, kk->x11.pixmap, new_pixmap, kk->x11.gc, 0, 0,
- kk->qq->width * kk->x11.font_width,
- kk->qq->height * kk->x11.font_height, 0, 0);
- XFreePixmap(kk->x11.dpy, kk->x11.pixmap);
- kk->x11.pixmap = new_pixmap;
- }
- else
- #endif
- #if defined(USE_WIN32)
- if(kk->driver == CACA_DRIVER_WIN32)
- {
- /* Nothing to do here. */
- }
- else
- #endif
- #if defined(USE_GL)
- if(kk->driver == CACA_DRIVER_GL)
- {
- kk->gl.width = kk->gl.new_width;
- kk->gl.height = kk->gl.new_height;
-
- new_width = kk->gl.width / kk->gl.font_width;
- new_height = (kk->gl.height / kk->gl.font_height) + 1;
-
- glMatrixMode(GL_PROJECTION);
- glPushMatrix();
- glLoadIdentity();
-
- glViewport(0, 0, kk->gl.width, kk->gl.height);
- gluOrtho2D(0, kk->gl.width, kk->gl.height, 0);
- glMatrixMode(GL_MODELVIEW);
- }
- else
- #endif
- {
- /* Dummy */
- }
-
- /* Tell libcucul we changed size */
- if(new_width != kk->qq->width || new_height != kk->qq->height)
- cucul_set_size(kk->qq, new_width, new_height);
- }
-
- #if defined(USE_SLANG)
- static void slang_init_palette(void)
- {
- /* See SLang ref., 5.4.4. */
- static char *slang_colors[16] =
- {
- /* Standard colours */
- "black",
- "blue",
- "green",
- "cyan",
- "red",
- "magenta",
- "brown",
- "lightgray",
- /* Bright colours */
- "gray",
- "brightblue",
- "brightgreen",
- "brightcyan",
- "brightred",
- "brightmagenta",
- "yellow",
- "white",
- };
-
- #if defined(OPTIMISE_SLANG_PALETTE)
- int i;
-
- for(i = 0; i < 16 * 16; i++)
- SLtt_set_color(i, NULL, slang_colors[slang_palette[i * 2]],
- slang_colors[slang_palette[i * 2 + 1]]);
- #else
- int fg, bg;
-
- for(bg = 0; bg < 16; bg++)
- for(fg = 0; fg < 16; fg++)
- {
- int i = fg + 16 * bg;
- SLtt_set_color(i, NULL, slang_colors[fg], slang_colors[bg]);
- }
- #endif
- }
- #endif /* USE_SLANG */
-
- #if defined(USE_X11)
- static int x11_error_handler(Display *dpy, XErrorEvent *event)
- {
- /* Ignore the error */
- return 0;
- }
- #endif
-
- #if defined(HAVE_SIGNAL) && (defined(USE_NCURSES) || defined(USE_SLANG))
- static RETSIGTYPE sigwinch_handler(int sig)
- {
- sigwinch_kk->resize_event = 1;
-
- signal(SIGWINCH, sigwinch_handler);;
- }
- #endif
-
- #if defined(USE_GL)
- static void gl_handle_keyboard(unsigned char key, int x, int y)
- {
- caca_t *kk = gl_kk;
-
- kk->gl.key = key;
- }
-
- static void gl_handle_special_key(int key, int x, int y)
- {
- caca_t *kk = gl_kk;
-
- kk->gl.special_key = key;
- }
-
- static void gl_handle_reshape(int w, int h)
- {
- caca_t *kk = gl_kk;
-
- if(kk->gl.bit) /* Do not handle reshaping at the first time */
- {
- kk->gl.new_width = w;
- kk->gl.new_height = h;
-
- kk->gl.resized = 1;
- }
- else
- kk->gl.bit = 1;
- }
-
- static void gl_handle_mouse(int button, int state, int x, int y)
- {
- caca_t *kk = gl_kk;
-
- kk->gl.mouse_clicked = 1;
- kk->gl.mouse_button = button;
- kk->gl.mouse_state = state;
- kk->gl.mouse_x = x / kk->gl.font_width;
- kk->gl.mouse_y = y / kk->gl.font_height;
- kk->gl.mouse_changed = 1;
- }
-
- static void gl_handle_mouse_motion(int x, int y)
- {
- caca_t *kk = gl_kk;
-
- kk->gl.mouse_x = x / kk->gl.font_width;
- kk->gl.mouse_y = y / kk->gl.font_height;
- kk->gl.mouse_changed = 1;
- }
- #endif
|