/***************************************************************************** * common.c: Windows video output common code ***************************************************************************** * Copyright (C) 2001-2009 VLC authors and VideoLAN * $Id$ * * Authors: Gildas Bazin * Martell Malone * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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: This file contains the functions related to the init of the vout * structure, the common display code, the screensaver, but not the * events and the Window Creation (events.c) *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #define COBJMACROS #include #define vout_display_sys_win32_t vout_display_sys_t #include "common.h" #include "../video_chroma/copy.h" #if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H) # include # include #endif static void CommonChangeThumbnailClip(vout_display_t *, bool show); #if !VLC_WINSTORE_APP static int CommonControlSetFullscreen(vout_display_t *, bool is_fullscreen); static bool GetWindowSize(const vout_display_sys_t *sys, UINT *w, UINT *h) { RECT out; if (!GetClientRect(sys->hwnd, &out)) return false; *w = out.right; // out.left is zero *h = out.bottom; // out.top is zero return true; } #endif static unsigned int GetPictureWidth(const vout_display_t *vd) { return vd->fmt.i_width; } static unsigned int GetPictureHeight(const vout_display_t *vd) { return vd->fmt.i_height; } /* */ int CommonInit(vout_display_t *vd) { vout_display_sys_t *sys = vd->sys; sys->hwnd = NULL; sys->hvideownd = NULL; sys->hparent = NULL; sys->hfswnd = NULL; sys->changes = 0; sys->is_first_display = true; sys->is_on_top = false; sys->pf_GetPictureWidth = GetPictureWidth; sys->pf_GetPictureHeight = GetPictureHeight; #if !VLC_WINSTORE_APP sys->pf_GetWindowSize = GetWindowSize; SetRectEmpty(&sys->rect_display); SetRectEmpty(&sys->rect_parent); var_Create(vd, "video-deco", VLC_VAR_BOOL | VLC_VAR_DOINHERIT); var_Create(vd, "disable-screensaver", VLC_VAR_BOOL | VLC_VAR_DOINHERIT); /* */ sys->event = EventThreadCreate(vd); if (!sys->event) return VLC_EGENERIC; #endif #if !VLC_WINSTORE_APP event_cfg_t cfg; memset(&cfg, 0, sizeof(cfg)); #ifdef MODULE_NAME_IS_direct3d9 cfg.use_desktop = sys->use_desktop; #endif #ifdef MODULE_NAME_IS_directdraw cfg.use_overlay = sys->use_overlay; #endif cfg.x = var_InheritInteger(vd, "video-x"); cfg.y = var_InheritInteger(vd, "video-y"); cfg.width = vd->cfg->display.width; cfg.height = vd->cfg->display.height; sys->src_fmt = &vd->source; event_hwnd_t hwnd; if (EventThreadStart(sys->event, &hwnd, &cfg)) return VLC_EGENERIC; sys->parent_window = hwnd.parent_window; sys->hparent = hwnd.hparent; sys->hwnd = hwnd.hwnd; sys->hvideownd = hwnd.hvideownd; sys->hfswnd = hwnd.hfswnd; if (vd->cfg->is_fullscreen) { if (CommonControlSetFullscreen(vd, true)) vout_display_SendEventFullscreen(vd, false, false); } #endif #if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H) sys->dxgidebug_dll = LoadLibrary(TEXT("DXGIDEBUG.DLL")); #endif return VLC_SUCCESS; } /* */ picture_pool_t *CommonPool(vout_display_t *vd, unsigned count) { VLC_UNUSED(count); return vd->sys->pool; } /***************************************************************************** * UpdateRects: update clipping rectangles ***************************************************************************** * This function is called when the window position or size are changed, and * its job is to update the source and destination RECTs used to display the * picture. *****************************************************************************/ void UpdateRects(vout_display_t *vd, const vout_display_cfg_t *cfg, bool is_forced) { vout_display_sys_t *sys = vd->sys; const video_format_t *source = sys->src_fmt; #define rect_src sys->rect_src #define rect_src_clipped sys->rect_src_clipped #define rect_dest sys->rect_dest #define rect_dest_clipped sys->rect_dest_clipped POINT point; /* */ if (!cfg) cfg = vd->cfg; /* Retrieve the window size */ UINT window_width, window_height; if (!sys->pf_GetWindowSize(sys, &window_width, &window_height)) return; /* Retrieve the window position */ point.x = point.y = 0; #if !VLC_WINSTORE_APP if (likely(sys->hwnd)) // internal rendering ClientToScreen(sys->hwnd, &point); #endif /* If nothing changed, we can return */ bool has_moved; bool is_resized; if (unlikely(!sys->event)) // external rendering { has_moved = false; is_resized = window_width != (UINT)(sys->rect_display.right - sys->rect_display.left) || window_height != (UINT)(sys->rect_display.bottom - sys->rect_display.top); sys->rect_display.left = 0; sys->rect_display.top = 0; sys->rect_display.right = window_width; sys->rect_display.bottom = window_height; } #if !VLC_WINSTORE_APP else { EventThreadUpdateWindowPosition(sys->event, &has_moved, &is_resized, point.x, point.y, window_width, window_height); } #endif if (is_resized) vout_display_SendEventDisplaySize(vd, window_width, window_height); if (!is_forced && !has_moved && !is_resized) return; /* Update the window position and size */ vout_display_cfg_t place_cfg = *cfg; place_cfg.display.width = window_width; place_cfg.display.height = window_height; #if (defined(MODULE_NAME_IS_glwin32)) /* Reverse vertical alignment as the GL tex are Y inverted */ if (place_cfg.align.vertical == VOUT_DISPLAY_ALIGN_TOP) place_cfg.align.vertical = VOUT_DISPLAY_ALIGN_BOTTOM; else if (place_cfg.align.vertical == VOUT_DISPLAY_ALIGN_BOTTOM) place_cfg.align.vertical = VOUT_DISPLAY_ALIGN_TOP; #endif vout_display_place_t place; vout_display_PlacePicture(&place, source, &place_cfg, false); #if !VLC_WINSTORE_APP if (likely(sys->event)) // internal rendering { EventThreadUpdateSourceAndPlace(sys->event, source, &place); if (sys->hvideownd) SetWindowPos(sys->hvideownd, 0, place.x, place.y, place.width, place.height, SWP_NOCOPYBITS | SWP_NOZORDER | SWP_ASYNCWINDOWPOS); } #endif /* Destination image position and dimensions */ #if (defined(MODULE_NAME_IS_direct3d9) || defined(MODULE_NAME_IS_direct3d11)) if (unlikely(!sys->event)) // external rendering { rect_dest.left = place.x; rect_dest.right = rect_dest.left + place.width; rect_dest.top = place.y; rect_dest.bottom = rect_dest.top + place.height; } else { rect_dest.left = 0; rect_dest.right = place.width; rect_dest.top = 0; rect_dest.bottom = place.height; } #else rect_dest.left = point.x + place.x; rect_dest.right = rect_dest.left + place.width; rect_dest.top = point.y + place.y; rect_dest.bottom = rect_dest.top + place.height; #ifdef MODULE_NAME_IS_directdraw /* Apply overlay hardware constraints */ if (sys->use_overlay) AlignRect(&rect_dest, sys->i_align_dest_boundary, sys->i_align_dest_size); #endif #endif #if defined(MODULE_NAME_IS_directdraw) /* UpdateOverlay directdraw function doesn't automatically clip to the * display size so we need to do it otherwise it will fail */ /* Clip the destination window */ if (!IntersectRect(&rect_dest_clipped, &rect_dest, &sys->rect_display)) { SetRectEmpty(&rect_src_clipped); goto exit; } #ifndef NDEBUG msg_Dbg(vd, "DirectXUpdateRects image_dst_clipped coords:" " %li,%li,%li,%li", rect_dest_clipped.left, rect_dest_clipped.top, rect_dest_clipped.right, rect_dest_clipped.bottom); #endif #else /* AFAIK, there are no clipping constraints in Direct3D, OpenGL and GDI */ rect_dest_clipped = rect_dest; #endif /* the 2 following lines are to fix a bug when clicking on the desktop */ if ((rect_dest_clipped.right - rect_dest_clipped.left) == 0 || (rect_dest_clipped.bottom - rect_dest_clipped.top) == 0) { rect_src_clipped = (RECT){0, 0, 0, 0}; goto exit; } /* src image dimensions */ rect_src.left = 0; rect_src.top = 0; rect_src.right = sys->pf_GetPictureWidth(vd); rect_src.bottom = sys->pf_GetPictureHeight(vd); /* Clip the source image */ rect_src_clipped.left = source->i_x_offset + (rect_dest_clipped.left - rect_dest.left) * source->i_visible_width / (rect_dest.right - rect_dest.left); rect_src_clipped.right = source->i_x_offset + source->i_visible_width - (rect_dest.right - rect_dest_clipped.right) * source->i_visible_width / (rect_dest.right - rect_dest.left); rect_src_clipped.top = source->i_y_offset + (rect_dest_clipped.top - rect_dest.top) * source->i_visible_height / (rect_dest.bottom - rect_dest.top); rect_src_clipped.bottom = source->i_y_offset + source->i_visible_height - (rect_dest.bottom - rect_dest_clipped.bottom) * source->i_visible_height / (rect_dest.bottom - rect_dest.top); #ifdef MODULE_NAME_IS_directdraw /* Apply overlay hardware constraints */ if (sys->use_overlay) AlignRect(&rect_src_clipped, sys->i_align_src_boundary, sys->i_align_src_size); #endif #ifndef NDEBUG msg_Dbg(vd, "DirectXUpdateRects source" " offset: %i,%i visible: %ix%i", source->i_x_offset, source->i_y_offset, source->i_visible_width, source->i_visible_height); msg_Dbg(vd, "DirectXUpdateRects image_src" " coords: %li,%li,%li,%li", rect_src.left, rect_src.top, rect_src.right, rect_src.bottom); msg_Dbg(vd, "DirectXUpdateRects image_src_clipped" " coords: %li,%li,%li,%li", rect_src_clipped.left, rect_src_clipped.top, rect_src_clipped.right, rect_src_clipped.bottom); msg_Dbg(vd, "DirectXUpdateRects image_dst" " coords: %li,%li,%li,%li", rect_dest.left, rect_dest.top, rect_dest.right, rect_dest.bottom); msg_Dbg(vd, "DirectXUpdateRects image_dst_clipped" " coords: %li,%li,%li,%li", rect_dest_clipped.left, rect_dest_clipped.top, rect_dest_clipped.right, rect_dest_clipped.bottom); #endif #ifdef MODULE_NAME_IS_directdraw /* The destination coordinates need to be relative to the current * directdraw primary surface (display) */ rect_dest_clipped.left -= sys->rect_display.left; rect_dest_clipped.right -= sys->rect_display.left; rect_dest_clipped.top -= sys->rect_display.top; rect_dest_clipped.bottom -= sys->rect_display.top; #endif CommonChangeThumbnailClip(vd, true); exit: /* Signal the change in size/position */ sys->changes |= DX_POSITION_CHANGE; #undef rect_src #undef rect_src_clipped #undef rect_dest #undef rect_dest_clipped } #if !VLC_WINSTORE_APP /* */ void CommonClean(vout_display_t *vd) { vout_display_sys_t *sys = vd->sys; if (sys->event) { CommonChangeThumbnailClip(vd, false); EventThreadStop(sys->event); EventThreadDestroy(sys->event); } #if !defined(NDEBUG) && defined(HAVE_DXGIDEBUG_H) HRESULT (WINAPI * pf_DXGIGetDebugInterface)(const GUID *riid, void **ppDebug); if (sys->dxgidebug_dll) { pf_DXGIGetDebugInterface = (void *)GetProcAddress(sys->dxgidebug_dll, "DXGIGetDebugInterface"); if (pf_DXGIGetDebugInterface) { IDXGIDebug *pDXGIDebug = NULL; HRESULT hr = pf_DXGIGetDebugInterface(&IID_IDXGIDebug, (void**)&pDXGIDebug); if (SUCCEEDED(hr) && pDXGIDebug) { hr = IDXGIDebug_ReportLiveObjects(pDXGIDebug, DXGI_DEBUG_ALL, DXGI_DEBUG_RLO_ALL); IDXGIDebug_Release(pDXGIDebug); } } FreeLibrary(sys->dxgidebug_dll); } #endif } void CommonManage(vout_display_t *vd) { vout_display_sys_t *sys = vd->sys; /* We used to call the Win32 PeekMessage function here to read the window * messages. But since window can stay blocked into this function for a * long time (for example when you move your window on the screen), I * decided to isolate PeekMessage in another thread. */ /* If we do not control our window, we check for geometry changes * ourselves because the parent might not send us its events. */ if (sys->hparent) { RECT rect_parent; POINT point; /* Check if the parent window has resized or moved */ GetClientRect(sys->hparent, &rect_parent); point.x = point.y = 0; ClientToScreen(sys->hparent, &point); OffsetRect(&rect_parent, point.x, point.y); if (!EqualRect(&rect_parent, &sys->rect_parent)) { sys->rect_parent = rect_parent; /* This code deals with both resize and move * * For most drivers(direct3d9, gdi, opengl), move is never * an issue. The surface automatically gets moved together * with the associated window (hvideownd) * * For directdraw, it is still important to call UpdateRects * on a move of the parent window, even if no resize occurred */ SetWindowPos(sys->hwnd, 0, 0, 0, rect_parent.right - rect_parent.left, rect_parent.bottom - rect_parent.top, SWP_NOZORDER); UpdateRects(vd, NULL, true); } } /* HasMoved means here resize or move */ if (!sys->event || EventThreadGetAndResetHasMoved(sys->event)) UpdateRects(vd, NULL, false); } /** * It ensures that the video window is shown after the first picture * is displayed. */ void CommonDisplay(vout_display_t *vd) { vout_display_sys_t *sys = vd->sys; if (!sys->is_first_display) return; /* Video window is initially hidden, show it now since we got a * picture to show. */ SetWindowPos(sys->hvideownd, 0, 0, 0, 0, 0, SWP_ASYNCWINDOWPOS| SWP_FRAMECHANGED| SWP_SHOWWINDOW| SWP_NOMOVE| SWP_NOSIZE| SWP_NOZORDER); sys->is_first_display = false; } #endif /** * It updates a picture data/pitches. */ int CommonUpdatePicture(picture_t *picture, picture_t **fallback, uint8_t *data, unsigned pitch) { if (fallback) { if (*fallback == NULL) { *fallback = picture_NewFromFormat(&picture->format); if (*fallback == NULL) return VLC_EGENERIC; } for (int n = 0; n < picture->i_planes; n++) { const plane_t *src = &(*fallback)->p[n]; plane_t *dst = &picture->p[n]; dst->p_pixels = src->p_pixels; dst->i_pitch = src->i_pitch; dst->i_lines = src->i_lines; } return VLC_SUCCESS; } return picture_UpdatePlanes(picture, data, pitch); } void AlignRect(RECT *r, int align_boundary, int align_size) { if (align_boundary) r->left = (r->left + align_boundary/2) & ~align_boundary; if (align_size) r->right = ((r->right - r->left + align_size/2) & ~align_size) + r->left; } #if !VLC_WINSTORE_APP /* */ static void CommonChangeThumbnailClip(vout_display_t *vd, bool show) { vout_display_sys_t *sys = vd->sys; /* Windows 7 taskbar thumbnail code */ OSVERSIONINFO winVer; winVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (!GetVersionEx(&winVer) || winVer.dwMajorVersion <= 5) return; if( FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED)) ) vlc_assert_unreachable(); void *ptr; if (S_OK == CoCreateInstance(&CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, &IID_ITaskbarList3, &ptr)) { ITaskbarList3 *taskbl = ptr; taskbl->lpVtbl->HrInit(taskbl); HWND hroot = GetAncestor(sys->hwnd,GA_ROOT); RECT video; if (show) { GetWindowRect(sys->hparent, &video); POINT client = {video.left, video.top}; if (ScreenToClient(hroot, &client)) { unsigned int width = video.right - video.left; unsigned int height = video.bottom - video.top; video.left = client.x; video.top = client.y; video.right = video.left + width; video.bottom = video.top + height; } } HRESULT hr; hr = taskbl->lpVtbl->SetThumbnailClip(taskbl, hroot, show ? &video : NULL); if ( hr != S_OK ) msg_Err(vd, "SetThumbNailClip failed: 0x%0lx", hr); taskbl->lpVtbl->Release(taskbl); } CoUninitialize(); } static int CommonControlSetFullscreen(vout_display_t *vd, bool is_fullscreen) { vout_display_sys_t *sys = vd->sys; #ifdef MODULE_NAME_IS_direct3d9 if (sys->use_desktop && is_fullscreen) return VLC_EGENERIC; #endif /* */ if (sys->parent_window) return VLC_EGENERIC; /* */ HWND hwnd = sys->hparent && sys->hfswnd ? sys->hfswnd : sys->hwnd; /* Save the current windows placement/placement to restore when fullscreen is over */ WINDOWPLACEMENT window_placement; window_placement.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(hwnd, &window_placement); if (is_fullscreen) { msg_Dbg(vd, "entering fullscreen mode"); /* Change window style, no borders and no title bar */ SetWindowLong(hwnd, GWL_STYLE, WS_CLIPCHILDREN | WS_VISIBLE); if (sys->hparent) { /* Retrieve current window position so fullscreen will happen *on the right screen */ HMONITOR hmon = MonitorFromWindow(sys->hparent, MONITOR_DEFAULTTONEAREST); MONITORINFO mi; mi.cbSize = sizeof(MONITORINFO); if (GetMonitorInfo(hmon, &mi)) SetWindowPos(hwnd, 0, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top, SWP_NOZORDER|SWP_FRAMECHANGED); } else { /* Maximize non embedded window */ ShowWindow(hwnd, SW_SHOWMAXIMIZED); } if (sys->hparent) { /* Hide the previous window */ RECT rect; GetClientRect(hwnd, &rect); SetParent(sys->hwnd, hwnd); SetWindowPos(sys->hwnd, 0, 0, 0, rect.right, rect.bottom, SWP_NOZORDER|SWP_FRAMECHANGED); HWND topLevelParent = GetAncestor(sys->hparent, GA_ROOT); ShowWindow(topLevelParent, SW_HIDE); } SetForegroundWindow(hwnd); } else { msg_Dbg(vd, "leaving fullscreen mode"); /* Change window style, no borders and no title bar */ SetWindowLong(hwnd, GWL_STYLE, EventThreadGetWindowStyle(sys->event)); if (sys->hparent) { RECT rect; GetClientRect(sys->hparent, &rect); SetParent(sys->hwnd, sys->hparent); SetWindowPos(sys->hwnd, 0, 0, 0, rect.right, rect.bottom, SWP_NOZORDER|SWP_FRAMECHANGED); HWND topLevelParent = GetAncestor(sys->hparent, GA_ROOT); ShowWindow(topLevelParent, SW_SHOW); SetForegroundWindow(sys->hparent); ShowWindow(hwnd, SW_HIDE); } else { /* return to normal window for non embedded vout */ SetWindowPlacement(hwnd, &window_placement); ShowWindow(hwnd, SW_SHOWNORMAL); } } return VLC_SUCCESS; } #else void CommonManage(vout_display_t *vd) { UpdateRects(vd, NULL, false); } void CommonClean(vout_display_t *vd) {} void CommonDisplay(vout_display_t *vd) {} void CommonChangeThumbnailClip(vout_display_t *vd, bool show) {} #endif int CommonControl(vout_display_t *vd, int query, va_list args) { vout_display_sys_t *sys = vd->sys; switch (query) { case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED: /* const vout_display_cfg_t *p_cfg */ case VOUT_DISPLAY_CHANGE_ZOOM: /* const vout_display_cfg_t *p_cfg */ case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT: case VOUT_DISPLAY_CHANGE_SOURCE_CROP: { const vout_display_cfg_t *cfg; if (query == VOUT_DISPLAY_CHANGE_SOURCE_CROP || query == VOUT_DISPLAY_CHANGE_SOURCE_ASPECT) { cfg = vd->cfg; } else { cfg = va_arg(args, const vout_display_cfg_t *); } UpdateRects(vd, cfg, true); return VLC_SUCCESS; } #if !VLC_WINSTORE_APP case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE: /* const vout_display_cfg_t *p_cfg */ { /* Update dimensions */ if (unlikely(!sys->event)) // external rendering return VLC_EGENERIC; const vout_display_cfg_t *cfg = va_arg(args, const vout_display_cfg_t *); RECT rect_window = { .top = 0, .left = 0, .right = cfg->display.width, .bottom = cfg->display.height, }; if (!cfg->is_fullscreen) { AdjustWindowRect(&rect_window, EventThreadGetWindowStyle(sys->event), 0); SetWindowPos(sys->hwnd, 0, 0, 0, rect_window.right - rect_window.left, rect_window.bottom - rect_window.top, SWP_NOMOVE); } UpdateRects(vd, cfg, false); return VLC_SUCCESS; } case VOUT_DISPLAY_CHANGE_WINDOW_STATE: { /* unsigned state */ if (unlikely(!sys->event)) // external rendering return VLC_EGENERIC; const unsigned state = va_arg(args, unsigned); const bool is_on_top = (state & VOUT_WINDOW_STATE_ABOVE) != 0; #ifdef MODULE_NAME_IS_direct3d9 if (sys->use_desktop && is_on_top) return VLC_EGENERIC; #endif HMENU hMenu = GetSystemMenu(sys->hwnd, FALSE); if (is_on_top && !(GetWindowLong(sys->hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST)) { CheckMenuItem(hMenu, IDM_TOGGLE_ON_TOP, MF_BYCOMMAND | MFS_CHECKED); SetWindowPos(sys->hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); } else if (!is_on_top && (GetWindowLong(sys->hwnd, GWL_EXSTYLE) & WS_EX_TOPMOST)) { CheckMenuItem(hMenu, IDM_TOGGLE_ON_TOP, MF_BYCOMMAND | MFS_UNCHECKED); SetWindowPos(sys->hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE); } sys->is_on_top = is_on_top; return VLC_SUCCESS; } case VOUT_DISPLAY_CHANGE_FULLSCREEN: { if (unlikely(!sys->event)) // external rendering return VLC_EGENERIC; bool fs = va_arg(args, int); if (CommonControlSetFullscreen(vd, fs)) return VLC_EGENERIC; UpdateRects(vd, NULL, false); return VLC_SUCCESS; } case VOUT_DISPLAY_RESET_PICTURES: vlc_assert_unreachable(); #endif default: return VLC_EGENERIC; } }