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