gstreamer/subprojects/gst-plugins-bad/sys/d3d11/gstd3d11window_win32.cpp
Alexander Slobodeniuk f92c27a49e d3d11window_win32: fix crash on RC unprepare() vs window_proc()
Unprepare method posts WM_GST_D3D11_DESTROY_INTERNAL_WINDOW
command to the window queue, and from that moment considers
internal_hwnd to be released, and so it sets it to null.
The problem is that it's possible that right at that moment
the window thread might be already processing some other
command, or just another command might be already in the queue.
On practice we met a crash when WM_PAINT got processed in between
(unprepare already finished and WM_GST_D3D11_DESTROY_INTERNAL_WINDOW
was not handled yet)

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6187>
2024-02-26 23:17:05 +00:00

1331 lines
41 KiB
C++

/*
* GStreamer
* Copyright (C) 2008 Julien Isorce <julien.isorce@gmail.com>
* Copyright (C) 2012 Matthew Waters <ystreet00@gmail.com>
* Copyright (C) 2019 Seungha Yang <seungha.yang@navercorp.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstd3d11window_win32.h"
#include <wrl.h>
/* *INDENT-OFF* */
using namespace Microsoft::WRL;
/* *INDENT-ON* */
GST_DEBUG_CATEGORY_EXTERN (gst_d3d11_window_debug);
#define GST_CAT_DEFAULT gst_d3d11_window_debug
G_LOCK_DEFINE_STATIC (create_lock);
G_LOCK_DEFINE_STATIC (get_instance_lock);
#define EXTERNAL_PROC_PROP_NAME "d3d11_window_external_proc"
#define D3D11_WINDOW_PROP_NAME "gst_d3d11_window_win32_object"
#define WM_GST_D3D11_FULLSCREEN (WM_USER + 1)
#define WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW (WM_USER + 2)
#define WM_GST_D3D11_DESTROY_INTERNAL_WINDOW (WM_USER + 3)
#define WM_GST_D3D11_MOVE_WINDOW (WM_USER + 4)
#define WM_GST_D3D11_SHOW_WINDOW (WM_USER + 5)
#define WS_GST_D3D11 (WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW)
static LRESULT CALLBACK window_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam);
static LRESULT FAR PASCAL sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam,
LPARAM lParam);
typedef enum
{
GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE = 0,
GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED,
GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED,
} GstD3D11WindowWin32OverlayState;
struct _GstD3D11WindowWin32
{
GstD3D11Window parent;
SRWLOCK lock;
CONDITION_VARIABLE cond;
GMainContext *main_context;
GMainLoop *loop;
gboolean visible;
GSource *msg_source;
GIOChannel *msg_io_channel;
GThread *thread;
GThread *internal_hwnd_thread;
GRecMutex hwnds_lock;
HWND internal_hwnd;
HWND external_hwnd;
GstD3D11WindowWin32OverlayState overlay_state;
gboolean have_swapchain1;
/* atomic */
gint pending_fullscreen_count;
gint pending_move_window;
/* fullscreen related */
LONG restore_style;
WINDOWPLACEMENT restore_placement;
/* Handle set_render_rectangle */
GstVideoRectangle render_rect;
gboolean flushing;
gboolean setup_external_hwnd;
};
#define gst_d3d11_window_win32_parent_class parent_class
G_DEFINE_TYPE (GstD3D11WindowWin32, gst_d3d11_window_win32,
GST_TYPE_D3D11_WINDOW);
static void gst_d3d11_window_win32_constructed (GObject * object);
static void gst_d3d11_window_win32_finalize (GObject * object);
static void gst_d3d11_window_win32_show (GstD3D11Window * window);
static void gst_d3d11_window_win32_update_swap_chain (GstD3D11Window * window);
static void
gst_d3d11_window_win32_change_fullscreen_mode (GstD3D11Window * window);
static gboolean
gst_d3d11_window_win32_create_swap_chain (GstD3D11Window * window,
DXGI_FORMAT format, guint width, guint height,
guint swapchain_flags, IDXGISwapChain ** swap_chain);
static GstFlowReturn gst_d3d11_window_win32_present (GstD3D11Window * window,
guint present_flags);
static gpointer gst_d3d11_window_win32_thread_func (gpointer data);
static gboolean
gst_d3d11_window_win32_create_internal_window (GstD3D11WindowWin32 * self);
static void gst_d3d11_window_win32_destroy_internal_window (HWND hwnd);
static void
gst_d3d11_window_win32_release_external_handle (GstD3D11WindowWin32 * self);
static void
gst_d3d11_window_win32_on_resize (GstD3D11Window * window,
guint width, guint height);
static GstFlowReturn gst_d3d11_window_win32_prepare (GstD3D11Window * window,
guint display_width, guint display_height, GstCaps * caps,
GstStructure * config, DXGI_FORMAT display_format, GError ** error);
static void gst_d3d11_window_win32_unprepare (GstD3D11Window * window);
static void
gst_d3d11_window_win32_set_render_rectangle (GstD3D11Window * window,
const GstVideoRectangle * rect);
static void gst_d3d11_window_win32_set_title (GstD3D11Window * window,
const gchar * title);
static gboolean gst_d3d11_window_win32_unlock (GstD3D11Window * window);
static gboolean gst_d3d11_window_win32_unlock_stop (GstD3D11Window * window);
static GstFlowReturn
gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self,
HWND hwnd);
static void
gst_d3d11_window_win32_class_init (GstD3D11WindowWin32Class * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstD3D11WindowClass *window_class = GST_D3D11_WINDOW_CLASS (klass);
gobject_class->constructed = gst_d3d11_window_win32_constructed;
gobject_class->finalize = gst_d3d11_window_win32_finalize;
window_class->show = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_show);
window_class->update_swap_chain =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_update_swap_chain);
window_class->change_fullscreen_mode =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_change_fullscreen_mode);
window_class->create_swap_chain =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_create_swap_chain);
window_class->present = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_present);
window_class->on_resize =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_on_resize);
window_class->prepare = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_prepare);
window_class->unprepare =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unprepare);
window_class->set_render_rectangle =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_set_render_rectangle);
window_class->set_title =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_set_title);
window_class->unlock = GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unlock);
window_class->unlock_stop =
GST_DEBUG_FUNCPTR (gst_d3d11_window_win32_unlock_stop);
}
static void
gst_d3d11_window_win32_init (GstD3D11WindowWin32 * self)
{
self->main_context = g_main_context_new ();
self->restore_placement.length = sizeof (WINDOWPLACEMENT);
g_rec_mutex_init (&self->hwnds_lock);
}
static void
gst_d3d11_window_win32_constructed (GObject * object)
{
GstD3D11Window *window = GST_D3D11_WINDOW (object);
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (object);
if (window->external_handle) {
/* Will setup internal child window on ::prepare() */
self->setup_external_hwnd = TRUE;
window->initialized = TRUE;
goto done;
}
AcquireSRWLockExclusive (&self->lock);
self->loop = g_main_loop_new (self->main_context, FALSE);
self->thread = g_thread_new ("GstD3D11WindowWin32",
(GThreadFunc) gst_d3d11_window_win32_thread_func, self);
while (!g_main_loop_is_running (self->loop))
SleepConditionVariableSRW (&self->cond, &self->lock, INFINITE, 0);
ReleaseSRWLockExclusive (&self->lock);
done:
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
gst_d3d11_window_win32_finalize (GObject * object)
{
GstD3D11WindowWin32 *self = (GstD3D11WindowWin32 *) object;
GST_DEBUG_OBJECT (object, "finalize");
gst_d3d11_window_win32_unprepare (GST_D3D11_WINDOW (object));
g_rec_mutex_clear (&self->hwnds_lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static GstFlowReturn
gst_d3d11_window_win32_prepare (GstD3D11Window * window, guint display_width,
guint display_height, GstCaps * caps, GstStructure * config,
DXGI_FORMAT display_format, GError ** error)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
HWND hwnd;
GstFlowReturn ret;
if (!self->setup_external_hwnd)
goto done;
hwnd = (HWND) window->external_handle;
if (!IsWindow (hwnd)) {
gst_structure_free (config);
GST_ERROR_OBJECT (self, "Invalid window handle");
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
"Invalid window handle");
return GST_FLOW_ERROR;
}
GST_DEBUG_OBJECT (self, "Preparing external handle");
ret = gst_d3d11_window_win32_set_external_handle (self, hwnd);
if (ret != GST_FLOW_OK) {
gst_structure_free (config);
if (ret == GST_FLOW_FLUSHING) {
GST_WARNING_OBJECT (self, "Flushing");
return GST_FLOW_FLUSHING;
}
GST_ERROR_OBJECT (self, "Couldn't configure internal window");
g_set_error (error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED,
"Window handle configuration failed");
return GST_FLOW_ERROR;
}
GST_DEBUG_OBJECT (self, "External handle got prepared");
self->setup_external_hwnd = FALSE;
done:
return GST_D3D11_WINDOW_CLASS (parent_class)->prepare (window, display_width,
display_height, caps, config, display_format, error);
}
static void
gst_d3d11_window_win32_unprepare (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
GST_DEBUG_OBJECT (self, "unprepare");
if (self->external_hwnd) {
G_LOCK (get_instance_lock);
gst_d3d11_window_win32_release_external_handle (self);
RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
G_UNLOCK (get_instance_lock);
if (self->internal_hwnd_thread == g_thread_self ()) {
/* State changing thread is identical to internal window thread.
* window can be closed here */
GST_INFO_OBJECT (self, "Closing internal window immediately");
gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
} else if (self->internal_hwnd) {
/* We cannot destroy internal window from non-window thread.
* and we cannot use synchronously SendMessage() method at this point
* since window thread might be waiting for current thread and SendMessage()
* will be blocked until it's called from window thread.
* Instead, posts message so that it can be closed from window thread
* asynchronously */
GST_INFO_OBJECT (self, "Posting custom destory message");
PostMessageA (self->internal_hwnd, WM_GST_D3D11_DESTROY_INTERNAL_WINDOW,
0, 0);
}
/* Hold hwnds lock to allow the window thread finish processing what it could
* already have in the queue at this moment, before we reset the handlers to null */
g_rec_mutex_lock (&self->hwnds_lock);
self->external_hwnd = NULL;
self->internal_hwnd = NULL;
self->internal_hwnd_thread = NULL;
g_rec_mutex_unlock (&self->hwnds_lock);
}
if (self->loop) {
g_main_loop_quit (self->loop);
}
if (self->thread) {
g_thread_join (self->thread);
self->thread = NULL;
}
if (self->loop) {
g_main_loop_unref (self->loop);
self->loop = NULL;
}
if (self->main_context) {
g_main_context_unref (self->main_context);
self->main_context = NULL;
}
}
static GstD3D11WindowWin32 *
gst_d3d11_window_win32_hwnd_get_instance (HWND hwnd)
{
HANDLE handle;
G_LOCK (get_instance_lock);
handle = GetPropA (hwnd, D3D11_WINDOW_PROP_NAME);
if (handle)
handle = gst_object_ref (handle);
G_UNLOCK (get_instance_lock);
return (GstD3D11WindowWin32 *) handle;
}
static void
gst_d3d11_window_win32_set_render_rectangle (GstD3D11Window * window,
const GstVideoRectangle * rect)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
self->render_rect = *rect;
if (self->external_hwnd && self->internal_hwnd) {
g_atomic_int_add (&self->pending_move_window, 1);
if (self->internal_hwnd_thread == g_thread_self ()) {
/* We are on message pumping thread already, handle this synchroniously */
SendMessageA (self->internal_hwnd, WM_GST_D3D11_MOVE_WINDOW, 0, 0);
} else {
/* Post message to message pumping thread. Handling HWND specific message
* on message pumping thread is not a worst idea in generall */
PostMessageA (self->internal_hwnd, WM_GST_D3D11_MOVE_WINDOW, 0, 0);
}
} else if (!window->external_handle && self->internal_hwnd) {
MoveWindow (self->internal_hwnd,
self->render_rect.x, self->render_rect.y, self->render_rect.w,
self->render_rect.h, TRUE);
}
}
static void
gst_d3d11_window_win32_set_title (GstD3D11Window * window, const gchar * title)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
/* Do this only when we are rendring on our own HWND */
if (!self->external_hwnd && self->internal_hwnd) {
gunichar2 *str = g_utf8_to_utf16 (title, -1, nullptr, nullptr, nullptr);
if (str) {
SetWindowTextW (self->internal_hwnd, (LPCWSTR) str);
g_free (str);
}
}
}
static gboolean
gst_d3d11_window_win32_unlock (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
GstD3D11SRWLockGuard lk (&self->lock);
GST_DEBUG_OBJECT (self, "Unlock");
self->flushing = TRUE;
WakeAllConditionVariable (&self->cond);
return TRUE;
}
static gboolean
gst_d3d11_window_win32_unlock_stop (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
GstD3D11SRWLockGuard lk (&self->lock);
GST_DEBUG_OBJECT (self, "Unlock stop");
self->flushing = FALSE;
WakeAllConditionVariable (&self->cond);
return TRUE;
}
static gboolean
running_cb (gpointer user_data)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (user_data);
GST_TRACE_OBJECT (self, "Main loop running now");
AcquireSRWLockExclusive (&self->lock);
WakeConditionVariable (&self->cond);
ReleaseSRWLockExclusive (&self->lock);
return G_SOURCE_REMOVE;
}
static gboolean
msg_cb (GIOChannel * source, GIOCondition condition, gpointer data)
{
MSG msg;
if (!PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
return G_SOURCE_CONTINUE;
TranslateMessage (&msg);
DispatchMessage (&msg);
return G_SOURCE_CONTINUE;
}
static gpointer
gst_d3d11_window_win32_thread_func (gpointer data)
{
GstD3D11Window *window = GST_D3D11_WINDOW (data);
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (data);
GSource *source;
GST_DEBUG_OBJECT (self, "Enter loop");
g_main_context_push_thread_default (self->main_context);
window->initialized = gst_d3d11_window_win32_create_internal_window (self);
/* Watching and dispatching all messages on this thread */
self->msg_io_channel = g_io_channel_win32_new_messages (0);
self->msg_source = g_io_create_watch (self->msg_io_channel, G_IO_IN);
g_source_set_callback (self->msg_source, (GSourceFunc) msg_cb, self, NULL);
g_source_attach (self->msg_source, self->main_context);
source = g_idle_source_new ();
g_source_set_callback (source, (GSourceFunc) running_cb, self, NULL);
g_source_attach (source, self->main_context);
g_source_unref (source);
g_main_loop_run (self->loop);
RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
self->internal_hwnd = NULL;
self->internal_hwnd_thread = NULL;
if (self->msg_source) {
g_source_destroy (self->msg_source);
g_source_unref (self->msg_source);
self->msg_source = NULL;
}
if (self->msg_io_channel) {
g_io_channel_unref (self->msg_io_channel);
self->msg_io_channel = NULL;
}
g_main_context_pop_thread_default (self->main_context);
GST_DEBUG_OBJECT (self, "Exit loop");
return NULL;
}
static void
gst_d3d11_window_win32_destroy_internal_window (HWND hwnd)
{
if (!hwnd)
return;
ShowWindow (hwnd, SW_HIDE);
SetParent (hwnd, NULL);
GST_INFO ("Destroying internal window %" G_GUINTPTR_FORMAT, (guintptr) hwnd);
if (!DestroyWindow (hwnd))
g_critical ("failed to destroy window %" G_GUINTPTR_FORMAT
", 0x%x", (guintptr) hwnd, (guint) GetLastError ());
}
static GstFlowReturn
gst_d3d11_window_win32_set_external_handle (GstD3D11WindowWin32 * self,
HWND hwnd)
{
WNDPROC external_window_proc;
GstFlowReturn ret = GST_FLOW_OK;
GstD3D11SRWLockGuard lk (&self->lock);
self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE;
self->external_hwnd = hwnd;
G_LOCK (get_instance_lock);
external_window_proc = (WNDPROC) GetWindowLongPtrA (hwnd, GWLP_WNDPROC);
GST_DEBUG_OBJECT (self, "set external window %" G_GUINTPTR_FORMAT
", original window procedure %p", (guintptr) hwnd, external_window_proc);
g_assert (external_window_proc != sub_class_proc);
g_warn_if_fail (GetPropA (hwnd, EXTERNAL_PROC_PROP_NAME) == NULL);
g_warn_if_fail (GetPropA (hwnd, D3D11_WINDOW_PROP_NAME) == NULL);
SetPropA (hwnd, EXTERNAL_PROC_PROP_NAME, (HANDLE) external_window_proc);
SetPropA (hwnd, D3D11_WINDOW_PROP_NAME, self);
SetWindowLongPtrA (hwnd, GWLP_WNDPROC, (LONG_PTR) sub_class_proc);
G_UNLOCK (get_instance_lock);
/* SendMessage() may cause deadlock if parent window thread is busy
* for changing pipeline's state. Post our message instead, and wait for
* the parent window's thread or flushing */
PostMessageA (hwnd, WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW, 0, 0);
while (self->external_hwnd &&
self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_NONE &&
!self->flushing) {
SleepConditionVariableSRW (&self->cond, &self->lock, INFINITE, 0);
}
if (self->overlay_state != GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED) {
if (self->flushing)
ret = GST_FLOW_FLUSHING;
else
ret = GST_FLOW_ERROR;
}
return ret;
}
static void
gst_d3d11_window_win32_release_external_handle (GstD3D11WindowWin32 * self)
{
WNDPROC external_proc;
HWND hwnd = self->external_hwnd;
if (!hwnd)
return;
self->external_hwnd = NULL;
external_proc = (WNDPROC) GetPropA (hwnd, EXTERNAL_PROC_PROP_NAME);
if (!external_proc) {
GST_WARNING_OBJECT (self, "Failed to get original window procedure");
return;
}
GST_DEBUG_OBJECT (self, "release external window %" G_GUINTPTR_FORMAT
", original window procedure %p", (guintptr) hwnd, external_proc);
RemovePropA (hwnd, EXTERNAL_PROC_PROP_NAME);
RemovePropA (hwnd, D3D11_WINDOW_PROP_NAME);
if (!SetWindowLongPtrA (hwnd, GWLP_WNDPROC, (LONG_PTR) external_proc))
GST_WARNING_OBJECT (self, "Couldn't restore original window procedure");
}
static gboolean
gst_d3d11_window_win32_create_internal_window (GstD3D11WindowWin32 * self)
{
WNDCLASSEXA wc;
ATOM atom = 0;
HINSTANCE hinstance = GetModuleHandleA (NULL);
GST_LOG_OBJECT (self, "Attempting to create a win32 window");
G_LOCK (create_lock);
atom = GetClassInfoExA (hinstance, "GSTD3D11", &wc);
if (atom == 0) {
GST_LOG_OBJECT (self, "Register internal window class");
ZeroMemory (&wc, sizeof (WNDCLASSEXA));
wc.cbSize = sizeof (WNDCLASSEXA);
wc.lpfnWndProc = window_proc;
wc.hInstance = hinstance;
wc.hIcon = LoadIcon (NULL, IDI_WINLOGO);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
wc.lpszClassName = "GSTD3D11";
atom = RegisterClassExA (&wc);
if (atom == 0) {
G_UNLOCK (create_lock);
GST_ERROR_OBJECT (self, "Failed to register window class 0x%x",
(unsigned int) GetLastError ());
return FALSE;
}
} else {
GST_LOG_OBJECT (self, "window class was already registered");
}
self->internal_hwnd = 0;
self->visible = FALSE;
self->internal_hwnd = CreateWindowExA (0,
"GSTD3D11",
"Direct3D11 renderer", WS_GST_D3D11,
CW_USEDEFAULT, CW_USEDEFAULT,
0, 0, (HWND) NULL, (HMENU) NULL, hinstance, self);
G_UNLOCK (create_lock);
if (!self->internal_hwnd) {
GST_ERROR_OBJECT (self, "Failed to create d3d11 window");
return FALSE;
}
GST_DEBUG_OBJECT (self, "d3d11 window created: %" G_GUINTPTR_FORMAT,
(guintptr) self->internal_hwnd);
GST_LOG_OBJECT (self,
"Created a internal d3d11 window %p", self->internal_hwnd);
self->internal_hwnd_thread = g_thread_self ();
return TRUE;
}
/* always called from window thread */
static void
gst_d3d11_window_win32_change_fullscreen_mode_internal (GstD3D11WindowWin32 *
self)
{
GstD3D11Window *window = GST_D3D11_WINDOW (self);
HWND hwnd = self->external_hwnd ? self->external_hwnd : self->internal_hwnd;
if (!window->swap_chain)
return;
if (window->requested_fullscreen == window->fullscreen)
return;
GST_DEBUG_OBJECT (self, "Change mode to %s",
window->requested_fullscreen ? "fullscreen" : "windowed");
window->fullscreen = !window->fullscreen;
if (!window->fullscreen) {
/* Restore the window's attributes and size */
SetWindowLongA (hwnd, GWL_STYLE, self->restore_style);
SetWindowPlacement (hwnd, &self->restore_placement);
} else {
ComPtr < IDXGIOutput > output;
DXGI_OUTPUT_DESC output_desc;
IDXGISwapChain *swap_chain = window->swap_chain;
/* remember current placement to restore window later */
GetWindowPlacement (hwnd, &self->restore_placement);
/* show window before change style */
ShowWindow (hwnd, SW_SHOW);
self->restore_style = GetWindowLong (hwnd, GWL_STYLE);
/* Make the window borderless so that the client area can fill the screen */
SetWindowLongA (hwnd, GWL_STYLE,
self->restore_style &
~(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SYSMENU |
WS_THICKFRAME | WS_MAXIMIZE));
swap_chain->GetContainingOutput (&output);
output->GetDesc (&output_desc);
SetWindowPos (hwnd, HWND_TOP,
output_desc.DesktopCoordinates.left,
output_desc.DesktopCoordinates.top,
output_desc.DesktopCoordinates.right,
output_desc.DesktopCoordinates.bottom,
SWP_FRAMECHANGED | SWP_NOACTIVATE);
ShowWindow (hwnd, SW_MAXIMIZE);
}
GST_DEBUG_OBJECT (self, "Fullscreen mode change done");
}
static void
gst_d3d11_window_win32_on_key_event (GstD3D11WindowWin32 * self,
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
GstD3D11Window *window = GST_D3D11_WINDOW (self);
gunichar2 wcrep[128];
const gchar *event;
if (!window->enable_navigation_events)
return;
if (GetKeyNameTextW (lParam, (LPWSTR) wcrep, 128)) {
gchar *utfrep = g_utf16_to_utf8 (wcrep, 128, NULL, NULL, NULL);
if (utfrep) {
if (uMsg == WM_KEYDOWN)
event = "key-press";
else
event = "key-release";
gst_d3d11_window_on_key_event (window, event, utfrep);
g_free (utfrep);
}
}
}
static void
gst_d3d11_window_win32_on_mouse_event (GstD3D11WindowWin32 * self,
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
GstD3D11Window *window = GST_D3D11_WINDOW (self);
gint button;
const gchar *event = NULL;
if (!window->enable_navigation_events)
return;
switch (uMsg) {
case WM_MOUSEMOVE:
button = 0;
event = "mouse-move";
break;
case WM_LBUTTONDOWN:
button = 1;
event = "mouse-button-press";
break;
case WM_LBUTTONUP:
button = 1;
event = "mouse-button-release";
break;
case WM_RBUTTONDOWN:
button = 2;
event = "mouse-button-press";
break;
case WM_RBUTTONUP:
button = 2;
event = "mouse-button-release";
break;
case WM_MBUTTONDOWN:
button = 3;
event = "mouse-button-press";
break;
case WM_MBUTTONUP:
button = 3;
event = "mouse-button-release";
break;
default:
break;
}
if (event)
gst_d3d11_window_on_mouse_event (window,
event, button, (gdouble) LOWORD (lParam), (gdouble) HIWORD (lParam));
}
static void
gst_d3d11_window_win32_handle_window_proc (GstD3D11WindowWin32 * self,
HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
GstD3D11Window *window = GST_D3D11_WINDOW (self);
switch (uMsg) {
case WM_SIZE:
gst_d3d11_window_win32_on_resize (window, 0, 0);
break;
case WM_CLOSE:
if (self->internal_hwnd) {
RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
gst_d3d11_window_win32_destroy_internal_window (hWnd);
self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED;
self->internal_hwnd = NULL;
self->internal_hwnd_thread = NULL;
}
break;
case WM_KEYDOWN:
case WM_KEYUP:
gst_d3d11_window_win32_on_key_event (self, hWnd, uMsg, wParam, lParam);
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
gst_d3d11_window_win32_on_mouse_event (self, hWnd, uMsg, wParam, lParam);
break;
case WM_SYSKEYDOWN:
if ((window->fullscreen_toggle_mode &
GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER)
== GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER) {
WORD state = GetKeyState (VK_RETURN);
BYTE high = HIBYTE (state);
if (high & 0x1) {
window->requested_fullscreen = !window->fullscreen;
gst_d3d11_window_win32_change_fullscreen_mode_internal (self);
}
}
break;
case WM_GST_D3D11_FULLSCREEN:
if (g_atomic_int_get (&self->pending_fullscreen_count)) {
g_atomic_int_dec_and_test (&self->pending_fullscreen_count);
if ((window->fullscreen_toggle_mode &
GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY)
== GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY)
gst_d3d11_window_win32_change_fullscreen_mode_internal (self);
}
break;
case WM_GST_D3D11_MOVE_WINDOW:
if (g_atomic_int_get (&self->pending_move_window)) {
g_atomic_int_set (&self->pending_move_window, 0);
if (self->internal_hwnd && self->external_hwnd) {
if (self->render_rect.w < 0 || self->render_rect.h < 0) {
RECT rect;
/* Reset render rect and back to full-size window */
if (GetClientRect (self->external_hwnd, &rect)) {
MoveWindow (self->internal_hwnd, 0, 0,
rect.right - rect.left, rect.bottom - rect.top, FALSE);
}
} else {
MoveWindow (self->internal_hwnd, self->render_rect.x,
self->render_rect.y, self->render_rect.w, self->render_rect.h,
FALSE);
}
}
}
break;
case WM_GST_D3D11_SHOW_WINDOW:
ShowWindow (self->internal_hwnd, SW_SHOW);
break;
default:
break;
}
return;
}
static LRESULT CALLBACK
window_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
GstD3D11WindowWin32 *self;
if (uMsg == WM_GST_D3D11_DESTROY_INTERNAL_WINDOW) {
GST_INFO ("Handle destroy window message");
gst_d3d11_window_win32_destroy_internal_window (hWnd);
return 0;
}
if (uMsg == WM_CREATE) {
self = GST_D3D11_WINDOW_WIN32 (((LPCREATESTRUCT) lParam)->lpCreateParams);
GST_LOG_OBJECT (self, "WM_CREATE");
SetPropA (hWnd, D3D11_WINDOW_PROP_NAME, self);
} else if ((self = gst_d3d11_window_win32_hwnd_get_instance (hWnd))) {
g_rec_mutex_lock (&self->hwnds_lock);
if (self->internal_hwnd != hWnd) {
g_warn_if_fail (self->internal_hwnd == NULL);
/* Most likely the instance is already in the destruction proccess,
* just let it process WM_GST_D3D11_DESTROY_INTERNAL_WINDOW */
g_rec_mutex_unlock (&self->hwnds_lock);
gst_object_unref (self);
return DefWindowProcA (hWnd, uMsg, wParam, lParam);
}
gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam,
lParam);
g_rec_mutex_unlock (&self->hwnds_lock);
switch (uMsg) {
case WM_SIZE:
/* We handled this event already */
gst_object_unref (self);
return 0;
case WM_NCHITTEST:
/* To passthrough mouse event if external window is used.
* Only hit-test succeeded window can receive/handle some mouse events
* and we want such events to be handled by parent (application) window
*/
if (self->external_hwnd) {
gst_object_unref (self);
return (LRESULT) HTTRANSPARENT;
}
break;
default:
break;
}
gst_object_unref (self);
}
return DefWindowProcA (hWnd, uMsg, wParam, lParam);
}
static LRESULT FAR PASCAL
sub_class_proc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
WNDPROC external_window_proc =
(WNDPROC) GetPropA (hWnd, EXTERNAL_PROC_PROP_NAME);
GstD3D11WindowWin32 *self = gst_d3d11_window_win32_hwnd_get_instance (hWnd);
if (self == NULL || self->flushing) {
GST_DEBUG ("No object attached to the window, chain up to default");
gst_clear_object (&self);
return CallWindowProcA (external_window_proc, hWnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_GST_D3D11_CONSTRUCT_INTERNAL_WINDOW:{
GstD3D11Window *window = GST_D3D11_WINDOW (self);
RECT rect;
GST_DEBUG_OBJECT (self, "Create internal window");
GstD3D11SRWLockGuard lk (&self->lock);
if (self->internal_hwnd) {
GST_WARNING_OBJECT (self,
"Window already created, probably we have received 2 creation messages");
g_warn_if_fail (self->overlay_state ==
GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED);
gst_object_unref (self);
return 0;
}
if (self->flushing) {
GST_DEBUG_OBJECT (self, "Flushing");
gst_object_unref (self);
return 0;
}
window->initialized =
gst_d3d11_window_win32_create_internal_window (self);
SetWindowLongPtrA (self->internal_hwnd, GWL_STYLE,
WS_CHILD | WS_MAXIMIZE);
SetParent (self->internal_hwnd, self->external_hwnd);
/* take changes into account: SWP_FRAMECHANGED */
GetClientRect (self->external_hwnd, &rect);
if (self->render_rect.x != 0 || self->render_rect.y != 0 ||
self->render_rect.w != 0 || self->render_rect.h != 0) {
rect.left = self->render_rect.x;
rect.top = self->render_rect.y;
rect.right = self->render_rect.x + self->render_rect.w;
rect.bottom = self->render_rect.y + self->render_rect.h;
}
SetWindowPos (self->internal_hwnd, HWND_TOP, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top,
SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_FRAMECHANGED | SWP_NOACTIVATE);
MoveWindow (self->internal_hwnd, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, FALSE);
self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_OPENED;
WakeAllConditionVariable (&self->cond);
/* don't need to be chained up to parent window procedure,
* as this is our custom message */
gst_object_unref (self);
return 0;
}
case WM_SIZE:
if (self->render_rect.x != 0 || self->render_rect.y != 0 ||
self->render_rect.w != 0 || self->render_rect.h != 0) {
MoveWindow (self->internal_hwnd,
self->render_rect.x, self->render_rect.y,
self->render_rect.w, self->render_rect.h, FALSE);
} else {
MoveWindow (self->internal_hwnd, 0, 0, LOWORD (lParam), HIWORD (lParam),
FALSE);
}
break;
case WM_CLOSE:
case WM_DESTROY:{
GstD3D11SRWLockGuard lk (&self->lock);
GST_WARNING_OBJECT (self, "external window is closing");
gst_d3d11_window_win32_release_external_handle (self);
if (self->internal_hwnd) {
RemovePropA (self->internal_hwnd, D3D11_WINDOW_PROP_NAME);
gst_d3d11_window_win32_destroy_internal_window (self->internal_hwnd);
}
self->internal_hwnd = NULL;
self->internal_hwnd_thread = NULL;
self->overlay_state = GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED;
WakeAllConditionVariable (&self->cond);
break;
}
default:
gst_d3d11_window_win32_handle_window_proc (self, hWnd, uMsg, wParam,
lParam);
break;
}
gst_object_unref (self);
return CallWindowProcA (external_window_proc, hWnd, uMsg, wParam, lParam);
}
static void
gst_d3d11_window_win32_disable_alt_enter (GstD3D11WindowWin32 * self,
GstD3D11Device * device, IDXGISwapChain * swap_chain, HWND hwnd)
{
ComPtr < IDXGIFactory1 > factory;
HRESULT hr;
hr = swap_chain->GetParent (IID_PPV_ARGS (&factory));
if (!gst_d3d11_result (hr, device) || !factory) {
GST_WARNING_OBJECT (self,
"Cannot get parent dxgi factory for swapchain %p, hr: 0x%x",
swap_chain, (guint) hr);
return;
}
hr = factory->MakeWindowAssociation (hwnd, DXGI_MWA_NO_ALT_ENTER);
if (!gst_d3d11_result (hr, device)) {
GST_WARNING_OBJECT (self,
"MakeWindowAssociation failure, hr: 0x%x", (guint) hr);
}
}
static IDXGISwapChain *
create_swap_chain (GstD3D11WindowWin32 * self, GstD3D11Device * device,
DXGI_SWAP_CHAIN_DESC * desc)
{
HRESULT hr;
IDXGISwapChain *swap_chain = NULL;
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device);
IDXGIFactory1 *factory = gst_d3d11_device_get_dxgi_factory_handle (device);
GstD3D11DeviceLockGuard lk (device);
hr = factory->CreateSwapChain (device_handle, desc, &swap_chain);
if (!gst_d3d11_result (hr, device)) {
GST_WARNING_OBJECT (self, "Cannot create SwapChain Object: 0x%x",
(guint) hr);
swap_chain = NULL;
}
return swap_chain;
}
static IDXGISwapChain1 *
create_swap_chain_for_hwnd (GstD3D11WindowWin32 * self, GstD3D11Device * device,
HWND hwnd, DXGI_SWAP_CHAIN_DESC1 * desc,
DXGI_SWAP_CHAIN_FULLSCREEN_DESC * fullscreen_desc, IDXGIOutput * output)
{
HRESULT hr;
IDXGISwapChain1 *swap_chain = NULL;
ID3D11Device *device_handle = gst_d3d11_device_get_device_handle (device);
IDXGIFactory1 *factory = gst_d3d11_device_get_dxgi_factory_handle (device);
ComPtr < IDXGIFactory2 > factory2;
hr = factory->QueryInterface (IID_PPV_ARGS (&factory2));
if (!gst_d3d11_result (hr, device)) {
GST_WARNING_OBJECT (self, "IDXGIFactory2 interface is unavailable");
return NULL;
}
GstD3D11DeviceLockGuard lk (device);
hr = factory2->CreateSwapChainForHwnd (device_handle, hwnd, desc,
fullscreen_desc, output, &swap_chain);
if (!gst_d3d11_result (hr, device)) {
GST_WARNING_OBJECT (self, "Cannot create SwapChain Object: 0x%x",
(guint) hr);
swap_chain = NULL;
}
return swap_chain;
}
static gboolean
gst_d3d11_window_win32_create_swap_chain (GstD3D11Window * window,
DXGI_FORMAT format, guint width, guint height,
guint swapchain_flags, IDXGISwapChain ** swap_chain)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
DXGI_SWAP_CHAIN_DESC desc = { 0, };
IDXGISwapChain *new_swapchain = NULL;
GstD3D11Device *device = window->device;
self->have_swapchain1 = FALSE;
{
DXGI_SWAP_CHAIN_DESC1 desc1 = { 0, };
desc1.Width = 0;
desc1.Height = 0;
desc1.Format = format;
/* FIXME: add support stereo */
desc1.Stereo = FALSE;
desc1.SampleDesc.Count = 1;
desc1.SampleDesc.Quality = 0;
desc1.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc1.BufferCount = 2;
desc1.Scaling = DXGI_SCALING_STRETCH;
/* scaling-stretch would break aspect-ratio so we prefer to use scaling-none,
* but Windows7 does not support this method */
if (gst_d3d11_is_windows_8_or_greater ())
desc1.Scaling = DXGI_SCALING_NONE;
desc1.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc1.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
desc1.Flags = swapchain_flags;
new_swapchain = create_swap_chain_for_hwnd (self, device,
self->internal_hwnd, &desc1, NULL, NULL);
if (!new_swapchain) {
GST_WARNING_OBJECT (self, "Failed to create swapchain1");
} else {
self->have_swapchain1 = TRUE;
}
}
if (!new_swapchain) {
DXGI_SWAP_EFFECT swap_effect = DXGI_SWAP_EFFECT_DISCARD;
if (gst_d3d11_is_windows_8_or_greater ())
swap_effect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
/* we will get client area at on_resize */
desc.BufferDesc.Width = 0;
desc.BufferDesc.Height = 0;
/* don't care refresh rate */
desc.BufferDesc.RefreshRate.Numerator = 0;
desc.BufferDesc.RefreshRate.Denominator = 1;
desc.BufferDesc.Format = format;
desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 2;
desc.SwapEffect = swap_effect;
desc.OutputWindow = self->internal_hwnd;
desc.Windowed = TRUE;
desc.Flags = swapchain_flags;
new_swapchain = create_swap_chain (self, device, &desc);
}
if (!new_swapchain) {
GST_ERROR_OBJECT (self, "Cannot create swapchain");
return FALSE;
}
/* disable alt+enter here. It should be manually handled */
GstD3D11DeviceLockGuard lk (device);
gst_d3d11_window_win32_disable_alt_enter (self,
device, new_swapchain, desc.OutputWindow);
*swap_chain = new_swapchain;
return TRUE;
}
static void
gst_d3d11_window_win32_show (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
gint width, height;
switch (window->method) {
case GST_VIDEO_ORIENTATION_90R:
case GST_VIDEO_ORIENTATION_90L:
case GST_VIDEO_ORIENTATION_UL_LR:
case GST_VIDEO_ORIENTATION_UR_LL:
width = GST_VIDEO_INFO_HEIGHT (&window->render_info);
height = GST_VIDEO_INFO_WIDTH (&window->render_info);
break;
default:
width = GST_VIDEO_INFO_WIDTH (&window->render_info);
height = GST_VIDEO_INFO_HEIGHT (&window->render_info);
break;
}
if (!self->visible) {
/* if no parent the real size has to be set now because this has not been done
* when at window creation */
if (!self->external_hwnd) {
if (self->render_rect.x != 0 || self->render_rect.y != 0 ||
self->render_rect.w != 0 || self->render_rect.h != 0) {
MoveWindow (self->internal_hwnd,
self->render_rect.x, self->render_rect.y, self->render_rect.w,
self->render_rect.h, FALSE);
} else {
RECT rect = { 0, };
rect.right = width;
rect.bottom = height;
if (AdjustWindowRect (&rect, WS_GST_D3D11, FALSE)) {
width = rect.right - rect.left;
height = rect.bottom - rect.top;
} else {
width += 2 * GetSystemMetrics (SM_CXSIZEFRAME);
height +=
2 * GetSystemMetrics (SM_CYSIZEFRAME) +
GetSystemMetrics (SM_CYCAPTION);
}
MoveWindow (self->internal_hwnd, 0, 0, width, height, FALSE);
}
ShowWindow (self->internal_hwnd, SW_SHOW);
} else if (self->internal_hwnd) {
/* ShowWindow will throw message to message pumping thread (app thread)
* synchroniously, which can be blocked at the moment.
* Post message to internal hwnd and do that from message pumping thread
*/
PostMessageA (self->internal_hwnd, WM_GST_D3D11_SHOW_WINDOW, 0, 0);
}
if (g_atomic_int_get (&self->pending_fullscreen_count) > 0)
PostMessageA (self->internal_hwnd, WM_GST_D3D11_FULLSCREEN, 0, 0);
self->visible = TRUE;
}
}
static GstFlowReturn
gst_d3d11_window_win32_present (GstD3D11Window * window, guint present_flags)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
HRESULT hr;
if ((!self->external_hwnd &&
self->overlay_state == GST_D3D11_WINDOW_WIN32_OVERLAY_STATE_CLOSED)
|| !self->internal_hwnd) {
GST_ERROR_OBJECT (self, "Output window was closed");
return GST_D3D11_WINDOW_FLOW_CLOSED;
}
if (self->have_swapchain1) {
IDXGISwapChain1 *swap_chain1 = (IDXGISwapChain1 *) window->swap_chain;
DXGI_PRESENT_PARAMETERS present_params = { 0, };
/* the first present should not specify dirty-rect */
if (!window->first_present && !window->emit_present) {
present_params.DirtyRectsCount = 1;
present_params.pDirtyRects = &window->render_rect;
}
hr = swap_chain1->Present1 (0, present_flags, &present_params);
} else {
hr = window->swap_chain->Present (0, present_flags);
}
if (!gst_d3d11_result (hr, window->device)) {
GST_WARNING_OBJECT (self, "Direct3D cannot present texture, hr: 0x%x",
(guint) hr);
}
return GST_FLOW_OK;
}
static void
gst_d3d11_window_win32_on_resize (GstD3D11Window * window,
guint width, guint height)
{
/* Set zero width and height here. dxgi will decide client area by itself */
GST_D3D11_WINDOW_CLASS (parent_class)->on_resize (window, 0, 0);
}
static void
gst_d3d11_window_win32_update_swap_chain (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
if (self->internal_hwnd)
PostMessageA (self->internal_hwnd, WM_SIZE, 0, 0);
return;
}
static void
gst_d3d11_window_win32_change_fullscreen_mode (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
if (self->internal_hwnd) {
g_atomic_int_add (&self->pending_fullscreen_count, 1);
if (self->visible)
PostMessageA (self->internal_hwnd, WM_GST_D3D11_FULLSCREEN, 0, 0);
}
}
HWND
gst_d3d11_window_win32_get_internal_hwnd (GstD3D11Window * window)
{
GstD3D11WindowWin32 *self = GST_D3D11_WINDOW_WIN32 (window);
return self->internal_hwnd;
}
GstD3D11Window *
gst_d3d11_window_win32_new (GstD3D11Device * device, guintptr handle)
{
GstD3D11Window *window;
g_return_val_if_fail (GST_IS_D3D11_DEVICE (device), NULL);
window = (GstD3D11Window *) g_object_new (GST_TYPE_D3D11_WINDOW_WIN32,
"d3d11device", device, "window-handle", handle, NULL);
if (!window->initialized) {
gst_object_unref (window);
return NULL;
}
gst_object_ref_sink (window);
return window;
}