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