| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2023 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / libcaca output filter | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/constants.h> | |
| #include <gpac/color.h> | |
| #include <gpac/list.h> | |
| #include <gpac/modules/video_out.h> | |
| #ifdef GPAC_HAS_LIBCACA | |
| #if defined(WIN32) && !defined(_WIN32_WCE) && !defined(__GNUC__) | |
| # pragma comment(lib, "libcaca.lib") | |
| #endif | |
| #include <caca.h> | |
| typedef struct | |
| { | |
| u32 w, h, pfmt; | |
| caca_dither_t *dither; | |
| } CacaDither; | |
| typedef struct | |
| { | |
| const char *drv; | |
| u32 wnd_w, wnd_h; | |
| u32 mouse_x, mouse_y; | |
| caca_canvas_t *canvas; | |
| caca_display_t *display; | |
| GF_List *dithers; | |
| u8 *backbuffer; | |
| caca_dither_t *bb_dither; | |
| } CacaOutCtx; | |
| static void cacao_shutdown(GF_VideoOutput *dr) | |
| { | |
| CacaOutCtx *ctx = (CacaOutCtx *)dr->opaque; | |
| if (ctx->display) caca_free_display(ctx->display); | |
| ctx->display=NULL; | |
| while (gf_list_count(ctx->dithers)) { | |
| CacaDither *d = gf_list_pop_back(ctx->dithers); | |
| if (d->dither) caca_free_dither(d->dither); | |
| gf_free(d); | |
| } | |
| if (ctx->canvas) caca_free_canvas(ctx->canvas); | |
| ctx->canvas=NULL; | |
| } | |
| static GF_Err cacao_setup(struct _video_out *dr, void *os_handle, void *os_display, u32 init_flags) | |
| { | |
| CacaOutCtx *ctx = (CacaOutCtx *)dr->opaque; | |
| cacao_shutdown(dr); | |
| ctx->canvas = caca_create_canvas(0, 0); | |
| if (!ctx->canvas) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[Caca] Failed to create canvas\n")); | |
| return GF_IO_ERR; | |
| } | |
| ctx->display = NULL; | |
| const char *opt = gf_module_get_key((GF_BaseInterface *)dr, "driver"); | |
| #ifdef GPAC_CONFIG_DARWIN | |
| //if running in xcode disable color logs (not supported by output console) | |
| if (!opt && getenv("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != NULL) { | |
| opt = "raw"; | |
| } | |
| #endif | |
| if (opt) | |
| ctx->display = caca_create_display_with_driver(ctx->canvas, opt); | |
| else | |
| ctx->display = caca_create_display(ctx->canvas); | |
| if (!ctx->display) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[Caca] Failed to create display using driver %s\n", opt ? opt : "defaul")); | |
| return GF_IO_ERR; | |
| } | |
| // caca_set_mouse(ctx->display, 0); | |
| caca_set_display_time(ctx->display, 0); | |
| GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[Caca] Canvas setup size %d %d driver %s\n", caca_get_canvas_width(ctx->canvas), caca_get_canvas_height(ctx->canvas), caca_get_display_driver(ctx->display) )); | |
| return GF_OK; | |
| } | |
| static GF_Err cacao_fullscreen(GF_VideoOutput *dr, Bool bFullScreenOn, u32 *screen_width, u32 *screen_height) | |
| { | |
| return GF_OK; | |
| } | |
| static GF_Err cacao_flush(GF_VideoOutput *dr, GF_Window *dest) | |
| { | |
| CacaOutCtx *ctx = (CacaOutCtx *)dr->opaque; | |
| if (ctx->backbuffer) { | |
| caca_dither_bitmap(ctx->canvas, 0, 0, caca_get_canvas_width(ctx->canvas), caca_get_canvas_height(ctx->canvas), ctx->bb_dither, ctx->backbuffer); | |
| } | |
| caca_refresh_display(ctx->display); | |
| return GF_OK; | |
| } | |
| static void cacao_check_event(GF_VideoOutput *dr) | |
| { | |
| caca_event_t ev; | |
| CacaOutCtx *ctx = (CacaOutCtx *)dr->opaque; | |
| while (caca_get_event(ctx->display, CACA_EVENT_ANY, &ev, 0)) { | |
| GF_Event evt; | |
| u32 etype=0; | |
| Bool is_key=GF_FALSE, is_mouse=GF_FALSE; | |
| if (caca_get_event_type(&ev) & CACA_EVENT_KEY_PRESS) { | |
| etype = GF_EVENT_KEYDOWN; | |
| is_key = GF_TRUE; | |
| } | |
| if (caca_get_event_type(&ev) & CACA_EVENT_KEY_RELEASE) { | |
| etype = GF_EVENT_KEYUP; | |
| is_key = GF_TRUE; | |
| } | |
| if (caca_get_event_type(&ev) & CACA_EVENT_MOUSE_MOTION) { | |
| etype = GF_EVENT_MOUSEMOVE; | |
| is_mouse = GF_TRUE; | |
| ctx->mouse_x = caca_get_event_mouse_x(&ev); | |
| ctx->mouse_x *= ctx->wnd_w; | |
| ctx->mouse_x /= caca_get_canvas_width(ctx->canvas); | |
| ctx->mouse_y = caca_get_event_mouse_y(&ev); | |
| ctx->mouse_y *= ctx->wnd_h; | |
| ctx->mouse_y /= caca_get_canvas_height(ctx->canvas); | |
| } | |
| if (caca_get_event_type(&ev) & CACA_EVENT_MOUSE_PRESS) { | |
| etype = GF_EVENT_MOUSEDOWN; | |
| is_mouse = GF_TRUE; | |
| } | |
| if (caca_get_event_type(&ev) & CACA_EVENT_MOUSE_RELEASE) { | |
| etype = GF_EVENT_MOUSEUP; | |
| is_mouse = GF_TRUE; | |
| } | |
| if (caca_get_event_type(&ev) & CACA_EVENT_QUIT) { | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = GF_EVENT_QUIT; | |
| dr->on_event(dr->evt_cbk_hdl, &evt); | |
| } | |
| if (caca_get_event_type(&ev) & CACA_EVENT_RESIZE) { | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = GF_EVENT_SIZE; | |
| // evt.size.width = caca_get_event_resize_width(&ev); | |
| // evt.size.height = caca_get_event_resize_height(&ev); | |
| // dr->on_event(dr->evt_cbk_hdl, &evt); | |
| } | |
| if (is_key) { | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = etype; | |
| evt.key.key_code = caca_get_event_key_ch(&ev); | |
| switch (evt.key.key_code) { | |
| case CACA_KEY_ESCAPE: evt.type = GF_EVENT_QUIT; break; | |
| case CACA_KEY_CTRL_C: evt.type = GF_EVENT_QUIT; break; | |
| case CACA_KEY_LEFT: evt.key.key_code = GF_KEY_LEFT; break; | |
| case CACA_KEY_RIGHT: evt.key.key_code = GF_KEY_RIGHT; break; | |
| case CACA_KEY_UP: evt.key.key_code = GF_KEY_UP; break; | |
| case CACA_KEY_DOWN: evt.key.key_code = GF_KEY_DOWN; break; | |
| case CACA_KEY_RETURN: evt.key.key_code = GF_KEY_ENTER; break; | |
| case CACA_KEY_BACKSPACE: evt.key.key_code = GF_KEY_BACKSPACE; break; | |
| case CACA_KEY_TAB: evt.key.key_code = GF_KEY_TAB; break; | |
| case CACA_KEY_DELETE: evt.key.key_code = GF_KEY_DEL; break; | |
| case CACA_KEY_PAUSE: evt.key.key_code = GF_KEY_PAUSE; break; | |
| case CACA_KEY_INSERT: evt.key.key_code = GF_KEY_INSERT; break; | |
| case CACA_KEY_HOME: evt.key.key_code = GF_KEY_HOME; break; | |
| case CACA_KEY_END: evt.key.key_code = GF_KEY_END; break; | |
| case CACA_KEY_PAGEUP: evt.key.key_code = GF_KEY_PAGEUP; break; | |
| case CACA_KEY_PAGEDOWN: evt.key.key_code = GF_KEY_PAGEDOWN; break; | |
| case CACA_KEY_F1: evt.key.key_code = GF_KEY_F1; break; | |
| case CACA_KEY_F2: evt.key.key_code = GF_KEY_F2; break; | |
| case CACA_KEY_F3: evt.key.key_code = GF_KEY_F3; break; | |
| case CACA_KEY_F4: evt.key.key_code = GF_KEY_F4; break; | |
| case CACA_KEY_F5: evt.key.key_code = GF_KEY_F5; break; | |
| case CACA_KEY_F6: evt.key.key_code = GF_KEY_F6; break; | |
| case CACA_KEY_F7: evt.key.key_code = GF_KEY_F7; break; | |
| case CACA_KEY_F8: evt.key.key_code = GF_KEY_F8; break; | |
| case CACA_KEY_F9: evt.key.key_code = GF_KEY_F9; break; | |
| case CACA_KEY_F10: evt.key.key_code = GF_KEY_F10; break; | |
| case CACA_KEY_F11: evt.key.key_code = GF_KEY_F11; break; | |
| case CACA_KEY_F12: evt.key.key_code = GF_KEY_F12; break; | |
| case CACA_KEY_F13: evt.key.key_code = GF_KEY_F13; break; | |
| case CACA_KEY_F14: evt.key.key_code = GF_KEY_F14; break; | |
| case CACA_KEY_F15: evt.key.key_code = GF_KEY_F15; break; | |
| default: | |
| if ((evt.key.key_code>=0x30) && (evt.key.key_code<=0x39)) | |
| evt.key.key_code = GF_KEY_0 + evt.key.key_code-0x30; | |
| else if ((evt.key.key_code>=0x41) && (evt.key.key_code<=0x5A)) | |
| evt.key.key_code = GF_KEY_A + evt.key.key_code-0x41; | |
| else if ((evt.key.key_code>=0x61) && (evt.key.key_code<=0x7A)) | |
| evt.key.key_code = GF_KEY_A + evt.key.key_code-0x61; | |
| break; | |
| } | |
| dr->on_event(dr->evt_cbk_hdl, &evt); | |
| } | |
| if (is_mouse) { | |
| memset(&evt, 0, sizeof(GF_Event)); | |
| evt.type = etype; | |
| evt.mouse.x = ctx->mouse_x; | |
| evt.mouse.y = ctx->mouse_y; | |
| dr->on_event(dr->evt_cbk_hdl, &evt); | |
| } | |
| } | |
| } | |
| static void cacao_print_message(CacaOutCtx *ctx, char *msg) | |
| { | |
| u32 y = 2; | |
| char *src = msg; | |
| while (1) { | |
| char *sep = strchr(src, '\n'); | |
| if (sep) sep[0] = 0; | |
| if (src[0]) | |
| caca_put_str(ctx->canvas, 10, y, src); | |
| y++; | |
| if (!sep) break; | |
| sep[0] = '\n'; | |
| src = sep+1; | |
| } | |
| } | |
| static GF_Err cacao_process_event(GF_VideoOutput *dr, GF_Event *evt) | |
| { | |
| if (!evt) { | |
| cacao_check_event(dr); | |
| return GF_OK; | |
| } | |
| CacaOutCtx *ctx = (CacaOutCtx *)dr->opaque; | |
| switch (evt->type) { | |
| case GF_EVENT_SET_CURSOR: | |
| caca_set_mouse(ctx->display, evt->cursor.cursor_type ? 1 : 0); | |
| caca_set_cursor(ctx->display, evt->cursor.cursor_type ? 1 : 0); | |
| break; | |
| case GF_EVENT_SET_CAPTION: | |
| caca_set_display_title(ctx->display, evt->caption.caption); | |
| break; | |
| case GF_EVENT_SIZE: | |
| ctx->wnd_w = evt->size.width; | |
| ctx->wnd_h = evt->size.height; | |
| if (ctx->backbuffer) gf_free(ctx->backbuffer); | |
| ctx->backbuffer = NULL; | |
| if (ctx->bb_dither) caca_free_dither(ctx->bb_dither); | |
| ctx->bb_dither = NULL; | |
| break; | |
| case GF_EVENT_VIDEO_SETUP: | |
| ctx->wnd_w = evt->setup.width; | |
| ctx->wnd_h = evt->setup.height; | |
| if (ctx->backbuffer) gf_free(ctx->backbuffer); | |
| ctx->backbuffer = NULL; | |
| if (ctx->bb_dither) caca_free_dither(ctx->bb_dither); | |
| ctx->bb_dither = NULL; | |
| break; | |
| case GF_EVENT_MESSAGE: | |
| cacao_print_message(ctx, (char *) evt->message.message); | |
| break; | |
| } | |
| return GF_OK; | |
| } | |
| static void set_dither_options(GF_VideoOutput *dr, caca_dither_t *dither) | |
| { | |
| const char *opt = gf_module_get_key((GF_BaseInterface *)dr, "antialias"); | |
| if (opt) caca_set_dither_antialias(dither, opt); | |
| opt = gf_module_get_key((GF_BaseInterface *)dr, "colors"); | |
| if (opt) caca_set_dither_color(dither, opt); | |
| opt = gf_module_get_key((GF_BaseInterface *)dr, "charset"); | |
| if (opt) caca_set_dither_charset(dither, opt); | |
| opt = gf_module_get_key((GF_BaseInterface *)dr, "algorithm"); | |
| if (opt) caca_set_dither_algorithm(dither, opt); | |
| opt = gf_module_get_key((GF_BaseInterface *)dr, "brightness"); | |
| if (opt) caca_set_dither_brightness(dither, (float)atof(opt)); | |
| opt = gf_module_get_key((GF_BaseInterface *)dr, "gamma"); | |
| if (opt) caca_set_dither_gamma(dither, (float)atof(opt)); | |
| opt = gf_module_get_key((GF_BaseInterface *)dr, "contrast"); | |
| if (opt) caca_set_dither_contrast(dither, (float)atof(opt)); | |
| } | |
| static GF_Err cacao_blit(GF_VideoOutput *dr, GF_VideoSurface *video_src, GF_Window *src_wnd, GF_Window *dst_wnd, u32 overlay_type) | |
| { | |
| CacaOutCtx *ctx = (CacaOutCtx *)dr->opaque; | |
| if (ctx->backbuffer) return GF_NOT_SUPPORTED; | |
| CacaDither *d = NULL; | |
| u32 i, count = gf_list_count(ctx->dithers); | |
| for (i=0; i<count; i++) { | |
| d = gf_list_get(ctx->dithers, i); | |
| if (d->pfmt == video_src->pixel_format) break; | |
| d = NULL; | |
| } | |
| if (!d | |
| || (d->w != video_src->width) | |
| || (d->h != video_src->height) | |
| ) { | |
| u32 rmask=0, bmask=0, gmask=0, amask=0; | |
| switch (video_src->pixel_format) { | |
| case GF_PIXEL_RGBA: | |
| amask = 0xff000000; | |
| case GF_PIXEL_RGB: | |
| case GF_PIXEL_RGBX: | |
| rmask = 0x0000ff; | |
| gmask = 0x00ff00; | |
| bmask = 0xff0000; | |
| break; | |
| case GF_PIXEL_BGRA: | |
| amask = 0xff000000; | |
| case GF_PIXEL_BGR: | |
| case GF_PIXEL_BGRX: | |
| bmask = 0x0000ff; | |
| gmask = 0x00ff00; | |
| rmask = 0xff0000; | |
| break; | |
| case GF_PIXEL_ARGB: | |
| amask = 0x000000ff; | |
| rmask = 0x0000ff00; | |
| gmask = 0x00ff0000; | |
| bmask = 0xff000000; | |
| break; | |
| case GF_PIXEL_ABGR: | |
| amask = 0x000000ff; | |
| bmask = 0x0000ff00; | |
| gmask = 0x00ff0000; | |
| rmask = 0xff000000; | |
| break; | |
| default: | |
| return GF_NOT_SUPPORTED; | |
| } | |
| if (!d) { | |
| GF_SAFEALLOC(d, CacaDither); | |
| d->pfmt = video_src->pixel_format; | |
| gf_list_add(ctx->dithers, d); | |
| } | |
| d->w = video_src->width; | |
| d->h = video_src->height; | |
| if (d->dither) caca_free_dither(d->dither); | |
| u32 bpp = gf_pixel_get_bytes_per_pixel(video_src->pixel_format); | |
| d->dither = caca_create_dither(bpp*8, d->w, d->h, (video_src->pitch_y>0) ? video_src->pitch_y : (bpp * d->w), | |
| rmask, gmask, bmask, amask); | |
| if (!d->dither) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_MMIO, ("[Caca] Failed to create dither\n")); | |
| return GF_IO_ERR; | |
| } | |
| //set options | |
| set_dither_options(dr, d->dither); | |
| } | |
| int x=0; | |
| int y=0; | |
| int w=caca_get_canvas_width(ctx->canvas); | |
| int h=caca_get_canvas_height(ctx->canvas); | |
| if (dst_wnd) { | |
| x = dst_wnd->x * w / ctx->wnd_w; | |
| y = dst_wnd->y * h / ctx->wnd_h; | |
| w = dst_wnd->w * w / ctx->wnd_w; | |
| h = dst_wnd->h * h / ctx->wnd_h; | |
| } | |
| caca_dither_bitmap(ctx->canvas, x, y, w, h, d->dither, video_src->video_buffer); | |
| return GF_OK; | |
| } | |
| static GF_Err cacao_lock_backbuffer(GF_VideoOutput *driv, GF_VideoSurface *video_info, Bool do_lock) | |
| { | |
| CacaOutCtx *ctx = (CacaOutCtx *)driv->opaque; | |
| if (!do_lock) return GF_OK; | |
| if (!ctx->backbuffer) { | |
| ctx->backbuffer = gf_malloc(3*ctx->wnd_w*ctx->wnd_h); | |
| if (!ctx->backbuffer) return GF_OUT_OF_MEM; | |
| ctx->bb_dither = caca_create_dither(24, ctx->wnd_w, ctx->wnd_h, ctx->wnd_w*3, 0x0000ff, 0x00ff00, 0xff0000, 0); | |
| if (!ctx->bb_dither) return GF_OUT_OF_MEM; | |
| set_dither_options(driv, ctx->bb_dither); | |
| } | |
| memset(video_info, 0, sizeof(GF_VideoSurface)); | |
| video_info->width = ctx->wnd_w; | |
| video_info->height = ctx->wnd_h; | |
| video_info->pitch_x = 3; | |
| video_info->pitch_y = ctx->wnd_w*3; | |
| video_info->pixel_format = GF_PIXEL_RGB; | |
| video_info->video_buffer = ctx->backbuffer; | |
| return GF_OK; | |
| } | |
| //do not change order | |
| static GF_GPACArg CacaArgs[] = { | |
| GF_DEF_ARG("driver", NULL, NULL, NULL, NULL, GF_ARG_STRING, 0), | |
| GF_DEF_ARG("antialias", NULL, NULL, NULL, NULL, GF_ARG_STRING, 0), | |
| GF_DEF_ARG("colors", NULL, NULL, NULL, NULL, GF_ARG_STRING, 0), | |
| GF_DEF_ARG("charset", NULL, NULL, NULL, NULL, GF_ARG_STRING, 0), | |
| GF_DEF_ARG("algorithm", NULL, NULL, NULL, NULL, GF_ARG_STRING, 0), | |
| GF_DEF_ARG("brightness", NULL, "image brightness", "1", NULL, GF_ARG_DOUBLE, 0), | |
| GF_DEF_ARG("gamma", NULL, "image gamma", "1", NULL, GF_ARG_DOUBLE, 0), | |
| GF_DEF_ARG("contrast", NULL, "image contrast", "1", NULL, GF_ARG_DOUBLE, 0), | |
| {0}, | |
| }; | |
| static GF_GPACArg *cacao_push_arg(GF_VideoOutput *driv, u32 *nb_args, const char *opt_desc) | |
| { | |
| GF_GPACArg *arg = &driv->args[ *nb_args] ; | |
| (*nb_args) ++; | |
| if (opt_desc) arg->description = gf_strdup(opt_desc); | |
| return arg; | |
| } | |
| static void print_opts(GF_VideoOutput *driv, u32 *nb_args, const char *opt_name, const char *opt_desc, char const * const * values, const char *def) | |
| { | |
| GF_GPACArg *arg = cacao_push_arg(driv, nb_args, opt_desc); | |
| arg->val = def ? gf_strdup(def) : NULL; | |
| u32 i; | |
| for (i=0; values[i]; i+=2) { | |
| gf_dynstrcat((char **)&arg->description, values[i], "- "); | |
| gf_dynstrcat((char **)&arg->description, values[i+1], ": "); | |
| if (values[i+2]) | |
| gf_dynstrcat((char **)&arg->description, "\n", NULL); | |
| } | |
| } | |
| static void cacao_load_options(GF_VideoOutput *drv) | |
| { | |
| u32 nb_args=0; | |
| caca_canvas_t *cv = caca_create_canvas(0, 0); | |
| caca_display_t *dp = NULL; | |
| #ifdef GPAC_CONFIG_DARWIN | |
| //if running in xcode disable color logs (not supported by output console) | |
| if (getenv("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != NULL) { | |
| dp = caca_create_display_with_driver(cv, "raw"); | |
| } | |
| #endif | |
| if (!dp) dp = caca_create_display(cv); | |
| print_opts(drv, &nb_args, "driver", "rendering backend\n", caca_get_display_driver_list(), NULL); | |
| caca_free_display(dp); | |
| caca_free_canvas(cv); | |
| caca_dither_t *d = caca_create_dither(24, 2, 2, 6, 0x0000FF, 0x00FF00, 0xFF0000, 0xFF000000); | |
| print_opts(drv, &nb_args, "antialias", "image dither mode\n", caca_get_dither_antialias_list(d), caca_get_dither_antialias(d)); | |
| print_opts(drv, &nb_args, "colors", "image color mode\n", caca_get_dither_color_list(d), caca_get_dither_color(d)); | |
| print_opts(drv, &nb_args, "charset", "image charset\n", caca_get_dither_charset_list(d), caca_get_dither_charset(d)); | |
| print_opts(drv, &nb_args, "algorithm", "image dithering algo\n", caca_get_dither_algorithm_list(d), caca_get_dither_algorithm(d)); | |
| caca_free_dither(d); | |
| } | |
| static void *cacao_new() | |
| { | |
| CacaOutCtx *ctx; | |
| GF_VideoOutput *driv; | |
| GF_SAFEALLOC(driv, GF_VideoOutput); | |
| GF_REGISTER_MODULE_INTERFACE(driv, GF_VIDEO_OUTPUT_INTERFACE, "caca", "gpac distribution"); | |
| GF_SAFEALLOC(ctx, CacaOutCtx); | |
| driv->opaque = ctx; | |
| driv->Setup = cacao_setup; | |
| driv->Shutdown = cacao_shutdown; | |
| driv->SetFullScreen = cacao_fullscreen; | |
| driv->Flush = cacao_flush; | |
| driv->ProcessEvent = cacao_process_event; | |
| driv->hw_caps = GF_VIDEO_HW_HAS_RGB | GF_VIDEO_HW_HAS_RGBA | GF_VIDEO_HW_HAS_STRETCH; | |
| driv->Blit = cacao_blit; | |
| driv->LockBackBuffer = cacao_lock_backbuffer; | |
| driv->LockOSContext = NULL; | |
| ctx->dithers = gf_list_new(); | |
| driv->args = CacaArgs; | |
| driv->description = "Video output in terminal using libcaca"; | |
| if (gf_opts_get_bool("temp", "gendoc")) | |
| cacao_load_options(driv); | |
| gf_opts_set_key("temp", "use_libcaca", "true"); | |
| return driv; | |
| } | |
| static void cacao_del(void *ifce) | |
| { | |
| GF_VideoOutput *dr = (GF_VideoOutput *)ifce; | |
| CacaOutCtx *ctx = dr->opaque; | |
| if (ctx->display) caca_free_display(ctx->display); | |
| if (ctx->canvas) caca_free_canvas(ctx->canvas); | |
| while (gf_list_count(ctx->dithers)) { | |
| CacaDither *d = gf_list_pop_back(ctx->dithers); | |
| if (d->dither) caca_free_dither(d->dither); | |
| gf_free(d); | |
| } | |
| if (dr->args) { | |
| for (u32 i=0; i<5; i++) { | |
| if (dr->args[i].description) gf_free((char *) dr->args[i].description); | |
| if (dr->args[i].val) gf_free((char *) dr->args[i].val); | |
| } | |
| } | |
| gf_list_del(ctx->dithers); | |
| if (ctx->bb_dither) caca_free_dither(ctx->bb_dither); | |
| if (ctx->backbuffer) gf_free(ctx->backbuffer); | |
| gf_opts_set_key("temp", "use_libcaca", NULL); | |
| gf_free(ctx); | |
| gf_free(dr); | |
| } | |
| /*interface query*/ | |
| GPAC_MODULE_EXPORT | |
| const u32 *QueryInterfaces() | |
| { | |
| static u32 si [] = { | |
| GF_VIDEO_OUTPUT_INTERFACE, | |
| 0 | |
| }; | |
| return si; | |
| } | |
| /*interface create*/ | |
| GPAC_MODULE_EXPORT | |
| GF_BaseInterface *LoadInterface(u32 InterfaceType) | |
| { | |
| if (InterfaceType == GF_VIDEO_OUTPUT_INTERFACE) return (GF_BaseInterface*)cacao_new(); | |
| return NULL; | |
| } | |
| /*interface destroy*/ | |
| GPAC_MODULE_EXPORT | |
| void ShutdownInterface(GF_BaseInterface *ifce) | |
| { | |
| switch (ifce->InterfaceType) { | |
| case GF_VIDEO_OUTPUT_INTERFACE: | |
| cacao_del(ifce); | |
| break; | |
| } | |
| } | |
| GPAC_MODULE_STATIC_DECLARATION( caca_out ) | |
| #endif // GPAC_HAS_LIBCACA |