diff --git a/include/vlc_vout_display.h b/include/vlc_vout_display.h new file mode 100644 index 0000000000..4625c284b3 --- /dev/null +++ b/include/vlc_vout_display.h @@ -0,0 +1,407 @@ +/***************************************************************************** + * vlc_vout_display.h: vout_display_t definitions + ***************************************************************************** + * Copyright (C) 2009 Laurent Aimar + * $Id$ + * + * Authors: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef VLC_VOUT_DISPLAY_H +#define VLC_VOUT_DISPLAY_H 1 + +/** + * \file + * This file defines vout display structures and functions in vlc + */ + +#include +#include +#include +#include +#include +#include + +/* XXX + * Do NOT use video_format_t::i_aspect but i_sar_num/den everywhere. i_aspect + * will be removed as soon as possible. + * + */ +typedef struct vout_display_t vout_display_t; +typedef struct vout_display_sys_t vout_display_sys_t; +typedef struct vout_display_owner_t vout_display_owner_t; +typedef struct vout_display_owner_sys_t vout_display_owner_sys_t; + +/** + * It specifies the possible alignment used in vout_display. + */ +typedef enum +{ + VOUT_DISPLAY_ALIGN_CENTER, + /* */ + VOUT_DISPLAY_ALIGN_LEFT, + VOUT_DISPLAY_ALIGN_RIGHT, + /* */ + VOUT_DISPLAY_ALIGN_TOP, + VOUT_DISPLAY_ALIGN_BOTTOM, +} vout_display_align_t; + +/** + * Initial/Current configuration for a vout_display_t + */ +typedef struct { + bool is_fullscreen; /* Is the display fullscreen */ + + /* Display properties */ + struct { + /* Window title (may be NULL) */ + const char *title; + + /* Display size */ + int width; + int height; + + /* Display SAR */ + struct { + int num; + int den; + } sar; + } display; + + /* Alignment of the picture inside the display */ + struct { + int horizontal; + int vertical; + } align; + + /* Do we fill up the display with the video */ + bool is_display_filled; + + /* Zoom to use + * It will be applied to the whole display if b_display_filled is set, otherwise + * only on the video source */ + struct { + int num; + int den; + } zoom; + +} vout_display_cfg_t; + +/** + * Informations from a vout_display_t to configure + * the core behaviour. + * + * By default they are all false. + * + */ +typedef struct { + bool is_slow; /* The picture memory has slow read/write */ + bool has_double_click; /* Is double-click generated */ + bool has_hide_mouse; /* Is mouse automatically hidden */ + bool has_pictures_invalid;/* Will VOUT_DISPLAY_EVENT_PICTURES_INVALID be used */ +} vout_display_info_t; + +/** + * Control query for vout_display_t + */ +enum { + /* Hide the mouse. It will be send when + * vout_display_t::info.b_hide_mouse is false */ + VOUT_DISPLAY_HIDE_MOUSE, + + /* Ask to reset the internal buffers after a VOUT_DISPLAY_EVENT_PICTURES_INVALID + * request. + */ + VOUT_DISPLAY_RESET_PICTURES, + + /* Ask the module to acknowledge/refuse the fullscreen state change after + * being requested (externaly or by VOUT_DISPLAY_EVENT_FULLSCREEN */ + VOUT_DISPLAY_CHANGE_FULLSCREEN, /* const vout_display_cfg_t *p_cfg */ + + /* Ask the module to acknowledge/refuse the on top state change after + * being requested externaly */ + VOUT_DISPLAY_CHANGE_ON_TOP, /* int b_on_top */ + + /* Ask the module to acknowledge/refuse the display size change requested + * (externaly or by VOUT_DISPLAY_EVENT_DISPLAY_SIZE) */ + VOUT_DISPLAY_CHANGE_DISPLAY_SIZE, /* const vout_display_cfg_t *p_cfg */ + + /* Ask the module to acknowledge/refuse fill display state change after + * being requested externaly */ + VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, /* const vout_display_cfg_t *p_cfg */ + + /* Ask the module to acknowledge/refuse zoom change after being requested + * externaly */ + VOUT_DISPLAY_CHANGE_ZOOM, /* const vout_display_cfg_t *p_cfg */ + + /* Ask the module to acknowledge/refuse source aspect ratio after being + * requested externaly */ + VOUT_DISPLAY_CHANGE_SOURCE_ASPECT, /* const video_format_t *p_source */ + + /* Ask the module to acknowledge/refuse source crop change after being + * requested externaly. + * The cropping requested is stored by video_format_t::i_x/y_offset and + * video_format_t::i_visible_width/height */ + VOUT_DISPLAY_CHANGE_SOURCE_CROP, /* const video_format_t *p_source */ +}; + +/** + * Event from vout_display_t + * + * For event that modifiy the state, you may send them multiple of times, + * only the transition will be kept and act upon. + */ +enum { + /* TODO: + * ZOOM ? DISPLAY_FILLED ? ON_TOP ? + */ + /* */ + VOUT_DISPLAY_EVENT_PICTURES_INVALID, /* The buffer are now invalid and need to be changed */ + + VOUT_DISPLAY_EVENT_FULLSCREEN, + + VOUT_DISPLAY_EVENT_DISPLAY_SIZE, /* The display size need to change : int i_width, int i_height */ + + /* */ + VOUT_DISPLAY_EVENT_CLOSE, + VOUT_DISPLAY_EVENT_KEY, + + /* Full mouse state. + * You can use it OR use the other mouse events. The core will do + * the conversion. + */ + VOUT_DISPLAY_EVENT_MOUSE_STATE, + + /* Mouse event */ + VOUT_DISPLAY_EVENT_MOUSE_MOVED, + VOUT_DISPLAY_EVENT_MOUSE_PRESSED, + VOUT_DISPLAY_EVENT_MOUSE_RELEASED, + VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK, +}; + +/** + * Vout owner structures + */ +struct vout_display_owner_t { + /* Private place holder for the vout_display_t creator + */ + vout_display_owner_sys_t *sys; + + /* Event comming from the module + * + * This function is set prior to the module instantiation and must not + * be overwritten nor used directly (use the vout_display_SendEvent* + * wrapper. + * + * You can send it at any time ie from any vout_display_t functions + * (TODO add support from a private thread). + */ + void (*event)(vout_display_t *, int, va_list); + + /* Window management + * + * These functions are set prior to the module instantiation and must not + * be overwritten nor used directly (use the vout_display_*Window + * wrapper */ + vout_window_t *(*window_new)(vout_display_t *, const vout_window_cfg_t *); + void (*window_del)(vout_display_t *, vout_window_t *); +}; + +struct vout_display_t { + VLC_COMMON_MEMBERS + + /* Module */ + module_t *module; + + /* Initial and current configuration. + * You cannot modify it directly, you must use the appropriate event. + * + * It reflects the current in use value ie after the event has been accepted + * and applied/configured if needed. + */ + const vout_display_cfg_t *cfg; + + /* video source format. + * + * You are guaranted that in the open function, no cropping is asked. + * You cannot change it. + */ + video_format_t source; + + /* picture_t format. + * + * You can only change it inside the module open function to + * match what you want and when a VOUT_DISPLAY_RESET_PICTURES + * is called on your module and was successfull. + * + * By default, it is equal to ::source except for the aspect ratio + * which is undefined(0) and is ignored. + */ + video_format_t fmt; + + /* Informations + * + * You can only set then in the open function. + */ + vout_display_info_t info; + + /* Return a new picture_t (mandatory). + * + * You can return NULL when you cannot/do not want to allocate + * more pictures. + * If you want to create a pool of reusable pictures, you can + * use a picture_pool_t. + */ + picture_t *(*get)(vout_display_t *); + + /* Prepare a picture for display (optional). + * + * It is called before the next pf_display call to provide as much + * time as possible to prepare the given picture for display. + * You are guaranted that pf_display will always be called and using + * the exact same picture_t. + * You cannot change the pixel content of the picture_t. + */ + void (*prepare)(vout_display_t *, picture_t *); + + /* Display a picture (mandatory). + * + * The picture must be displayed as soon as possible. + * You cannot change the pixel content of the picture_t. + * + * This function gives away the ownership of the picture, so you must + * release it as soon as possible. + */ + void (*display)(vout_display_t *, picture_t *); + + /* Control on the module (mandatory) */ + int (*control)(vout_display_t *, int, va_list); + + /* Manage pending event (mandatory for now) */ + void (*manage)(vout_display_t *); + + /* Private place holder for the vout_display_t module (optional) + * + * A module is free to used it as it wishes. + */ + vout_display_sys_t *sys; + + /* Reserved for the vout_display_t owner. + * + * It must not be overwritten nor used directly by a module. + */ + vout_display_owner_t owner; +}; + +static inline void vout_display_SendEvent(vout_display_t *vd, int query, ...) +{ + va_list args; + va_start(args, query); + vd->owner.event(vd, query, args); + va_end(args); +} + +static inline void vout_display_SendEventDisplaySize(vout_display_t *vd, int width, int height) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_DISPLAY_SIZE, width, height); +} +static inline void vout_display_SendEventPicturesInvalid(vout_display_t *vd) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_PICTURES_INVALID); +} +static inline void vout_display_SendEventClose(vout_display_t *vd) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_CLOSE); +} +static inline void vout_display_SendEventKey(vout_display_t *vd, int key) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_KEY, key); +} +static inline void vout_display_SendEventFullscreen(vout_display_t *vd, bool is_fullscreen) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_FULLSCREEN, is_fullscreen); +} +/* The mouse position (State and Moved event) must be expressed against vout_display_t::source unit */ +static inline void vout_display_SendEventMouseState(vout_display_t *vd, int x, int y, int button_mask) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_MOUSE_STATE, x, y, button_mask); +} +static inline void vout_display_SendEventMouseMoved(vout_display_t *vd, int x, int y) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_MOUSE_MOVED, x, y); +} +static inline void vout_display_SendEventMousePressed(vout_display_t *vd, int button) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_MOUSE_PRESSED, button); +} +static inline void vout_display_SendEventMouseReleased(vout_display_t *vd, int button) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_MOUSE_RELEASED, button); +} +static inline void vout_display_SendEventMouseDoubleClick(vout_display_t *vd) +{ + vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK); +} + +/** + * Ask for a new window with the given configuration as hint. + * + * b_standalone/i_x/i_y may be overwritten by the core + */ +static inline vout_window_t *vout_display_NewWindow(vout_display_t *vd, const vout_window_cfg_t *cfg) +{ + return vd->owner.window_new(vd, cfg); +} +static inline void vout_display_DeleteWindow(vout_display_t *vd, + vout_window_t *window) +{ + vd->owner.window_del(vd, window); +} + +/** + * It computes the default display size given the source and + * the display configuration. + * + * It supposes that the picture will already be cropped. + */ +VLC_EXPORT( void, vout_display_GetDefaultDisplaySize, (int *width, int *height, const video_format_t *source, const vout_display_cfg_t *) ); + + +/** + * Structure used to store the result of a vout_display_PlacePicture. + */ +typedef struct { + int x; + int y; + int width; + int height; +} vout_display_place_t; + +/** + * It computes how to place a picture inside the display to respect + * the given parameters. + * It supposes that the cropping is done by an external mean. + * + * \param p_place Place inside the window (window pixel unit) + * \param p_source Video source format + * \param p_cfg Display configuration + * \param b_clip If true, prevent the video to go outside the display (break zoom). + */ +VLC_EXPORT( void, vout_display_PlacePicture, (vout_display_place_t *place, const video_format_t *source, const vout_display_cfg_t *cfg, bool do_clipping) ); + +#endif /* VLC_VOUT_DISPLAY_H */ + diff --git a/include/vlc_vout_wrapper.h b/include/vlc_vout_wrapper.h new file mode 100644 index 0000000000..89f78b9fd5 --- /dev/null +++ b/include/vlc_vout_wrapper.h @@ -0,0 +1,92 @@ +/***************************************************************************** + * vlc_vout_wrapper.h: definitions for vout wrappers (temporary) + ***************************************************************************** + * Copyright (C) 2009 Laurent Aimar + * $Id$ + * + * Authors: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef VLC_VOUT_WRAPPER_H +#define VLC_VOUT_WRAPPER_H 1 + +#include + +/* XXX DO NOT use it ouside the vout module wrapper XXX */ + +/** + * It retreive a picture from the display + */ +static inline picture_t *vout_display_Get(vout_display_t *vd) +{ + return vd->get(vd); +} + +/** + * It preparse a picture for display. + */ +static inline void vout_display_Prepare(vout_display_t *vd, picture_t *picture) +{ + if (vd->prepare ) + vd->prepare(vd, picture); +} + +/** + * It display a picture. + */ +static inline void vout_display_Display(vout_display_t *vd, picture_t *picture) +{ + vd->display(vd, picture); +} + +/** + * It holds a state for a vout display. + */ +typedef struct { + vout_display_cfg_t cfg; + + bool is_on_top; + struct { + int num; + int den; + } sar; +} vout_display_state_t; + +/** + * It creates a vout managed display. + */ +VLC_EXPORT(vout_display_t *, vout_NewDisplay, ( vout_thread_t *, const video_format_t *, const vout_display_state_t *, const char *psz_module, mtime_t i_double_click_timeout, mtime_t i_hide_timeout )); +/** + * It destroy a vout managed display. + */ +VLC_EXPORT(void, vout_DeleteDisplay, (vout_display_t *, vout_display_state_t *)); + +VLC_EXPORT(bool, vout_IsDisplayFiltered, (vout_display_t *)); +VLC_EXPORT(picture_t *, vout_FilterDisplay, (vout_display_t *, picture_t *)); +VLC_EXPORT(bool, vout_AreDisplayPicturesInvalid, (vout_display_t *)); + +VLC_EXPORT(void, vout_ManageDisplay, (vout_display_t *)); + +VLC_EXPORT(void, vout_SetDisplayFullscreen, (vout_display_t *, bool is_fullscreen)); +VLC_EXPORT(void, vout_SetDisplayFilled, (vout_display_t *, bool is_filled)); +VLC_EXPORT(void, vout_SetDisplayZoom, (vout_display_t *, int num, int den)); +VLC_EXPORT(void, vout_SetDisplayOnTop, (vout_display_t *, bool is_on_top)); +VLC_EXPORT(void, vout_SetDisplayAspect, (vout_display_t *, unsigned sar_num, unsigned sar_den)); +VLC_EXPORT(void, vout_SetDisplayCrop, (vout_display_t *, unsigned crop_num, unsigned crop_den, unsigned x, unsigned y, unsigned width, unsigned height)); + +#endif /* VLC_VOUT_WRAPPER_H */ + diff --git a/src/Makefile.am b/src/Makefile.am index 0eb0ad8ecf..30a3da64bb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -95,6 +95,7 @@ pluginsinclude_HEADERS = \ ../include/vlc_vlm.h \ ../include/vlc_video_splitter.h \ ../include/vlc_vout.h \ + ../include/vlc_vout_display.h \ ../include/vlc_vout_window.h \ ../include/vlc_xml.h \ $(NULL) @@ -344,6 +345,8 @@ SOURCES_libvlc_common = \ input/stream_memory.c \ input/subtitles.c \ input/var.c \ + video_output/display.c \ + video_output/display.h \ video_output/snapshot.c \ video_output/snapshot.h \ video_output/statistic.h \ diff --git a/src/libvlccore.sym b/src/libvlccore.sym index 9358e44133..9db46ae351 100644 --- a/src/libvlccore.sym +++ b/src/libvlccore.sym @@ -575,6 +575,20 @@ vout_UnlinkPicture vout_window_New vout_window_Control vout_window_Delete +vout_NewDisplay +vout_DeleteDisplay +vout_AreDisplayPicturesInvalid +vout_IsDisplayFiltered +vout_FilterDisplay +vout_ManageDisplay +vout_SetDisplayFullscreen +vout_SetDisplayFilled +vout_SetDisplayZoom +vout_SetDisplayOnTop +vout_SetDisplayAspect +vout_SetDisplayCrop +vout_display_GetDefaultDisplaySize +vout_display_PlacePicture __xml_Create text_style_Copy text_style_Delete diff --git a/src/video_output/display.c b/src/video_output/display.c new file mode 100644 index 0000000000..16a54015b6 --- /dev/null +++ b/src/video_output/display.c @@ -0,0 +1,1330 @@ +/***************************************************************************** + * display.c: "vout display" managment + ***************************************************************************** + * Copyright (C) 2009 Laurent Aimar + * $Id$ + * + * Authors: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include + +#include +#include +#include +#include + +#include + +#include "display.h" + +#include "event.h" + +static void SplitterClose(vout_display_t *vd); + +/***************************************************************************** + * FIXME/TODO see how to have direct rendering here (interact with vout.c) + *****************************************************************************/ +static picture_t *VideoBufferNew(filter_t *filter) +{ + const video_format_t *fmt = &filter->fmt_out.video; + + picture_t *picture = picture_New(fmt->i_chroma, + fmt->i_width, fmt->i_height, + fmt->i_aspect); + if (!picture) + msg_Err(filter, "Failed to allocate picture"); + return picture; +} +static void VideoBufferDelete(filter_t *filter, picture_t *picture) +{ + VLC_UNUSED(filter); + picture_Release(picture); +} + +static int FilterAllocationInit(filter_t *filter, void *data) +{ + VLC_UNUSED(data); + + filter->pf_vout_buffer_new = VideoBufferNew; + filter->pf_vout_buffer_del = VideoBufferDelete; + + return VLC_SUCCESS; +} +static void FilterAllocationClean(filter_t *filter) +{ + filter->pf_vout_buffer_new = NULL; + filter->pf_vout_buffer_del = NULL; +} + +/***************************************************************************** + * + *****************************************************************************/ + +/** + * It creates a new vout_display_t using the given configuration. + */ +static vout_display_t *vout_display_New(vlc_object_t *obj, + const char *module, bool load_module, + const video_format_t *fmt, + const vout_display_cfg_t *cfg, + vout_display_owner_t *owner) +{ + /* */ + vout_display_t *vd = vlc_object_create(obj, sizeof(*vd)); + + /* */ + video_format_Copy(&vd->source, fmt); + + /* Picture buffer does not have the concept of aspect ratio */ + video_format_Copy(&vd->fmt, fmt); + vd->fmt.i_aspect = 0; + vd->fmt.i_sar_num = 0; + vd->fmt.i_sar_den = 0; + + vd->info.is_slow = false; + vd->info.has_double_click = false; + vd->info.has_hide_mouse = false; + vd->info.has_pictures_invalid = false; + + vd->cfg = cfg; + vd->get = NULL; + vd->prepare = NULL; + vd->display = NULL; + vd->control = NULL; + vd->manage = NULL; + vd->sys = NULL; + + vd->owner = *owner; + + vlc_object_attach(vd, obj); + + if (load_module) { + vd->module = module_need(vd, "vout display", module, module && *module != '\0'); + if (!vd->module) { + vlc_object_detach(vd); + vlc_object_release(vd); + return NULL; + } + } else { + vd->module = NULL; + } + return vd; +} + +/** + * It deletes a vout_display_t + */ +static void vout_display_Delete(vout_display_t *vd) +{ + vlc_object_detach(vd); + + if (vd->module) + module_unneed(vd, vd->module); + + vlc_object_release(vd); +} + +/** + * It controls a vout_display_t + */ +static int vout_display_Control(vout_display_t *vd, int query, ...) +{ + va_list args; + int result; + + va_start(args, query); + result = vd->control(vd, query, args); + va_end(args); + + return result; +} +static void vout_display_Manage(vout_display_t *vd) +{ + vd->manage(vd); +} + +/* */ +void vout_display_GetDefaultDisplaySize(int *width, int *height, + const video_format_t *source, + const vout_display_cfg_t *cfg) +{ + if (cfg->display.width > 0 && cfg->display.height > 0) { + *width = cfg->display.width; + *height = cfg->display.height; + } else if (cfg->display.width > 0) { + *width = cfg->display.width; + *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.width * cfg->display.sar.num / + source->i_visible_width / source->i_sar_num / cfg->display.sar.den; + } else if (cfg->display.height > 0) { + *width = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.height * cfg->display.sar.den / + source->i_visible_height / source->i_sar_den / cfg->display.sar.num; + *height = cfg->display.height; + } else if (source->i_sar_num >= source->i_sar_den) { + *width = (int64_t)source->i_visible_width * source->i_sar_num * cfg->display.sar.den / source->i_sar_den / cfg->display.sar.num; + *height = source->i_visible_height; + } else { + *width = source->i_visible_width; + *height = (int64_t)source->i_visible_height * source->i_sar_den * cfg->display.sar.num / source->i_sar_num / cfg->display.sar.den; + } + + *width = *width * cfg->zoom.num / cfg->zoom.den; + *height = *height * cfg->zoom.num / cfg->zoom.den; +} + +/* */ +void vout_display_PlacePicture(vout_display_place_t *place, + const video_format_t *source, + const vout_display_cfg_t *cfg, + bool do_clipping) +{ + /* */ + memset(place, 0, sizeof(*place)); + if (cfg->display.width <= 0 || cfg->display.height <= 0) + return; + + /* */ + int width; + int height; + int display_width; + int display_height; + + if (cfg->is_display_filled) { + width = source->i_visible_width; + height = source->i_visible_height; + display_width = cfg->display.width; + display_height = cfg->display.height; + } else { + vout_display_cfg_t cfg_tmp = *cfg; + + cfg_tmp.display.width = 0; + cfg_tmp.display.height = 0; + vout_display_GetDefaultDisplaySize(&width, &height, + source, &cfg_tmp); + + display_width = width; + display_height = height; + if (do_clipping) { + display_width = __MIN(display_width, cfg->display.width); + display_height = __MIN(display_height, cfg->display.height); + } + } + + /* Compute the height if we use the width to fill up display_width */ + 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; + /* And the same but switching width/height */ + 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; + + /* We keep the solution that avoid filling outside the display */ + if (scaled_width <= cfg->display.width) { + place->width = scaled_width; + place->height = display_height; + } else { + place->width = display_width; + place->height = scaled_height; + } + + /* Compute position */ + switch (cfg->align.horizontal) { + case VOUT_DISPLAY_ALIGN_LEFT: + place->x = 0; + break; + case VOUT_DISPLAY_ALIGN_RIGHT: + place->x = cfg->display.width - place->width; + break; + default: + place->x = (cfg->display.width - place->width) / 2; + break; + } + + switch (cfg->align.vertical) { + case VOUT_DISPLAY_ALIGN_TOP: + place->y = 0; + break; + case VOUT_DISPLAY_ALIGN_BOTTOM: + place->y = cfg->display.height - place->height; + break; + default: + place->y = (cfg->display.height - place->height) / 2; + break; + } +} + +struct vout_display_owner_sys_t { + vout_thread_t *vout; + bool is_wrapper; /* Is the current display a wrapper */ + vout_display_t *wrapper; /* Vout display wrapper */ + + /* mouse state */ + struct { + vlc_mouse_t state; + + mtime_t last_pressed; + mtime_t last_moved; + bool is_hidden; + + /* */ + mtime_t double_click_timeout; + mtime_t hide_timeout; + } mouse; + + /* */ + vout_display_cfg_t cfg; + bool is_on_top_initial; + struct { + unsigned num; + unsigned den; + } sar_initial; + + /* */ + int width_saved; + int height_saved; + + struct { + unsigned num; + unsigned den; + } crop_saved; + + /* */ + bool reset_pictures; + + bool ch_fullscreen; + bool is_fullscreen; + + bool ch_display_size; + int display_width; + int display_height; + + bool ch_display_filled; + bool is_display_filled; + + bool ch_zoom; + struct { + int num; + int den; + } zoom; + + bool ch_on_top; + bool is_on_top; + + bool ch_sar; + struct { + unsigned num; + unsigned den; + } sar; + + bool ch_crop; + struct { + unsigned x; + unsigned y; + unsigned width; + unsigned height; + unsigned num; + unsigned den; + } crop; + + /* */ + video_format_t source; + filter_chain_t *filters; +}; + +static void VoutDisplayCreateRender(vout_display_t *vd) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + osys->filters = NULL; + + video_format_t v_src = vd->source; + v_src.i_aspect = 0; + v_src.i_sar_num = 0; + v_src.i_sar_den = 0; + + video_format_t v_dst = vd->fmt; + v_dst.i_aspect = 0; + v_dst.i_sar_num = 0; + v_dst.i_sar_den = 0; + + const bool convert = memcmp(&v_src, &v_dst, sizeof(v_src)) != 0; + if (!convert) + return; + + osys->filters = filter_chain_New(vd, "video filter2", false, + FilterAllocationInit, + FilterAllocationClean, NULL); + assert(osys->filters); /* TODO critical */ + + /* */ + es_format_t src; + es_format_InitFromVideo(&src, &v_src); + + /* */ + es_format_t dst; + es_format_InitFromVideo(&dst, &v_dst); + + filter_chain_Reset(osys->filters, &src, &dst); + + msg_Err(vd, "A filter to adapt decoder to display is needed"); + filter_t *filter = filter_chain_AppendFilter(osys->filters, + NULL, NULL, &src, &dst); + if (!filter) + { + msg_Err(vd, "VoutDisplayCreateRender FAILED"); + /* TODO */ + assert(0); + } +} + +static void VoutDisplayDestroyRender(vout_display_t *vd) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (osys->filters) + filter_chain_Delete(osys->filters); +} + +static void VoutDisplayResetRender(vout_display_t *vd) +{ + VoutDisplayDestroyRender(vd); + VoutDisplayCreateRender(vd); +} +static void VoutDisplayEventMouse(vout_display_t *vd, const vlc_mouse_t *mouse) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + /* */ + vlc_mouse_t m = *mouse; + + /* Emulate double-click if needed */ + if (!vd->info.has_double_click && + vlc_mouse_HasPressed(&osys->mouse.state, &m, MOUSE_BUTTON_LEFT)) { + const mtime_t i_date = mdate(); + + if (i_date - osys->mouse.last_pressed < osys->mouse.double_click_timeout ) { + m.b_double_click = true; + osys->mouse.last_pressed = 0; + } else { + osys->mouse.last_pressed = mdate(); + } + } + + /* */ + osys->mouse.state = m; + + vout_SendDisplayEventMouse(osys->vout, &m); +} + +static void VoutDisplayEvent(vout_display_t *vd, int event, va_list args) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + switch (event) { + case VOUT_DISPLAY_EVENT_CLOSE: { + msg_Dbg(vd, "VoutDisplayEvent 'close'"); + vout_SendEventClose(osys->vout); + break; + } + case VOUT_DISPLAY_EVENT_KEY: { + const int key = (int)va_arg(args, int); + msg_Dbg(vd, "VoutDisplayEvent 'key' 0x%2.2x", key); + vout_SendEventKey(osys->vout, key); + break; + } + case VOUT_DISPLAY_EVENT_MOUSE_STATE: { + const int x = (int)va_arg(args, int); + const int y = (int)va_arg(args, int); + const int button_mask = (int)va_arg(args, int); + + vlc_mouse_t m; + + vlc_mouse_Init(&m); + m.i_x = x; + m.i_y = y; + m.i_pressed = button_mask; + + VoutDisplayEventMouse(vd, &m); + break; + } + case VOUT_DISPLAY_EVENT_MOUSE_MOVED: { + const int x = (int)va_arg(args, int); + const int y = (int)va_arg(args, int); + if (x != osys->mouse.state.i_x || y != osys->mouse.state.i_y) { + msg_Dbg(vd, "VoutDisplayEvent 'mouse' @%d,%d", x, y); + + /* */ + osys->mouse.is_hidden = false; + if (!vd->info.has_hide_mouse) + osys->mouse.last_moved = mdate(); + vout_SendEventMouseVisible(osys->vout); + + vlc_mouse_t m = osys->mouse.state; + m.i_x = x; + m.i_y = y; + m.b_double_click = false; + + VoutDisplayEventMouse(vd, &m); + } + break; + } + case VOUT_DISPLAY_EVENT_MOUSE_PRESSED: + case VOUT_DISPLAY_EVENT_MOUSE_RELEASED: { + const int button = (int)va_arg(args, int); + const int button_mask = 1 << button; + + /* Ignore inconistent event */ + if (event == VOUT_DISPLAY_EVENT_MOUSE_PRESSED && (osys->mouse.state.i_pressed & button_mask)) + break; + if (event == VOUT_DISPLAY_EVENT_MOUSE_RELEASED && !(osys->mouse.state.i_pressed & button_mask)) + break; + + /* */ + msg_Dbg(vd, "VoutDisplayEvent 'mouse button' %d t=%d", button, event); + + osys->mouse.is_hidden = false; + if (!vd->info.has_hide_mouse) + osys->mouse.last_moved = mdate(); + vout_SendEventMouseVisible(osys->vout); + + vlc_mouse_t m = osys->mouse.state; + m.b_double_click = false; + if (event == VOUT_DISPLAY_EVENT_MOUSE_PRESSED) + m.i_pressed |= button_mask; + else + m.i_pressed &= ~button_mask; + + VoutDisplayEventMouse(vd, &m); + break; + } + case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK: { + msg_Dbg(vd, "VoutDisplayEvent 'double click'"); + + vlc_mouse_t m = osys->mouse.state; + m.b_double_click = true; + VoutDisplayEventMouse(vd, &m); + break; + } + + case VOUT_DISPLAY_EVENT_FULLSCREEN: { + const int is_fullscreen = (int)va_arg(args, int); + + msg_Dbg(vd, "VoutDisplayEvent 'fullscreen' %d", is_fullscreen); + + if (!is_fullscreen == !osys->is_fullscreen) + break; + osys->ch_fullscreen = true; + osys->is_fullscreen = is_fullscreen; + break; + } + + case VOUT_DISPLAY_EVENT_DISPLAY_SIZE: { + const int width = (int)va_arg(args, int); + const int height = (int)va_arg(args, int); + msg_Dbg(vd, "VoutDisplayEvent 'resize' %dx%d", width, height); + + /* */ + osys->ch_display_size = true; + osys->display_width = width; + osys->display_height = height; + break; + } + + case VOUT_DISPLAY_EVENT_PICTURES_INVALID: { + msg_Warn(vd, "VoutDisplayEvent 'pictures invalid'"); + + /* */ + assert(vd->info.has_pictures_invalid); + osys->reset_pictures = true; + break; + } + default: + msg_Err(vd, "VoutDisplayEvent received event %d", event); + /* TODO add an assert when all event are handled */ + break; + } +} + +static vout_window_t *VoutDisplayNewWindow(vout_display_t *vd, const vout_window_cfg_t *cfg) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + return vout_NewDisplayWindow(osys->vout, vd, cfg); +} +static void VoutDisplayDelWindow(vout_display_t *vd, vout_window_t *window) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + vout_DeleteDisplayWindow(osys->vout, vd, window); +} + +void vout_ManageDisplay(vout_display_t *vd) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + vout_display_Manage(vd); + + /* Handle mouse timeout */ + const mtime_t date = mdate(); + if (!osys->mouse.is_hidden && + osys->mouse.last_moved + osys->mouse.hide_timeout < date) { + if (!vd->info.has_hide_mouse) { + msg_Dbg(vd, "auto hidding mouse"); + vout_display_Control(vd, VOUT_DISPLAY_HIDE_MOUSE); + } + osys->mouse.is_hidden = true; + + vout_SendEventMouseHidden(osys->vout); + } + + bool reset_pictures = false; + for (;;) { + if (!osys->ch_fullscreen && + !osys->ch_display_size && + !osys->reset_pictures && + !osys->ch_display_filled && + !osys->ch_zoom && + !osys->ch_on_top && + !osys->ch_sar && + !osys->ch_crop) + break; + + /* */ + if (osys->ch_fullscreen) { + vout_display_cfg_t cfg = osys->cfg; + + cfg.is_fullscreen = osys->is_fullscreen; + cfg.display.width = cfg.is_fullscreen ? 0 : osys->width_saved; + cfg.display.height = cfg.is_fullscreen ? 0 : osys->height_saved; + + if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_FULLSCREEN, &cfg)) { + msg_Err(vd, "Failed to set fullscreen"); + osys->is_fullscreen = osys->cfg.is_fullscreen; + } + osys->cfg.is_fullscreen = osys->is_fullscreen; + osys->ch_fullscreen = false; + + /* */ + vout_SendEventFullscreen(osys->vout, osys->cfg.is_fullscreen); + } + + /* */ + if (osys->ch_display_size) { + vout_display_cfg_t cfg = osys->cfg; + cfg.display.width = osys->display_width; + cfg.display.height = osys->display_height; + + if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_SIZE, &cfg)) { + msg_Err(vd, "Failed to resize display"); + + /* We ignore the resized */ + osys->display_width = osys->cfg.display.width; + osys->display_height = osys->cfg.display.height; + } + osys->cfg.display.width = osys->display_width; + osys->cfg.display.height = osys->display_height; + + if (!osys->is_fullscreen) { + osys->width_saved = osys->display_width; + osys->height_saved = osys->display_height; + } + osys->ch_display_size = false; + } + /* */ + if (osys->ch_display_filled) { + vout_display_cfg_t cfg = osys->cfg; + + cfg.is_display_filled = osys->is_display_filled; + + if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_DISPLAY_FILLED, &cfg)) { + msg_Err(vd, "Failed to change display filled state"); + osys->is_display_filled = osys->cfg.is_display_filled; + } + osys->cfg.is_display_filled = osys->is_display_filled; + osys->ch_display_filled = false; + + vout_SendEventDisplayFilled(osys->vout, osys->cfg.is_display_filled); + } + /* */ + if (osys->ch_zoom) { + vout_display_cfg_t cfg = osys->cfg; + + cfg.zoom.num = osys->zoom.num; + cfg.zoom.den = osys->zoom.den; + + if (10 * cfg.zoom.num <= cfg.zoom.den) { + cfg.zoom.num = 1; + cfg.zoom.den = 10; + } else if (cfg.zoom.num >= 10 * cfg.zoom.den) { + cfg.zoom.num = 10; + cfg.zoom.den = 1; + } + + if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_ZOOM, &cfg)) { + msg_Err(vd, "Failed to change zoom"); + osys->zoom.num = osys->cfg.zoom.num; + osys->zoom.den = osys->cfg.zoom.den; + } else if (cfg.is_display_filled) { + osys->ch_display_size = true; + osys->display_width = (int64_t)vd->source.i_width * osys->zoom.num / osys->zoom.den; + osys->display_height = (int64_t)vd->source.i_height * osys->zoom.num / osys->zoom.den; + } + + osys->cfg.zoom.num = osys->zoom.num; + osys->cfg.zoom.den = osys->zoom.den; + osys->ch_zoom = false; + + vout_SendEventZoom(osys->vout, osys->cfg.zoom.num, osys->cfg.zoom.den); + } + /* */ + if (osys->ch_on_top) { + bool is_on_top = osys->is_on_top; + + if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_ON_TOP, is_on_top)) { + msg_Err(vd, "Failed to set on top"); + is_on_top = osys->is_on_top_initial; + } + osys->is_on_top_initial = + osys->is_on_top = is_on_top; + osys->ch_on_top = false; + + /* */ + vout_SendEventOnTop(osys->vout, osys->is_on_top_initial); + } + /* */ + if (osys->ch_sar) { + video_format_t source = vd->source; + + if (osys->sar.num > 0 && osys->sar.den > 0) { + source.i_sar_num = osys->sar.num; + source.i_sar_den = osys->sar.den; + } else { + source.i_sar_num = osys->source.i_sar_num; + source.i_sar_den = osys->source.i_sar_den; + } + + if (vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_ASPECT, &source)) { + /* There nothing much we can do. The only reason a vout display + * does not support it is because it need the core to add black border + * to the video for it. + * TODO add black borders ? + */ + msg_Err(vd, "Failed to change source AR"); + source = vd->source; + } + vd->source = source; + osys->sar.num = source.i_sar_num; + osys->sar.den = source.i_sar_den; + osys->ch_sar = false; + + /* */ + if (osys->sar.num == osys->source.i_sar_num && + osys->sar.den == osys->source.i_sar_den) + { + vout_SendEventSourceAspect(osys->vout, 0, 0); + } + else + { + unsigned dar_num, dar_den; + vlc_ureduce( &dar_num, &dar_den, + osys->sar.num * vd->source.i_visible_width, + osys->sar.den * vd->source.i_visible_height, + 65536); + vout_SendEventSourceAspect(osys->vout, dar_num, dar_den); + } + } + /* */ + if (osys->ch_crop) { + video_format_t source = vd->source; + unsigned crop_num = osys->crop.num; + unsigned crop_den = osys->crop.den; + + source.i_x_offset = osys->crop.x; + source.i_y_offset = osys->crop.y; + source.i_visible_width = osys->crop.width; + source.i_visible_height = osys->crop.height; + + /* */ + const bool is_valid = source.i_x_offset < source.i_width && + source.i_y_offset < source.i_height && + source.i_x_offset + source.i_visible_width <= source.i_width && + source.i_y_offset + source.i_visible_height <= source.i_height && + source.i_visible_width > 0 && source.i_visible_height > 0; + + if (!is_valid || vout_display_Control(vd, VOUT_DISPLAY_CHANGE_SOURCE_CROP, &source)) { + if (is_valid) + msg_Err(vd, "Failed to change source crop TODO implement crop at core"); + else + msg_Err(vd, "Invalid crop requested"); + + source = vd->source; + crop_num = osys->crop_saved.num; + crop_den = osys->crop_saved.den; + /* FIXME implement cropping in the core if not supported by the + * vout module (easy) + */ + } + vd->source = source; + osys->crop.x = source.i_x_offset; + osys->crop.y = source.i_y_offset; + osys->crop.width = source.i_visible_width; + osys->crop.height = source.i_visible_height; + osys->crop.num = crop_num; + osys->crop.den = crop_den; + osys->ch_crop = false; + + /* TODO fix when a ratio is used (complicated). */ + const unsigned left = osys->crop.x - osys->source.i_x_offset; + const unsigned top = osys->crop.y - osys->source.i_y_offset; + const unsigned right = osys->source.i_visible_width - (osys->crop.width + osys->crop.x); + const unsigned bottom = osys->source.i_visible_height - (osys->crop.height + osys->crop.y); + vout_SendEventSourceCrop(osys->vout, + osys->crop.num, osys->crop.den, + left, top, right, bottom); + } + + /* */ + if (osys->reset_pictures) { + if (vout_display_Control(vd, VOUT_DISPLAY_RESET_PICTURES)) { + /* FIXME what to do here ? */ + msg_Err(vd, "Failed to reset pictures (probably fatal)"); + } + reset_pictures = true; + + osys->reset_pictures = false; + } + } + if (reset_pictures) + VoutDisplayResetRender(vd); +} + +bool vout_AreDisplayPicturesInvalid(vout_display_t *vd) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + return osys->reset_pictures; +} + +bool vout_IsDisplayFiltered(vout_display_t *vd) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + return osys->filters != NULL; +} + +picture_t *vout_FilterDisplay(vout_display_t *vd, picture_t *picture) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (!osys->filters) + return picture; + return filter_chain_VideoFilter(osys->filters, picture); +} + +void vout_SetDisplayFullscreen(vout_display_t *vd, bool is_fullscreen) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (!osys->is_fullscreen != !is_fullscreen) { + osys->ch_fullscreen = true; + osys->is_fullscreen = is_fullscreen; + } +} + +void vout_SetDisplayFilled(vout_display_t *vd, bool is_filled) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (!osys->is_display_filled != !is_filled) { + osys->ch_display_filled = true; + osys->is_display_filled = is_filled; + } +} + +void vout_SetDisplayZoom(vout_display_t *vd, int num, int den) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (osys->zoom.num != num || osys->zoom.den != den) { + osys->ch_zoom = true; + osys->zoom.num = num; + osys->zoom.den = den; + } +} +void vout_SetDisplayOnTop(vout_display_t *vd, bool is_on_top) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (!osys->is_on_top != !is_on_top) { + osys->ch_on_top = true; + osys->is_on_top = is_on_top; + } +} +void vout_SetDisplayAspect(vout_display_t *vd, unsigned sar_num, unsigned sar_den) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (osys->sar.num != sar_num || osys->sar.den != sar_den) { + osys->ch_sar = true; + osys->sar.num = sar_num; + osys->sar.den = sar_den; + } +} +void vout_SetDisplayCrop(vout_display_t *vd, + unsigned crop_num, unsigned crop_den, + unsigned x, unsigned y, unsigned width, unsigned height) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (osys->crop.x != x || osys->crop.y != y || + osys->crop.width != width || osys->crop.height != height) { + + osys->crop.x = x; + osys->crop.y = y; + osys->crop.width = width; + osys->crop.height = height; + osys->crop.num = crop_num; + osys->crop.den = crop_den; + + osys->ch_crop = true; + } +} + + +static vout_display_t *DisplayNew(vout_thread_t *vout, + const video_format_t *source_org, + const vout_display_state_t *state, + const char *module, + bool is_wrapper, vout_display_t *wrapper, + mtime_t double_click_timeout, + mtime_t hide_timeout, + const vout_display_owner_t *owner_ptr) +{ + /* */ + vout_display_owner_sys_t *osys = calloc(1, sizeof(*osys)); + vout_display_cfg_t *cfg = &osys->cfg; + + *cfg = state->cfg; + osys->is_on_top_initial = state->is_on_top;; + osys->sar_initial.num = state->sar.num; + osys->sar_initial.den = state->sar.den; + vout_display_GetDefaultDisplaySize(&cfg->display.width, &cfg->display.height, + source_org, cfg); + + osys->vout = vout; + osys->is_wrapper = is_wrapper; + osys->wrapper = wrapper; + + vlc_mouse_Init(&osys->mouse.state); + osys->mouse.last_moved = mdate(); + osys->mouse.double_click_timeout = double_click_timeout; + osys->mouse.hide_timeout = hide_timeout; + osys->is_fullscreen = cfg->is_fullscreen; + osys->width_saved = + osys->display_width = cfg->display.width; + osys->height_saved = + osys->display_height = cfg->display.height; + osys->is_display_filled = cfg->is_display_filled; + osys->zoom.num = cfg->zoom.num; + osys->zoom.den = cfg->zoom.den; + + osys->source = *source_org; + + video_format_t source = *source_org; + + source.i_x_offset = + osys->crop.x = 0; + source.i_y_offset = + osys->crop.y = 0; + source.i_visible_width = + osys->crop.width = source.i_width; + source.i_visible_height = + osys->crop.height = source.i_height; + osys->crop_saved.num = 0; + osys->crop_saved.den = 0; + osys->crop.num = 0; + osys->crop.den = 0; + + osys->sar.num = osys->sar_initial.num ? osys->sar_initial.num : source.i_sar_num; + osys->sar.den = osys->sar_initial.den ? osys->sar_initial.den : source.i_sar_den; + + vout_display_owner_t owner; + if (owner_ptr) { + owner = *owner_ptr; + } else { + owner.event = VoutDisplayEvent; + owner.window_new = VoutDisplayNewWindow; + owner.window_del = VoutDisplayDelWindow; + } + owner.sys = osys; + + /* */ + vout_display_t *p_display = vout_display_New(VLC_OBJECT(vout), + module, !is_wrapper, + &source, cfg, &owner); + if (!p_display) { + free(osys); + return NULL; + } + + VoutDisplayCreateRender(p_display); + + /* Setup delayed request */ + if (osys->sar.num != source_org->i_sar_num || + osys->sar.den != source_org->i_sar_den) + osys->ch_sar = true; + if (osys->is_on_top) + osys->ch_on_top = true; + if (osys->crop.x != source_org->i_x_offset || + osys->crop.y != source_org->i_y_offset || + osys->crop.width != source_org->i_visible_width || + osys->crop.height != source_org->i_visible_height) + osys->ch_crop = true; + + return p_display; +} + +void vout_DeleteDisplay(vout_display_t *vd, vout_display_state_t *state) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + if (state) { + if (!osys->is_wrapper ) + state->cfg = osys->cfg; + state->is_on_top = osys->is_on_top_initial; + state->sar.num = osys->sar_initial.num; + state->sar.den = osys->sar_initial.den; + } + + VoutDisplayDestroyRender(vd); + if (osys->is_wrapper) + SplitterClose(vd); + vout_display_Delete(vd); + free(osys); +} + +/***************************************************************************** + * + *****************************************************************************/ +vout_display_t *vout_NewDisplay(vout_thread_t *vout, + const video_format_t *source, + const vout_display_state_t *state, + const char *module, + mtime_t double_click_timeout, + mtime_t hide_timeout) +{ + return DisplayNew(vout, source, state, module, false, NULL, + double_click_timeout, hide_timeout, NULL); +} + +static void SplitterClose(vout_display_t *vd) +{ + assert(0); +} + +#if 0 +/***************************************************************************** + * + *****************************************************************************/ +struct vout_display_sys_t { + video_splitter_t *splitter; + + /* */ + int count; + picture_t **picture; + vout_display_t **display; +}; +struct video_splitter_owner_t { + vout_display_t *wrapper; +}; + +static vout_window_t *SplitterNewWindow(vout_display_t *vd, const vout_window_cfg_t *cfg_ptr) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + vout_window_cfg_t cfg = *cfg_ptr; + cfg.is_standalone = true; + cfg.x += 0;//output->window.i_x; FIXME + cfg.y += 0;//output->window.i_y; + + return vout_NewDisplayWindow(osys->vout, vd, &cfg); +} +static void SplitterDelWindow(vout_display_t *vd, vout_window_t *window) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + vout_DeleteDisplayWindow(osys->vout, vd, window); +} +static void SplitterEvent(vout_display_t *vd, int event, va_list args) +{ + vout_display_owner_sys_t *osys = vd->owner.sys; + + switch (event) { +#if 0 + case VOUT_DISPLAY_EVENT_MOUSE_STATE: + case VOUT_DISPLAY_EVENT_MOUSE_MOVED: + case VOUT_DISPLAY_EVENT_MOUSE_PRESSED: + case VOUT_DISPLAY_EVENT_MOUSE_RELEASED: + /* TODO */ + break; +#endif + case VOUT_DISPLAY_EVENT_MOUSE_DOUBLE_CLICK: + case VOUT_DISPLAY_EVENT_KEY: + case VOUT_DISPLAY_EVENT_CLOSE: + case VOUT_DISPLAY_EVENT_FULLSCREEN: + case VOUT_DISPLAY_EVENT_DISPLAY_SIZE: + case VOUT_DISPLAY_EVENT_PICTURES_INVALID: + VoutDisplayEvent(vd, event, args); + break; + + default: + msg_Err(vd, "SplitterEvent TODO"); + break; + } +} + +static picture_t *SplitterGet(vout_display_t *vd) +{ + /* TODO pool ? */ + return picture_NewFromFormat(&vd->fmt); +} +static void SplitterPrepare(vout_display_t *vd, picture_t *picture) +{ + vout_display_sys_t *sys = vd->sys; + + picture_Hold(picture); + + if (video_splitter_Filter(sys->splitter, sys->picture, picture)) { + for (int i = 0; i < sys->count; i++) + sys->picture[i] = NULL; + picture_Release(picture); + return; + } + + for (int i = 0; i < sys->count; i++) { + /* */ + sys->picture[i] = vout_FilterDisplay(sys->display[i], sys->picture[i]); + if (!sys->picture[i]) + continue; + + /* */ + picture_t *direct = vout_display_Get(sys->display[i]); + if (!direct) { + msg_Err(vd, "Failed to get a direct buffer"); + picture_Release(sys->picture[i]); + sys->picture[i] = NULL; + continue; + } + + /* FIXME not always needed (easy when there is a osys->filters) */ + picture_Copy(direct, sys->picture[i]); + picture_Release(sys->picture[i]); + sys->picture[i] = direct; + + vout_display_Prepare(sys->display[i], sys->picture[i]); + } +} +static void SplitterDisplay(vout_display_t *vd, picture_t *picture) +{ + vout_display_sys_t *sys = vd->sys; + + for (int i = 0; i < sys->count; i++) { + if (sys->picture[i]) + vout_display_Display(sys->display[i], sys->picture[i]); + } + picture_Release(picture); +} +static int SplitterControl(vout_display_t *vd, int query, va_list args) +{ + return VLC_EGENERIC; +} +static void SplitterManage(vout_display_t *vd) +{ + vout_display_sys_t *sys = vd->sys; + + for (int i = 0; i < sys->count; i++) + vout_ManageDisplay(sys->display[i]); +} + +static int SplitterPictureNew(video_splitter_t *splitter, picture_t *picture[]) +{ + vout_display_sys_t *wsys = splitter->p_owner->wrapper->sys; + + for (int i = 0; i < wsys->count; i++) { + /* TODO pool ? */ + picture[i] = picture_NewFromFormat(&wsys->display[i]->source); + if (!picture[i]) { + for (int j = 0; j < i; j++) + picture_Release(picture[j]); + return VLC_EGENERIC; + } + } + return VLC_SUCCESS; +} +static void SplitterPictureDel(video_splitter_t *splitter, picture_t *picture[]) +{ + vout_display_sys_t *wsys = splitter->p_owner->wrapper->sys; + + for (int i = 0; i < wsys->count; i++) + picture_Release(picture[i]); +} +static void SplitterClose(vout_display_t *vd) +{ + vout_display_sys_t *sys = vd->sys; + + /* */ + video_splitter_t *splitter = sys->splitter; + free(splitter->p_owner); + video_splitter_Delete(splitter); + + /* */ + for (int i = 0; i < sys->count; i++) + vout_DeleteDisplay(sys->display[i], NULL); + TAB_CLEAN(sys->count, sys->display); + free(sys->picture); + + free(sys); +} + +vout_display_t *vout_NewSplitter(vout_thread_t *vout, + const video_format_t *source, + const vout_display_state_t *state, + const char *module, + const char *splitter_module, + mtime_t double_click_timeout, + mtime_t hide_timeout) +{ + video_splitter_t *splitter = + video_splitter_New(VLC_OBJECT(vout), splitter_module, source); + if (!splitter) + return NULL; + + /* */ + vout_display_t *wrapper = + DisplayNew(vout, source, state, module, true, NULL, + double_click_timeout, hide_timeout, NULL); + if (!wrapper) { + video_splitter_Delete(splitter); + return NULL; + } + vout_display_sys_t *sys = malloc(sizeof(*sys)); + if (!sys) + abort(); + sys->picture = calloc(splitter->i_output, sizeof(*sys->picture)); + if (!sys->picture ) + abort(); + sys->splitter = splitter; + + wrapper->get = SplitterGet; + wrapper->prepare = SplitterPrepare; + wrapper->display = SplitterDisplay; + wrapper->control = SplitterControl; + wrapper->manage = SplitterManage; + wrapper->sys = sys; + + /* */ + video_splitter_owner_t *owner = malloc(sizeof(*owner)); + if (!owner) + abort(); + owner->wrapper = wrapper; + splitter->p_owner = owner; + splitter->pf_picture_new = SplitterPictureNew; + splitter->pf_picture_del = SplitterPictureDel; + + /* */ + TAB_INIT(sys->count, sys->display); + for (int i = 0; i < splitter->i_output; i++) { + vout_display_owner_t owner; + + owner.event = SplitterEvent; + owner.window_new = SplitterNewWindow; + owner.window_del = SplitterDelWindow; + + const video_splitter_output_t *output = &splitter->p_output[i]; + vout_display_state_t ostate; + + memset(&ostate, 0, sizeof(ostate)); + ostate.cfg.is_fullscreen = false; + ostate.cfg.display = state->cfg.display; + ostate.cfg.align.horizontal = 0; /* TODO */ + ostate.cfg.align.vertical = 0; /* TODO */ + ostate.cfg.is_display_filled = true; + ostate.cfg.zoom.num = 1; + ostate.cfg.zoom.den = 1; + + vout_display_t *vd = DisplayNew(vout, &output->fmt, &ostate, + output->psz_module ? output->psz_module : module, + false, wrapper, + double_click_timeout, hide_timeout, &owner); + if (!vd) { + vout_DeleteDisplay(wrapper, NULL); + return NULL; + } + TAB_APPEND(sys->count, sys->display, vd); + } + + return wrapper; +} +#endif + +/***************************************************************************** + * TODO move out + *****************************************************************************/ +#include "vout_internal.h" +void vout_SendDisplayEventMouse(vout_thread_t *vout, const vlc_mouse_t *m) +{ + if (vlc_mouse_HasMoved(&vout->p->mouse, m)) { + vout_SendEventMouseMoved(vout, m->i_x, m->i_y); + } + if (vlc_mouse_HasButton(&vout->p->mouse, m)) { + static const int buttons[] = { + MOUSE_BUTTON_LEFT, + MOUSE_BUTTON_CENTER, + MOUSE_BUTTON_RIGHT, + MOUSE_BUTTON_WHEEL_UP, + MOUSE_BUTTON_WHEEL_DOWN, + -1 + }; + for (int i = 0; buttons[i] >= 0; i++) { + const int button = buttons[i]; + if (vlc_mouse_HasPressed(&vout->p->mouse, m, button)) + vout_SendEventMousePressed(vout, button); + else if (vlc_mouse_HasReleased(&vout->p->mouse, m, button)) + vout_SendEventMouseReleased(vout, button); + } + } + if (m->b_double_click) + vout_SendEventMouseDoubleClick(vout); + vout->p->mouse = *m; +} +vout_window_t * vout_NewDisplayWindow(vout_thread_t *vout, vout_display_t *vd, const vout_window_cfg_t *cfg) +{ + vout_window_cfg_t cfg_override = *cfg; + + if( !config_GetInt( vout, "embedded-video" ) ) + cfg_override.is_standalone = true; + + return vout_window_New(VLC_OBJECT(vout), NULL, &cfg_override); +} +void vout_DeleteDisplayWindow(vout_thread_t *vout, vout_display_t *vd, vout_window_t *window) +{ + vout_window_Delete(window); +} + diff --git a/src/video_output/display.h b/src/video_output/display.h new file mode 100644 index 0000000000..16f7039917 --- /dev/null +++ b/src/video_output/display.h @@ -0,0 +1,116 @@ +/***************************************************************************** + * display.h: "vout display" managment + ***************************************************************************** + * Copyright (C) 2009 Laurent Aimar + * $Id$ + * + * Authors: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#if defined(__PLUGIN__) || defined(__BUILTIN__) || !defined(__LIBVLC__) +# error This header file can only be included from LibVLC. +#endif + +#include + +#if 0 +#include +#include + +/** + * It retreive a picture from the display + */ +static inline picture_t *vout_display_Get(vout_display_t *vd) +{ + return vd->get(vd); +} + +/** + * It preparse a picture for display. + */ +static inline void vout_display_Prepare(vout_display_t *vd, picture_t *picture) +{ + if (vd->prepare ) + vd->prepare(vd, picture); +} + +/** + * It display a picture. + */ +static inline void vout_display_Display(vout_display_t *vd, picture_t *picture) +{ + vd->display(vd, picture); +} + +/** + * It holds a state for a vout display. + */ +typedef struct { + vout_display_cfg_t cfg; + + bool is_on_top; + struct { + int num; + int den; + } sar; +} vout_display_state_t; + +/** + * It creates a vout managed display. + */ +vout_display_t *vout_NewDisplay( vout_thread_t *, + const video_format_t *, + const vout_display_state_t *, + const char *psz_module, + mtime_t i_double_click_timeout, + mtime_t i_hide_timeout ); +/** + * It creates a vout managed display wrapping a video splitter. + */ +vout_display_t *vout_NewSplitter(vout_thread_t *, + const video_format_t *source, + const vout_display_state_t *state, + const char *module, + const char *splitter, + mtime_t double_click_timeout, + mtime_t hide_timeout ); + +/** + * It destroy a vout managed display. + */ +void vout_DeleteDisplay(vout_display_t *, vout_display_state_t *); + +picture_t *vout_FilterDisplay(vout_display_t *, picture_t *); + +void vout_ManageDisplay(vout_display_t *); + +void vout_SetDisplayFullscreen(vout_display_t *, bool is_fullscreen); +void vout_SetDisplayFilled(vout_display_t *, bool is_filled); +void vout_SetDisplayZoom(vout_display_t *, int num, int den); +void vout_SetDisplayOnTop(vout_display_t *, bool is_on_top); +void vout_SetDisplayAspect(vout_display_t *, unsigned sar_num, unsigned sar_den); +void vout_SetDisplayCrop(vout_display_t *, + unsigned crop_num, unsigned crop_den, + unsigned x, unsigned y, unsigned width, unsigned height); + +#endif + +/* FIXME should not be there */ +void vout_SendDisplayEventMouse(vout_thread_t *, const vlc_mouse_t *); +vout_window_t *vout_NewDisplayWindow(vout_thread_t *, vout_display_t *, const vout_window_cfg_t *); +void vout_DeleteDisplayWindow(vout_thread_t *, vout_display_t *, vout_window_t *); + diff --git a/src/video_output/event.h b/src/video_output/event.h new file mode 100644 index 0000000000..1e7c8b40ee --- /dev/null +++ b/src/video_output/event.h @@ -0,0 +1,263 @@ +/***************************************************************************** + * event.h: vout event + ***************************************************************************** + * Copyright (C) 2009 Laurent Aimar + * $Id$ + * + * Authors: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#if defined(__PLUGIN__) || defined(__BUILTIN__) || !defined(__LIBVLC__) +# error This header file can only be included from LibVLC. +#endif + +#include +#include +#include + +#include "vout_control.h" + +/* TODO/FIXME + * + * It should be converted to something like input_thread_t: + * one intf-event can be grabbed by a callback, all others + * variable only var_Change + * + * Maybe a intf-mouse can be used too (don't like it). + * + * (Some case may infinite loop otherwise here) + */ + +static inline void vout_SendEventClose(vout_thread_t *vout) +{ + /* Ask to stop + * FIXME works only for input handled by the playlist + */ + playlist_t *playlist = pl_Hold(vout); + if (playlist) { + playlist_Stop(playlist); + pl_Release(vout); + } +} +static inline void vout_SendEventKey(vout_thread_t *vout, int key) +{ + var_SetInteger(vout->p_libvlc, "key-pressed", key); +} +static inline void vout_SendEventMouseMoved(vout_thread_t *vout, int x, int y) +{ + var_SetInteger(vout, "mouse-x", x); + var_SetInteger(vout, "mouse-y", y); + var_SetBool(vout, "mouse-moved", true); +} +static inline void vout_SendEventMousePressed(vout_thread_t *vout, int button) +{ + int current = var_GetInteger(vout, "mouse-button-down"); + current |= 1 << button; + var_SetInteger(vout, "mouse-button-down", current); + + switch (button) + { + case MOUSE_BUTTON_LEFT: + var_SetBool(vout, "mouse-clicked", true); + var_SetBool(vout->p_libvlc, "intf-popupmenu", false); + break; + case MOUSE_BUTTON_CENTER: + var_SetBool(vout->p_libvlc, "intf-show", + !var_GetBool(vout->p_libvlc, "intf-show")); + break; + case MOUSE_BUTTON_RIGHT: + var_SetBool(vout->p_libvlc, "intf-popupmenu", true); + break; + } +} +static inline void vout_SendEventMouseReleased(vout_thread_t *vout, int button) +{ + int current = var_GetInteger(vout, "mouse-button-down"); + current &= ~(1 << button); + var_SetInteger(vout, "mouse-button-down", current); +} +static inline void vout_SendEventMouseDoubleClick(vout_thread_t *vout) +{ + //vout_ControlSetFullscreen(vout, !var_GetBool(vout, "fullscreen")); + var_ToggleBool(vout, "fullscreen"); +} +static inline void vout_SendEventMouseVisible(vout_thread_t *vout) +{ + /* TODO */ + VLC_UNUSED(vout); +} +static inline void vout_SendEventMouseHidden(vout_thread_t *vout) +{ + /* TODO */ + VLC_UNUSED(vout); +} + +static inline void vout_SendEventFullscreen(vout_thread_t *vout, bool is_fullscreen) +{ + if (!var_GetBool(vout, "fullscreen") != !is_fullscreen) + var_SetBool(vout, "fullscreen", is_fullscreen); +} + +static inline void vout_SendEventDisplayFilled(vout_thread_t *vout, bool is_display_filled) +{ + if (!var_GetBool(vout, "autoscale") != !is_display_filled) + var_SetBool(vout, "autoscale", is_display_filled); +} + +static inline void vout_SendEventZoom(vout_thread_t *vout, int num, int den) +{ + /* FIXME deadlock problems with current vout */ +#if 0 + const float zoom = (float)num / (float)den; + + /* XXX 0.1% is arbitrary */ + if (fabs(zoom - var_GetFloat(vout, "scale")) > 0.001) + var_SetFloat(vout, "scale", zoom); +#endif +} + +static inline void vout_SendEventOnTop(vout_thread_t *vout, bool is_on_top) +{ + /* FIXME deadlock problems with current vout */ +#if 0 + + if (!var_GetBool(vout, "video-on-top") != !is_on_top) + var_SetBool(vout, "video-on-top", is_on_top); +#endif +} + +/** + * It must be called on source aspect ratio changes, with the new DAR (Display + * Aspect Ratio) value. + */ +static inline void vout_SendEventSourceAspect(vout_thread_t *vout, + unsigned num, unsigned den) +{ + /* FIXME the value stored in "aspect-ratio" are not reduced + * creating a lot of problems here */ +#if 0 + char *ar; + if (num > 0 && den > 0) { + if (asprintf(&ar, "%u:%u", num, den) < 0) + return; + } else { + ar = strdup(""); + } + + char *current = var_GetString(vout, "aspect-ratio"); + msg_Err(vout, "vout_SendEventSourceAspect %s -> %s", current, ar); + if (ar && current && strcmp(ar, current)) + var_SetString(vout, "aspect-ratio", ar); + + free(current); + free(ar); +#endif +} +static inline void vout_SendEventSourceCrop(vout_thread_t *vout, + unsigned num, unsigned den, + unsigned left, unsigned top, + unsigned right, unsigned bottom) +{ + vlc_value_t val; + + /* I cannot use var_Set here, infinite loop otherwise */ + + /* */ + val.i_int = left; + var_Change(vout, "crop-left", VLC_VAR_SETVALUE, &val, NULL); + val.i_int = top; + var_Change(vout, "crop-top", VLC_VAR_SETVALUE, &val, NULL); + val.i_int = right; + var_Change(vout, "crop-right", VLC_VAR_SETVALUE, &val, NULL); + val.i_int = bottom; + var_Change(vout, "crop-bottom", VLC_VAR_SETVALUE, &val, NULL); + + /* FIXME the value stored in "crop" are not reduced + * creating a lot of problems here */ +#if 0 + char *crop; + if (num > 0 && den > 0) { + if (asprintf(&crop, "%u:%u", num, den) < 0) + crop = NULL; + } else if (left > 0 || top > 0 || right > 0 || bottom > 0) { + if (asprintf(&crop, "%u+%u+%u+%u", left, top, right, bottom) < 0) + crop = NULL; + } else { + crop = strdup(""); + } + if (crop) { + val.psz_string = crop; + var_Change(vout, "crop", VLC_VAR_SETVALUE, &val, NULL); + free(crop); + } +#endif +} +#if 0 +static inline void vout_SendEventSnapshot(vout_thread_t *vout, const char *filename) +{ + /* Generate a media player event - Right now just trigger a global libvlc var + CHECK: Could not find a more local object. The goal is to communicate + vout_thread with libvlc_media_player or its input_thread */ + var_SetString(vout->p_libvlc, "vout-snapshottaken", filename); +} + +#warning "FIXME clean up postproc event" + +extern void vout_InstallDeprecatedPostProcessing(vout_thread_t *); +extern void vout_UninstallDeprecatedPostProcessing(vout_thread_t *); + +static inline void vout_SendEventPostProcessing(vout_thread_t *vout, bool is_available) +{ + if (is_available) + vout_InstallDeprecatedPostProcessing(vout); + else + vout_UninstallDeprecatedPostProcessing(vout); +} + +static inline void vout_SendEventFilters(vout_thread_t *vout) +{ + vout_filter_t **filter; + int filter_count; + + vout_ControlGetFilters(vout, &filter, &filter_count); + + char *list = strdup(""); + for (int i = 0; i < filter_count; i++) { + char *psz; + + if (asprintf(&psz, "%s%s%s", + list, i > 0 ? ":" : "", filter[i]->name) < 0) { + free(list); + list = NULL; + break; + } + free(list); + list = psz; + } + + if (list) { + vlc_value_t val; + val.psz_string = list; + var_Change(vout, "video-filter", VLC_VAR_SETVALUE, &val, NULL); + free(list); + } + + for (int i = 0; i < filter_count; i++) + vout_filter_Delete(filter[i]); + free(filter); +} +#endif diff --git a/src/video_output/video_output.c b/src/video_output/video_output.c index 7f9783b91d..76cfa30c5f 100644 --- a/src/video_output/video_output.c +++ b/src/video_output/video_output.c @@ -397,6 +397,8 @@ vout_thread_t * __vout_Create( vlc_object_t *p_parent, video_format_t *p_fmt ) p_vout->p->b_picture_empty = false; p_vout->p->i_picture_qtype = QTYPE_NONE; + vlc_mouse_Init( &p_vout->p->mouse ); + vout_snapshot_Init( &p_vout->p->snapshot ); /* Initialize locks */ diff --git a/src/video_output/vout_internal.h b/src/video_output/vout_internal.h index b22c81e195..1c3106efd4 100644 --- a/src/video_output/vout_internal.h +++ b/src/video_output/vout_internal.h @@ -97,6 +97,9 @@ struct vout_thread_sys_t int i_title_position; char *psz_title; + + /* */ + vlc_mouse_t mouse; }; /* DO NOT use vout_RenderPicture unless you are in src/video_ouput */