/* * Copyright (C) 2010-2011 Mamadou Diop. * * Contact: Mamadou Diop * * This file is part of Open Source Doubango Framework. * * DOUBANGO 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 3 of the License, or * (at your option) any later version. * * DOUBANGO 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 DOUBANGO. * */ #include "internals/DSDisplay.h" #include "internals/DSUtils.h" #include "tsk_list.h" #include "tsk_debug.h" #include using namespace std; #define USE_OVERLAY 0 #define OVERLAY_TIMEOUT 3 #define WM_GRAPHNOTIFY WM_APP + 1 #define FSCREEN_MIN_IDEAL_WIDTH 352 #define FSCREEN_MIN_IDEAL_HEIGHT 288 typedef struct tdshow_display_s { TSK_DECLARE_OBJECT; HWND hwnd; DSDisplay* display; } tdshow_display_t; typedef tsk_list_t tdshow_displays_L_t; const tsk_object_def_t *tdshow_display_def_t; // Static list to find which display is link to a given hWnd static tdshow_displays_L_t* __directshow__Displays = tsk_null; /*== Predicate function to find tdshow_display_t object by HWND. */ static int __pred_find_display_by_hwnd(const tsk_list_item_t *item, const void *hWnd) { if(item && item->data){ const tdshow_display_t *display = (const tdshow_display_t *)item->data; int ret = 0; tsk_subsat_int32_ptr(display->hwnd, *((HWND*)hWnd), &ret); return ret; } return -1; } // C Callback that dispatch event to the right display static LRESULT CALLBACK __directshow__WndProcWindow(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT result = FALSE; BOOL resultSet = FALSE; if(__directshow__Displays){ tsk_list_lock(__directshow__Displays); const tdshow_display_t *display = (const tdshow_display_t *)tsk_list_find_object_by_pred(__directshow__Displays, __pred_find_display_by_hwnd, &hWnd); if((resultSet = (display && display->display))){ result = display->display->handleEvents(hWnd, uMsg, wParam, lParam); } tsk_list_unlock(__directshow__Displays); } return resultSet ? result : DefWindowProc(hWnd, uMsg, wParam, lParam); } DSDisplay::DSDisplay(HRESULT *hr) { this->window = NULL; this->parentWindowProc = NULL; this->hooked = false; this->fullscreen = false; this->bPluginFirefox = false; this->top = 0; this->left = 0; this->width = this->imgWidth = 176; this->height = this->imgHeight = 144; this->fps = 15; this->graph = new DSDisplayGraph(hr); if (FAILED(*hr)) return; #if USE_OVERLAY this->overlay = new DSDisplayOverlay(); #else this->overlay = NULL; #endif this->graph->getVideoWindow()->put_Visible(OAFALSE); } DSDisplay::~DSDisplay() { this->unhook(); SAFE_DELETE_PTR(this->overlay); SAFE_DELETE_PTR(this->graph); } void DSDisplay::start() { if (!this->graph->isRunning()){ this->hook(); } if (!this->graph->isRunning() || this->graph->isPaused()){ this->graph->start(); } this->graph->getVideoWindow()->put_Visible(OATRUE); } void DSDisplay::pause() { this->graph->pause(); } void DSDisplay::stop() { if (this->graph->isRunning()){ this->setFullscreen(false); this->graph->stop(); this->unhook(); } } void DSDisplay::attach(INT64 parent) { this->attach((void*)parent); } void DSDisplay::attach(void *parent) { // Don't reattach if this is the same parent if (this->isAttached() && parent){ HWND hwnd = reinterpret_cast(parent); if (hwnd != this->window){ this->detach(); } } // Gets the handle of the parent this->window = reinterpret_cast(parent); // Hook to the parent WindowProc this->hook(); #if USE_OVERLAY // Allows the overlay to initialize this->overlay->attach(this->window, this->graph); #endif } void DSDisplay::detach(void *parent) { // The detach action is only valid and if this is the same parent if (parent){ HWND hwnd = reinterpret_cast(parent); if (hwnd == this->window){ this->detach(); } } } void DSDisplay::detach() { if (!this->isAttached()){ return; } #if USE_OVERLAY // Clean up overlay this->overlay->detach(); #endif // Unhook from the parent WindowProc this->unhook(); // Set the handle of the parent to NULL this->window = NULL; } bool DSDisplay::isAttached() { return (this->window != NULL); } int DSDisplay::getWidth() { return this->width; } int DSDisplay::getHeight() { return this->height; } void DSDisplay::setSize(int w, int h) { //this->width = w; //this->height = h; if (!this->fullscreen){ this->graph->setImageFormat(w, h); if(this->hooked){ #if 0 #if defined(VMR9_WINDOWLESS) RECT rc; SetRect(&rc, 0, 0, w, h); this->graph->getWindowlessControl()->SetVideoPosition(&rc, &rc); #else this->graph->getVideoWindow()->SetWindowPosition(0, 0, this->width , this->height); #endif #endif } } } void DSDisplay::applyRatio(RECT rect) { long w = rect.right - rect.left; long h = rect.bottom - rect.top; float ratio = ((float)this->imgWidth/(float)this->imgHeight); // (w/h)=ratio => // 1) h=w/ratio // and // 2) w=h*ratio this->width = (int)(w/ratio) > h ? (int)(h * ratio) : w; this->height = (int)(this->width/ratio) > h ? h : (int)(this->width/ratio); this->left = ((w - this->width) >> 1); this->top = ((h - this->height) >> 1); } bool DSDisplay::isFullscreen() { #if defined(VMR9_WINDOWLESS) // TODO #else long result; HRESULT hr = this->graph->getVideoWindow()->get_FullScreenMode(&result); if (SUCCEEDED(hr)){ this->fullscreen = (result == OATRUE); } else{ TSK_DEBUG_ERROR("get_FullScreenMode failed with %ld", hr); this->fullscreen = FALSE; } #endif return this->fullscreen; } void DSDisplay::setFullscreen(bool value) { if(!this->canFullscreen()){ TSK_DEBUG_WARN("Cannot fullscreen"); return; } HRESULT hr; #if defined(VMR9_WINDOWLESS) // TODO #else if (this->isFullscreen() == value){ return; } hr = this->graph->getVideoWindow()->put_FullScreenMode(value ? OATRUE : OAFALSE); if (SUCCEEDED(hr)){ this->fullscreen = value; #if USE_OVERLAY this->overlay->show(this->fullscreen ? (OVERLAY_TIMEOUT * this->graph->getDisplayFps()) : 0); #endif } else{ TSK_DEBUG_ERROR("put_FullScreenMode failed with %ld", hr); } #endif } void DSDisplay::setPluginFirefox(bool value) { bPluginFirefox = value; } bool DSDisplay::canFullscreen() { #if defined(VMR9_WINDOWLESS) // TODO #else if(this->graph){ UINT image_w, image_h; if( this->graph->getImageFormat(image_w, image_h) ){ //this->graph->getVideoWindow()->GetMinIdealImageSize(&ideal_w, &ideal_h); return (((long)image_w >= FSCREEN_MIN_IDEAL_WIDTH) && ((long)image_h >= FSCREEN_MIN_IDEAL_HEIGHT)); } } #endif return false; } void DSDisplay::setFps(int fps_) { this->fps = fps_; this->graph->setDisplayFps(fps_); } // w and h are the size of the buffer not the display void DSDisplay::handleVideoFrame(const void* data, int w, int h) { if (this->graph->isRunning()){ // The graph will take care of changing the source filter if needed // in case of dimension change or anything else... this->graph->handleFrame(data, w, h); if(this->imgWidth != w || this->imgHeight != h){ this->imgWidth = w; this->imgHeight = h; if(this->window){ SendMessage(this->window, WM_SIZE, SIZE_RESTORED, MAKELPARAM(this->width , this->height)); } } #if USE_OVERLAY this->overlay->update(); #endif } } LRESULT DSDisplay::handleEvents(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_CREATE: case WM_SIZE: case WM_MOVE: { RECT rect = {0}; GetWindowRect(hWnd, &rect); applyRatio(rect); #if defined(VMR9_WINDOWLESS) this->graph->getWindowlessControl()->SetVideoPosition(&rect, &rect); #else this->graph->getVideoWindow()->SetWindowPosition(this->left, this->top, this->width , this->height); #endif } break; case WM_LBUTTONDBLCLK: if(this->canFullscreen()){ this->setFullscreen(true); } break; case WM_FULLSCREEN_SET: if(this->canFullscreen()){ this->setFullscreen(!this->isFullscreen()); } break; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_KEYDOWN: if(this->isFullscreen()) { #if USE_OVERLAY // Re-Show overlay this->overlay->show(OVERLAY_TIMEOUT * this->graph->getDisplayFps()); #endif } break; case WM_CHAR: case WM_KEYUP: if(this->isFullscreen() && (wParam == 0x1B || wParam == VK_ESCAPE)) { // escape this->setFullscreen(false); } break; case WM_GRAPHNOTIFY: { long evCode; LONG_PTR param1, param2; HRESULT hr; while (hr = this->graph->getMediaEvent()->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr)) { hr = this->graph->getMediaEvent()->FreeEventParams(evCode, param1, param2); switch(evCode) { case EC_FULLSCREEN_LOST: #if USE_OVERLAY this->overlay->show(0); #endif break; case EC_COMPLETE: case EC_USERABORT: default: break; } } } break; #if defined(VMR9_WINDOWLESS) case WM_DISPLAYCHANGE: { this->graph->getWindowlessControl()->DisplayModeChanged(); } break; case WM_PAINT: { RECT rect = {0}; GetWindowRect(hWnd, &rect); PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); this->graph->getWindowlessControl()->RepaintVideo(hWnd, hdc); EndPaint(hWnd, &ps); } break; #endif } return bPluginFirefox ? DefWindowProc(hWnd, uMsg, wParam, lParam) : CallWindowProc(this->parentWindowProc, hWnd, uMsg, wParam, lParam); } void DSDisplay::hook() { HRESULT hr; if (!this->window){ return; } if(this->hooked){ return; } this->hooked = TRUE; bool lock = (__directshow__Displays != NULL); if(lock) tsk_list_lock(__directshow__Displays); { // Gets the parent Window procedure #if defined(_WIN32_WCE) // Workaround for bug in SetWindowLong, call twice the API //this->parentWindowProc = (WNDPROC)SetWindowLong( this->window, GWL_WNDPROC, (LONG) __directshow__WndProcWindow ); //this->parentWindowProc = (WNDPROC)SetWindowLong( this->window, GWL_WNDPROC, (LONG) __directshow__WndProcWindow ); //__directshow__Displays[this->window] = this; #else this->parentWindowProc = (WNDPROC) SetWindowLongPtr(this->window, GWLP_WNDPROC, (LONG_PTR) __directshow__WndProcWindow); // Add this instance to the callback map tsk_object_new(tdshow_display_def_t, this->window, this); #endif } if(lock) tsk_list_unlock(__directshow__Displays); RECT rect; GetWindowRect(this->window, &rect); applyRatio(rect); #if defined(VMR9_WINDOWLESS) rect.left = 0; rect.top = 0; rect.right = this->width; rect.bottom = this->height; // TODO : Review hr = this->graph->getWindowlessControl()->SetVideoClippingWindow(this->window); hr = this->graph->getWindowlessControl()->SetBorderColor(RGB(0, 0, 128)); hr = this->graph->getWindowlessControl()->SetVideoPosition(NULL, &rect); #else // TODO : Review the order hr = this->graph->getVideoWindow()->put_Owner((OAHWND) this->window); hr = this->graph->getVideoWindow()->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); hr = this->graph->getVideoWindow()->SetWindowPosition(this->left, this->top, this->width, this->height); hr = this->graph->getVideoWindow()->put_MessageDrain((OAHWND) this->window); hr = this->graph->getVideoWindow()->put_Visible(OATRUE); #endif hr = this->graph->getMediaEvent()->SetNotifyWindow((OAHWND) this->window, WM_GRAPHNOTIFY, 0); } void DSDisplay::unhook() { HRESULT hr; if(!this->window){ return; } if(!this->hooked){ return; } hr = this->graph->getMediaEvent()->SetNotifyWindow(NULL, WM_GRAPHNOTIFY, 0); #if defined(VMR9_WINDOWLESS) // TODO : Review hr = this->graph->getWindowlessControl()->SetVideoClippingWindow(NULL); #else // TODO : Review the order hr = this->graph->getVideoWindow()->put_Visible(OAFALSE); hr = this->graph->getVideoWindow()->put_MessageDrain((OAHWND) NULL); hr = this->graph->getVideoWindow()->put_Owner((OAHWND) NULL); hr = this->graph->getVideoWindow()->put_AutoShow(OAFALSE); #endif bool lock = (__directshow__Displays != NULL); if(lock) tsk_list_lock(__directshow__Displays); { // Remove this instance from the callback map tsk_list_remove_item_by_pred(__directshow__Displays, __pred_find_display_by_hwnd, &this->window); // Restore parent Window procedure #if defined(_WIN32_WCE) // Workaround for bug in SetWindowLong, call twice the API //this->parentWindowProc = (WNDPROC)SetWindowLong( this->window, GWL_WNDPROC, (LONG) this->parentWindowProc ); //this->parentWindowProc = (WNDPROC)SetWindowLong( this->window, GWL_WNDPROC, (LONG) this->parentWindowProc ); #else SetWindowLongPtr(this->window, GWLP_WNDPROC, (LONG_PTR) this->parentWindowProc); #endif } if(lock) tsk_list_unlock(__directshow__Displays); this->hooked = FALSE; } //================================================================================================= // String object definition // static tsk_object_t* tdshow_display_ctor(tsk_object_t * self, va_list * app) { tdshow_display_t *display = (tdshow_display_t *)self; if(display){ display->hwnd = va_arg(*app, HWND); display->display = va_arg(*app, DSDisplay*); if(!__directshow__Displays){ __directshow__Displays = tsk_list_create(); } tsk_list_push_back_data(__directshow__Displays, (void**)&display); } return self; } static tsk_object_t* tdshow_display_dtor(tsk_object_t * self) { tdshow_display_t *display = (tdshow_display_t *)self; if(display){ if(__directshow__Displays){ tsk_list_remove_item_by_data(__directshow__Displays, display); //if(TSK_LIST_IS_EMPTY(__directshow__Displays)){ // TSK_OBJECT_SAFE_FREE(__directshow__Displays); //} } } return self; } static int tdshow_display_cmp(const tsk_object_t *_d1, const tsk_object_t *_d2) { const tdshow_display_t *d1 = (const tdshow_display_t *)_d1; const tdshow_display_t *d2 = (const tdshow_display_t *)_d2; if(d1 && d2){ int ret = 0; tsk_subsat_int32_ptr(d1->hwnd, d2->hwnd, &ret); return ret; } else if(!d1 && !d2) return 0; else return -1; } static const tsk_object_def_t tdshow_display_def_s = { sizeof(tdshow_display_t), tdshow_display_ctor, tdshow_display_dtor, tdshow_display_cmp, }; extern const tsk_object_def_t *tdshow_display_def_t = &tdshow_display_def_s;