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