1 /*****************************************************************************
2 * display.c: "vout display" managment
3 *****************************************************************************
4 * Copyright (C) 2009 Laurent Aimar
7 * Authors: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_video_splitter.h>
34 #include <vlc_vout_display.h>
36 #include <vlc_block.h>
37 #include <vlc_modules.h>
46 static void SplitterClose(vout_display_t *vd);
48 /*****************************************************************************
49 * FIXME/TODO see how to have direct rendering here (interact with vout.c)
50 *****************************************************************************/
51 static picture_t *VideoBufferNew(filter_t *filter)
53 vout_display_t *vd = filter->owner.sys;
54 const video_format_t *fmt = &filter->fmt_out.video;
56 assert(vd->fmt.i_chroma == fmt->i_chroma &&
57 vd->fmt.i_width == fmt->i_width &&
58 vd->fmt.i_height == fmt->i_height);
60 picture_pool_t *pool = vout_display_Pool(vd, 3);
63 return picture_pool_Get(pool);
66 /*****************************************************************************
68 *****************************************************************************/
71 * It creates a new vout_display_t using the given configuration.
73 static vout_display_t *vout_display_New(vlc_object_t *obj,
74 const char *module, bool load_module,
75 const video_format_t *fmt,
76 const vout_display_cfg_t *cfg,
77 vout_display_owner_t *owner)
80 vout_display_t *vd = vlc_custom_create(obj, sizeof(*vd), "vout display" );
83 video_format_Copy(&vd->source, fmt);
85 /* Picture buffer does not have the concept of aspect ratio */
86 video_format_Copy(&vd->fmt, fmt);
87 vd->fmt.i_sar_num = 0;
88 vd->fmt.i_sar_den = 0;
90 vd->info.is_slow = false;
91 vd->info.has_double_click = false;
92 vd->info.has_hide_mouse = false;
93 vd->info.has_pictures_invalid = false;
94 vd->info.has_event_thread = false;
95 vd->info.subpicture_chromas = NULL;
108 vd->module = module_need(vd, "vout display", module, module && *module != '\0');
110 vlc_object_release(vd);
120 * It deletes a vout_display_t
122 static void vout_display_Delete(vout_display_t *vd)
125 module_unneed(vd, vd->module);
127 video_format_Clean(&vd->source);
128 video_format_Clean(&vd->fmt);
130 vlc_object_release(vd);
134 * It controls a vout_display_t
136 static int vout_display_Control(vout_display_t *vd, int query, ...)
141 va_start(args, query);
142 result = vd->control(vd, query, args);
148 static void vout_display_Manage(vout_display_t *vd)
155 void vout_display_GetDefaultDisplaySize(unsigned *width, unsigned *height,
156 const video_format_t *source,
157 const vout_display_cfg_t *cfg)
159 if (cfg->display.width > 0 && cfg->display.height > 0) {
160 *width = cfg->display.width;
161 *height = cfg->display.height;
162 } else if (cfg->display.width > 0) {
163 *width = cfg->display.width;
164 *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.width * cfg->display.sar.num /
165 source->i_visible_width / source->i_sar_num / cfg->display.sar.den;
166 } else if (cfg->display.height > 0) {
167 *width = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.height * cfg->display.sar.den /
168 source->i_visible_height / source->i_sar_den / cfg->display.sar.num;
169 *height = cfg->display.height;
170 } else if (source->i_sar_num >= source->i_sar_den) {
171 *width = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.sar.den / source->i_sar_den / cfg->display.sar.num;
172 *height = source->i_visible_height;
174 *width = source->i_visible_width;
175 *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.sar.num / source->i_sar_num / cfg->display.sar.den;
178 *width = *width * cfg->zoom.num / cfg->zoom.den;
179 *height = *height * cfg->zoom.num / cfg->zoom.den;
181 if (ORIENT_IS_SWAP(source->orientation)) {
183 unsigned store = *width;
190 void vout_display_PlacePicture(vout_display_place_t *place,
191 const video_format_t *source,
192 const vout_display_cfg_t *cfg,
196 memset(place, 0, sizeof(*place));
197 if (cfg->display.width <= 0 || cfg->display.height <= 0)
201 unsigned display_width;
202 unsigned display_height;
204 video_format_t source_rot;
205 video_format_ApplyRotation(&source_rot, source);
206 source = &source_rot;
208 if (cfg->is_display_filled) {
209 display_width = cfg->display.width;
210 display_height = cfg->display.height;
212 vout_display_cfg_t cfg_tmp = *cfg;
214 cfg_tmp.display.width = 0;
215 cfg_tmp.display.height = 0;
216 vout_display_GetDefaultDisplaySize(&display_width, &display_height,
220 display_width = __MIN(display_width, cfg->display.width);
221 display_height = __MIN(display_height, cfg->display.height);
225 const unsigned width = source->i_visible_width;
226 const unsigned height = source->i_visible_height;
227 /* Compute the height if we use the width to fill up display_width */
228 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;
229 /* And the same but switching width/height */
230 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;
232 /* We keep the solution that avoid filling outside the display */
233 if (scaled_width <= cfg->display.width) {
234 place->width = scaled_width;
235 place->height = display_height;
237 place->width = display_width;
238 place->height = scaled_height;
241 /* Compute position */
242 switch (cfg->align.horizontal) {
243 case VOUT_DISPLAY_ALIGN_LEFT:
246 case VOUT_DISPLAY_ALIGN_RIGHT:
247 place->x = cfg->display.width - place->width;
250 place->x = ((int)cfg->display.width - (int)place->width) / 2;
254 switch (cfg->align.vertical) {
255 case VOUT_DISPLAY_ALIGN_TOP:
258 case VOUT_DISPLAY_ALIGN_BOTTOM:
259 place->y = cfg->display.height - place->height;
262 place->y = ((int)cfg->display.height - (int)place->height) / 2;
267 void vout_display_SendMouseMovedDisplayCoordinates(vout_display_t *vd, video_orientation_t orient_display, int m_x, int m_y, vout_display_place_t *place)
269 video_format_t source_rot = vd->source;
270 video_format_TransformTo(&source_rot, orient_display);
272 if (place->width > 0 && place->height > 0) {
274 int x = (int)(source_rot.i_x_offset +
275 (int64_t)(m_x - place->x) * source_rot.i_visible_width / place->width);
276 int y = (int)(source_rot.i_y_offset +
277 (int64_t)(m_y - place->y) * source_rot.i_visible_height/ place->height);
279 video_transform_t transform = video_format_GetTransform(vd->source.orientation, orient_display);
288 y = vd->source.i_visible_height - store;
291 x = vd->source.i_visible_width - x;
292 y = vd->source.i_visible_height - y;
296 x = vd->source.i_visible_width - y;
299 case TRANSFORM_HFLIP:
300 x = vd->source.i_visible_width - x;
302 case TRANSFORM_VFLIP:
303 y = vd->source.i_visible_height - y;
305 case TRANSFORM_TRANSPOSE:
310 case TRANSFORM_ANTI_TRANSPOSE:
312 x = vd->source.i_visible_width - y;
313 y = vd->source.i_visible_height - store;
319 vout_display_SendEventMouseMoved (vd, x, y);
323 struct vout_display_owner_sys_t {
325 bool is_wrapper; /* Is the current display a wrapper */
326 vout_display_t *wrapper; /* Vout display wrapper */
329 vout_display_cfg_t cfg;
336 unsigned width_saved;
337 unsigned height_saved;
345 bool ch_display_filled;
346 bool is_display_filled;
353 #if defined(_WIN32) || defined(__OS2__)
356 unsigned wm_state_initial;
375 video_format_t source;
376 filter_chain_t *filters;
378 /* Lock protecting the variables used by
379 * VoutDisplayEvent(ie vout_display_SendEvent) */
386 mtime_t last_pressed;
392 mtime_t double_click_timeout;
393 mtime_t hide_timeout;
401 bool ch_display_size;
413 static void VoutDisplayCreateRender(vout_display_t *vd)
415 vout_display_owner_sys_t *osys = vd->owner.sys;
417 osys->filters = NULL;
419 video_format_t v_src = vd->source;
423 video_format_t v_dst = vd->fmt;
427 video_format_t v_dst_cmp = v_dst;
428 if ((v_src.i_chroma == VLC_CODEC_J420 && v_dst.i_chroma == VLC_CODEC_I420) ||
429 (v_src.i_chroma == VLC_CODEC_J422 && v_dst.i_chroma == VLC_CODEC_I422) ||
430 (v_src.i_chroma == VLC_CODEC_J440 && v_dst.i_chroma == VLC_CODEC_I440) ||
431 (v_src.i_chroma == VLC_CODEC_J444 && v_dst.i_chroma == VLC_CODEC_I444))
432 v_dst_cmp.i_chroma = v_src.i_chroma;
434 const bool convert = memcmp(&v_src, &v_dst_cmp, sizeof(v_src)) != 0;
438 msg_Dbg(vd, "A filter to adapt decoder to display is needed");
440 filter_owner_t owner = {
443 .buffer_new = VideoBufferNew,
447 osys->filters = filter_chain_NewVideo(vd, false, &owner);
448 assert(osys->filters); /* TODO critical */
452 es_format_InitFromVideo(&src, &v_src);
456 for (int i = 0; i < 1 + (v_dst_cmp.i_chroma != v_dst.i_chroma); i++) {
459 es_format_InitFromVideo(&dst, i == 0 ? &v_dst : &v_dst_cmp);
461 filter_chain_Reset(osys->filters, &src, &dst);
462 filter = filter_chain_AppendFilter(osys->filters,
463 NULL, NULL, &src, &dst);
464 es_format_Clean(&dst);
468 es_format_Clean(&src);
470 msg_Err(vd, "Failed to adapt decoder format to display");
473 static void VoutDisplayDestroyRender(vout_display_t *vd)
475 vout_display_owner_sys_t *osys = vd->owner.sys;
478 filter_chain_Delete(osys->filters);
481 static void VoutDisplayResetRender(vout_display_t *vd)
483 VoutDisplayDestroyRender(vd);
484 VoutDisplayCreateRender(vd);
486 static void VoutDisplayEventMouse(vout_display_t *vd, int event, va_list args)
488 vout_display_owner_sys_t *osys = vd->owner.sys;
490 vlc_mutex_lock(&osys->lock);
493 vlc_mouse_t m = osys->mouse.state;
494 bool is_ignored = false;
497 case VOUT_DISPLAY_EVENT_MOUSE_STATE: {
498 const int x = (int)va_arg(args, int);
499 const int y = (int)va_arg(args, int);
500 const int button_mask = (int)va_arg(args, int);
505 m.i_pressed = button_mask;
508 case VOUT_DISPLAY_EVENT_MOUSE_MOVED: {
509 const int x = (int)va_arg(args, int);
510 const int y = (int)va_arg(args, int);
512 //msg_Dbg(vd, "VoutDisplayEvent 'mouse' @%d,%d", x, y);
516 m.b_double_click = false;
519 case VOUT_DISPLAY_EVENT_MOUSE_PRESSED:
520 case VOUT_DISPLAY_EVENT_MOUSE_RELEASED: {
521 const int button = (int)va_arg(args, int);
522 const int button_mask = 1 << button;
524 /* Ignore inconsistent event */
525 if ((event == VOUT_DISPLAY_EVENT_MOUSE_PRESSED && (osys->mouse.state.i_pressed & button_mask)) ||
526 (event == VOUT_DISPLAY_EVENT_MOUSE_RELEASED && !(osys->mouse.state.i_pressed & button_mask))) {
532 msg_Dbg(vd, "VoutDisplayEvent 'mouse button' %d t=%d", button, event);
534 m.b_double_click = false;
535 if (event == VOUT_DISPLAY_EVENT_MOUSE_PRESSED)
536 m.i_pressed |= button_mask;
538 m.i_pressed &= ~button_mask;
541 case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK:
542 msg_Dbg(vd, "VoutDisplayEvent 'double click'");
544 m.b_double_click = true;
547 vlc_assert_unreachable();
551 vlc_mutex_unlock(&osys->lock);
555 /* Emulate double-click if needed */
556 if (!vd->info.has_double_click &&
557 vlc_mouse_HasPressed(&osys->mouse.state, &m, MOUSE_BUTTON_LEFT)) {
558 const mtime_t i_date = mdate();
560 if (i_date - osys->mouse.last_pressed < osys->mouse.double_click_timeout ) {
561 m.b_double_click = true;
562 osys->mouse.last_pressed = 0;
564 osys->mouse.last_pressed = mdate();
569 osys->mouse.state = m;
572 osys->mouse.ch_activity = true;
573 if (!vd->info.has_hide_mouse)
574 osys->mouse.last_moved = mdate();
577 vout_SendEventMouseVisible(osys->vout);
578 vout_SendDisplayEventMouse(osys->vout, &m);
579 vlc_mutex_unlock(&osys->lock);
583 static void *VoutDisplayEventKeyDispatch(void *data)
585 vout_display_owner_sys_t *osys = data;
588 block_t *event = block_FifoGet(osys->event.fifo);
590 int cancel = vlc_savecancel();
593 memcpy(&key, event->p_buffer, sizeof(key));
594 vout_SendEventKey(osys->vout, key);
595 block_Release(event);
597 vlc_restorecancel(cancel);
601 static void VoutDisplayEventKey(vout_display_t *vd, int key)
603 vout_display_owner_sys_t *osys = vd->owner.sys;
605 if (!osys->event.fifo) {
606 osys->event.fifo = block_FifoNew();
607 if (!osys->event.fifo)
609 if (vlc_clone(&osys->event.thread, VoutDisplayEventKeyDispatch,
610 osys, VLC_THREAD_PRIORITY_LOW)) {
611 block_FifoRelease(osys->event.fifo);
612 osys->event.fifo = NULL;
616 block_t *event = block_Alloc(sizeof(key));
618 memcpy(event->p_buffer, &key, sizeof(key));
619 block_FifoPut(osys->event.fifo, event);
623 static void VoutDisplayEvent(vout_display_t *vd, int event, va_list args)
625 vout_display_owner_sys_t *osys = vd->owner.sys;
628 case VOUT_DISPLAY_EVENT_CLOSE: {
629 msg_Dbg(vd, "VoutDisplayEvent 'close'");
630 vout_SendEventClose(osys->vout);
633 case VOUT_DISPLAY_EVENT_KEY: {
634 const int key = (int)va_arg(args, int);
635 msg_Dbg(vd, "VoutDisplayEvent 'key' 0x%2.2x", key);
636 if (vd->info.has_event_thread)
637 vout_SendEventKey(osys->vout, key);
639 VoutDisplayEventKey(vd, key);
642 case VOUT_DISPLAY_EVENT_MOUSE_STATE:
643 case VOUT_DISPLAY_EVENT_MOUSE_MOVED:
644 case VOUT_DISPLAY_EVENT_MOUSE_PRESSED:
645 case VOUT_DISPLAY_EVENT_MOUSE_RELEASED:
646 case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK:
647 VoutDisplayEventMouse(vd, event, args);
650 case VOUT_DISPLAY_EVENT_FULLSCREEN: {
651 const int is_fullscreen = (int)va_arg(args, int);
653 msg_Dbg(vd, "VoutDisplayEvent 'fullscreen' %d", is_fullscreen);
655 vlc_mutex_lock(&osys->lock);
656 if (!is_fullscreen != !osys->is_fullscreen) {
657 osys->ch_fullscreen = true;
658 osys->is_fullscreen = is_fullscreen;
660 vlc_mutex_unlock(&osys->lock);
663 #if defined(_WIN32) || defined(__OS2__)
664 case VOUT_DISPLAY_EVENT_WINDOW_STATE: {
665 const unsigned state = va_arg(args, unsigned);
667 msg_Dbg(vd, "VoutDisplayEvent 'window state' %u", state);
669 vlc_mutex_lock(&osys->lock);
670 if (state != osys->wm_state) {
671 osys->ch_wm_state = true;
672 osys->wm_state = state;
674 vlc_mutex_unlock(&osys->lock);
678 case VOUT_DISPLAY_EVENT_DISPLAY_SIZE: {
679 const int width = (int)va_arg(args, int);
680 const int height = (int)va_arg(args, int);
681 msg_Dbg(vd, "VoutDisplayEvent 'resize' %dx%d", width, height);
684 vlc_mutex_lock(&osys->lock);
686 osys->ch_display_size = true;
687 osys->display_width = width;
688 osys->display_height = height;
690 vlc_mutex_unlock(&osys->lock);
694 case VOUT_DISPLAY_EVENT_PICTURES_INVALID: {
695 msg_Warn(vd, "VoutDisplayEvent 'pictures invalid'");
698 assert(vd->info.has_pictures_invalid);
700 vlc_mutex_lock(&osys->lock);
701 osys->reset_pictures = true;
702 vlc_mutex_unlock(&osys->lock);
706 msg_Err(vd, "VoutDisplayEvent received event %d", event);
707 /* TODO add an assert when all event are handled */
712 static vout_window_t *VoutDisplayNewWindow(vout_display_t *vd, unsigned type)
714 vout_display_owner_sys_t *osys = vd->owner.sys;
715 vout_window_t *window = vout_NewDisplayWindow(osys->vout, type);
717 vout_display_window_Attach(window, vd);
721 static void VoutDisplayDelWindow(vout_display_t *vd, vout_window_t *window)
723 vout_display_owner_sys_t *osys = vd->owner.sys;
726 vout_display_window_Detach(window);
727 vout_DeleteDisplayWindow(osys->vout, window);
730 static void VoutDisplayFitWindow(vout_display_t *vd, bool default_size)
732 vout_display_owner_sys_t *osys = vd->owner.sys;
733 vout_display_cfg_t cfg = osys->cfg;
735 if (!cfg.is_display_filled)
738 cfg.display.width = 0;
740 cfg.display.height = 0;
742 cfg.display.height = osys->height_saved;
747 unsigned display_width;
748 unsigned display_height;
749 vout_display_GetDefaultDisplaySize(&display_width, &display_height,
751 vout_SetDisplayWindowSize(osys->vout, display_width, display_height);
754 static void VoutDisplayCropRatio(int *left, int *top, int *right, int *bottom,
755 const video_format_t *source,
756 unsigned num, unsigned den)
758 unsigned scaled_width = (uint64_t)source->i_visible_height * num * source->i_sar_den / den / source->i_sar_num;
759 unsigned scaled_height = (uint64_t)source->i_visible_width * den * source->i_sar_num / num / source->i_sar_den;
761 if (scaled_width < source->i_visible_width) {
762 *left = (source->i_visible_width - scaled_width) / 2;
764 *right = *left + scaled_width;
765 *bottom = *top + source->i_visible_height;
768 *top = (source->i_visible_height - scaled_height) / 2;
769 *right = *left + source->i_visible_width;
770 *bottom = *top + scaled_height;
774 bool vout_ManageDisplay(vout_display_t *vd, bool allow_reset_pictures)
776 vout_display_owner_sys_t *osys = vd->owner.sys;
778 vout_display_Manage(vd);
780 /* Handle mouse timeout */
781 const mtime_t date = mdate();
782 bool hide_mouse = false;
784 vlc_mutex_lock(&osys->lock);
786 if (!osys->mouse.is_hidden &&
787 osys->mouse.last_moved + osys->mouse.hide_timeout < date) {
788 osys->mouse.is_hidden = hide_mouse = true;
789 } else if (osys->mouse.ch_activity) {
790 osys->mouse.is_hidden = false;
792 osys->mouse.ch_activity = false;
793 vlc_mutex_unlock(&osys->lock);
796 if (!vd->info.has_hide_mouse) {
797 msg_Dbg(vd, "auto hiding mouse cursor");
798 vout_display_Control(vd, VOUT_DISPLAY_HIDE_MOUSE);
800 vout_SendEventMouseHidden(osys->vout);
803 bool reset_render = false;
806 vlc_mutex_lock(&osys->lock);
808 bool ch_fullscreen = osys->ch_fullscreen;
809 bool is_fullscreen = osys->is_fullscreen;
810 osys->ch_fullscreen = false;
812 #if defined(_WIN32) || defined(__OS2__)
813 bool ch_wm_state = osys->ch_wm_state;
814 unsigned wm_state = osys->wm_state;
815 osys->ch_wm_state = false;
818 bool ch_display_size = osys->ch_display_size;
819 int display_width = osys->display_width;
820 int display_height = osys->display_height;
821 osys->ch_display_size = false;
824 if (allow_reset_pictures) {
825 reset_pictures = osys->reset_pictures;
826 osys->reset_pictures = false;
828 reset_pictures = false;
831 vlc_mutex_unlock(&osys->lock);
833 if (!ch_fullscreen &&
836 !osys->ch_display_filled &&
838 #if defined(_WIN32) || defined(__OS2__)
844 if (!osys->cfg.is_fullscreen && osys->fit_window != 0) {
845 VoutDisplayFitWindow(vd, osys->fit_window == -1);
846 osys->fit_window = 0;
854 if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_FULLSCREEN,
855 is_fullscreen) == VLC_SUCCESS) {
856 osys->cfg.is_fullscreen = is_fullscreen;
859 vout_SetDisplayWindowSize(osys->vout, osys->width_saved,
862 is_fullscreen = osys->cfg.is_fullscreen;
864 msg_Err(vd, "Failed to set fullscreen");
869 if (ch_display_size) {
870 vout_display_cfg_t cfg = osys->cfg;
871 cfg.display.width = display_width;
872 cfg.display.height = display_height;
874 osys->width_saved = osys->cfg.display.width;
875 osys->height_saved = osys->cfg.display.height;
877 vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_SIZE, &cfg);
879 osys->cfg.display.width = display_width;
880 osys->cfg.display.height = display_height;
883 if (osys->ch_display_filled) {
884 vout_display_cfg_t cfg = osys->cfg;
886 cfg.is_display_filled = osys->is_display_filled;
888 if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, &cfg)) {
889 msg_Err(vd, "Failed to change display filled state");
890 osys->is_display_filled = osys->cfg.is_display_filled;
892 osys->cfg.is_display_filled = osys->is_display_filled;
893 osys->ch_display_filled = false;
897 vout_display_cfg_t cfg = osys->cfg;
899 cfg.zoom.num = osys->zoom.num;
900 cfg.zoom.den = osys->zoom.den;
902 if (10 * cfg.zoom.num <= cfg.zoom.den) {
905 } else if (cfg.zoom.num >= 10 * cfg.zoom.den) {
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;
915 osys->fit_window = -1;
918 osys->cfg.zoom.num = osys->zoom.num;
919 osys->cfg.zoom.den = osys->zoom.den;
920 osys->ch_zoom = false;
922 #if defined(_WIN32) || defined(__OS2__)
925 if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_WINDOW_STATE, wm_state)) {
926 msg_Err(vd, "Failed to set on top");
927 wm_state = osys->wm_state;
929 osys->wm_state_initial = wm_state;
934 video_format_t source = vd->source;
936 if (osys->sar.num > 0 && osys->sar.den > 0) {
937 source.i_sar_num = osys->sar.num;
938 source.i_sar_den = osys->sar.den;
940 source.i_sar_num = osys->source.i_sar_num;
941 source.i_sar_den = osys->source.i_sar_den;
944 if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT, &source)) {
945 /* There nothing much we can do. The only reason a vout display
946 * does not support it is because it need the core to add black border
947 * to the video for it.
948 * TODO add black borders ?
950 msg_Err(vd, "Failed to change source AR");
952 } else if (!osys->fit_window) {
953 osys->fit_window = 1;
956 osys->sar.num = source.i_sar_num;
957 osys->sar.den = source.i_sar_den;
958 osys->ch_sar = false;
960 /* If a crop ratio is requested, recompute the parameters */
961 if (osys->crop.num > 0 && osys->crop.den > 0)
962 osys->ch_crop = true;
966 video_format_t source = vd->source;
968 unsigned crop_num = osys->crop.num;
969 unsigned crop_den = osys->crop.den;
970 if (crop_num > 0 && crop_den > 0) {
971 video_format_t fmt = osys->source;
972 fmt.i_sar_num = source.i_sar_num;
973 fmt.i_sar_den = source.i_sar_den;
974 VoutDisplayCropRatio(&osys->crop.left, &osys->crop.top,
975 &osys->crop.right, &osys->crop.bottom,
976 &fmt, crop_num, crop_den);
978 const int right_max = osys->source.i_x_offset + osys->source.i_visible_width;
979 const int bottom_max = osys->source.i_y_offset + osys->source.i_visible_height;
980 int left = VLC_CLIP((int)osys->source.i_x_offset + osys->crop.left,
982 int top = VLC_CLIP((int)osys->source.i_y_offset + osys->crop.top,
985 if (osys->crop.right <= 0)
986 right = (int)(osys->source.i_x_offset + osys->source.i_visible_width) + osys->crop.right;
988 right = (int)osys->source.i_x_offset + osys->crop.right;
989 right = VLC_CLIP(right, left + 1, right_max);
990 if (osys->crop.bottom <= 0)
991 bottom = (int)(osys->source.i_y_offset + osys->source.i_visible_height) + osys->crop.bottom;
993 bottom = (int)osys->source.i_y_offset + osys->crop.bottom;
994 bottom = VLC_CLIP(bottom, top + 1, bottom_max);
996 source.i_x_offset = left;
997 source.i_y_offset = top;
998 source.i_visible_width = right - left;
999 source.i_visible_height = bottom - top;
1000 video_format_Print(VLC_OBJECT(vd), "SOURCE ", &osys->source);
1001 video_format_Print(VLC_OBJECT(vd), "CROPPED", &source);
1002 if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_CROP, &source)) {
1003 msg_Err(vd, "Failed to change source crop TODO implement crop at core");
1005 source = vd->source;
1006 crop_num = osys->crop_saved.num;
1007 crop_den = osys->crop_saved.den;
1008 /* FIXME implement cropping in the core if not supported by the
1009 * vout module (easy)
1011 } else if (!osys->fit_window) {
1012 osys->fit_window = 1;
1014 vd->source = source;
1015 osys->crop.left = source.i_x_offset - osys->source.i_x_offset;
1016 osys->crop.top = source.i_y_offset - osys->source.i_y_offset;
1017 /* FIXME for right/bottom we should keep the 'type' border vs window */
1018 osys->crop.right = (source.i_x_offset + source.i_visible_width) -
1019 (osys->source.i_x_offset + osys->source.i_visible_width);
1020 osys->crop.bottom = (source.i_y_offset + source.i_visible_height) -
1021 (osys->source.i_y_offset + osys->source.i_visible_height);
1022 osys->crop.num = crop_num;
1023 osys->crop.den = crop_den;
1024 osys->ch_crop = false;
1028 if (reset_pictures) {
1029 if (vout_display_Control(vd, VOUT_DISPLAY_RESET_PICTURES)) {
1030 /* FIXME what to do here ? */
1031 msg_Err(vd, "Failed to reset pictures (probably fatal)");
1033 reset_render = true;
1037 VoutDisplayResetRender(vd);
1039 return reset_render;
1042 bool vout_AreDisplayPicturesInvalid(vout_display_t *vd)
1044 vout_display_owner_sys_t *osys = vd->owner.sys;
1046 vlc_mutex_lock(&osys->lock);
1047 const bool reset_pictures = osys->reset_pictures;
1048 vlc_mutex_unlock(&osys->lock);
1050 return reset_pictures;
1053 bool vout_IsDisplayFiltered(vout_display_t *vd)
1055 vout_display_owner_sys_t *osys = vd->owner.sys;
1057 return osys->filters != NULL;
1060 picture_t *vout_FilterDisplay(vout_display_t *vd, picture_t *picture)
1062 vout_display_owner_sys_t *osys = vd->owner.sys;
1064 assert(osys->filters);
1065 if (filter_chain_GetLength(osys->filters) <= 0) {
1066 picture_Release(picture);
1069 return filter_chain_VideoFilter(osys->filters, picture);
1072 void vout_UpdateDisplaySourceProperties(vout_display_t *vd, const video_format_t *source)
1074 vout_display_owner_sys_t *osys = vd->owner.sys;
1076 if (source->i_sar_num * osys->source.i_sar_den !=
1077 source->i_sar_den * osys->source.i_sar_num) {
1079 osys->source.i_sar_num = source->i_sar_num;
1080 osys->source.i_sar_den = source->i_sar_den;
1081 vlc_ureduce(&osys->source.i_sar_num, &osys->source.i_sar_den,
1082 osys->source.i_sar_num, osys->source.i_sar_den, 0);
1084 /* FIXME it will override any AR that the user would have forced */
1085 osys->ch_sar = true;
1086 osys->sar.num = osys->source.i_sar_num;
1087 osys->sar.den = osys->source.i_sar_den;
1089 if (source->i_x_offset != osys->source.i_x_offset ||
1090 source->i_y_offset != osys->source.i_y_offset ||
1091 source->i_visible_width != osys->source.i_visible_width ||
1092 source->i_visible_height != osys->source.i_visible_height) {
1094 video_format_CopyCrop(&osys->source, source);
1096 /* Force the vout to reapply the current user crop settings over the new decoder
1098 osys->ch_crop = true;
1102 void vout_SetDisplayFullscreen(vout_display_t *vd, bool is_fullscreen)
1104 vout_display_owner_sys_t *osys = vd->owner.sys;
1106 vlc_mutex_lock(&osys->lock);
1107 if (!osys->is_fullscreen != !is_fullscreen) {
1108 osys->ch_fullscreen = true;
1109 osys->is_fullscreen = is_fullscreen;
1111 vlc_mutex_unlock(&osys->lock);
1114 void vout_SetDisplayFilled(vout_display_t *vd, bool is_filled)
1116 vout_display_owner_sys_t *osys = vd->owner.sys;
1118 if (!osys->is_display_filled != !is_filled) {
1119 osys->ch_display_filled = true;
1120 osys->is_display_filled = is_filled;
1124 void vout_SetDisplayZoom(vout_display_t *vd, unsigned num, unsigned den)
1126 vout_display_owner_sys_t *osys = vd->owner.sys;
1128 if (num > 0 && den > 0) {
1129 vlc_ureduce(&num, &den, num, den, 0);
1135 if (osys->is_display_filled ||
1136 osys->zoom.num != num || osys->zoom.den != den) {
1137 osys->ch_zoom = true;
1138 osys->zoom.num = num;
1139 osys->zoom.den = den;
1143 void vout_SetDisplayAspect(vout_display_t *vd, unsigned dar_num, unsigned dar_den)
1145 vout_display_owner_sys_t *osys = vd->owner.sys;
1147 unsigned sar_num, sar_den;
1148 if (dar_num > 0 && dar_den > 0) {
1149 sar_num = dar_num * osys->source.i_visible_height;
1150 sar_den = dar_den * osys->source.i_visible_width;
1151 vlc_ureduce(&sar_num, &sar_den, sar_num, sar_den, 0);
1157 if (osys->sar.num != sar_num || osys->sar.den != sar_den) {
1158 osys->ch_sar = true;
1159 osys->sar.num = sar_num;
1160 osys->sar.den = sar_den;
1163 void vout_SetDisplayCrop(vout_display_t *vd,
1164 unsigned crop_num, unsigned crop_den,
1165 unsigned left, unsigned top, int right, int bottom)
1167 vout_display_owner_sys_t *osys = vd->owner.sys;
1169 if (osys->crop.left != (int)left || osys->crop.top != (int)top ||
1170 osys->crop.right != right || osys->crop.bottom != bottom ||
1171 (crop_num > 0 && crop_den > 0 &&
1172 (crop_num != osys->crop.num || crop_den != osys->crop.den))) {
1174 osys->crop.left = left;
1175 osys->crop.top = top;
1176 osys->crop.right = right;
1177 osys->crop.bottom = bottom;
1178 osys->crop.num = crop_num;
1179 osys->crop.den = crop_den;
1181 osys->ch_crop = true;
1185 static vout_display_t *DisplayNew(vout_thread_t *vout,
1186 const video_format_t *source,
1187 const vout_display_state_t *state,
1189 bool is_wrapper, vout_display_t *wrapper,
1190 mtime_t double_click_timeout,
1191 mtime_t hide_timeout,
1192 const vout_display_owner_t *owner_ptr)
1195 vout_display_owner_sys_t *osys = calloc(1, sizeof(*osys));
1196 vout_display_cfg_t *cfg = &osys->cfg;
1199 osys->sar_initial.num = state->sar.num;
1200 osys->sar_initial.den = state->sar.den;
1201 vout_display_GetDefaultDisplaySize(&cfg->display.width, &cfg->display.height,
1205 osys->is_wrapper = is_wrapper;
1206 osys->wrapper = wrapper;
1208 vlc_mutex_init(&osys->lock);
1210 vlc_mouse_Init(&osys->mouse.state);
1211 osys->mouse.last_moved = mdate();
1212 osys->mouse.double_click_timeout = double_click_timeout;
1213 osys->mouse.hide_timeout = hide_timeout;
1214 osys->is_fullscreen = cfg->is_fullscreen;
1215 osys->display_width = cfg->display.width;
1216 osys->display_height = cfg->display.height;
1217 osys->is_display_filled = cfg->is_display_filled;
1218 osys->width_saved = cfg->display.width;
1219 osys->height_saved = cfg->display.height;
1220 if (osys->is_fullscreen) {
1221 vout_display_cfg_t cfg_windowed = *cfg;
1222 cfg_windowed.is_fullscreen = false;
1223 cfg_windowed.display.width = 0;
1224 cfg_windowed.display.height = 0;
1225 vout_display_GetDefaultDisplaySize(&osys->width_saved,
1226 &osys->height_saved,
1227 source, &cfg_windowed);
1230 osys->zoom.num = cfg->zoom.num;
1231 osys->zoom.den = cfg->zoom.den;
1232 #if defined(_WIN32) || defined(__OS2__)
1233 osys->wm_state_initial = VOUT_WINDOW_STATE_NORMAL;
1234 osys->wm_state = state->wm_state;
1235 osys->ch_wm_state = true;
1237 osys->fit_window = 0;
1238 osys->event.fifo = NULL;
1240 osys->source = *source;
1241 osys->crop.left = 0;
1243 osys->crop.right = 0;
1244 osys->crop.bottom = 0;
1245 osys->crop_saved.num = 0;
1246 osys->crop_saved.den = 0;
1250 osys->sar.num = osys->sar_initial.num ? osys->sar_initial.num : source->i_sar_num;
1251 osys->sar.den = osys->sar_initial.den ? osys->sar_initial.den : source->i_sar_den;
1253 vout_display_owner_t owner;
1257 owner.event = VoutDisplayEvent;
1258 owner.window_new = VoutDisplayNewWindow;
1259 owner.window_del = VoutDisplayDelWindow;
1263 vout_display_t *p_display = vout_display_New(VLC_OBJECT(vout),
1264 module, !is_wrapper,
1265 source, cfg, &owner);
1271 VoutDisplayCreateRender(p_display);
1273 /* Setup delayed request */
1274 if (osys->sar.num != source->i_sar_num ||
1275 osys->sar.den != source->i_sar_den)
1276 osys->ch_sar = true;
1281 void vout_DeleteDisplay(vout_display_t *vd, vout_display_state_t *state)
1283 vout_display_owner_sys_t *osys = vd->owner.sys;
1286 if (!osys->is_wrapper )
1287 state->cfg = osys->cfg;
1288 #if defined(_WIN32) || defined(__OS2__)
1289 state->wm_state = osys->wm_state;
1291 state->sar.num = osys->sar_initial.num;
1292 state->sar.den = osys->sar_initial.den;
1295 VoutDisplayDestroyRender(vd);
1296 if (osys->is_wrapper)
1298 vout_display_Delete(vd);
1299 if (osys->event.fifo) {
1300 vlc_cancel(osys->event.thread);
1301 vlc_join(osys->event.thread, NULL);
1302 block_FifoRelease(osys->event.fifo);
1304 vlc_mutex_destroy(&osys->lock);
1308 /*****************************************************************************
1310 *****************************************************************************/
1311 vout_display_t *vout_NewDisplay(vout_thread_t *vout,
1312 const video_format_t *source,
1313 const vout_display_state_t *state,
1315 mtime_t double_click_timeout,
1316 mtime_t hide_timeout)
1318 return DisplayNew(vout, source, state, module, false, NULL,
1319 double_click_timeout, hide_timeout, NULL);
1322 /*****************************************************************************
1324 *****************************************************************************/
1325 struct vout_display_sys_t {
1326 picture_pool_t *pool;
1327 video_splitter_t *splitter;
1331 picture_t **picture;
1332 vout_display_t **display;
1334 struct video_splitter_owner_t {
1335 vout_display_t *wrapper;
1338 static vout_window_t *SplitterNewWindow(vout_display_t *vd, unsigned type)
1340 vout_display_owner_sys_t *osys = vd->owner.sys;
1341 vout_window_t *window;
1342 vout_window_cfg_t cfg = {
1344 .width = vd->cfg->display.width,
1345 .height = vd->cfg->display.height,
1346 .is_standalone = true,
1349 window = vout_display_window_New(osys->vout, &cfg);
1351 vout_display_window_Attach(window, vd);
1355 static void SplitterDelWindow(vout_display_t *vd, vout_window_t *window)
1357 if (window != NULL) {
1358 vout_display_window_Detach(window);
1359 vout_display_window_Delete(window);
1364 static void SplitterEvent(vout_display_t *vd, int event, va_list args)
1366 //vout_display_owner_sys_t *osys = vd->owner.sys;
1370 case VOUT_DISPLAY_EVENT_MOUSE_STATE:
1371 case VOUT_DISPLAY_EVENT_MOUSE_MOVED:
1372 case VOUT_DISPLAY_EVENT_MOUSE_PRESSED:
1373 case VOUT_DISPLAY_EVENT_MOUSE_RELEASED:
1377 case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK:
1378 case VOUT_DISPLAY_EVENT_KEY:
1379 case VOUT_DISPLAY_EVENT_CLOSE:
1380 case VOUT_DISPLAY_EVENT_FULLSCREEN:
1381 case VOUT_DISPLAY_EVENT_DISPLAY_SIZE:
1382 case VOUT_DISPLAY_EVENT_PICTURES_INVALID:
1383 VoutDisplayEvent(vd, event, args);
1387 msg_Err(vd, "splitter event not implemented: %d", event);
1392 static picture_pool_t *SplitterPool(vout_display_t *vd, unsigned count)
1394 vout_display_sys_t *sys = vd->sys;
1396 sys->pool = picture_pool_NewFromFormat(&vd->fmt, count);
1399 static void SplitterPrepare(vout_display_t *vd,
1401 subpicture_t *subpicture)
1403 vout_display_sys_t *sys = vd->sys;
1405 picture_Hold(picture);
1406 assert(!subpicture);
1408 if (video_splitter_Filter(sys->splitter, sys->picture, picture)) {
1409 for (int i = 0; i < sys->count; i++)
1410 sys->picture[i] = NULL;
1411 picture_Release(picture);
1415 for (int i = 0; i < sys->count; i++) {
1416 if (vout_IsDisplayFiltered(sys->display[i]))
1417 sys->picture[i] = vout_FilterDisplay(sys->display[i], sys->picture[i]);
1418 if (sys->picture[i])
1419 vout_display_Prepare(sys->display[i], sys->picture[i], NULL);
1422 static void SplitterDisplay(vout_display_t *vd,
1424 subpicture_t *subpicture)
1426 vout_display_sys_t *sys = vd->sys;
1428 assert(!subpicture);
1429 for (int i = 0; i < sys->count; i++) {
1430 if (sys->picture[i])
1431 vout_display_Display(sys->display[i], sys->picture[i], NULL);
1433 picture_Release(picture);
1435 static int SplitterControl(vout_display_t *vd, int query, va_list args)
1437 (void)vd; (void)query; (void)args;
1438 return VLC_EGENERIC;
1440 static void SplitterManage(vout_display_t *vd)
1442 vout_display_sys_t *sys = vd->sys;
1444 for (int i = 0; i < sys->count; i++)
1445 vout_ManageDisplay(sys->display[i], true);
1448 static int SplitterPictureNew(video_splitter_t *splitter, picture_t *picture[])
1450 vout_display_sys_t *wsys = splitter->p_owner->wrapper->sys;
1452 for (int i = 0; i < wsys->count; i++) {
1453 if (vout_IsDisplayFiltered(wsys->display[i])) {
1454 /* TODO use a pool ? */
1455 picture[i] = picture_NewFromFormat(&wsys->display[i]->source);
1457 picture_pool_t *pool = vout_display_Pool(wsys->display[i], 1);
1458 picture[i] = pool ? picture_pool_Get(pool) : NULL;
1461 for (int j = 0; j < i; j++)
1462 picture_Release(picture[j]);
1463 return VLC_EGENERIC;
1468 static void SplitterPictureDel(video_splitter_t *splitter, picture_t *picture[])
1470 vout_display_sys_t *wsys = splitter->p_owner->wrapper->sys;
1472 for (int i = 0; i < wsys->count; i++)
1473 picture_Release(picture[i]);
1475 static void SplitterClose(vout_display_t *vd)
1477 vout_display_sys_t *sys = vd->sys;
1480 video_splitter_t *splitter = sys->splitter;
1481 free(splitter->p_owner);
1482 video_splitter_Delete(splitter);
1485 picture_pool_Release(sys->pool);
1488 for (int i = 0; i < sys->count; i++)
1489 vout_DeleteDisplay(sys->display[i], NULL);
1490 TAB_CLEAN(sys->count, sys->display);
1496 vout_display_t *vout_NewSplitter(vout_thread_t *vout,
1497 const video_format_t *source,
1498 const vout_display_state_t *state,
1500 const char *splitter_module,
1501 mtime_t double_click_timeout,
1502 mtime_t hide_timeout)
1504 video_splitter_t *splitter =
1505 video_splitter_New(VLC_OBJECT(vout), splitter_module, source);
1510 vout_display_t *wrapper =
1511 DisplayNew(vout, source, state, module, true, NULL,
1512 double_click_timeout, hide_timeout, NULL);
1514 video_splitter_Delete(splitter);
1517 vout_display_sys_t *sys = malloc(sizeof(*sys));
1520 sys->picture = calloc(splitter->i_output, sizeof(*sys->picture));
1523 sys->splitter = splitter;
1526 wrapper->pool = SplitterPool;
1527 wrapper->prepare = SplitterPrepare;
1528 wrapper->display = SplitterDisplay;
1529 wrapper->control = SplitterControl;
1530 wrapper->manage = SplitterManage;
1534 video_splitter_owner_t *vso = xmalloc(sizeof(*vso));
1535 vso->wrapper = wrapper;
1536 splitter->p_owner = vso;
1537 splitter->pf_picture_new = SplitterPictureNew;
1538 splitter->pf_picture_del = SplitterPictureDel;
1541 TAB_INIT(sys->count, sys->display);
1542 for (int i = 0; i < splitter->i_output; i++) {
1543 vout_display_owner_t vdo = {
1544 .event = SplitterEvent,
1545 .window_new = SplitterNewWindow,
1546 .window_del = SplitterDelWindow,
1548 const video_splitter_output_t *output = &splitter->p_output[i];
1549 vout_display_state_t ostate;
1551 memset(&ostate, 0, sizeof(ostate));
1552 ostate.cfg.is_fullscreen = false;
1553 ostate.cfg.display = state->cfg.display;
1554 ostate.cfg.align.horizontal = 0; /* TODO */
1555 ostate.cfg.align.vertical = 0; /* TODO */
1556 ostate.cfg.is_display_filled = true;
1557 ostate.cfg.zoom.num = 1;
1558 ostate.cfg.zoom.den = 1;
1560 vout_display_t *vd = DisplayNew(vout, &output->fmt, &ostate,
1561 output->psz_module ? output->psz_module : module,
1563 double_click_timeout, hide_timeout, &vdo);
1565 vout_DeleteDisplay(wrapper, NULL);
1568 TAB_APPEND(sys->count, sys->display, vd);
1574 /*****************************************************************************
1576 *****************************************************************************/
1577 #include "vout_internal.h"
1578 void vout_SendDisplayEventMouse(vout_thread_t *vout, const vlc_mouse_t *m)
1580 vlc_mouse_t tmp1, tmp2;
1582 /* The check on spu is needed as long as ALLOW_DUMMY_VOUT is defined */
1583 if (vout->p->spu && spu_ProcessMouse( vout->p->spu, m, &vout->p->display.vd->source))
1586 vlc_mutex_lock( &vout->p->filter.lock );
1587 if (vout->p->filter.chain_static && vout->p->filter.chain_interactive) {
1588 if (!filter_chain_MouseFilter(vout->p->filter.chain_interactive, &tmp1, m))
1590 if (!filter_chain_MouseFilter(vout->p->filter.chain_static, &tmp2, m))
1593 vlc_mutex_unlock( &vout->p->filter.lock );
1595 if (vlc_mouse_HasMoved(&vout->p->mouse, m)) {
1596 vout_SendEventMouseMoved(vout, m->i_x, m->i_y);
1598 if (vlc_mouse_HasButton(&vout->p->mouse, m)) {
1599 for (unsigned button = 0; button < MOUSE_BUTTON_MAX; button++) {
1600 if (vlc_mouse_HasPressed(&vout->p->mouse, m, button))
1601 vout_SendEventMousePressed(vout, button);
1602 else if (vlc_mouse_HasReleased(&vout->p->mouse, m, button))
1603 vout_SendEventMouseReleased(vout, button);
1606 if (m->b_double_click)
1607 vout_SendEventMouseDoubleClick(vout);
1608 vout->p->mouse = *m;