contrib: cargo: use cargo/vendored-openssl if needed
[vlc.git] / modules / video_output / caca.c
blob079d72e65ab276b0ba35d3444e5f1f1a16e460fc
1 /*****************************************************************************
2 * caca.c: Color ASCII Art "vout display" module using libcaca
3 *****************************************************************************
4 * Copyright (C) 2003-2009 VLC authors and VideoLAN
6 * Authors: Sam Hocevar <sam@zoy.org>
7 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
25 * Preamble
26 *****************************************************************************/
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
32 #include <stdbool.h>
33 #include <stdint.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <vlc_common.h>
37 #include <vlc_queue.h>
38 #include <vlc_plugin.h>
39 #include <vlc_vout_display.h>
40 #if !defined(_WIN32) && !defined(__APPLE__)
41 # ifdef X_DISPLAY_MISSING
42 # error Xlib required due to XInitThreads
43 # endif
44 # include <vlc_xlib.h>
45 #endif
47 #include <caca.h>
49 /* */
50 struct vout_display_sys_t {
51 cucul_canvas_t *cv;
52 caca_display_t *dp;
53 cucul_dither_t *dither;
55 bool dead;
56 vlc_queue_t q;
57 vlc_thread_t thread;
58 vout_window_t *window;
59 vout_display_place_t place;
61 vlc_tick_t cursor_timeout;
62 vlc_tick_t cursor_deadline;
65 typedef struct vlc_caca_event {
66 struct vlc_caca_event *next;
67 int key;
68 } vlc_caca_event_t;
70 static void *VoutDisplayEventKeyDispatch(void *data)
72 vout_display_t *vd = data;
73 vout_display_sys_t *sys = vd->sys;
74 vlc_caca_event_t *event;
76 while ((event = vlc_queue_DequeueKillable(&sys->q, &sys->dead)) != NULL) {
77 vout_window_ReportKeyPress(sys->window, event->key);
78 free(event);
81 return NULL;
84 static void VoutDisplayEventKey(vout_display_sys_t *sys, int key)
86 vlc_caca_event_t *event = malloc(sizeof (*event));
88 if (likely(event != NULL)) {
89 event->key = key;
90 vlc_queue_Enqueue(&sys->q, event);
94 /**
95 * Compute the place in canvas unit.
97 static void Place(vout_display_t *vd)
99 vout_display_sys_t *sys = vd->sys;
101 const int canvas_width = cucul_get_canvas_width(sys->cv);
102 const int canvas_height = cucul_get_canvas_height(sys->cv);
103 const int display_width = caca_get_display_width(sys->dp);
104 const int display_height = caca_get_display_height(sys->dp);
106 if (display_width > 0 && display_height > 0) {
107 sys->place.x = sys->place.x * canvas_width / display_width;
108 sys->place.y = sys->place.y * canvas_height / display_height;
109 sys->place.width = (sys->place.width * canvas_width + display_width/2) / display_width;
110 sys->place.height = (sys->place.height * canvas_height + display_height/2) / display_height;
111 } else {
112 sys->place.x = 0;
113 sys->place.y = 0;
114 sys->place.width = canvas_width;
115 sys->place.height = display_height;
119 static void Manage(vout_display_t *vd);
122 * Prepare a picture for display */
123 static void Prepare(vout_display_t *vd, picture_t *picture,
124 subpicture_t *subpicture, vlc_tick_t date)
126 Manage(vd);
127 VLC_UNUSED(date);
129 vout_display_sys_t *sys = vd->sys;
131 if (!sys->dither) {
132 /* Create the libcaca dither object */
133 sys->dither = cucul_create_dither(32,
134 vd->source->i_visible_width,
135 vd->source->i_visible_height,
136 picture->p[0].i_pitch,
137 picture->format.i_rmask,
138 picture->format.i_gmask,
139 picture->format.i_bmask,
140 0x00000000);
142 if (!sys->dither) {
143 msg_Err(vd, "could not create libcaca dither object");
144 return;
148 cucul_set_color_ansi(sys->cv, CUCUL_COLOR_DEFAULT, CUCUL_COLOR_BLACK);
149 cucul_clear_canvas(sys->cv);
151 const int crop_offset = vd->source->i_y_offset * picture->p->i_pitch +
152 vd->source->i_x_offset * picture->p->i_pixel_pitch;
153 cucul_dither_bitmap(sys->cv, sys->place.x, sys->place.y,
154 sys->place.width, sys->place.height,
155 sys->dither,
156 &picture->p->p_pixels[crop_offset]);
157 VLC_UNUSED(subpicture);
161 * Display a picture
163 static void PictureDisplay(vout_display_t *vd, picture_t *picture)
165 vout_display_sys_t *sys = vd->sys;
167 caca_refresh_display(sys->dp);
168 VLC_UNUSED(picture);
172 * Control for vout display
174 static int Control(vout_display_t *vd, int query, va_list args)
176 vout_display_sys_t *sys = vd->sys;
178 (void) args;
180 switch (query) {
181 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
182 if (sys->dither)
183 cucul_free_dither(sys->dither);
184 sys->dither = NULL;
185 /* fall through */
186 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
187 case VOUT_DISPLAY_CHANGE_ZOOM:
188 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
189 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
190 return VLC_SUCCESS;
192 default:
193 msg_Err(vd, "Unsupported query in vout display caca");
194 return VLC_EGENERIC;
198 /* */
199 static const struct {
200 int caca;
201 int vlc;
202 } keys[] = {
204 { CACA_KEY_CTRL_A, KEY_MODIFIER_CTRL | 'a' },
205 { CACA_KEY_CTRL_B, KEY_MODIFIER_CTRL | 'b' },
206 { CACA_KEY_CTRL_C, KEY_MODIFIER_CTRL | 'c' },
207 { CACA_KEY_CTRL_D, KEY_MODIFIER_CTRL | 'd' },
208 { CACA_KEY_CTRL_E, KEY_MODIFIER_CTRL | 'e' },
209 { CACA_KEY_CTRL_F, KEY_MODIFIER_CTRL | 'f' },
210 { CACA_KEY_CTRL_G, KEY_MODIFIER_CTRL | 'g' },
211 { CACA_KEY_BACKSPACE, KEY_BACKSPACE },
212 { CACA_KEY_TAB, KEY_TAB },
213 { CACA_KEY_CTRL_J, KEY_MODIFIER_CTRL | 'j' },
214 { CACA_KEY_CTRL_K, KEY_MODIFIER_CTRL | 'k' },
215 { CACA_KEY_CTRL_L, KEY_MODIFIER_CTRL | 'l' },
216 { CACA_KEY_RETURN, KEY_ENTER },
218 { CACA_KEY_CTRL_N, KEY_MODIFIER_CTRL | 'n' },
219 { CACA_KEY_CTRL_O, KEY_MODIFIER_CTRL | 'o' },
220 { CACA_KEY_CTRL_P, KEY_MODIFIER_CTRL | 'p' },
221 { CACA_KEY_CTRL_Q, KEY_MODIFIER_CTRL | 'q' },
222 { CACA_KEY_CTRL_R, KEY_MODIFIER_CTRL | 'r' },
224 { CACA_KEY_PAUSE, -1 },
225 { CACA_KEY_CTRL_T, KEY_MODIFIER_CTRL | 't' },
226 { CACA_KEY_CTRL_U, KEY_MODIFIER_CTRL | 'u' },
227 { CACA_KEY_CTRL_V, KEY_MODIFIER_CTRL | 'v' },
228 { CACA_KEY_CTRL_W, KEY_MODIFIER_CTRL | 'w' },
229 { CACA_KEY_CTRL_X, KEY_MODIFIER_CTRL | 'x' },
230 { CACA_KEY_CTRL_Y, KEY_MODIFIER_CTRL | 'y' },
231 { CACA_KEY_CTRL_Z, KEY_MODIFIER_CTRL | 'z' },
233 { CACA_KEY_ESCAPE, KEY_ESC },
234 { CACA_KEY_DELETE, KEY_DELETE },
236 { CACA_KEY_F1, KEY_F1 },
237 { CACA_KEY_F2, KEY_F2 },
238 { CACA_KEY_F3, KEY_F3 },
239 { CACA_KEY_F4, KEY_F4 },
240 { CACA_KEY_F5, KEY_F5 },
241 { CACA_KEY_F6, KEY_F6 },
242 { CACA_KEY_F7, KEY_F7 },
243 { CACA_KEY_F8, KEY_F8 },
244 { CACA_KEY_F9, KEY_F9 },
245 { CACA_KEY_F10, KEY_F10 },
246 { CACA_KEY_F11, KEY_F11 },
247 { CACA_KEY_F12, KEY_F12 },
248 { CACA_KEY_F13, -1 },
249 { CACA_KEY_F14, -1 },
250 { CACA_KEY_F15, -1 },
252 { CACA_KEY_UP, KEY_UP },
253 { CACA_KEY_DOWN, KEY_DOWN },
254 { CACA_KEY_LEFT, KEY_LEFT },
255 { CACA_KEY_RIGHT, KEY_RIGHT },
257 { CACA_KEY_INSERT, KEY_INSERT },
258 { CACA_KEY_HOME, KEY_HOME },
259 { CACA_KEY_END, KEY_END },
260 { CACA_KEY_PAGEUP, KEY_PAGEUP },
261 { CACA_KEY_PAGEDOWN,KEY_PAGEDOWN },
263 /* */
264 { -1, -1 }
267 static const struct {
268 int caca;
269 int vlc;
270 } mouses[] = {
271 { 1, MOUSE_BUTTON_LEFT },
272 { 2, MOUSE_BUTTON_CENTER },
273 { 3, MOUSE_BUTTON_RIGHT },
274 { 4, MOUSE_BUTTON_WHEEL_UP },
275 { 5, MOUSE_BUTTON_WHEEL_DOWN },
277 /* */
278 { -1, -1 }
281 #define INVALID_DEADLINE INT64_MAX
284 * Proccess pending event
286 static void Manage(vout_display_t *vd)
288 vout_display_sys_t *sys = vd->sys;
290 if (sys->cursor_deadline != INVALID_DEADLINE && sys->cursor_deadline < vlc_tick_now()) {
291 caca_set_mouse(sys->dp, 0);
292 sys->cursor_deadline = INVALID_DEADLINE;
295 struct caca_event ev;
296 while (caca_get_event(sys->dp, CACA_EVENT_ANY, &ev, 0) > 0) {
297 switch (caca_get_event_type(&ev)) {
298 case CACA_EVENT_KEY_PRESS: {
299 const int caca = caca_get_event_key_ch(&ev);
301 for (int i = 0; keys[i].caca != -1; i++) {
302 if (keys[i].caca == caca) {
303 const int vlc = keys[i].vlc;
305 if (vlc >= 0)
306 VoutDisplayEventKey(sys, vlc);
307 return;
310 if (caca >= 0x20 && caca <= 0x7f) {
311 VoutDisplayEventKey(sys, caca);
312 return;
314 break;
316 case CACA_EVENT_RESIZE:
317 break;
318 case CACA_EVENT_MOUSE_MOTION:
319 caca_set_mouse(sys->dp, 1);
320 sys->cursor_deadline = vlc_tick_now() + sys->cursor_timeout;
321 vout_window_ReportMouseMoved(sys->window,
322 caca_get_event_mouse_x(&ev),
323 caca_get_event_mouse_y(&ev));
324 break;
325 case CACA_EVENT_MOUSE_PRESS:
326 case CACA_EVENT_MOUSE_RELEASE: {
327 caca_set_mouse(sys->dp, 1);
328 sys->cursor_deadline = vlc_tick_now() + sys->cursor_timeout;
330 const int caca = caca_get_event_mouse_button(&ev);
331 for (int i = 0; mouses[i].caca != -1; i++) {
332 if (mouses[i].caca == caca) {
333 if (caca_get_event_type(&ev) == CACA_EVENT_MOUSE_PRESS)
334 vout_window_ReportMousePressed(sys->window,
335 mouses[i].vlc);
336 else
337 vout_window_ReportMouseReleased(sys->window,
338 mouses[i].vlc);
339 return;
342 break;
344 case CACA_EVENT_QUIT:
345 vout_window_ReportClose(sys->window);
346 break;
347 default:
348 break;
354 * Close a libcaca video output
356 static void Close(vout_display_t *vd)
358 vout_display_sys_t *sys = vd->sys;
360 vlc_queue_Kill(&sys->q, &sys->dead);
361 vlc_join(sys->thread, NULL);
363 if (sys->dither)
364 cucul_free_dither(sys->dither);
365 caca_free_display(sys->dp);
366 cucul_free_canvas(sys->cv);
368 #if defined(_WIN32)
369 FreeConsole();
370 #endif
372 free(sys);
376 * This function initializes libcaca vout method.
378 static int Open(vout_display_t *vd, const vout_display_cfg_t *cfg,
379 video_format_t *fmtp, vlc_video_context *context)
381 vout_display_sys_t *sys;
383 (void) context;
385 if (vout_display_cfg_IsWindowed(cfg))
386 return VLC_EGENERIC;
387 #if !defined(__APPLE__) && !defined(_WIN32)
388 # ifndef X_DISPLAY_MISSING
389 if (!vlc_xlib_init(VLC_OBJECT(vd)))
390 return VLC_EGENERIC;
391 # endif
392 #endif
394 #if defined(_WIN32)
395 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
396 SMALL_RECT rect;
397 COORD coord;
398 HANDLE hstdout;
400 if (!AllocConsole()) {
401 msg_Err(vd, "cannot create console");
402 return VLC_EGENERIC;
405 hstdout =
406 CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
407 FILE_SHARE_READ | FILE_SHARE_WRITE,
408 NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
409 if (!hstdout || hstdout == INVALID_HANDLE_VALUE) {
410 msg_Err(vd, "cannot create screen buffer");
411 FreeConsole();
412 return VLC_EGENERIC;
415 if (!SetConsoleActiveScreenBuffer(hstdout)) {
416 msg_Err(vd, "cannot set active screen buffer");
417 FreeConsole();
418 return VLC_EGENERIC;
421 coord = GetLargestConsoleWindowSize(hstdout);
422 msg_Dbg(vd, "SetConsoleWindowInfo: %ix%i", coord.X, coord.Y);
424 /* Force size for now */
425 coord.X = 100;
426 coord.Y = 40;
428 if (!SetConsoleScreenBufferSize(hstdout, coord))
429 msg_Warn(vd, "SetConsoleScreenBufferSize %i %i",
430 coord.X, coord.Y);
432 /* Get the current screen buffer size and window position. */
433 if (GetConsoleScreenBufferInfo(hstdout, &csbiInfo)) {
434 rect.Top = 0; rect.Left = 0;
435 rect.Right = csbiInfo.dwMaximumWindowSize.X - 1;
436 rect.Bottom = csbiInfo.dwMaximumWindowSize.Y - 1;
437 if (!SetConsoleWindowInfo(hstdout, TRUE, &rect))
438 msg_Dbg(vd, "SetConsoleWindowInfo failed: %ix%i",
439 rect.Right, rect.Bottom);
441 #endif
443 /* Allocate structure */
444 vd->sys = sys = calloc(1, sizeof(*sys));
445 if (!sys)
446 goto error;
448 sys->cv = cucul_create_canvas(0, 0);
449 if (!sys->cv) {
450 msg_Err(vd, "cannot initialize libcucul");
451 goto error;
454 sys->window = cfg->window;
455 const char *driver = NULL;
456 #ifdef __APPLE__
457 // Make sure we don't try to open a window.
458 driver = "ncurses";
459 #endif
461 sys->dp = caca_create_display_with_driver(sys->cv, driver);
462 if (!sys->dp) {
463 msg_Err(vd, "cannot initialize libcaca");
464 goto error;
467 char *title = var_InheritString(vd, "video-title");
468 caca_set_display_title(sys->dp,
469 (title != NULL) ? title : VOUT_TITLE "(Colour AsCii Art)");
470 free(title);
472 sys->dead = false;
473 vlc_queue_Init(&sys->q, offsetof (vlc_caca_event_t, next));
475 if (vlc_clone(&sys->thread, VoutDisplayEventKeyDispatch, vd,
476 VLC_THREAD_PRIORITY_LOW))
477 goto error;
479 sys->cursor_timeout = VLC_TICK_FROM_MS( var_InheritInteger(vd, "mouse-hide-timeout") );
480 sys->cursor_deadline = INVALID_DEADLINE;
482 /* Fix format */
483 if (fmtp->i_chroma != VLC_CODEC_RGB32) {
484 fmtp->i_chroma = VLC_CODEC_RGB32;
485 fmtp->i_rmask = 0x00ff0000;
486 fmtp->i_gmask = 0x0000ff00;
487 fmtp->i_bmask = 0x000000ff;
490 /* Setup vout_display now that everything is fine */
491 vd->prepare = Prepare;
492 vd->display = PictureDisplay;
493 vd->control = Control;
494 vd->close = Close;
496 /* Fix initial state */
497 caca_refresh_display(sys->dp);
499 Place(vd);
501 return VLC_SUCCESS;
503 error:
504 if (sys) {
505 if (sys->dither)
506 cucul_free_dither(sys->dither);
507 if (sys->dp)
508 caca_free_display(sys->dp);
509 if (sys->cv)
510 cucul_free_canvas(sys->cv);
512 free(sys);
514 #if defined(_WIN32)
515 FreeConsole();
516 #endif
517 return VLC_EGENERIC;
520 /*****************************************************************************
521 * Module descriptor
522 *****************************************************************************/
523 vlc_module_begin()
524 set_shortname("Caca")
525 set_category(CAT_VIDEO)
526 set_subcategory(SUBCAT_VIDEO_VOUT)
527 set_description(N_("Color ASCII art video output"))
528 set_callback_display(Open, 15)
529 vlc_module_end()