1 /*****************************************************************************
2 * caca.c: Color ASCII Art "vout display" module using libcaca
3 *****************************************************************************
4 * Copyright (C) 2003-2009 VLC authors and VideoLAN
5 * $Id: 33a0409820488f57ba62e15b3839a8c89570ad5c $
6 *
7 * Authors: Sam Hocevar <sam@zoy.org>
8 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
9 *
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
24
25 /*****************************************************************************
26 * Preamble
27 *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_vout_display.h>
36 #include <vlc_picture_pool.h>
37 #if !defined(_WIN32) && !defined(__APPLE__)
38 # ifdef X_DISPLAY_MISSING
39 # error Xlib required due to XInitThreads
40 # endif
41 # include <vlc_xlib.h>
42 #endif
43
44 #include <caca.h>
45 #include "event_thread.h"
46
47 /*****************************************************************************
48 * Module descriptor
49 *****************************************************************************/
50 static int Open (vlc_object_t *);
51 static void Close(vlc_object_t *);
52
53 vlc_module_begin()
54 set_shortname("Caca")
55 set_category(CAT_VIDEO)
56 set_subcategory(SUBCAT_VIDEO_VOUT)
57 set_description(N_("Color ASCII art video output"))
58 set_capability("vout display", 15)
59 set_callbacks(Open, Close)
60 vlc_module_end()
61
62 /*****************************************************************************
63 * Local prototypes
64 *****************************************************************************/
65 static picture_pool_t *Pool (vout_display_t *, unsigned);
66 static void Prepare(vout_display_t *, picture_t *, subpicture_t *);
67 static void PictureDisplay(vout_display_t *, picture_t *, subpicture_t *);
68 static int Control(vout_display_t *, int, va_list);
69
70 /* */
71 static void Manage(vout_display_t *);
72 static void Refresh(vout_display_t *);
73 static void Place(vout_display_t *, vout_display_place_t *);
74
75 /* */
76 struct vout_display_sys_t {
77 cucul_canvas_t *cv;
78 caca_display_t *dp;
79 cucul_dither_t *dither;
80
81 picture_pool_t *pool;
82 vout_display_event_thread_t *et;
83 };
84
85 /**
86 * This function initializes libcaca vout method.
87 */
Open(vlc_object_t * object)88 static int Open(vlc_object_t *object)
89 {
90 vout_display_t *vd = (vout_display_t *)object;
91 vout_display_sys_t *sys;
92
93 if (vout_display_IsWindowed(vd))
94 return VLC_EGENERIC;
95 #if !defined(__APPLE__) && !defined(_WIN32)
96 # ifndef X_DISPLAY_MISSING
97 if (!vlc_xlib_init(object))
98 return VLC_EGENERIC;
99 # endif
100 #endif
101
102 #if defined(_WIN32)
103 CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
104 SMALL_RECT rect;
105 COORD coord;
106 HANDLE hstdout;
107
108 if (!AllocConsole()) {
109 msg_Err(vd, "cannot create console");
110 return VLC_EGENERIC;
111 }
112
113 hstdout =
114 CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
115 FILE_SHARE_READ | FILE_SHARE_WRITE,
116 NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
117 if (!hstdout || hstdout == INVALID_HANDLE_VALUE) {
118 msg_Err(vd, "cannot create screen buffer");
119 FreeConsole();
120 return VLC_EGENERIC;
121 }
122
123 if (!SetConsoleActiveScreenBuffer(hstdout)) {
124 msg_Err(vd, "cannot set active screen buffer");
125 FreeConsole();
126 return VLC_EGENERIC;
127 }
128
129 coord = GetLargestConsoleWindowSize(hstdout);
130 msg_Dbg(vd, "SetConsoleWindowInfo: %ix%i", coord.X, coord.Y);
131
132 /* Force size for now */
133 coord.X = 100;
134 coord.Y = 40;
135
136 if (!SetConsoleScreenBufferSize(hstdout, coord))
137 msg_Warn(vd, "SetConsoleScreenBufferSize %i %i",
138 coord.X, coord.Y);
139
140 /* Get the current screen buffer size and window position. */
141 if (GetConsoleScreenBufferInfo(hstdout, &csbiInfo)) {
142 rect.Top = 0; rect.Left = 0;
143 rect.Right = csbiInfo.dwMaximumWindowSize.X - 1;
144 rect.Bottom = csbiInfo.dwMaximumWindowSize.Y - 1;
145 if (!SetConsoleWindowInfo(hstdout, TRUE, &rect))
146 msg_Dbg(vd, "SetConsoleWindowInfo failed: %ix%i",
147 rect.Right, rect.Bottom);
148 }
149 #endif
150
151 /* Allocate structure */
152 vd->sys = sys = calloc(1, sizeof(*sys));
153 if (!sys)
154 goto error;
155
156 sys->cv = cucul_create_canvas(0, 0);
157 if (!sys->cv) {
158 msg_Err(vd, "cannot initialize libcucul");
159 goto error;
160 }
161
162 const char *driver = NULL;
163 #ifdef __APPLE__
164 // Make sure we don't try to open a window.
165 driver = "ncurses";
166 #endif
167
168 sys->dp = caca_create_display_with_driver(sys->cv, driver);
169 if (!sys->dp) {
170 msg_Err(vd, "cannot initialize libcaca");
171 goto error;
172 }
173
174 if (vd->cfg->display.title)
175 caca_set_display_title(sys->dp,
176 vd->cfg->display.title);
177 else
178 caca_set_display_title(sys->dp,
179 VOUT_TITLE "(Colour AsCii Art)");
180
181 sys->et = VoutDisplayEventCreateThread(vd);
182
183 /* Fix format */
184 video_format_t fmt = vd->fmt;
185 if (fmt.i_chroma != VLC_CODEC_RGB32) {
186 fmt.i_chroma = VLC_CODEC_RGB32;
187 fmt.i_rmask = 0x00ff0000;
188 fmt.i_gmask = 0x0000ff00;
189 fmt.i_bmask = 0x000000ff;
190 }
191
192 /* Setup vout_display now that everything is fine */
193 vd->fmt = fmt;
194 vd->info.needs_hide_mouse = true;
195
196 vd->pool = Pool;
197 vd->prepare = Prepare;
198 vd->display = PictureDisplay;
199 vd->control = Control;
200 vd->manage = Manage;
201
202 /* Fix initial state */
203 Refresh(vd);
204
205 return VLC_SUCCESS;
206
207 error:
208 if (sys) {
209 if (sys->pool)
210 picture_pool_Release(sys->pool);
211 if (sys->dither)
212 cucul_free_dither(sys->dither);
213 if (sys->dp)
214 caca_free_display(sys->dp);
215 if (sys->cv)
216 cucul_free_canvas(sys->cv);
217
218 free(sys);
219 }
220 #if defined(_WIN32)
221 FreeConsole();
222 #endif
223 return VLC_EGENERIC;
224 }
225
226 /**
227 * Close a libcaca video output
228 */
Close(vlc_object_t * object)229 static void Close(vlc_object_t *object)
230 {
231 vout_display_t *vd = (vout_display_t *)object;
232 vout_display_sys_t *sys = vd->sys;
233
234 VoutDisplayEventKillThread(sys->et);
235 if (sys->pool)
236 picture_pool_Release(sys->pool);
237 if (sys->dither)
238 cucul_free_dither(sys->dither);
239 caca_free_display(sys->dp);
240 cucul_free_canvas(sys->cv);
241
242 #if defined(_WIN32)
243 FreeConsole();
244 #endif
245
246 free(sys);
247 }
248
249 /**
250 * Return a pool of direct buffers
251 */
Pool(vout_display_t * vd,unsigned count)252 static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
253 {
254 vout_display_sys_t *sys = vd->sys;
255
256 if (!sys->pool)
257 sys->pool = picture_pool_NewFromFormat(&vd->fmt, count);
258 return sys->pool;
259 }
260
261 /**
262 * Prepare a picture for display */
Prepare(vout_display_t * vd,picture_t * picture,subpicture_t * subpicture)263 static void Prepare(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
264 {
265 vout_display_sys_t *sys = vd->sys;
266
267 if (!sys->dither) {
268 /* Create the libcaca dither object */
269 sys->dither = cucul_create_dither(32,
270 vd->source.i_visible_width,
271 vd->source.i_visible_height,
272 picture->p[0].i_pitch,
273 vd->fmt.i_rmask,
274 vd->fmt.i_gmask,
275 vd->fmt.i_bmask,
276 0x00000000);
277
278 if (!sys->dither) {
279 msg_Err(vd, "could not create libcaca dither object");
280 return;
281 }
282 }
283
284 vout_display_place_t place;
285 Place(vd, &place);
286
287 cucul_set_color_ansi(sys->cv, CUCUL_COLOR_DEFAULT, CUCUL_COLOR_BLACK);
288 cucul_clear_canvas(sys->cv);
289
290 const int crop_offset = vd->source.i_y_offset * picture->p->i_pitch +
291 vd->source.i_x_offset * picture->p->i_pixel_pitch;
292 cucul_dither_bitmap(sys->cv, place.x, place.y,
293 place.width, place.height,
294 sys->dither,
295 &picture->p->p_pixels[crop_offset]);
296 VLC_UNUSED(subpicture);
297 }
298
299 /**
300 * Display a picture
301 */
PictureDisplay(vout_display_t * vd,picture_t * picture,subpicture_t * subpicture)302 static void PictureDisplay(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
303 {
304 Refresh(vd);
305 picture_Release(picture);
306 VLC_UNUSED(subpicture);
307 }
308
309 /**
310 * Control for vout display
311 */
Control(vout_display_t * vd,int query,va_list args)312 static int Control(vout_display_t *vd, int query, va_list args)
313 {
314 vout_display_sys_t *sys = vd->sys;
315
316 (void) args;
317
318 switch (query) {
319 case VOUT_DISPLAY_HIDE_MOUSE:
320 caca_set_mouse(sys->dp, 0);
321 return VLC_SUCCESS;
322
323 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
324 case VOUT_DISPLAY_CHANGE_ZOOM:
325 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
326 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
327 return VLC_EGENERIC;
328
329 case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
330 if (sys->dither)
331 cucul_free_dither(sys->dither);
332 sys->dither = NULL;
333 return VLC_SUCCESS;
334
335 default:
336 msg_Err(vd, "Unsupported query in vout display caca");
337 return VLC_EGENERIC;
338 }
339 }
340
341 /**
342 * Refresh the display and send resize event
343 */
Refresh(vout_display_t * vd)344 static void Refresh(vout_display_t *vd)
345 {
346 vout_display_sys_t *sys = vd->sys;
347
348 /* */
349 caca_refresh_display(sys->dp);
350
351 /* */
352 const unsigned width = caca_get_display_width(sys->dp);
353 const unsigned height = caca_get_display_height(sys->dp);
354
355 if (width != vd->cfg->display.width ||
356 height != vd->cfg->display.height)
357 vout_display_SendEventDisplaySize(vd, width, height);
358 }
359
360 /**
361 * Compute the place in canvas unit.
362 */
Place(vout_display_t * vd,vout_display_place_t * place)363 static void Place(vout_display_t *vd, vout_display_place_t *place)
364 {
365 vout_display_sys_t *sys = vd->sys;
366
367 vout_display_PlacePicture(place, &vd->source, vd->cfg, false);
368
369 const int canvas_width = cucul_get_canvas_width(sys->cv);
370 const int canvas_height = cucul_get_canvas_height(sys->cv);
371 const int display_width = caca_get_display_width(sys->dp);
372 const int display_height = caca_get_display_height(sys->dp);
373
374 if (display_width > 0 && display_height > 0) {
375 place->x = place->x * canvas_width / display_width;
376 place->y = place->y * canvas_height / display_height;
377 place->width = (place->width * canvas_width + display_width/2) / display_width;
378 place->height = (place->height * canvas_height + display_height/2) / display_height;
379 } else {
380 place->x = 0;
381 place->y = 0;
382 place->width = canvas_width;
383 place->height = display_height;
384 }
385 }
386
387 /* */
388 static const struct {
389 int caca;
390 int vlc;
391 } keys[] = {
392
393 { CACA_KEY_CTRL_A, KEY_MODIFIER_CTRL | 'a' },
394 { CACA_KEY_CTRL_B, KEY_MODIFIER_CTRL | 'b' },
395 { CACA_KEY_CTRL_C, KEY_MODIFIER_CTRL | 'c' },
396 { CACA_KEY_CTRL_D, KEY_MODIFIER_CTRL | 'd' },
397 { CACA_KEY_CTRL_E, KEY_MODIFIER_CTRL | 'e' },
398 { CACA_KEY_CTRL_F, KEY_MODIFIER_CTRL | 'f' },
399 { CACA_KEY_CTRL_G, KEY_MODIFIER_CTRL | 'g' },
400 { CACA_KEY_BACKSPACE, KEY_BACKSPACE },
401 { CACA_KEY_TAB, KEY_TAB },
402 { CACA_KEY_CTRL_J, KEY_MODIFIER_CTRL | 'j' },
403 { CACA_KEY_CTRL_K, KEY_MODIFIER_CTRL | 'k' },
404 { CACA_KEY_CTRL_L, KEY_MODIFIER_CTRL | 'l' },
405 { CACA_KEY_RETURN, KEY_ENTER },
406
407 { CACA_KEY_CTRL_N, KEY_MODIFIER_CTRL | 'n' },
408 { CACA_KEY_CTRL_O, KEY_MODIFIER_CTRL | 'o' },
409 { CACA_KEY_CTRL_P, KEY_MODIFIER_CTRL | 'p' },
410 { CACA_KEY_CTRL_Q, KEY_MODIFIER_CTRL | 'q' },
411 { CACA_KEY_CTRL_R, KEY_MODIFIER_CTRL | 'r' },
412
413 { CACA_KEY_PAUSE, -1 },
414 { CACA_KEY_CTRL_T, KEY_MODIFIER_CTRL | 't' },
415 { CACA_KEY_CTRL_U, KEY_MODIFIER_CTRL | 'u' },
416 { CACA_KEY_CTRL_V, KEY_MODIFIER_CTRL | 'v' },
417 { CACA_KEY_CTRL_W, KEY_MODIFIER_CTRL | 'w' },
418 { CACA_KEY_CTRL_X, KEY_MODIFIER_CTRL | 'x' },
419 { CACA_KEY_CTRL_Y, KEY_MODIFIER_CTRL | 'y' },
420 { CACA_KEY_CTRL_Z, KEY_MODIFIER_CTRL | 'z' },
421
422 { CACA_KEY_ESCAPE, KEY_ESC },
423 { CACA_KEY_DELETE, KEY_DELETE },
424
425 { CACA_KEY_F1, KEY_F1 },
426 { CACA_KEY_F2, KEY_F2 },
427 { CACA_KEY_F3, KEY_F3 },
428 { CACA_KEY_F4, KEY_F4 },
429 { CACA_KEY_F5, KEY_F5 },
430 { CACA_KEY_F6, KEY_F6 },
431 { CACA_KEY_F7, KEY_F7 },
432 { CACA_KEY_F8, KEY_F8 },
433 { CACA_KEY_F9, KEY_F9 },
434 { CACA_KEY_F10, KEY_F10 },
435 { CACA_KEY_F11, KEY_F11 },
436 { CACA_KEY_F12, KEY_F12 },
437 { CACA_KEY_F13, -1 },
438 { CACA_KEY_F14, -1 },
439 { CACA_KEY_F15, -1 },
440
441 { CACA_KEY_UP, KEY_UP },
442 { CACA_KEY_DOWN, KEY_DOWN },
443 { CACA_KEY_LEFT, KEY_LEFT },
444 { CACA_KEY_RIGHT, KEY_RIGHT },
445
446 { CACA_KEY_INSERT, KEY_INSERT },
447 { CACA_KEY_HOME, KEY_HOME },
448 { CACA_KEY_END, KEY_END },
449 { CACA_KEY_PAGEUP, KEY_PAGEUP },
450 { CACA_KEY_PAGEDOWN,KEY_PAGEDOWN },
451
452 /* */
453 { -1, -1 }
454 };
455
456 static const struct {
457 int caca;
458 int vlc;
459 } mouses[] = {
460 { 1, MOUSE_BUTTON_LEFT },
461 { 2, MOUSE_BUTTON_CENTER },
462 { 3, MOUSE_BUTTON_RIGHT },
463 { 4, MOUSE_BUTTON_WHEEL_UP },
464 { 5, MOUSE_BUTTON_WHEEL_DOWN },
465
466 /* */
467 { -1, -1 }
468 };
469
470 /**
471 * Proccess pending event
472 */
Manage(vout_display_t * vd)473 static void Manage(vout_display_t *vd)
474 {
475 vout_display_sys_t *sys = vd->sys;
476
477 struct caca_event ev;
478 while (caca_get_event(sys->dp, CACA_EVENT_ANY, &ev, 0) > 0) {
479 switch (caca_get_event_type(&ev)) {
480 case CACA_EVENT_KEY_PRESS: {
481 const int caca = caca_get_event_key_ch(&ev);
482
483 for (int i = 0; keys[i].caca != -1; i++) {
484 if (keys[i].caca == caca) {
485 const int vlc = keys[i].vlc;
486
487 if (vlc >= 0)
488 vout_display_SendEventKey(vd, vlc);
489 return;
490 }
491 }
492 if (caca >= 0x20 && caca <= 0x7f) {
493 vout_display_SendEventKey(vd, caca);
494 return;
495 }
496 break;
497 }
498 case CACA_EVENT_RESIZE:
499 vout_display_SendEventDisplaySize(vd, caca_get_event_resize_width(&ev),
500 caca_get_event_resize_height(&ev));
501 break;
502 case CACA_EVENT_MOUSE_MOTION: {
503 vout_display_place_t place;
504 Place(vd, &place);
505
506 const unsigned x = vd->source.i_x_offset +
507 (int64_t)(caca_get_event_mouse_x(&ev) - place.x) *
508 vd->source.i_visible_width / place.width;
509 const unsigned y = vd->source.i_y_offset +
510 (int64_t)(caca_get_event_mouse_y(&ev) - place.y) *
511 vd->source.i_visible_height / place.height;
512
513 caca_set_mouse(sys->dp, 1);
514 vout_display_SendEventMouseMoved(vd, x, y);
515 break;
516 }
517 case CACA_EVENT_MOUSE_PRESS:
518 case CACA_EVENT_MOUSE_RELEASE: {
519 caca_set_mouse(sys->dp, 1);
520 const int caca = caca_get_event_mouse_button(&ev);
521 for (int i = 0; mouses[i].caca != -1; i++) {
522 if (mouses[i].caca == caca) {
523 if (caca_get_event_type(&ev) == CACA_EVENT_MOUSE_PRESS)
524 vout_display_SendEventMousePressed(vd, mouses[i].vlc);
525 else
526 vout_display_SendEventMouseReleased(vd, mouses[i].vlc);
527 return;
528 }
529 }
530 break;
531 }
532 case CACA_EVENT_QUIT:
533 vout_display_SendEventClose(vd);
534 break;
535 default:
536 break;
537 }
538 }
539 }
540
541