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