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