diff --git a/AUTHORS b/AUTHORS index 7cb262d..3357107 100644 --- a/AUTHORS +++ b/AUTHORS @@ -28,7 +28,8 @@ Nicolas Vion Adrien Grand - Java bindings -Alex Foulon +Alex Foulon - Python bindings + - SDL driver */ diff --git a/caca/Makefile.am b/caca/Makefile.am index ea881e8..10d4fd3 100644 --- a/caca/Makefile.am +++ b/caca/Makefile.am @@ -83,7 +83,7 @@ uninstall-local: rmdir $(DESTDIR)$(pkgconfigdir) 2>/dev/null || true if USE_PLUGINS -plugin_LTLIBRARIES = libx11_plugin.la libgl_plugin.la +plugin_LTLIBRARIES = libx11_plugin.la libgl_plugin.la libsdl_plugin.la plugindir = $(libdir)/caca libx11_plugin_la_SOURCES = driver/x11.c @@ -95,8 +95,13 @@ libgl_plugin_la_SOURCES = driver/gl.c libgl_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(GL_CFLAGS) #$(FTGL_CFLAGS) libgl_plugin_la_LDFLAGS = -no-undefined -module -no-version libgl_plugin_la_LIBADD = libcaca.la $(GL_LIBS) #$(FTGL_LIBS) + +libsdl_plugin_la_SOURCES = driver/sdl.c +libsdl_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(SDL_CFLAGS) +libsdl_plugin_la_LDFLAGS = -no-undefined -module -no-version +libsdl_plugin_la_LIBADD = libcaca.la $(SDL_LIBS) else -driver_source += driver/x11.c driver/gl.c +driver_source += driver/x11.c driver/gl.c driver/sdl.c endif if USE_COCOA diff --git a/caca/caca.c b/caca/caca.c index 327a8f9..68e71d5 100644 --- a/caca/caca.c +++ b/caca/caca.c @@ -35,6 +35,7 @@ #if defined(USE_PLUGINS) # define gl_install(p) caca_plugin_install(p, "gl") # define x11_install(p) caca_plugin_install(p, "x11") +# define sdl_install(p) caca_plugin_install(p, "sdl") #endif static int caca_can_resize(caca_display_t *); @@ -171,6 +172,9 @@ char const * const * caca_get_display_driver_list(void) #if defined(USE_GL) "gl", "OpenGL window", #endif +#if defined(USE_SDL) + "sdl", "SDL window", +#endif #if defined(USE_SLANG) "slang", "S-Lang console library", #endif @@ -336,7 +340,7 @@ static int caca_install_driver(caca_display_t *dp, char const *driver) dp->events.autorepeat_ticks = 0; dp->events.last_key_event.type = CACA_EVENT_NONE; #endif -#if defined(USE_SLANG) || defined(USE_NCURSES) || defined(USE_CONIO) || defined(USE_GL) +#if defined(USE_SLANG) || defined(USE_NCURSES) || defined(USE_CONIO) || defined(USE_GL) || defined(USE_SDL) dp->events.queue = 0; #endif @@ -405,6 +409,9 @@ static int caca_select_driver(caca_display_t *dp, char const *driver) #if defined(USE_GL) if(!strcasecmp(var, "gl")) return gl_install(dp); #endif +#if defined(USE_SDL) + if(!strcasecmp(var, "sdl")) return sdl_install(dp); +#endif #if !defined(__KERNEL__) if(!strcasecmp(var, "raw")) return raw_install(dp); #endif @@ -441,6 +448,9 @@ static int caca_select_driver(caca_display_t *dp, char const *driver) #endif #if defined(USE_GL) if(gl_install(dp) == 0) return 0; +#endif +#if defined(USE_SDL) + if(sdl_install(dp) == 0) return 0; #endif /* ncurses has a higher priority than slang because it has better colour * support across terminal types, despite being slightly slower. */ diff --git a/caca/caca_internals.h b/caca/caca_internals.h index b2cedba..dd8d64c 100644 --- a/caca/caca_internals.h +++ b/caca/caca_internals.h @@ -106,6 +106,9 @@ enum caca_driver #if defined(USE_X11) CACA_DRIVER_X11 = 9, #endif +#if defined(USE_SDL) + CACA_DRIVER_SDL = 10, +#endif }; /* Available external drivers */ @@ -135,6 +138,9 @@ int win32_install(caca_display_t *); #if defined(USE_X11) int x11_install(caca_display_t *); #endif +#if defined(USE_SDL) +int sdl_install(caca_display_t *); +#endif /* Timer structure */ struct caca_timer @@ -251,7 +257,7 @@ extern int _caca_getticks(caca_timer_t *); /* Internal event functions */ extern void _caca_handle_resize(caca_display_t *); -#if defined(USE_SLANG) || defined(USE_NCURSES) || defined(USE_CONIO) || defined(USE_GL) +#if defined(USE_SLANG) || defined(USE_NCURSES) || defined(USE_CONIO) || defined(USE_GL) || defined(USE_SDL) extern void _push_event(caca_display_t *, caca_privevent_t *); extern int _pop_event(caca_display_t *, caca_privevent_t *); #endif diff --git a/caca/driver/sdl.c b/caca/driver/sdl.c new file mode 100644 index 0000000..20bca2e --- /dev/null +++ b/caca/driver/sdl.c @@ -0,0 +1,483 @@ +/* + * libcaca Colour ASCII-Art library + * Copyright (c) 2002-2015 Sam Hocevar + * 2013-2015 Alex Foulon + * All Rights Reserved + * + * This library is free software. It comes without any warranty, to + * the extent permitted by applicable law. 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://www.wtfpl.net/ for more details. + * + */ + +/* + * This file contains a SDL libcaca input and output driver + */ + +#include "config.h" + +#if defined(USE_SDL) + +#include +#include +#include + +#include +#include +#include +#if defined HAVE_LOCALE_H +# include +#endif + +#include "caca.h" +#include "caca_internals.h" + +static const int video_bpp = 32; +static const Uint32 video_flags = SDL_HWSURFACE + | SDL_HWPALETTE + | SDL_ASYNCBLIT + | SDL_DOUBLEBUF + | SDL_RESIZABLE; + +struct driver_private { + SDL_Surface *screen; + caca_font_t *font; + TTF_Font *ttf_font; + int font_width, font_height; + int is_active, use_ttf; + uint32_t attr, ch; + +}; + +static void sdl_write_utf32(caca_display_t *, int, int); + +static int sdl_init_graphics(caca_display_t *dp) +{ + int width, height, ttf_size; + char const *ttf_font_file, *ttf_font_size; + char const *geometry; + char const * const *fonts; + + dp->drv.p = malloc(sizeof(struct driver_private)); + + if(SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "error: %s\n", SDL_GetError()); + return -1; + } + + SDL_EnableUNICODE(SDL_ENABLE); + SDL_WM_SetCaption("Caca for SDL", NULL); + + /* ignore unused events */ + SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE); + SDL_EventState(SDL_VIDEOEXPOSE, SDL_IGNORE); + SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE); + + width = caca_get_canvas_width(dp->cv); + height = caca_get_canvas_height(dp->cv); + + dp->drv.p->use_ttf = 0; + fonts = caca_get_font_list(); + if(fonts[0] == NULL) { + fprintf(stderr, "error: libcaca was compiled without any fonts\n"); + return -1; + } + dp->drv.p->font = caca_load_font(fonts[0], 0); + if(dp->drv.p->font == NULL) { + fprintf(stderr, "error: could not load font \"%s\"\n", fonts[0]); + return -1; + } + + dp->drv.p->font_width = caca_get_font_width(dp->drv.p->font); + dp->drv.p->font_height = caca_get_font_height(dp->drv.p->font); + +#if defined(HAVE_GETENV) + ttf_font_file = getenv("CACA_FONT"); + if(ttf_font_file && *ttf_font_file) { + if(TTF_Init() < 0) + fprintf(stderr, "error: %s\n", TTF_GetError()); + + ttf_font_size = getenv("CACA_FONT_SIZE"); + if(ttf_font_size && *ttf_font_size) + sscanf(ttf_font_size, "%u", &ttf_size); + else + ttf_size = 12; + + /* no need to return error, fallback on compiled font */ + dp->drv.p->ttf_font = TTF_OpenFont(ttf_font_file, ttf_size); + if(dp->drv.p->ttf_font == NULL) { + fprintf(stderr, "error: %s\n", TTF_GetError()); + } + else if(!TTF_FontFaceIsFixedWidth(dp->drv.p->ttf_font)) { + fprintf(stderr, "error: SDL driver accept only fixed width font\n"); + } + else { + dp->drv.p->use_ttf = 1; + TTF_SizeUTF8(dp->drv.p->ttf_font, "a", + &dp->drv.p->font_width, &dp->drv.p->font_height); + } + } + + geometry = getenv("CACA_GEOMETRY"); + if(geometry && *geometry) + sscanf(geometry, "%ux%u", &width, &height); +#endif + +#if defined HAVE_LOCALE_H + setlocale(LC_ALL, ""); +#endif + + dp->drv.p->screen = SDL_SetVideoMode(width * dp->drv.p->font_width, + height * dp->drv.p->font_height, + video_bpp, video_flags); + if(dp->drv.p->screen == NULL) { + fprintf(stderr, "error: %s\n", SDL_GetError()); + return -1; + } + + dp->drv.p->is_active = 1; + caca_add_dirty_rect(dp->cv, 0, 0, dp->cv->width, dp->cv->height); + dp->resize.allow = 1; + caca_set_canvas_size(dp->cv, width ? width : 80, height ? height : 32); + width = caca_get_canvas_width(dp->cv); + height = caca_get_canvas_height(dp->cv); + dp->resize.allow = 0; + + return 0; +} + +static int sdl_end_graphics(caca_display_t *dp) +{ + if(SDL_WasInit(SDL_INIT_VIDEO) != 0) + SDL_Quit(); + + if(TTF_WasInit() != 0) { + TTF_CloseFont(dp->drv.p->ttf_font); + TTF_Quit(); + } + + if(dp->drv.p->font != NULL) + caca_free_font(dp->drv.p->font); + free(dp->drv.p); + + return 0; +} + +static int sdl_set_display_title(caca_display_t *dp, char const *title) +{ + SDL_WM_SetCaption(title, NULL); + return 0; +} + +static int sdl_get_display_width(caca_display_t const *dp) +{ + return (caca_get_canvas_width(dp->cv) * dp->drv.p->font_width); +} + +static int sdl_get_display_height(caca_display_t const *dp) +{ + return (caca_get_canvas_height(dp->cv) * dp->drv.p->font_height); +} + +static void sdl_display(caca_display_t *dp) +{ + int x, y, i; + size_t len; + void *buffer; + SDL_RWops *rw; + SDL_Surface *img; + + if(!dp->drv.p->is_active) + return; + + if(!dp->drv.p->use_ttf) { + buffer = caca_export_canvas_to_memory(dp->cv, "tga", &len); + if(len > 0) { + rw = SDL_RWFromMem(buffer, len); + img = IMG_LoadTyped_RW(rw, 1, "TGA"); + SDL_FillRect(dp->drv.p->screen, NULL, 0x000000); + SDL_BlitSurface(img, NULL, dp->drv.p->screen, NULL); + SDL_FreeSurface(img); + if(buffer != NULL) + free(buffer); + } + } + else { + for(i = 0; i < caca_get_dirty_rect_count(dp->cv); i++) + { + uint32_t const *cvchars, *cvattrs; + int dx, dy, dw, dh; + + caca_get_dirty_rect(dp->cv, i, &dx, &dy, &dw, &dh); + + cvchars = caca_get_canvas_chars(dp->cv) + dx + dy * dp->cv->width; + cvattrs = caca_get_canvas_attrs(dp->cv) + dx + dy * dp->cv->width; + + for(y = dy; y < dy + dh; y++) + { + for(x = dx; x < dx + dw; x++) + { + dp->drv.p->ch = *cvchars++; + dp->drv.p->attr = *cvattrs++; + sdl_write_utf32(dp, x, y); + } + cvchars += dp->cv->width - dw; + cvattrs += dp->cv->width - dw; + } + } + } + + SDL_Flip(dp->drv.p->screen); +} + +static void sdl_write_utf32(caca_display_t *dp, int x, int y) +{ + int bytes; + int style = TTF_STYLE_NORMAL; + char buf[10]; + uint16_t color; + uint32_t bg; + SDL_Color fg; + SDL_Rect pos; + SDL_Surface *text; + + if(dp->drv.p->ch == CACA_MAGIC_FULLWIDTH) + return; + + bytes = caca_utf32_to_utf8(buf, dp->drv.p->ch); + buf[bytes] = '\0'; + + if(dp->drv.p->attr & CACA_BOLD) + style |= TTF_STYLE_BOLD; + if(dp->drv.p->attr & CACA_ITALICS) + style |= TTF_STYLE_ITALIC; + if(dp->drv.p->attr & CACA_UNDERLINE) + style |= TTF_STYLE_UNDERLINE; + + color = caca_attr_to_rgb12_fg(dp->drv.p->attr); + fg.r = ((color & 0xf00) >> 8) * 0x11; + fg.g = ((color & 0xf0) >> 4) * 0x11; + fg.b = ((color & 0xf) >> 0) * 0x11; + + color = caca_attr_to_rgb12_bg(dp->drv.p->attr); + bg = ((color & 0xf00) >> 8) * 0x110000; + bg |= ((color & 0x0f0) >> 4) * 0x001100; + bg |= ((color & 0x00f) >> 0) * 0x000011; + + pos.x = x * dp->drv.p->font_width; + pos.y = y * dp->drv.p->font_height; + pos.w = dp->drv.p->font_width; + pos.h = dp->drv.p->font_height; + + if(caca_utf32_is_fullwidth(dp->drv.p->ch)) { + pos.w *= 2; + pos.h *= 2; + SDL_FillRect(dp->drv.p->screen, &pos, bg); + } + else { + SDL_FillRect(dp->drv.p->screen, &pos, bg); + } + + TTF_SetFontStyle(dp->drv.p->ttf_font, style); + text = TTF_RenderUTF8_Blended(dp->drv.p->ttf_font, buf, fg); + SDL_BlitSurface(text, NULL, dp->drv.p->screen, &pos); + SDL_FreeSurface(text); +} + +static int sdl_get_event(caca_display_t *dp, caca_privevent_t *ev) +{ + SDL_Event event; + + while(SDL_PollEvent(&event)) + { + size_t bytes; + + if(event.type == SDL_QUIT) { + ev->type = CACA_EVENT_QUIT; + return 1; + } + + if(event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) { + if(event.type == SDL_KEYDOWN) + ev->type = CACA_EVENT_KEY_PRESS; + else if(event.type == SDL_KEYUP) + ev->type = CACA_EVENT_KEY_RELEASE; + + switch(event.key.keysym.sym) + { + case SDLK_BACKSPACE: + ev->data.key.ch = CACA_KEY_BACKSPACE; break; + case SDLK_DELETE: + ev->data.key.ch = CACA_KEY_DELETE; break; + case SDLK_LEFT: + ev->data.key.ch = CACA_KEY_LEFT; break; + case SDLK_RIGHT: + ev->data.key.ch = CACA_KEY_RIGHT; break; + case SDLK_UP: + ev->data.key.ch = CACA_KEY_UP; break; + case SDLK_DOWN: + ev->data.key.ch = CACA_KEY_DOWN; break; + case SDLK_END: + ev->data.key.ch = CACA_KEY_END; break; + case SDLK_ESCAPE: + ev->data.key.ch = CACA_KEY_ESCAPE; break; + case SDLK_HOME: + ev->data.key.ch = CACA_KEY_HOME; break; + case SDLK_INSERT: + ev->data.key.ch = CACA_KEY_INSERT; break; + case SDLK_PAGEDOWN: + ev->data.key.ch = CACA_KEY_PAGEDOWN; break; + case SDLK_PAGEUP: + ev->data.key.ch = CACA_KEY_PAGEUP; break; + case SDLK_PAUSE: + ev->data.key.ch = CACA_KEY_PAUSE; break; + case SDLK_RETURN: + ev->data.key.ch = CACA_KEY_RETURN; break; + case SDLK_TAB: + ev->data.key.ch = CACA_KEY_TAB; break; + + case SDLK_F1: ev->data.key.ch = CACA_KEY_F1; break; + case SDLK_F2: ev->data.key.ch = CACA_KEY_F2; break; + case SDLK_F3: ev->data.key.ch = CACA_KEY_F3; break; + case SDLK_F4: ev->data.key.ch = CACA_KEY_F4; break; + case SDLK_F5: ev->data.key.ch = CACA_KEY_F5; break; + case SDLK_F6: ev->data.key.ch = CACA_KEY_F6; break; + case SDLK_F7: ev->data.key.ch = CACA_KEY_F7; break; + case SDLK_F8: ev->data.key.ch = CACA_KEY_F8; break; + case SDLK_F9: ev->data.key.ch = CACA_KEY_F9; break; + case SDLK_F10: ev->data.key.ch = CACA_KEY_F10; break; + case SDLK_F11: ev->data.key.ch = CACA_KEY_F11; break; + case SDLK_F12: ev->data.key.ch = CACA_KEY_F12; break; + + case SDLK_KP0: + case SDLK_0: ev->data.key.ch = '0'; break; + case SDLK_KP1: + case SDLK_1: ev->data.key.ch = '1'; break; + case SDLK_KP2: + case SDLK_2: ev->data.key.ch = '2'; break; + case SDLK_KP3: + case SDLK_3: ev->data.key.ch = '3'; break; + case SDLK_KP4: + case SDLK_4: ev->data.key.ch = '4'; break; + case SDLK_KP5: + case SDLK_5: ev->data.key.ch = '5'; break; + case SDLK_KP6: + case SDLK_6: ev->data.key.ch = '6'; break; + case SDLK_KP7: + case SDLK_7: ev->data.key.ch = '7'; break; + case SDLK_KP8: + case SDLK_8: ev->data.key.ch = '8'; break; + case SDLK_KP9: + case SDLK_9: ev->data.key.ch = '9'; break; + + default: + ev->data.key.ch = event.key.keysym.unicode; break; + break; + } + + ev->data.key.utf32 = (uint32_t) event.key.keysym.sym; + bytes = caca_utf32_to_utf8(ev->data.key.utf8, ev->data.key.utf32); + if(bytes) { + ev->data.key.utf8[bytes] = '\0'; + } + else { + ev->data.key.utf8[0] = ev->data.key.ch; + ev->data.key.utf8[1] = '\0'; + } + + return 1; + } + + if(event.type == SDL_VIDEORESIZE) { + ev->type = CACA_EVENT_RESIZE; + ev->data.resize.w = event.resize.w / dp->drv.p->font_width; + ev->data.resize.h = event.resize.h / dp->drv.p->font_height; + + dp->resize.w = ev->data.resize.w; + dp->resize.h = ev->data.resize.h; + dp->resize.resized = 1; + + return 1; + } + + if(event.type == SDL_MOUSEMOTION) { + ev->type = CACA_EVENT_MOUSE_MOTION; + ev->data.mouse.x = event.motion.x / dp->drv.p->font_width; + ev->data.mouse.y = event.motion.y / dp->drv.p->font_height; + return 1; + } + + if(event.type == SDL_MOUSEBUTTONDOWN + || event.type == SDL_MOUSEBUTTONUP) { + if(event.type == SDL_MOUSEBUTTONDOWN) + ev->type = CACA_EVENT_MOUSE_PRESS; + else if(event.type == SDL_MOUSEBUTTONUP) + ev->type = CACA_EVENT_MOUSE_RELEASE; + + if(event.button.button == SDL_BUTTON_LEFT + || event.button.button == SDL_BUTTON_MIDDLE + || event.button.button == SDL_BUTTON_RIGHT) { + ev->data.mouse.button = event.button.button; + return 1; + } + else { + break; + } + } + } + + ev->type = CACA_EVENT_NONE; + return 0; +} + +static void sdl_handle_resize(caca_display_t *dp) +{ + dp->resize.allow = 1; + caca_set_canvas_size(dp->cv, dp->resize.w, dp->resize.h); + dp->resize.allow = 0; + + dp->drv.p->screen = SDL_SetVideoMode(sdl_get_display_width(dp), + sdl_get_display_height(dp), + video_bpp, video_flags); +} + +static void sdl_set_mouse(caca_display_t *dp, int flags) +{ + if(!flags) + SDL_ShowCursor(SDL_DISABLE); + else + SDL_ShowCursor(SDL_ENABLE); +} + +/* + * Driver initialisation + */ + +int sdl_install(caca_display_t *dp) +{ +#if defined HAVE_GETENV + if(!getenv("DISPLAY") || !*(getenv("DISPLAY"))) + return -1; +#endif + + dp->drv.id = CACA_DRIVER_SDL; + dp->drv.driver = "sdl"; + + dp->drv.init_graphics = sdl_init_graphics; + dp->drv.end_graphics = sdl_end_graphics; + dp->drv.set_display_title = sdl_set_display_title; + dp->drv.get_display_width = sdl_get_display_width; + dp->drv.get_display_height = sdl_get_display_height; + dp->drv.display = sdl_display; + dp->drv.handle_resize = sdl_handle_resize; + dp->drv.get_event = sdl_get_event; + dp->drv.set_mouse = sdl_set_mouse; + dp->drv.set_cursor = NULL; + + return 0; +} + +#endif /* USE_SDL */ + diff --git a/configure.ac b/configure.ac index dfc9d8a..9f1769f 100644 --- a/configure.ac +++ b/configure.ac @@ -70,6 +70,8 @@ AC_ARG_ENABLE(x11, [ --enable-x11 X11 support (autodetected)]) AC_ARG_ENABLE(gl, [ --enable-gl OpenGL support (autodetected)]) +AC_ARG_ENABLE(sdl, + [ --enable-sdl, SDL support (autodetected)]) AC_ARG_ENABLE(cocoa, [ --enable-cocoa Cocoa support (autodetected)]) AC_ARG_ENABLE(network, @@ -351,6 +353,19 @@ if test "${enable_vga}" != "no"; then fi fi +if test "${enable_sdl}" != "no"; then + ac_cv_my_have_sdl="no" + AC_CHECK_HEADERS(SDL/SDL.h SDL/SDL_image.h SDL/SDL_ttf.h, + [ac_cv_my_have_sdl="yes"]) + if test "${ac_cv_my_have_sdl}" = "yes"; then + AC_DEFINE(USE_SDL, 1, Define to 1 to activate the SDL backend driver) + SDL_LIBS="-lSDL -lSDL_ttf -lSDL_image" + CACA_DRIVERS="${CACA_DRIVERS} sdl" + elif test "${enable_sdl}" = "yes"; then + AC_MSG_ERROR([cannot find sdl development files]) + fi +fi + if test "${enable_debug}" = "yes"; then AC_DEFINE(DEBUG, 1, Define to 1 to activate debug) fi @@ -363,9 +378,12 @@ if test "${enable_plugins}" = "yes"; then ac_cv_my_have_plugins="yes" AC_DEFINE(USE_PLUGINS, 1, Define to 1 to activate plugins) CACA_LIBS="${CACA_LIBS} -ldl" + if test "${enable_sdl}" = "yes"; then + CACA_LIBS="${CACA_LIBS} ${SDL_LIBS}" + fi else - CACA_CFLAGS="${CACA_CFLAGS} ${X11_CFLAGS} ${GL_CFLAGS} ${FTGL_CFLAGS}" - CACA_LIBS="${CACA_LIBS} ${X11_LIBS} ${GL_LIBS} ${FTGL_LIBS}" + CACA_CFLAGS="${CACA_CFLAGS} ${X11_CFLAGS} ${GL_CFLAGS} ${FTGL_CFLAGS} ${SDL_CFLAGS}" + CACA_LIBS="${CACA_LIBS} ${X11_LIBS} ${GL_LIBS} ${FTGL_LIBS} ${SDL_LIBS}" fi AM_CONDITIONAL(USE_PLUGINS, test "${ac_cv_my_have_plugins}" = "yes")