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