1 /*****************************************************************************
2 * caca.c: Color ASCII Art "vout display" module using libcaca
3 *****************************************************************************
4 * Copyright (C) 2003-2009 VLC authors and VideoLAN
6 * Authors: Sam Hocevar <sam@zoy.org>
7 * Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
36 #include <vlc_common.h>
37 #include <vlc_queue.h>
38 #include <vlc_plugin.h>
39 #include <vlc_vout_display.h>
40 #if !defined(_WIN32) && !defined(__APPLE__)
41 # ifdef X_DISPLAY_MISSING
42 # error Xlib required due to XInitThreads
44 # include <vlc_xlib.h>
50 struct vout_display_sys_t
{
53 cucul_dither_t
*dither
;
58 vout_window_t
*window
;
59 vout_display_place_t place
;
61 vlc_tick_t cursor_timeout
;
62 vlc_tick_t cursor_deadline
;
65 typedef struct vlc_caca_event
{
66 struct vlc_caca_event
*next
;
70 static void *VoutDisplayEventKeyDispatch(void *data
)
72 vout_display_t
*vd
= data
;
73 vout_display_sys_t
*sys
= vd
->sys
;
74 vlc_caca_event_t
*event
;
76 while ((event
= vlc_queue_DequeueKillable(&sys
->q
, &sys
->dead
)) != NULL
) {
77 vout_window_ReportKeyPress(sys
->window
, event
->key
);
84 static void VoutDisplayEventKey(vout_display_sys_t
*sys
, int key
)
86 vlc_caca_event_t
*event
= malloc(sizeof (*event
));
88 if (likely(event
!= NULL
)) {
90 vlc_queue_Enqueue(&sys
->q
, event
);
95 * Compute the place in canvas unit.
97 static void Place(vout_display_t
*vd
)
99 vout_display_sys_t
*sys
= vd
->sys
;
101 const int canvas_width
= cucul_get_canvas_width(sys
->cv
);
102 const int canvas_height
= cucul_get_canvas_height(sys
->cv
);
103 const int display_width
= caca_get_display_width(sys
->dp
);
104 const int display_height
= caca_get_display_height(sys
->dp
);
106 if (display_width
> 0 && display_height
> 0) {
107 sys
->place
.x
= sys
->place
.x
* canvas_width
/ display_width
;
108 sys
->place
.y
= sys
->place
.y
* canvas_height
/ display_height
;
109 sys
->place
.width
= (sys
->place
.width
* canvas_width
+ display_width
/2) / display_width
;
110 sys
->place
.height
= (sys
->place
.height
* canvas_height
+ display_height
/2) / display_height
;
114 sys
->place
.width
= canvas_width
;
115 sys
->place
.height
= display_height
;
119 static void Manage(vout_display_t
*vd
);
122 * Prepare a picture for display */
123 static void Prepare(vout_display_t
*vd
, picture_t
*picture
,
124 subpicture_t
*subpicture
, vlc_tick_t date
)
129 vout_display_sys_t
*sys
= vd
->sys
;
132 /* Create the libcaca dither object */
133 sys
->dither
= cucul_create_dither(32,
134 vd
->source
->i_visible_width
,
135 vd
->source
->i_visible_height
,
136 picture
->p
[0].i_pitch
,
137 picture
->format
.i_rmask
,
138 picture
->format
.i_gmask
,
139 picture
->format
.i_bmask
,
143 msg_Err(vd
, "could not create libcaca dither object");
148 cucul_set_color_ansi(sys
->cv
, CUCUL_COLOR_DEFAULT
, CUCUL_COLOR_BLACK
);
149 cucul_clear_canvas(sys
->cv
);
151 const int crop_offset
= vd
->source
->i_y_offset
* picture
->p
->i_pitch
+
152 vd
->source
->i_x_offset
* picture
->p
->i_pixel_pitch
;
153 cucul_dither_bitmap(sys
->cv
, sys
->place
.x
, sys
->place
.y
,
154 sys
->place
.width
, sys
->place
.height
,
156 &picture
->p
->p_pixels
[crop_offset
]);
157 VLC_UNUSED(subpicture
);
163 static void PictureDisplay(vout_display_t
*vd
, picture_t
*picture
)
165 vout_display_sys_t
*sys
= vd
->sys
;
167 caca_refresh_display(sys
->dp
);
172 * Control for vout display
174 static int Control(vout_display_t
*vd
, int query
, va_list args
)
176 vout_display_sys_t
*sys
= vd
->sys
;
181 case VOUT_DISPLAY_CHANGE_SOURCE_CROP
:
183 cucul_free_dither(sys
->dither
);
186 case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE
:
187 case VOUT_DISPLAY_CHANGE_ZOOM
:
188 case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED
:
189 case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT
:
193 msg_Err(vd
, "Unsupported query in vout display caca");
199 static const struct {
204 { CACA_KEY_CTRL_A
, KEY_MODIFIER_CTRL
| 'a' },
205 { CACA_KEY_CTRL_B
, KEY_MODIFIER_CTRL
| 'b' },
206 { CACA_KEY_CTRL_C
, KEY_MODIFIER_CTRL
| 'c' },
207 { CACA_KEY_CTRL_D
, KEY_MODIFIER_CTRL
| 'd' },
208 { CACA_KEY_CTRL_E
, KEY_MODIFIER_CTRL
| 'e' },
209 { CACA_KEY_CTRL_F
, KEY_MODIFIER_CTRL
| 'f' },
210 { CACA_KEY_CTRL_G
, KEY_MODIFIER_CTRL
| 'g' },
211 { CACA_KEY_BACKSPACE
, KEY_BACKSPACE
},
212 { CACA_KEY_TAB
, KEY_TAB
},
213 { CACA_KEY_CTRL_J
, KEY_MODIFIER_CTRL
| 'j' },
214 { CACA_KEY_CTRL_K
, KEY_MODIFIER_CTRL
| 'k' },
215 { CACA_KEY_CTRL_L
, KEY_MODIFIER_CTRL
| 'l' },
216 { CACA_KEY_RETURN
, KEY_ENTER
},
218 { CACA_KEY_CTRL_N
, KEY_MODIFIER_CTRL
| 'n' },
219 { CACA_KEY_CTRL_O
, KEY_MODIFIER_CTRL
| 'o' },
220 { CACA_KEY_CTRL_P
, KEY_MODIFIER_CTRL
| 'p' },
221 { CACA_KEY_CTRL_Q
, KEY_MODIFIER_CTRL
| 'q' },
222 { CACA_KEY_CTRL_R
, KEY_MODIFIER_CTRL
| 'r' },
224 { CACA_KEY_PAUSE
, -1 },
225 { CACA_KEY_CTRL_T
, KEY_MODIFIER_CTRL
| 't' },
226 { CACA_KEY_CTRL_U
, KEY_MODIFIER_CTRL
| 'u' },
227 { CACA_KEY_CTRL_V
, KEY_MODIFIER_CTRL
| 'v' },
228 { CACA_KEY_CTRL_W
, KEY_MODIFIER_CTRL
| 'w' },
229 { CACA_KEY_CTRL_X
, KEY_MODIFIER_CTRL
| 'x' },
230 { CACA_KEY_CTRL_Y
, KEY_MODIFIER_CTRL
| 'y' },
231 { CACA_KEY_CTRL_Z
, KEY_MODIFIER_CTRL
| 'z' },
233 { CACA_KEY_ESCAPE
, KEY_ESC
},
234 { CACA_KEY_DELETE
, KEY_DELETE
},
236 { CACA_KEY_F1
, KEY_F1
},
237 { CACA_KEY_F2
, KEY_F2
},
238 { CACA_KEY_F3
, KEY_F3
},
239 { CACA_KEY_F4
, KEY_F4
},
240 { CACA_KEY_F5
, KEY_F5
},
241 { CACA_KEY_F6
, KEY_F6
},
242 { CACA_KEY_F7
, KEY_F7
},
243 { CACA_KEY_F8
, KEY_F8
},
244 { CACA_KEY_F9
, KEY_F9
},
245 { CACA_KEY_F10
, KEY_F10
},
246 { CACA_KEY_F11
, KEY_F11
},
247 { CACA_KEY_F12
, KEY_F12
},
248 { CACA_KEY_F13
, -1 },
249 { CACA_KEY_F14
, -1 },
250 { CACA_KEY_F15
, -1 },
252 { CACA_KEY_UP
, KEY_UP
},
253 { CACA_KEY_DOWN
, KEY_DOWN
},
254 { CACA_KEY_LEFT
, KEY_LEFT
},
255 { CACA_KEY_RIGHT
, KEY_RIGHT
},
257 { CACA_KEY_INSERT
, KEY_INSERT
},
258 { CACA_KEY_HOME
, KEY_HOME
},
259 { CACA_KEY_END
, KEY_END
},
260 { CACA_KEY_PAGEUP
, KEY_PAGEUP
},
261 { CACA_KEY_PAGEDOWN
,KEY_PAGEDOWN
},
267 static const struct {
271 { 1, MOUSE_BUTTON_LEFT
},
272 { 2, MOUSE_BUTTON_CENTER
},
273 { 3, MOUSE_BUTTON_RIGHT
},
274 { 4, MOUSE_BUTTON_WHEEL_UP
},
275 { 5, MOUSE_BUTTON_WHEEL_DOWN
},
281 #define INVALID_DEADLINE INT64_MAX
284 * Proccess pending event
286 static void Manage(vout_display_t
*vd
)
288 vout_display_sys_t
*sys
= vd
->sys
;
290 if (sys
->cursor_deadline
!= INVALID_DEADLINE
&& sys
->cursor_deadline
< vlc_tick_now()) {
291 caca_set_mouse(sys
->dp
, 0);
292 sys
->cursor_deadline
= INVALID_DEADLINE
;
295 struct caca_event ev
;
296 while (caca_get_event(sys
->dp
, CACA_EVENT_ANY
, &ev
, 0) > 0) {
297 switch (caca_get_event_type(&ev
)) {
298 case CACA_EVENT_KEY_PRESS
: {
299 const int caca
= caca_get_event_key_ch(&ev
);
301 for (int i
= 0; keys
[i
].caca
!= -1; i
++) {
302 if (keys
[i
].caca
== caca
) {
303 const int vlc
= keys
[i
].vlc
;
306 VoutDisplayEventKey(sys
, vlc
);
310 if (caca
>= 0x20 && caca
<= 0x7f) {
311 VoutDisplayEventKey(sys
, caca
);
316 case CACA_EVENT_RESIZE
:
318 case CACA_EVENT_MOUSE_MOTION
:
319 caca_set_mouse(sys
->dp
, 1);
320 sys
->cursor_deadline
= vlc_tick_now() + sys
->cursor_timeout
;
321 vout_window_ReportMouseMoved(sys
->window
,
322 caca_get_event_mouse_x(&ev
),
323 caca_get_event_mouse_y(&ev
));
325 case CACA_EVENT_MOUSE_PRESS
:
326 case CACA_EVENT_MOUSE_RELEASE
: {
327 caca_set_mouse(sys
->dp
, 1);
328 sys
->cursor_deadline
= vlc_tick_now() + sys
->cursor_timeout
;
330 const int caca
= caca_get_event_mouse_button(&ev
);
331 for (int i
= 0; mouses
[i
].caca
!= -1; i
++) {
332 if (mouses
[i
].caca
== caca
) {
333 if (caca_get_event_type(&ev
) == CACA_EVENT_MOUSE_PRESS
)
334 vout_window_ReportMousePressed(sys
->window
,
337 vout_window_ReportMouseReleased(sys
->window
,
344 case CACA_EVENT_QUIT
:
345 vout_window_ReportClose(sys
->window
);
354 * Close a libcaca video output
356 static void Close(vout_display_t
*vd
)
358 vout_display_sys_t
*sys
= vd
->sys
;
360 vlc_queue_Kill(&sys
->q
, &sys
->dead
);
361 vlc_join(sys
->thread
, NULL
);
364 cucul_free_dither(sys
->dither
);
365 caca_free_display(sys
->dp
);
366 cucul_free_canvas(sys
->cv
);
376 * This function initializes libcaca vout method.
378 static int Open(vout_display_t
*vd
, const vout_display_cfg_t
*cfg
,
379 video_format_t
*fmtp
, vlc_video_context
*context
)
381 vout_display_sys_t
*sys
;
385 if (vout_display_cfg_IsWindowed(cfg
))
387 #if !defined(__APPLE__) && !defined(_WIN32)
388 # ifndef X_DISPLAY_MISSING
389 if (!vlc_xlib_init(VLC_OBJECT(vd
)))
395 CONSOLE_SCREEN_BUFFER_INFO csbiInfo
;
400 if (!AllocConsole()) {
401 msg_Err(vd
, "cannot create console");
406 CreateConsoleScreenBuffer(GENERIC_READ
| GENERIC_WRITE
,
407 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
408 NULL
, CONSOLE_TEXTMODE_BUFFER
, NULL
);
409 if (!hstdout
|| hstdout
== INVALID_HANDLE_VALUE
) {
410 msg_Err(vd
, "cannot create screen buffer");
415 if (!SetConsoleActiveScreenBuffer(hstdout
)) {
416 msg_Err(vd
, "cannot set active screen buffer");
421 coord
= GetLargestConsoleWindowSize(hstdout
);
422 msg_Dbg(vd
, "SetConsoleWindowInfo: %ix%i", coord
.X
, coord
.Y
);
424 /* Force size for now */
428 if (!SetConsoleScreenBufferSize(hstdout
, coord
))
429 msg_Warn(vd
, "SetConsoleScreenBufferSize %i %i",
432 /* Get the current screen buffer size and window position. */
433 if (GetConsoleScreenBufferInfo(hstdout
, &csbiInfo
)) {
434 rect
.Top
= 0; rect
.Left
= 0;
435 rect
.Right
= csbiInfo
.dwMaximumWindowSize
.X
- 1;
436 rect
.Bottom
= csbiInfo
.dwMaximumWindowSize
.Y
- 1;
437 if (!SetConsoleWindowInfo(hstdout
, TRUE
, &rect
))
438 msg_Dbg(vd
, "SetConsoleWindowInfo failed: %ix%i",
439 rect
.Right
, rect
.Bottom
);
443 /* Allocate structure */
444 vd
->sys
= sys
= calloc(1, sizeof(*sys
));
448 sys
->cv
= cucul_create_canvas(0, 0);
450 msg_Err(vd
, "cannot initialize libcucul");
454 sys
->window
= cfg
->window
;
455 const char *driver
= NULL
;
457 // Make sure we don't try to open a window.
461 sys
->dp
= caca_create_display_with_driver(sys
->cv
, driver
);
463 msg_Err(vd
, "cannot initialize libcaca");
467 char *title
= var_InheritString(vd
, "video-title");
468 caca_set_display_title(sys
->dp
,
469 (title
!= NULL
) ? title
: VOUT_TITLE
"(Colour AsCii Art)");
473 vlc_queue_Init(&sys
->q
, offsetof (vlc_caca_event_t
, next
));
475 if (vlc_clone(&sys
->thread
, VoutDisplayEventKeyDispatch
, vd
,
476 VLC_THREAD_PRIORITY_LOW
))
479 sys
->cursor_timeout
= VLC_TICK_FROM_MS( var_InheritInteger(vd
, "mouse-hide-timeout") );
480 sys
->cursor_deadline
= INVALID_DEADLINE
;
483 if (fmtp
->i_chroma
!= VLC_CODEC_RGB32
) {
484 fmtp
->i_chroma
= VLC_CODEC_RGB32
;
485 fmtp
->i_rmask
= 0x00ff0000;
486 fmtp
->i_gmask
= 0x0000ff00;
487 fmtp
->i_bmask
= 0x000000ff;
490 /* Setup vout_display now that everything is fine */
491 vd
->prepare
= Prepare
;
492 vd
->display
= PictureDisplay
;
493 vd
->control
= Control
;
496 /* Fix initial state */
497 caca_refresh_display(sys
->dp
);
506 cucul_free_dither(sys
->dither
);
508 caca_free_display(sys
->dp
);
510 cucul_free_canvas(sys
->cv
);
520 /*****************************************************************************
522 *****************************************************************************/
524 set_shortname("Caca")
525 set_category(CAT_VIDEO
)
526 set_subcategory(SUBCAT_VIDEO_VOUT
)
527 set_description(N_("Color ASCII art video output"))
528 set_callback_display(Open
, 15)