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