]> git.sesse.net Git - vlc/blob - src/video_output/display.c
bbc6010afc650fec1d2d746f99c3163d19e09f26
[vlc] / src / video_output / display.c
1 /*****************************************************************************
2  * display.c: "vout display" managment
3  *****************************************************************************
4  * Copyright (C) 2009 Laurent Aimar
5  * $Id$
6  *
7  * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30 #include <assert.h>
31
32 #include <vlc_common.h>
33 #include <vlc_video_splitter.h>
34 #include <vlc_vout_display.h>
35 #include <vlc_vout.h>
36 #include <vlc_block.h>
37
38 #include <libvlc.h>
39
40 #include "display.h"
41
42 #include "event.h"
43
44 /* It must be present as long as a vout_display_t must be created using a dummy
45  * vout (as an opengl provider) */
46 #define ALLOW_DUMMY_VOUT
47
48 static void SplitterClose(vout_display_t *vd);
49
50 /*****************************************************************************
51  * FIXME/TODO see how to have direct rendering here (interact with vout.c)
52  *****************************************************************************/
53 static picture_t *VideoBufferNew(filter_t *filter)
54 {
55     vout_display_t *vd = (vout_display_t*)filter->p_owner;
56     const video_format_t *fmt = &filter->fmt_out.video;
57
58     assert(vd->fmt.i_chroma == fmt->i_chroma &&
59            vd->fmt.i_width  == fmt->i_width  &&
60            vd->fmt.i_height == fmt->i_height);
61
62     picture_pool_t *pool = vout_display_Pool(vd, 3);
63     if (!pool)
64         return NULL;
65     return picture_pool_Get(pool);
66 }
67 static void VideoBufferDelete(filter_t *filter, picture_t *picture)
68 {
69     VLC_UNUSED(filter);
70     picture_Release(picture);
71 }
72
73 static int  FilterAllocationInit(filter_t *filter, void *vd)
74 {
75     filter->pf_video_buffer_new = VideoBufferNew;
76     filter->pf_video_buffer_del = VideoBufferDelete;
77     filter->p_owner             = vd;
78
79     return VLC_SUCCESS;
80 }
81 static void FilterAllocationClean(filter_t *filter)
82 {
83     filter->pf_video_buffer_new = NULL;
84     filter->pf_video_buffer_del = NULL;
85     filter->p_owner             = NULL;
86 }
87
88 /*****************************************************************************
89  *
90  *****************************************************************************/
91
92 /**
93  * It creates a new vout_display_t using the given configuration.
94  */
95 static vout_display_t *vout_display_New(vlc_object_t *obj,
96                                         const char *module, bool load_module,
97                                         const video_format_t *fmt,
98                                         const vout_display_cfg_t *cfg,
99                                         vout_display_owner_t *owner)
100 {
101     /* */
102     vout_display_t *vd = vlc_object_create(obj, sizeof(*vd));
103
104     /* */
105     video_format_Copy(&vd->source, fmt);
106
107     /* Picture buffer does not have the concept of aspect ratio */
108     video_format_Copy(&vd->fmt, fmt);
109     vd->fmt.i_sar_num = 0;
110     vd->fmt.i_sar_den = 0;
111
112     vd->info.is_slow = false;
113     vd->info.has_double_click = false;
114     vd->info.has_hide_mouse = false;
115     vd->info.has_pictures_invalid = false;
116     vd->info.has_event_thread = false;
117
118     vd->cfg = cfg;
119     vd->pool = NULL;
120     vd->prepare = NULL;
121     vd->display = NULL;
122     vd->control = NULL;
123     vd->manage = NULL;
124     vd->sys = NULL;
125
126     vd->owner = *owner;
127
128     vlc_object_attach(vd, obj);
129
130     if (load_module) {
131         vd->module = module_need(vd, "vout display", module, module && *module != '\0');
132         if (!vd->module) {
133             vlc_object_release(vd);
134             return NULL;
135         }
136     } else {
137         vd->module = NULL;
138     }
139     return vd;
140 }
141
142 /**
143  * It deletes a vout_display_t
144  */
145 static void vout_display_Delete(vout_display_t *vd)
146 {
147     if (vd->module)
148         module_unneed(vd, vd->module);
149
150     vlc_object_release(vd);
151 }
152
153 /**
154  * It controls a vout_display_t
155  */
156 static int vout_display_Control(vout_display_t *vd, int query, ...)
157 {
158     va_list args;
159     int result;
160
161     va_start(args, query);
162     result = vd->control(vd, query, args);
163     va_end(args);
164
165     return result;
166 }
167 static void vout_display_Manage(vout_display_t *vd)
168 {
169     if (vd->manage)
170         vd->manage(vd);
171 }
172
173 /* */
174 void vout_display_GetDefaultDisplaySize(unsigned *width, unsigned *height,
175                                         const video_format_t *source,
176                                         const vout_display_cfg_t *cfg)
177 {
178     if (cfg->display.width > 0 && cfg->display.height > 0) {
179         *width  = cfg->display.width;
180         *height = cfg->display.height;
181     } else if (cfg->display.width > 0) {
182         *width  = cfg->display.width;
183         *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.width * cfg->display.sar.num /
184             source->i_visible_width / source->i_sar_num / cfg->display.sar.den;
185     } else if (cfg->display.height > 0) {
186         *width  = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.height * cfg->display.sar.den /
187             source->i_visible_height / source->i_sar_den / cfg->display.sar.num;
188         *height = cfg->display.height;
189     } else if (source->i_sar_num >= source->i_sar_den) {
190         *width  = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.sar.den / source->i_sar_den / cfg->display.sar.num;
191         *height = source->i_visible_height;
192     } else {
193         *width  = source->i_visible_width;
194         *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.sar.num / source->i_sar_num / cfg->display.sar.den;
195     }
196
197     *width  = *width  * cfg->zoom.num / cfg->zoom.den;
198     *height = *height * cfg->zoom.num / cfg->zoom.den;
199 }
200
201 /* */
202 void vout_display_PlacePicture(vout_display_place_t *place,
203                                const video_format_t *source,
204                                const vout_display_cfg_t *cfg,
205                                bool do_clipping)
206 {
207     /* */
208     memset(place, 0, sizeof(*place));
209     if (cfg->display.width <= 0 || cfg->display.height <= 0)
210         return;
211
212     /* */
213     unsigned display_width;
214     unsigned display_height;
215
216     if (cfg->is_display_filled) {
217         display_width  = cfg->display.width;
218         display_height = cfg->display.height;
219     } else {
220         vout_display_cfg_t cfg_tmp = *cfg;
221
222         cfg_tmp.display.width  = 0;
223         cfg_tmp.display.height = 0;
224         vout_display_GetDefaultDisplaySize(&display_width, &display_height,
225                                            source, &cfg_tmp);
226
227         if (do_clipping) {
228             display_width  = __MIN(display_width,  cfg->display.width);
229             display_height = __MIN(display_height, cfg->display.height);
230         }
231     }
232
233     const unsigned width  = source->i_visible_width;
234     const unsigned height = source->i_visible_height;
235     /* Compute the height if we use the width to fill up display_width */
236     const int64_t scaled_height = (int64_t)height * display_width  * cfg->display.sar.num * source->i_sar_den / width  / source->i_sar_num / cfg->display.sar.den;
237     /* And the same but switching width/height */
238     const int64_t scaled_width  = (int64_t)width  * display_height * cfg->display.sar.den * source->i_sar_num / height / source->i_sar_den / cfg->display.sar.num;
239
240     /* We keep the solution that avoid filling outside the display */
241     if (scaled_width <= cfg->display.width) {
242         place->width  = scaled_width;
243         place->height = display_height;
244     } else {
245         place->width  = display_width;
246         place->height = scaled_height;
247     }
248
249     /*  Compute position */
250     switch (cfg->align.horizontal) {
251     case VOUT_DISPLAY_ALIGN_LEFT:
252         place->x = 0;
253         break;
254     case VOUT_DISPLAY_ALIGN_RIGHT:
255         place->x = cfg->display.width - place->width;
256         break;
257     default:
258         place->x = (cfg->display.width - place->width) / 2;
259         break;
260     }
261
262     switch (cfg->align.vertical) {
263     case VOUT_DISPLAY_ALIGN_TOP:
264         place->y = 0;
265         break;
266     case VOUT_DISPLAY_ALIGN_BOTTOM:
267         place->y = cfg->display.height - place->height;
268         break;
269     default:
270         place->y = (cfg->display.height - place->height) / 2;
271         break;
272     }
273 }
274
275 struct vout_display_owner_sys_t {
276     vout_thread_t   *vout;
277     bool            is_wrapper;  /* Is the current display a wrapper */
278     vout_display_t  *wrapper; /* Vout display wrapper */
279
280     /* */
281     vout_display_cfg_t cfg;
282     unsigned     wm_state_initial;
283     struct {
284         unsigned num;
285         unsigned den;
286     } sar_initial;
287
288     /* */
289     int  width_saved;
290     int  height_saved;
291
292     struct {
293         unsigned num;
294         unsigned den;
295     } crop_saved;
296
297     /* */
298     bool ch_display_filled;
299     bool is_display_filled;
300
301     bool ch_zoom;
302     struct {
303         int  num;
304         int  den;
305     } zoom;
306
307     bool ch_wm_state;
308     unsigned wm_state;
309
310     bool ch_sar;
311     struct {
312         unsigned num;
313         unsigned den;
314     } sar;
315
316     bool ch_crop;
317     struct {
318         unsigned x;
319         unsigned y;
320         unsigned width;
321         unsigned height;
322         unsigned num;
323         unsigned den;
324     } crop;
325
326     /* */
327     video_format_t source;
328     filter_chain_t *filters;
329
330     /* Lock protecting the variables used by
331      * VoutDisplayEvent(ie vout_display_SendEvent) */
332     vlc_mutex_t lock;
333
334     /* mouse state */
335     struct {
336         vlc_mouse_t state;
337
338         mtime_t last_pressed;
339         mtime_t last_moved;
340         bool    is_hidden;
341         bool    ch_activity;
342
343         /* */
344         mtime_t double_click_timeout;
345         mtime_t hide_timeout;
346     } mouse;
347
348     bool reset_pictures;
349
350     bool ch_fullscreen;
351     bool is_fullscreen;
352
353     bool ch_display_size;
354     int  display_width;
355     int  display_height;
356     bool display_is_fullscreen;
357     bool display_is_forced;
358
359     int  fit_window;
360
361     struct {
362         vlc_thread_t thread;
363         block_fifo_t *fifo;
364     } event;
365
366 #ifdef ALLOW_DUMMY_VOUT
367     vlc_mouse_t vout_mouse;
368 #endif
369 };
370
371 static void DummyVoutSendDisplayEventMouse(vout_thread_t *, vlc_mouse_t *fallback, const vlc_mouse_t *m);
372
373 static void VoutDisplayCreateRender(vout_display_t *vd)
374 {
375     vout_display_owner_sys_t *osys = vd->owner.sys;
376
377     osys->filters = NULL;
378
379     video_format_t v_src = vd->source;
380     v_src.i_sar_num = 0;
381     v_src.i_sar_den = 0;
382
383     video_format_t v_dst = vd->fmt;
384     v_dst.i_sar_num = 0;
385     v_dst.i_sar_den = 0;
386
387     video_format_t v_dst_cmp = v_dst;
388     if ((v_src.i_chroma == VLC_CODEC_J420 && v_dst.i_chroma == VLC_CODEC_I420) ||
389         (v_src.i_chroma == VLC_CODEC_J422 && v_dst.i_chroma == VLC_CODEC_I422) ||
390         (v_src.i_chroma == VLC_CODEC_J440 && v_dst.i_chroma == VLC_CODEC_I440) ||
391         (v_src.i_chroma == VLC_CODEC_J444 && v_dst.i_chroma == VLC_CODEC_I444))
392         v_dst_cmp.i_chroma = v_src.i_chroma;
393
394     const bool convert = memcmp(&v_src, &v_dst_cmp, sizeof(v_src)) != 0;
395     if (!convert)
396         return;
397
398     msg_Dbg(vd, "A filter to adapt decoder to display is needed");
399
400     osys->filters = filter_chain_New(vd, "video filter2", false,
401                                      FilterAllocationInit,
402                                      FilterAllocationClean, vd);
403     assert(osys->filters); /* TODO critical */
404
405     /* */
406     es_format_t src;
407     es_format_InitFromVideo(&src, &v_src);
408
409     /* */
410     es_format_t dst;
411
412     filter_t *filter;
413     for (int i = 0; i < 1 + (v_dst_cmp.i_chroma != v_dst.i_chroma); i++) {
414
415         es_format_InitFromVideo(&dst, i == 0 ? &v_dst : &v_dst_cmp);
416
417         filter_chain_Reset(osys->filters, &src, &dst);
418         filter = filter_chain_AppendFilter(osys->filters,
419                                            NULL, NULL, &src, &dst);
420         if (filter)
421             break;
422     }
423     if (!filter)
424     {
425         msg_Err(vd, "VoutDisplayCreateRender FAILED");
426         /* TODO */
427         assert(0);
428     }
429 }
430
431 static void VoutDisplayDestroyRender(vout_display_t *vd)
432 {
433     vout_display_owner_sys_t *osys = vd->owner.sys;
434
435     if (osys->filters)
436         filter_chain_Delete(osys->filters);
437 }
438
439 static void VoutDisplayResetRender(vout_display_t *vd)
440 {
441     VoutDisplayDestroyRender(vd);
442     VoutDisplayCreateRender(vd);
443 }
444 static void VoutDisplayEventMouse(vout_display_t *vd, int event, va_list args)
445 {
446     vout_display_owner_sys_t *osys = vd->owner.sys;
447
448     vlc_mutex_lock(&osys->lock);
449
450     /* */
451     vlc_mouse_t m = osys->mouse.state;
452     bool is_ignored = false;
453
454     switch (event) {
455     case VOUT_DISPLAY_EVENT_MOUSE_STATE: {
456         const int x = (int)va_arg(args, int);
457         const int y = (int)va_arg(args, int);
458         const int button_mask = (int)va_arg(args, int);
459
460         vlc_mouse_Init(&m);
461         m.i_x = x;
462         m.i_y = y;
463         m.i_pressed = button_mask;
464         break;
465     }
466     case VOUT_DISPLAY_EVENT_MOUSE_MOVED: {
467         const int x = (int)va_arg(args, int);
468         const int y = (int)va_arg(args, int);
469         if (x != osys->mouse.state.i_x || y != osys->mouse.state.i_y) {
470             //msg_Dbg(vd, "VoutDisplayEvent 'mouse' @%d,%d", x, y);
471
472             m.i_x = x;
473             m.i_y = y;
474             m.b_double_click = false;
475         } else {
476             is_ignored = true;
477         }
478         break;
479     }
480     case VOUT_DISPLAY_EVENT_MOUSE_PRESSED:
481     case VOUT_DISPLAY_EVENT_MOUSE_RELEASED: {
482         const int button = (int)va_arg(args, int);
483         const int button_mask = 1 << button;
484
485         /* Ignore inconsistent event */
486         if ((event == VOUT_DISPLAY_EVENT_MOUSE_PRESSED  &&  (osys->mouse.state.i_pressed & button_mask)) ||
487             (event == VOUT_DISPLAY_EVENT_MOUSE_RELEASED && !(osys->mouse.state.i_pressed & button_mask))) {
488             is_ignored = true;
489             break;
490         }
491
492         /* */
493         msg_Dbg(vd, "VoutDisplayEvent 'mouse button' %d t=%d", button, event);
494
495         m.b_double_click = false;
496         if (event == VOUT_DISPLAY_EVENT_MOUSE_PRESSED)
497             m.i_pressed |= button_mask;
498         else
499             m.i_pressed &= ~button_mask;
500         break;
501     }
502     case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK:
503         msg_Dbg(vd, "VoutDisplayEvent 'double click'");
504
505         m.b_double_click = true;
506         break;
507     default:
508         assert(0);
509     }
510
511     if (is_ignored) {
512         vlc_mutex_unlock(&osys->lock);
513         return;
514     }
515
516     /* Emulate double-click if needed */
517     if (!vd->info.has_double_click &&
518         vlc_mouse_HasPressed(&osys->mouse.state, &m, MOUSE_BUTTON_LEFT)) {
519         const mtime_t i_date = mdate();
520
521         if (i_date - osys->mouse.last_pressed < osys->mouse.double_click_timeout ) {
522             m.b_double_click = true;
523             osys->mouse.last_pressed = 0;
524         } else {
525             osys->mouse.last_pressed = mdate();
526         }
527     }
528
529     /* */
530     osys->mouse.state = m;
531
532     /* */
533     osys->mouse.ch_activity = true;
534     if (!vd->info.has_hide_mouse)
535         osys->mouse.last_moved = mdate();
536
537     /* */
538     vout_SendEventMouseVisible(osys->vout);
539 #ifdef ALLOW_DUMMY_VOUT
540     DummyVoutSendDisplayEventMouse(osys->vout, &osys->vout_mouse, &m);
541 #else
542     vout_SendDisplayEventMouse(osys->vout, &m);
543 #endif
544     vlc_mutex_unlock(&osys->lock);
545 }
546
547 static void *VoutDisplayEventKeyDispatch(void *data)
548 {
549     vout_display_t *vd = data;
550     vout_display_owner_sys_t *osys = vd->owner.sys;
551
552     for (;;) {
553         block_t *event = block_FifoGet(osys->event.fifo);
554         if (!event)
555             return NULL;
556
557         int key;
558         memcpy(&key, event->p_buffer, sizeof(key));
559         vout_SendEventKey(osys->vout, key);
560         block_Release(event);
561     }
562 }
563
564 static void VoutDisplayEventKey(vout_display_t *vd, int key)
565 {
566     vout_display_owner_sys_t *osys = vd->owner.sys;
567
568     if (!osys->event.fifo) {
569         osys->event.fifo = block_FifoNew();
570         if (!osys->event.fifo)
571             return;
572         if (vlc_clone(&osys->event.thread, VoutDisplayEventKeyDispatch,
573                       vd, VLC_THREAD_PRIORITY_LOW)) {
574             block_FifoRelease(osys->event.fifo);
575             osys->event.fifo = NULL;
576             return;
577         }
578     }
579     block_t *event = block_Alloc(sizeof(key));
580     if (event) {
581         memcpy(event->p_buffer, &key, sizeof(key));
582         block_FifoPut(osys->event.fifo, event);
583     }
584 }
585
586 static void VoutDisplayEvent(vout_display_t *vd, int event, va_list args)
587 {
588     vout_display_owner_sys_t *osys = vd->owner.sys;
589
590     switch (event) {
591     case VOUT_DISPLAY_EVENT_CLOSE: {
592         msg_Dbg(vd, "VoutDisplayEvent 'close'");
593         vout_SendEventClose(osys->vout);
594         break;
595     }
596     case VOUT_DISPLAY_EVENT_KEY: {
597         const int key = (int)va_arg(args, int);
598         msg_Dbg(vd, "VoutDisplayEvent 'key' 0x%2.2x", key);
599         if (vd->info.has_event_thread)
600             vout_SendEventKey(osys->vout, key);
601         else
602             VoutDisplayEventKey(vd, key);
603         break;
604     }
605     case VOUT_DISPLAY_EVENT_MOUSE_STATE:
606     case VOUT_DISPLAY_EVENT_MOUSE_MOVED:
607     case VOUT_DISPLAY_EVENT_MOUSE_PRESSED:
608     case VOUT_DISPLAY_EVENT_MOUSE_RELEASED:
609     case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK:
610         VoutDisplayEventMouse(vd, event, args);
611         break;
612
613     case VOUT_DISPLAY_EVENT_FULLSCREEN: {
614         const int is_fullscreen = (int)va_arg(args, int);
615
616         msg_Dbg(vd, "VoutDisplayEvent 'fullscreen' %d", is_fullscreen);
617
618         vlc_mutex_lock(&osys->lock);
619         if (!is_fullscreen != !osys->is_fullscreen) {
620             osys->ch_fullscreen = true;
621             osys->is_fullscreen = is_fullscreen;
622         }
623         vlc_mutex_unlock(&osys->lock);
624         break;
625     }
626
627     case VOUT_DISPLAY_EVENT_WINDOW_STATE: {
628         const unsigned state = va_arg(args, unsigned);
629
630         msg_Dbg(vd, "VoutDisplayEvent 'window state' %u", state);
631
632         vlc_mutex_lock(&osys->lock);
633         if (state != osys->wm_state) {
634             osys->ch_wm_state = true;
635             osys->wm_state = state;
636         }
637         vlc_mutex_unlock(&osys->lock);
638         break;
639     }
640
641     case VOUT_DISPLAY_EVENT_DISPLAY_SIZE: {
642         const int width  = (int)va_arg(args, int);
643         const int height = (int)va_arg(args, int);
644         const bool is_fullscreen = (bool)va_arg(args, int);
645         msg_Dbg(vd, "VoutDisplayEvent 'resize' %dx%d %s",
646                 width, height, is_fullscreen ? "fullscreen" : "window");
647
648         /* */
649         vlc_mutex_lock(&osys->lock);
650
651         osys->ch_display_size       = true;
652         osys->display_width         = width;
653         osys->display_height        = height;
654         osys->display_is_fullscreen = is_fullscreen;
655         osys->display_is_forced     = false;
656
657         vlc_mutex_unlock(&osys->lock);
658         break;
659     }
660
661     case VOUT_DISPLAY_EVENT_PICTURES_INVALID: {
662         msg_Warn(vd, "VoutDisplayEvent 'pictures invalid'");
663
664         /* */
665         assert(vd->info.has_pictures_invalid);
666
667         vlc_mutex_lock(&osys->lock);
668         osys->reset_pictures = true;
669         vlc_mutex_unlock(&osys->lock);
670         break;
671     }
672     default:
673         msg_Err(vd, "VoutDisplayEvent received event %d", event);
674         /* TODO add an assert when all event are handled */
675         break;
676     }
677 }
678
679 static vout_window_t *VoutDisplayNewWindow(vout_display_t *vd, const vout_window_cfg_t *cfg)
680 {
681     vout_display_owner_sys_t *osys = vd->owner.sys;
682
683     return vout_NewDisplayWindow(osys->vout, vd, cfg);
684 }
685 static void VoutDisplayDelWindow(vout_display_t *vd, vout_window_t *window)
686 {
687     vout_display_owner_sys_t *osys = vd->owner.sys;
688
689     vout_DeleteDisplayWindow(osys->vout, vd, window);
690 }
691
692 static void VoutDisplayFitWindow(vout_display_t *vd, bool default_size)
693 {
694     vout_display_owner_sys_t *osys = vd->owner.sys;
695     vout_display_cfg_t cfg = osys->cfg;
696
697     if (!cfg.is_display_filled)
698         return;
699
700     cfg.display.width = 0;
701     if (default_size) {
702         cfg.display.height = 0;
703     } else {
704         cfg.display.height = osys->height_saved;
705         cfg.zoom.num = 1;
706         cfg.zoom.den = 1;
707     }
708
709     unsigned display_width;
710     unsigned display_height;
711     vout_display_GetDefaultDisplaySize(&display_width, &display_height,
712                                        &vd->source, &cfg);
713
714     vlc_mutex_lock(&osys->lock);
715
716     osys->ch_display_size       = true;
717     osys->display_width         = display_width;
718     osys->display_height        = display_height;
719     osys->display_is_fullscreen = osys->cfg.is_fullscreen;
720     osys->display_is_forced     = true;
721
722     vlc_mutex_unlock(&osys->lock);
723 }
724
725 static void VoutDisplayCropRatio(unsigned *x, unsigned *y,
726                                  unsigned *width, unsigned *height,
727                                  const video_format_t *source,
728                                  unsigned num, unsigned den)
729 {
730     unsigned scaled_width  = (uint64_t)source->i_visible_height * num * source->i_sar_den / den / source->i_sar_num;
731     unsigned scaled_height = (uint64_t)source->i_visible_width  * den * source->i_sar_num / num / source->i_sar_den;
732
733     if (scaled_width < source->i_visible_width) {
734         *x      = (source->i_visible_width - scaled_width) / 2;
735         *y      = 0;
736         *width  = scaled_width;
737         *height = source->i_visible_height;
738     } else {
739         *x      = 0;
740         *y      = (source->i_visible_height - scaled_height) / 2;
741         *width  = source->i_visible_width;
742         *height = scaled_height;
743     }
744     *x += source->i_x_offset;
745     *y += source->i_y_offset;
746 }
747
748 void vout_ManageDisplay(vout_display_t *vd, bool allow_reset_pictures)
749 {
750     vout_display_owner_sys_t *osys = vd->owner.sys;
751
752     vout_display_Manage(vd);
753
754     /* Handle mouse timeout */
755     const mtime_t date = mdate();
756     bool  hide_mouse = false;
757
758     vlc_mutex_lock(&osys->lock);
759
760     if (!osys->mouse.is_hidden &&
761         osys->mouse.last_moved + osys->mouse.hide_timeout < date) {
762         osys->mouse.is_hidden = hide_mouse = true;
763     } else if (osys->mouse.ch_activity) {
764         osys->mouse.is_hidden = false;
765     }
766     osys->mouse.ch_activity = false;
767     vlc_mutex_unlock(&osys->lock);
768
769     if (hide_mouse) {
770         if (!vd->info.has_hide_mouse) {
771             msg_Dbg(vd, "auto hidding mouse");
772             vout_display_Control(vd, VOUT_DISPLAY_HIDE_MOUSE);
773         }
774         vout_SendEventMouseHidden(osys->vout);
775     }
776
777     bool reset_render = false;
778     for (;;) {
779
780         vlc_mutex_lock(&osys->lock);
781
782         bool ch_fullscreen  = osys->ch_fullscreen;
783         bool is_fullscreen  = osys->is_fullscreen;
784         osys->ch_fullscreen = false;
785
786         bool ch_wm_state  = osys->ch_wm_state;
787         unsigned wm_state  = osys->wm_state;
788         osys->ch_wm_state = false;
789
790         bool ch_display_size       = osys->ch_display_size;
791         int  display_width         = osys->display_width;
792         int  display_height        = osys->display_height;
793         bool display_is_fullscreen = osys->display_is_fullscreen;
794         bool display_is_forced     = osys->display_is_forced;
795         osys->ch_display_size = false;
796
797         bool reset_pictures;
798         if (allow_reset_pictures) {
799             reset_pictures = osys->reset_pictures;
800             osys->reset_pictures = false;
801         } else {
802             reset_pictures = false;
803         }
804
805         vlc_mutex_unlock(&osys->lock);
806
807         if (!ch_fullscreen &&
808             !ch_display_size &&
809             !reset_pictures &&
810             !osys->ch_display_filled &&
811             !osys->ch_zoom &&
812             !ch_wm_state &&
813             !osys->ch_sar &&
814             !osys->ch_crop) {
815
816             if (!osys->cfg.is_fullscreen && osys->fit_window != 0) {
817                 VoutDisplayFitWindow(vd, osys->fit_window == -1);
818                 osys->fit_window = 0;
819                 continue;
820             }
821             break;
822         }
823
824         /* */
825         if (ch_fullscreen) {
826             vout_display_cfg_t cfg = osys->cfg;
827
828             cfg.is_fullscreen  = is_fullscreen;
829             cfg.display.width  = cfg.is_fullscreen ? 0 : osys->width_saved;
830             cfg.display.height = cfg.is_fullscreen ? 0 : osys->height_saved;
831
832             if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_FULLSCREEN, &cfg)) {
833                 msg_Err(vd, "Failed to set fullscreen");
834                 is_fullscreen = osys->cfg.is_fullscreen;
835             } else if (!is_fullscreen) {
836                 vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_SIZE, &cfg, true);
837             }
838             osys->cfg.is_fullscreen = is_fullscreen;
839
840             /* */
841             vout_SendEventFullscreen(osys->vout, osys->cfg.is_fullscreen);
842         }
843
844         /* */
845         if (ch_display_size) {
846             vout_display_cfg_t cfg = osys->cfg;
847             cfg.display.width  = display_width;
848             cfg.display.height = display_height;
849
850             if (!cfg.is_fullscreen != !display_is_fullscreen ||
851                 vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_SIZE, &cfg, display_is_forced)) {
852                 if (!cfg.is_fullscreen == !display_is_fullscreen)
853                     msg_Err(vd, "Failed to resize display");
854
855                 /* We ignore the resized */
856                 display_width  = osys->cfg.display.width;
857                 display_height = osys->cfg.display.height;
858             }
859             osys->cfg.display.width  = display_width;
860             osys->cfg.display.height = display_height;
861
862             if (!display_is_fullscreen) {
863                 osys->width_saved  = display_width;
864                 osys->height_saved = display_height;
865             }
866         }
867         /* */
868         if (osys->ch_display_filled) {
869             vout_display_cfg_t cfg = osys->cfg;
870
871             cfg.is_display_filled = osys->is_display_filled;
872
873             if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, &cfg)) {
874                 msg_Err(vd, "Failed to change display filled state");
875                 osys->is_display_filled = osys->cfg.is_display_filled;
876             }
877             osys->cfg.is_display_filled = osys->is_display_filled;
878             osys->ch_display_filled = false;
879
880             vout_SendEventDisplayFilled(osys->vout, osys->cfg.is_display_filled);
881         }
882         /* */
883         if (osys->ch_zoom) {
884             vout_display_cfg_t cfg = osys->cfg;
885
886             cfg.zoom.num = osys->zoom.num;
887             cfg.zoom.den = osys->zoom.den;
888
889             if (10 * cfg.zoom.num <= cfg.zoom.den) {
890                 cfg.zoom.num = 1;
891                 cfg.zoom.den = 10;
892             } else if (cfg.zoom.num >= 10 * cfg.zoom.den) {
893                 cfg.zoom.num = 10;
894                 cfg.zoom.den = 1;
895             }
896
897             if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_ZOOM, &cfg)) {
898                 msg_Err(vd, "Failed to change zoom");
899                 osys->zoom.num = osys->cfg.zoom.num;
900                 osys->zoom.den = osys->cfg.zoom.den;
901             } else {
902                 osys->fit_window = -1;
903             }
904
905             osys->cfg.zoom.num = osys->zoom.num;
906             osys->cfg.zoom.den = osys->zoom.den;
907             osys->ch_zoom = false;
908
909             vout_SendEventZoom(osys->vout, osys->cfg.zoom.num, osys->cfg.zoom.den);
910         }
911         /* */
912         if (ch_wm_state) {
913             if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_WINDOW_STATE, wm_state)) {
914                 msg_Err(vd, "Failed to set on top");
915                 wm_state = osys->wm_state;
916             }
917             osys->wm_state_initial = wm_state;
918
919             /* */
920             vout_SendEventOnTop(osys->vout, osys->wm_state_initial);
921         }
922         /* */
923         if (osys->ch_sar) {
924             video_format_t source = vd->source;
925
926             if (osys->sar.num > 0 && osys->sar.den > 0) {
927                 source.i_sar_num = osys->sar.num;
928                 source.i_sar_den = osys->sar.den;
929             } else {
930                 source.i_sar_num = osys->source.i_sar_num;
931                 source.i_sar_den = osys->source.i_sar_den;
932             }
933
934             if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT, &source)) {
935                 /* There nothing much we can do. The only reason a vout display
936                  * does not support it is because it need the core to add black border
937                  * to the video for it.
938                  * TODO add black borders ?
939                  */
940                 msg_Err(vd, "Failed to change source AR");
941                 source = vd->source;
942             } else if (!osys->fit_window) {
943                 osys->fit_window = 1;
944             }
945             vd->source = source;
946             osys->sar.num = source.i_sar_num;
947             osys->sar.den = source.i_sar_den;
948             osys->ch_sar  = false;
949
950             /* */
951             if (osys->sar.num == osys->source.i_sar_num &&
952                 osys->sar.den == osys->source.i_sar_den)
953             {
954                 vout_SendEventSourceAspect(osys->vout, 0, 0);
955             }
956             else
957             {
958                 unsigned dar_num, dar_den;
959                 vlc_ureduce( &dar_num, &dar_den,
960                              osys->sar.num * vd->source.i_visible_width,
961                              osys->sar.den * vd->source.i_visible_height,
962                              65536);
963                 vout_SendEventSourceAspect(osys->vout, dar_num, dar_den);
964             }
965             /* If a crop ratio is requested, recompute the parameters */
966             if (osys->crop.num > 0 && osys->crop.den > 0)
967                 osys->ch_crop = true;
968         }
969         /* */
970         if (osys->ch_crop) {
971             video_format_t source = vd->source;
972
973             unsigned crop_num = osys->crop.num;
974             unsigned crop_den = osys->crop.den;
975             if (crop_num > 0 && crop_den > 0) {
976                 video_format_t fmt = osys->source;
977                 fmt.i_sar_num = source.i_sar_num;
978                 fmt.i_sar_den = source.i_sar_den;
979                 VoutDisplayCropRatio(&osys->crop.x, &osys->crop.y,
980                                      &osys->crop.width, &osys->crop.height,
981                                      &fmt, crop_num, crop_den);
982             }
983
984             source.i_x_offset       = osys->crop.x;
985             source.i_y_offset       = osys->crop.y;
986             source.i_visible_width  = osys->crop.width;
987             source.i_visible_height = osys->crop.height;
988
989             /* */
990             const bool is_valid = source.i_x_offset < source.i_width &&
991                                   source.i_y_offset < source.i_height &&
992                                   source.i_x_offset + source.i_visible_width  <= source.i_width &&
993                                   source.i_y_offset + source.i_visible_height <= source.i_height &&
994                                   source.i_visible_width > 0 && source.i_visible_height > 0;
995
996             if (!is_valid || vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_CROP, &source)) {
997                 if (is_valid)
998                     msg_Err(vd, "Failed to change source crop TODO implement crop at core");
999                 else
1000                     msg_Err(vd, "Invalid crop requested");
1001
1002                 source = vd->source;
1003                 crop_num = osys->crop_saved.num;
1004                 crop_den = osys->crop_saved.den;
1005                 /* FIXME implement cropping in the core if not supported by the
1006                  * vout module (easy)
1007                  */
1008             } else if (!osys->fit_window) {
1009                 osys->fit_window = 1;
1010             }
1011             vd->source = source;
1012             osys->crop.x      = source.i_x_offset;
1013             osys->crop.y      = source.i_y_offset;
1014             osys->crop.width  = source.i_visible_width;
1015             osys->crop.height = source.i_visible_height;
1016             osys->crop.num    = crop_num;
1017             osys->crop.den    = crop_den;
1018             osys->ch_crop = false;
1019
1020             /* TODO fix when a ratio is used (complicated). */
1021             const unsigned left   = osys->crop.x - osys->source.i_x_offset;
1022             const unsigned top    = osys->crop.y - osys->source.i_y_offset;
1023             const unsigned right  = osys->source.i_visible_width  - (osys->crop.width  + osys->crop.x);
1024             const unsigned bottom = osys->source.i_visible_height - (osys->crop.height + osys->crop.y);
1025             vout_SendEventSourceCrop(osys->vout,
1026                                      osys->crop.num, osys->crop.den,
1027                                      left, top, right, bottom);
1028         }
1029
1030         /* */
1031         if (reset_pictures) {
1032             if (vout_display_Control(vd, VOUT_DISPLAY_RESET_PICTURES)) {
1033                 /* FIXME what to do here ? */
1034                 msg_Err(vd, "Failed to reset pictures (probably fatal)");
1035             }
1036             reset_render = true;
1037         }
1038     }
1039     if (reset_render)
1040         VoutDisplayResetRender(vd);
1041 }
1042
1043 bool vout_AreDisplayPicturesInvalid(vout_display_t *vd)
1044 {
1045     vout_display_owner_sys_t *osys = vd->owner.sys;
1046
1047     vlc_mutex_lock(&osys->lock);
1048     const bool reset_pictures = osys->reset_pictures;
1049     vlc_mutex_unlock(&osys->lock);
1050
1051     return reset_pictures;
1052 }
1053
1054 bool vout_IsDisplayFiltered(vout_display_t *vd)
1055 {
1056     vout_display_owner_sys_t *osys = vd->owner.sys;
1057
1058     return osys->filters != NULL;
1059 }
1060
1061 picture_t *vout_FilterDisplay(vout_display_t *vd, picture_t *picture)
1062 {
1063     vout_display_owner_sys_t *osys = vd->owner.sys;
1064
1065     assert(osys->filters);
1066     return filter_chain_VideoFilter(osys->filters, picture);
1067 }
1068
1069 void vout_SetDisplayFullscreen(vout_display_t *vd, bool is_fullscreen)
1070 {
1071     vout_display_owner_sys_t *osys = vd->owner.sys;
1072
1073     vlc_mutex_lock(&osys->lock);
1074     if (!osys->is_fullscreen != !is_fullscreen) {
1075         osys->ch_fullscreen = true;
1076         osys->is_fullscreen = is_fullscreen;
1077     }
1078     vlc_mutex_unlock(&osys->lock);
1079 }
1080
1081 void vout_SetDisplayFilled(vout_display_t *vd, bool is_filled)
1082 {
1083     vout_display_owner_sys_t *osys = vd->owner.sys;
1084
1085     if (!osys->is_display_filled != !is_filled) {
1086         osys->ch_display_filled = true;
1087         osys->is_display_filled = is_filled;
1088     }
1089 }
1090
1091 void vout_SetDisplayZoom(vout_display_t *vd, int num, int den)
1092 {
1093     vout_display_owner_sys_t *osys = vd->owner.sys;
1094
1095     if (osys->is_display_filled ||
1096         osys->zoom.num != num || osys->zoom.den != den) {
1097         osys->ch_zoom = true;
1098         osys->zoom.num = num;
1099         osys->zoom.den = den;
1100     }
1101 }
1102
1103 void vout_SetWindowState(vout_display_t *vd, unsigned state)
1104 {
1105     vout_display_owner_sys_t *osys = vd->owner.sys;
1106
1107     vlc_mutex_lock(&osys->lock);
1108     if (osys->wm_state != state) {
1109         osys->ch_wm_state = true;
1110         osys->wm_state = state;
1111     }
1112     vlc_mutex_unlock(&osys->lock);
1113 }
1114
1115 void vout_SetDisplayAspect(vout_display_t *vd, unsigned sar_num, unsigned sar_den)
1116 {
1117     vout_display_owner_sys_t *osys = vd->owner.sys;
1118
1119     if (osys->sar.num != sar_num || osys->sar.den != sar_den) {
1120         osys->ch_sar = true;
1121         osys->sar.num = sar_num;
1122         osys->sar.den = sar_den;
1123     }
1124 }
1125 void vout_SetDisplayCrop(vout_display_t *vd,
1126                          unsigned crop_num, unsigned crop_den,
1127                          unsigned x, unsigned y, unsigned width, unsigned height)
1128 {
1129     vout_display_owner_sys_t *osys = vd->owner.sys;
1130
1131     if (osys->crop.x != x || osys->crop.y != y ||
1132         osys->crop.width  != width || osys->crop.height != height ||
1133         (crop_num > 0 && crop_den > 0 &&
1134          (crop_num != osys->crop.num || crop_den != osys->crop.den))) {
1135
1136         osys->crop.x      = x;
1137         osys->crop.y      = y;
1138         osys->crop.width  = width;
1139         osys->crop.height = height;
1140         osys->crop.num    = crop_num;
1141         osys->crop.den    = crop_den;
1142
1143         osys->ch_crop = true;
1144     }
1145 }
1146 vout_opengl_t *vout_GetDisplayOpengl(vout_display_t *vd)
1147 {
1148     vout_opengl_t *gl;
1149     if (vout_display_Control(vd, VOUT_DISPLAY_GET_OPENGL, &gl))
1150         return NULL;
1151     return gl;
1152 }
1153
1154 static vout_display_t *DisplayNew(vout_thread_t *vout,
1155                                   const video_format_t *source_org,
1156                                   const vout_display_state_t *state,
1157                                   const char *module,
1158                                   bool is_wrapper, vout_display_t *wrapper,
1159                                   mtime_t double_click_timeout,
1160                                   mtime_t hide_timeout,
1161                                   const vout_display_owner_t *owner_ptr)
1162 {
1163     /* */
1164     vout_display_owner_sys_t *osys = calloc(1, sizeof(*osys));
1165     vout_display_cfg_t *cfg = &osys->cfg;
1166
1167     *cfg = state->cfg;
1168     osys->wm_state_initial = -1;
1169     osys->sar_initial.num = state->sar.num;
1170     osys->sar_initial.den = state->sar.den;
1171     vout_display_GetDefaultDisplaySize(&cfg->display.width, &cfg->display.height,
1172                                        source_org, cfg);
1173
1174     osys->vout = vout;
1175     osys->is_wrapper = is_wrapper;
1176     osys->wrapper = wrapper;
1177
1178     vlc_mutex_init(&osys->lock);
1179
1180     vlc_mouse_Init(&osys->mouse.state);
1181     osys->mouse.last_moved = mdate();
1182     osys->mouse.double_click_timeout = double_click_timeout;
1183     osys->mouse.hide_timeout = hide_timeout;
1184     osys->is_fullscreen  = cfg->is_fullscreen;
1185     osys->display_width  = cfg->display.width;
1186     osys->display_height = cfg->display.height;
1187     osys->is_display_filled = cfg->is_display_filled;
1188     osys->width_saved    = cfg->display.width;
1189     osys->height_saved   = cfg->display.height;
1190     if (osys->is_fullscreen) {
1191         vout_display_cfg_t cfg_windowed = *cfg;
1192         cfg_windowed.is_fullscreen  = false;
1193         cfg_windowed.display.width  = 0;
1194         cfg_windowed.display.height = 0;
1195         vout_display_GetDefaultDisplaySize(&osys->width_saved,
1196                                            &osys->height_saved,
1197                                            source_org, &cfg_windowed);
1198     }
1199     osys->zoom.num = cfg->zoom.num;
1200     osys->zoom.den = cfg->zoom.den;
1201     osys->wm_state = state->wm_state;
1202     osys->fit_window = 0;
1203     osys->event.fifo = NULL;
1204
1205     osys->source = *source_org;
1206
1207     video_format_t source = *source_org;
1208
1209     source.i_x_offset =
1210     osys->crop.x  = 0;
1211     source.i_y_offset =
1212     osys->crop.y  = 0;
1213     source.i_visible_width  =
1214     osys->crop.width    = source.i_width;
1215     source.i_visible_height =
1216     osys->crop.height   = source.i_height;
1217     osys->crop_saved.num = 0;
1218     osys->crop_saved.den = 0;
1219     osys->crop.num = 0;
1220     osys->crop.den = 0;
1221
1222     osys->sar.num = osys->sar_initial.num ? osys->sar_initial.num : source.i_sar_num;
1223     osys->sar.den = osys->sar_initial.den ? osys->sar_initial.den : source.i_sar_den;
1224 #ifdef ALLOW_DUMMY_VOUT
1225     vlc_mouse_Init(&osys->vout_mouse);
1226 #endif
1227
1228     vout_display_owner_t owner;
1229     if (owner_ptr) {
1230         owner = *owner_ptr;
1231     } else {
1232         owner.event      = VoutDisplayEvent;
1233         owner.window_new = VoutDisplayNewWindow;
1234         owner.window_del = VoutDisplayDelWindow;
1235     }
1236     owner.sys = osys;
1237
1238     /* */
1239     vout_display_t *p_display = vout_display_New(VLC_OBJECT(vout),
1240                                                  module, !is_wrapper,
1241                                                  &source, cfg, &owner);
1242     if (!p_display) {
1243         free(osys);
1244         return NULL;
1245     }
1246
1247     VoutDisplayCreateRender(p_display);
1248
1249     /* Setup delayed request */
1250     if (osys->sar.num != source_org->i_sar_num ||
1251         osys->sar.den != source_org->i_sar_den)
1252         osys->ch_sar = true;
1253     if (osys->wm_state != osys->wm_state_initial)
1254         osys->ch_wm_state = true;
1255     if (osys->crop.x      != source_org->i_x_offset ||
1256         osys->crop.y      != source_org->i_y_offset ||
1257         osys->crop.width  != source_org->i_visible_width ||
1258         osys->crop.height != source_org->i_visible_height)
1259         osys->ch_crop = true;
1260
1261     return p_display;
1262 }
1263
1264 void vout_DeleteDisplay(vout_display_t *vd, vout_display_state_t *state)
1265 {
1266     vout_display_owner_sys_t *osys = vd->owner.sys;
1267
1268     if (state) {
1269         if (!osys->is_wrapper )
1270             state->cfg = osys->cfg;
1271         state->wm_state = osys->wm_state;
1272         state->sar.num  = osys->sar_initial.num;
1273         state->sar.den  = osys->sar_initial.den;
1274     }
1275
1276     VoutDisplayDestroyRender(vd);
1277     if (osys->is_wrapper)
1278         SplitterClose(vd);
1279     vout_display_Delete(vd);
1280     if (osys->event.fifo) {
1281         block_FifoWake(osys->event.fifo);
1282         vlc_join(osys->event.thread, NULL);
1283         block_FifoRelease(osys->event.fifo);
1284     }
1285     vlc_mutex_destroy(&osys->lock);
1286     free(osys);
1287 }
1288
1289 /*****************************************************************************
1290  *
1291  *****************************************************************************/
1292 vout_display_t *vout_NewDisplay(vout_thread_t *vout,
1293                                 const video_format_t *source,
1294                                 const vout_display_state_t *state,
1295                                 const char *module,
1296                                 mtime_t double_click_timeout,
1297                                 mtime_t hide_timeout)
1298 {
1299     return DisplayNew(vout, source, state, module, false, NULL,
1300                       double_click_timeout, hide_timeout, NULL);
1301 }
1302
1303 /*****************************************************************************
1304  *
1305  *****************************************************************************/
1306 struct vout_display_sys_t {
1307     picture_pool_t   *pool;
1308     video_splitter_t *splitter;
1309
1310     /* */
1311     int            count;
1312     picture_t      **picture;
1313     vout_display_t **display;
1314 };
1315 struct video_splitter_owner_t {
1316     vout_display_t *wrapper;
1317 };
1318
1319 static vout_window_t *SplitterNewWindow(vout_display_t *vd, const vout_window_cfg_t *cfg_ptr)
1320 {
1321     vout_display_owner_sys_t *osys = vd->owner.sys;
1322
1323     vout_window_cfg_t cfg = *cfg_ptr;
1324     cfg.is_standalone = true;
1325     cfg.x += 0;//output->window.i_x; FIXME
1326     cfg.y += 0;//output->window.i_y;
1327
1328     return vout_NewDisplayWindow(osys->vout, vd, &cfg);
1329 }
1330 static void SplitterDelWindow(vout_display_t *vd, vout_window_t *window)
1331 {
1332     vout_display_owner_sys_t *osys = vd->owner.sys;
1333
1334     vout_DeleteDisplayWindow(osys->vout, vd, window);
1335 }
1336 static void SplitterEvent(vout_display_t *vd, int event, va_list args)
1337 {
1338     //vout_display_owner_sys_t *osys = vd->owner.sys;
1339
1340     switch (event) {
1341 #if 0
1342     case VOUT_DISPLAY_EVENT_MOUSE_STATE:
1343     case VOUT_DISPLAY_EVENT_MOUSE_MOVED:
1344     case VOUT_DISPLAY_EVENT_MOUSE_PRESSED:
1345     case VOUT_DISPLAY_EVENT_MOUSE_RELEASED:
1346         /* TODO */
1347         break;
1348 #endif
1349     case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK:
1350     case VOUT_DISPLAY_EVENT_KEY:
1351     case VOUT_DISPLAY_EVENT_CLOSE:
1352     case VOUT_DISPLAY_EVENT_FULLSCREEN:
1353     case VOUT_DISPLAY_EVENT_DISPLAY_SIZE:
1354     case VOUT_DISPLAY_EVENT_PICTURES_INVALID:
1355         VoutDisplayEvent(vd, event, args);
1356         break;
1357
1358     default:
1359         msg_Err(vd, "SplitterEvent TODO");
1360         break;
1361     }
1362 }
1363
1364 static picture_pool_t *SplitterPool(vout_display_t *vd, unsigned count)
1365 {
1366     vout_display_sys_t *sys = vd->sys;
1367     if (!sys->pool)
1368         sys->pool = picture_pool_NewFromFormat(&vd->fmt, count);
1369     return sys->pool;
1370 }
1371 static void SplitterPrepare(vout_display_t *vd, picture_t *picture)
1372 {
1373     vout_display_sys_t *sys = vd->sys;
1374
1375     picture_Hold(picture);
1376
1377     if (video_splitter_Filter(sys->splitter, sys->picture, picture)) {
1378         for (int i = 0; i < sys->count; i++)
1379             sys->picture[i] = NULL;
1380         picture_Release(picture);
1381         return;
1382     }
1383
1384     for (int i = 0; i < sys->count; i++) {
1385         if (vout_IsDisplayFiltered(sys->display[i]))
1386             sys->picture[i] = vout_FilterDisplay(sys->display[i], sys->picture[i]);
1387         if (sys->picture[i])
1388             vout_display_Prepare(sys->display[i], sys->picture[i]);
1389     }
1390 }
1391 static void SplitterDisplay(vout_display_t *vd, picture_t *picture)
1392 {
1393     vout_display_sys_t *sys = vd->sys;
1394
1395     for (int i = 0; i < sys->count; i++) {
1396         if (sys->picture[i])
1397             vout_display_Display(sys->display[i], sys->picture[i]);
1398     }
1399     picture_Release(picture);
1400 }
1401 static int SplitterControl(vout_display_t *vd, int query, va_list args)
1402 {
1403     return VLC_EGENERIC;
1404 }
1405 static void SplitterManage(vout_display_t *vd)
1406 {
1407     vout_display_sys_t *sys = vd->sys;
1408
1409     for (int i = 0; i < sys->count; i++)
1410         vout_ManageDisplay(sys->display[i], true);
1411 }
1412
1413 static int SplitterPictureNew(video_splitter_t *splitter, picture_t *picture[])
1414 {
1415     vout_display_sys_t *wsys = splitter->p_owner->wrapper->sys;
1416
1417     for (int i = 0; i < wsys->count; i++) {
1418         if (vout_IsDisplayFiltered(wsys->display[i])) {
1419             /* TODO use a pool ? */
1420             picture[i] = picture_NewFromFormat(&wsys->display[i]->source);
1421         } else {
1422             picture_pool_t *pool = vout_display_Pool(wsys->display[i], 1);
1423             picture[i] = pool ? picture_pool_Get(pool) : NULL;
1424         }
1425         if (!picture[i]) {
1426             for (int j = 0; j < i; j++)
1427                 picture_Release(picture[j]);
1428             return VLC_EGENERIC;
1429         }
1430     }
1431     return VLC_SUCCESS;
1432 }
1433 static void SplitterPictureDel(video_splitter_t *splitter, picture_t *picture[])
1434 {
1435     vout_display_sys_t *wsys = splitter->p_owner->wrapper->sys;
1436
1437     for (int i = 0; i < wsys->count; i++)
1438         picture_Release(picture[i]);
1439 }
1440 static void SplitterClose(vout_display_t *vd)
1441 {
1442     vout_display_sys_t *sys = vd->sys;
1443
1444     /* */
1445     video_splitter_t *splitter = sys->splitter;
1446     free(splitter->p_owner);
1447     video_splitter_Delete(splitter);
1448
1449     if (sys->pool)
1450         picture_pool_Delete(sys->pool);
1451
1452     /* */
1453     for (int i = 0; i < sys->count; i++)
1454         vout_DeleteDisplay(sys->display[i], NULL);
1455     TAB_CLEAN(sys->count, sys->display);
1456     free(sys->picture);
1457
1458     free(sys);
1459 }
1460
1461 vout_display_t *vout_NewSplitter(vout_thread_t *vout,
1462                                  const video_format_t *source,
1463                                  const vout_display_state_t *state,
1464                                  const char *module,
1465                                  const char *splitter_module,
1466                                  mtime_t double_click_timeout,
1467                                  mtime_t hide_timeout)
1468 {
1469     video_splitter_t *splitter =
1470         video_splitter_New(VLC_OBJECT(vout), splitter_module, source);
1471     if (!splitter)
1472         return NULL;
1473
1474     /* */
1475     vout_display_t *wrapper =
1476         DisplayNew(vout, source, state, module, true, NULL,
1477                     double_click_timeout, hide_timeout, NULL);
1478     if (!wrapper) {
1479         video_splitter_Delete(splitter);
1480         return NULL;
1481     }
1482     vout_display_sys_t *sys = malloc(sizeof(*sys));
1483     if (!sys)
1484         abort();
1485     sys->picture = calloc(splitter->i_output, sizeof(*sys->picture));
1486     if (!sys->picture )
1487         abort();
1488     sys->splitter = splitter;
1489     sys->pool     = NULL;
1490
1491     wrapper->pool    = SplitterPool;
1492     wrapper->prepare = SplitterPrepare;
1493     wrapper->display = SplitterDisplay;
1494     wrapper->control = SplitterControl;
1495     wrapper->manage  = SplitterManage;
1496     wrapper->sys     = sys;
1497
1498     /* */
1499     video_splitter_owner_t *owner = malloc(sizeof(*owner));
1500     if (!owner)
1501         abort();
1502     owner->wrapper = wrapper;
1503     splitter->p_owner = owner;
1504     splitter->pf_picture_new = SplitterPictureNew;
1505     splitter->pf_picture_del = SplitterPictureDel;
1506
1507     /* */
1508     TAB_INIT(sys->count, sys->display);
1509     for (int i = 0; i < splitter->i_output; i++) {
1510         vout_display_owner_t owner;
1511
1512         owner.event      = SplitterEvent;
1513         owner.window_new = SplitterNewWindow;
1514         owner.window_del = SplitterDelWindow;
1515
1516         const video_splitter_output_t *output = &splitter->p_output[i];
1517         vout_display_state_t ostate;
1518
1519         memset(&ostate, 0, sizeof(ostate));
1520         ostate.cfg.is_fullscreen = false;
1521         ostate.cfg.display = state->cfg.display;
1522         ostate.cfg.align.horizontal = 0; /* TODO */
1523         ostate.cfg.align.vertical = 0; /* TODO */
1524         ostate.cfg.is_display_filled = true;
1525         ostate.cfg.zoom.num = 1;
1526         ostate.cfg.zoom.den = 1;
1527
1528         vout_display_t *vd = DisplayNew(vout, &output->fmt, &ostate,
1529                                            output->psz_module ? output->psz_module : module,
1530                                            false, wrapper,
1531                                            double_click_timeout, hide_timeout, &owner);
1532         if (!vd) {
1533             vout_DeleteDisplay(wrapper, NULL);
1534             return NULL;
1535         }
1536         TAB_APPEND(sys->count, sys->display, vd);
1537     }
1538
1539     return wrapper;
1540 }
1541
1542 /*****************************************************************************
1543  * TODO move out
1544  *****************************************************************************/
1545 #include "vout_internal.h"
1546 void vout_SendDisplayEventMouse(vout_thread_t *vout, const vlc_mouse_t *m)
1547 {
1548     vlc_mouse_t tmp;
1549
1550     /* The check on p_spu is needed as long as ALLOW_DUMMY_VOUT is defined */
1551     if (vout->p->p_spu && spu_ProcessMouse( vout->p->p_spu, m, &vout->p->display.vd->source))
1552         return;
1553
1554     vlc_mutex_lock( &vout->p->vfilter_lock );
1555     if (vout->p->vfilter_chain) {
1556         if (!filter_chain_MouseFilter(vout->p->vfilter_chain, &tmp, m))
1557             m = &tmp;
1558     }
1559     vlc_mutex_unlock( &vout->p->vfilter_lock );
1560
1561     if (vlc_mouse_HasMoved(&vout->p->mouse, m)) {
1562         vout_SendEventMouseMoved(vout, m->i_x, m->i_y);
1563     }
1564     if (vlc_mouse_HasButton(&vout->p->mouse, m)) {
1565         static const int buttons[] = {
1566             MOUSE_BUTTON_LEFT,
1567             MOUSE_BUTTON_CENTER,
1568             MOUSE_BUTTON_RIGHT,
1569             MOUSE_BUTTON_WHEEL_UP,
1570             MOUSE_BUTTON_WHEEL_DOWN,
1571             -1
1572         };
1573         for (int i = 0; buttons[i] >= 0; i++) {
1574             const int button = buttons[i];
1575             if (vlc_mouse_HasPressed(&vout->p->mouse, m, button))
1576                 vout_SendEventMousePressed(vout, button);
1577             else if (vlc_mouse_HasReleased(&vout->p->mouse, m, button))
1578                 vout_SendEventMouseReleased(vout, button);
1579         }
1580     }
1581     if (m->b_double_click)
1582         vout_SendEventMouseDoubleClick(vout);
1583     vout->p->mouse = *m;
1584 }
1585 #ifdef ALLOW_DUMMY_VOUT
1586 static void DummyVoutSendDisplayEventMouse(vout_thread_t *vout, vlc_mouse_t *fallback, const vlc_mouse_t *m)
1587 {
1588     vout_thread_sys_t p;
1589
1590     if (!vout->p) {
1591         p.mouse = *fallback;
1592         vlc_mutex_init(&p.vfilter_lock);
1593         p.vfilter_chain = NULL;
1594         p.p_spu = NULL;
1595         vout->p = &p;
1596     }
1597     vout_SendDisplayEventMouse(vout, m);
1598     if (vout->p == &p) {
1599         vlc_mutex_destroy(&p.vfilter_lock);
1600         *fallback = p.mouse;
1601         vout->p = NULL;
1602     }
1603 }
1604 #endif
1605