| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qwinrtwindow.h" |
| #include "qwinrtscreen.h" |
| #include <private/qeventdispatcher_winrt_p.h> |
| |
| #include <EGL/egl.h> |
| #include <EGL/eglext.h> |
| |
| #include <qfunctions_winrt.h> |
| #include <qpa/qplatformscreen.h> |
| #include <qpa/qwindowsysteminterface.h> |
| #include <QtGui/QGuiApplication> |
| #include <QtGui/QOpenGLContext> |
| #include <QtGui/QWindow> |
| #include <QtEglSupport/private/qeglconvenience_p.h> |
| |
| #include <functional> |
| #include <wrl.h> |
| #include <windows.foundation.h> |
| #include <windows.foundation.collections.h> |
| #include <windows.ui.xaml.h> |
| #include <windows.ui.xaml.controls.h> |
| #include <windows.ui.viewmanagement.h> |
| |
| using namespace ABI::Windows::UI::ViewManagement; |
| using namespace Microsoft::WRL; |
| using namespace Microsoft::WRL::Wrappers; |
| using namespace ABI::Windows::Foundation; |
| using namespace ABI::Windows::Foundation::Collections; |
| using namespace ABI::Windows::UI; |
| using namespace ABI::Windows::UI::Xaml; |
| using namespace ABI::Windows::UI::Xaml::Controls; |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_LOGGING_CATEGORY(lcQpaWindows, "qt.qpa.windows"); |
| |
| static void setUIElementVisibility(IUIElement *uiElement, bool visibility) |
| { |
| Q_ASSERT(uiElement); |
| QEventDispatcherWinRT::runOnXamlThread([uiElement, visibility]() { |
| HRESULT hr; |
| hr = uiElement->put_Visibility(visibility ? Visibility_Visible : Visibility_Collapsed); |
| Q_ASSERT_SUCCEEDED(hr); |
| return S_OK; |
| }); |
| } |
| |
| class QWinRTWindowPrivate |
| { |
| public: |
| QWinRTScreen *screen; |
| |
| QSurfaceFormat surfaceFormat; |
| QString windowTitle; |
| Qt::WindowStates state; |
| EGLDisplay display; |
| EGLSurface surface; |
| |
| ComPtr<ISwapChainPanel> swapChainPanel; |
| ComPtr<ICanvasStatics> canvas; |
| ComPtr<IUIElement> uiElement; |
| }; |
| |
| QWinRTWindow::QWinRTWindow(QWindow *window) |
| : QPlatformWindow(window) |
| , d_ptr(new QWinRTWindowPrivate) |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this; |
| |
| d->surface = EGL_NO_SURFACE; |
| d->display = EGL_NO_DISPLAY; |
| d->screen = static_cast<QWinRTScreen *>(screen()); |
| handleContentOrientationChange(window->contentOrientation()); |
| |
| d->surfaceFormat.setMajorVersion(3); |
| d->surfaceFormat.setMinorVersion(0); |
| d->surfaceFormat.setAlphaBufferSize(0); |
| d->surfaceFormat.setRedBufferSize(8); |
| d->surfaceFormat.setGreenBufferSize(8); |
| d->surfaceFormat.setBlueBufferSize(8); |
| d->surfaceFormat.setDepthBufferSize(24); |
| d->surfaceFormat.setStencilBufferSize(8); |
| d->surfaceFormat.setRenderableType(QSurfaceFormat::OpenGLES); |
| d->surfaceFormat.setSamples(1); |
| d->surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer); |
| |
| HRESULT hr; |
| hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_Xaml_Controls_Canvas).Get(), |
| IID_PPV_ARGS(&d->canvas)); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = QEventDispatcherWinRT::runOnXamlThread([d]() { |
| // Create a new swapchain and place it inside the canvas |
| HRESULT hr; |
| hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_UI_Xaml_Controls_SwapChainPanel).Get(), |
| &d->swapChainPanel); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->swapChainPanel.As(&d->uiElement); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| ComPtr<Xaml::IFrameworkElement> frameworkElement; |
| hr = d->swapChainPanel.As(&frameworkElement); |
| Q_ASSERT_SUCCEEDED(hr); |
| const QSizeF size = QSizeF(d->screen->geometry().size()) / d->screen->scaleFactor(); |
| hr = frameworkElement->put_Width(size.width()); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = frameworkElement->put_Height(size.height()); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| ComPtr<IDependencyObject> canvas = d->screen->canvas(); |
| ComPtr<IPanel> panel; |
| hr = canvas.As(&panel); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IVector<UIElement *>> children; |
| hr = panel->get_Children(&children); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = children->Append(d->uiElement.Get()); |
| Q_ASSERT_SUCCEEDED(hr); |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| |
| setWindowFlags(window->flags()); |
| setWindowState(window->windowStates()); |
| setWindowTitle(window->title()); |
| |
| setGeometry(window->geometry()); |
| } |
| |
| QWinRTWindow::~QWinRTWindow() |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this; |
| |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([d]() { |
| HRESULT hr; |
| ComPtr<IDependencyObject> canvas = d->screen->canvas(); |
| ComPtr<IPanel> panel; |
| hr = canvas.As(&panel); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<IVector<UIElement *>> children; |
| hr = panel->get_Children(&children); |
| Q_ASSERT_SUCCEEDED(hr); |
| quint32 index; |
| boolean found; |
| hr = children->IndexOf(d->uiElement.Get(), &index, &found); |
| Q_ASSERT_SUCCEEDED(hr); |
| if (found) { |
| hr = children->RemoveAt(index); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| return S_OK; |
| }); |
| RETURN_VOID_IF_FAILED("Failed to completely destroy window resources, likely because the application is shutting down"); |
| |
| if (d->screen->mouseGrabWindow() == this) |
| d->screen->setMouseGrabWindow(this, false); |
| if (d->screen->keyboardGrabWindow() == this) |
| d->screen->setKeyboardGrabWindow(this, false); |
| |
| d->screen->removeWindow(window()); |
| |
| if (!d->surface) |
| return; |
| |
| qCDebug(lcQpaWindows) << __FUNCTION__ << ": Destroying surface"; |
| |
| EGLBoolean value = eglDestroySurface(d->display, d->surface); |
| d->surface = EGL_NO_SURFACE; |
| if (Q_UNLIKELY(value == EGL_FALSE)) |
| qCritical("Failed to destroy EGL window surface: 0x%x", eglGetError()); |
| } |
| |
| QSurfaceFormat QWinRTWindow::format() const |
| { |
| Q_D(const QWinRTWindow); |
| return d->surfaceFormat; |
| } |
| |
| bool QWinRTWindow::isActive() const |
| { |
| Q_D(const QWinRTWindow); |
| return d->screen->topWindow() == window(); |
| } |
| |
| bool QWinRTWindow::isExposed() const |
| { |
| Q_D(const QWinRTWindow); |
| const bool exposed = isActive() && !d->screen->resizePending(); |
| return exposed; |
| } |
| |
| void QWinRTWindow::setGeometry(const QRect &rect) |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this << rect; |
| |
| const Qt::WindowFlags windowFlags = window()->flags(); |
| const Qt::WindowFlags windowType = windowFlags & Qt::WindowType_Mask; |
| if (window()->isTopLevel() && (windowType == Qt::Window || windowType == Qt::Dialog)) { |
| const QRect screenRect = windowFlags & Qt::MaximizeUsingFullscreenGeometryHint |
| ? d->screen->geometry() : d->screen->availableGeometry(); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << "top-level, overwrite" << screenRect; |
| QPlatformWindow::setGeometry(screenRect); |
| QWindowSystemInterface::handleGeometryChange(window(), geometry()); |
| } else { |
| QPlatformWindow::setGeometry(rect); |
| QWindowSystemInterface::handleGeometryChange(window(), rect); |
| } |
| |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([this, d]() { |
| HRESULT hr; |
| const QRect windowGeometry = geometry(); |
| const QPointF topLeft= QPointF(windowGeometry.topLeft()) / d->screen->scaleFactor(); |
| hr = d->canvas->SetTop(d->uiElement.Get(), topLeft.y()); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = d->canvas->SetLeft(d->uiElement.Get(), topLeft.x()); |
| Q_ASSERT_SUCCEEDED(hr); |
| ComPtr<Xaml::IFrameworkElement> frameworkElement; |
| hr = d->swapChainPanel.As(&frameworkElement); |
| Q_ASSERT_SUCCEEDED(hr); |
| const QSizeF size = QSizeF(windowGeometry.size()) / d->screen->scaleFactor(); |
| hr = frameworkElement->put_Width(size.width()); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = frameworkElement->put_Height(size.height()); |
| Q_ASSERT_SUCCEEDED(hr); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << "(setGeometry Xaml)" << this |
| << topLeft << size; |
| return S_OK; |
| }); |
| Q_ASSERT_SUCCEEDED(hr); |
| } |
| |
| void QWinRTWindow::setVisible(bool visible) |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this << visible; |
| |
| if (!window()->isTopLevel()) |
| return; |
| if (visible) { |
| d->screen->addWindow(window()); |
| setUIElementVisibility(d->uiElement.Get(), d->state != Qt::WindowMinimized); |
| } else { |
| d->screen->removeWindow(window()); |
| setUIElementVisibility(d->uiElement.Get(), false); |
| } |
| } |
| |
| void QWinRTWindow::setWindowTitle(const QString &title) |
| { |
| Q_D(QWinRTWindow); |
| d->windowTitle = title; |
| |
| if (d->screen->topWindow() == window()) |
| d->screen->updateWindowTitle(title); |
| } |
| |
| void QWinRTWindow::raise() |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this; |
| if (!window()->isTopLevel()) |
| return; |
| d->screen->raise(window()); |
| } |
| |
| void QWinRTWindow::lower() |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this; |
| if (!window()->isTopLevel()) |
| return; |
| d->screen->lower(window()); |
| } |
| |
| WId QWinRTWindow::winId() const |
| { |
| Q_D(const QWinRTWindow); |
| return WId(d->swapChainPanel.Get()); |
| } |
| |
| qreal QWinRTWindow::devicePixelRatio() const |
| { |
| return screen()->devicePixelRatio(); |
| } |
| |
| void QWinRTWindow::setWindowState(Qt::WindowStates state) |
| { |
| Q_D(QWinRTWindow); |
| qCDebug(lcQpaWindows) << __FUNCTION__ << this << state; |
| |
| if (d->state == state) |
| return; |
| |
| if (state & Qt::WindowMinimized) { |
| setUIElementVisibility(d->uiElement.Get(), false); |
| d->state = state; |
| return; |
| } |
| |
| if (state & Qt::WindowFullScreen) { |
| HRESULT hr; |
| boolean success; |
| hr = QEventDispatcherWinRT::runOnXamlThread([&hr, &success]() { |
| ComPtr<IApplicationViewStatics2> applicationViewStatics; |
| hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_ViewManagement_ApplicationView).Get(), |
| IID_PPV_ARGS(&applicationViewStatics)); |
| RETURN_HR_IF_FAILED("Could not access application view statics."); |
| ComPtr<IApplicationView> view; |
| hr = applicationViewStatics->GetForCurrentView(&view); |
| RETURN_HR_IF_FAILED("Could not access application view."); |
| ComPtr<IApplicationView3> view3; |
| hr = view.As(&view3); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = view3->TryEnterFullScreenMode(&success); |
| return hr; |
| }); |
| if (FAILED(hr) || !success) { |
| qCDebug(lcQpaWindows) << "Failed to enter full screen mode."; |
| return; |
| } |
| d->screen->setResizePending(); |
| d->state = state; |
| return; |
| } |
| |
| if (d->state & Qt::WindowFullScreen) { |
| HRESULT hr; |
| hr = QEventDispatcherWinRT::runOnXamlThread([&hr]() { |
| ComPtr<IApplicationViewStatics2> applicationViewStatics; |
| hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_UI_ViewManagement_ApplicationView).Get(), |
| IID_PPV_ARGS(&applicationViewStatics)); |
| RETURN_HR_IF_FAILED("Could not access application view statics."); |
| ComPtr<IApplicationView> view; |
| hr = applicationViewStatics->GetForCurrentView(&view); |
| RETURN_HR_IF_FAILED("Could not access application view."); |
| ComPtr<IApplicationView3> view3; |
| hr = view.As(&view3); |
| Q_ASSERT_SUCCEEDED(hr); |
| hr = view3->ExitFullScreenMode(); |
| return hr; |
| }); |
| if (FAILED(hr)) { |
| qCDebug(lcQpaWindows) << "Failed to exit full screen mode."; |
| return; |
| } |
| d->screen->setResizePending(); |
| } |
| |
| if (d->state & Qt::WindowMinimized || state == Qt::WindowNoState || state == Qt::WindowActive) |
| setUIElementVisibility(d->uiElement.Get(), true); |
| |
| d->state = state; |
| } |
| |
| bool QWinRTWindow::setMouseGrabEnabled(bool grab) |
| { |
| Q_D(QWinRTWindow); |
| if (!isActive() && grab) { |
| qWarning("%s: Not setting mouse grab for invisible window %s/'%s'", |
| __FUNCTION__, window()->metaObject()->className(), |
| qPrintable(window()->objectName())); |
| return false; |
| } |
| return d->screen->setMouseGrabWindow(this, grab); |
| } |
| |
| bool QWinRTWindow::setKeyboardGrabEnabled(bool grab) |
| { |
| Q_D(QWinRTWindow); |
| return d->screen->setKeyboardGrabWindow(this, grab); |
| } |
| |
| EGLSurface QWinRTWindow::eglSurface() const |
| { |
| Q_D(const QWinRTWindow); |
| return d->surface; |
| } |
| |
| void QWinRTWindow::createEglSurface(EGLDisplay display, EGLConfig config) |
| { |
| Q_D(QWinRTWindow); |
| if (d->surface == EGL_NO_SURFACE) { |
| d->display = display; |
| QEventDispatcherWinRT::runOnXamlThread([this, d, display, config]() { |
| d->surface = eglCreateWindowSurface(display, config, |
| reinterpret_cast<EGLNativeWindowType>(winId()), |
| nullptr); |
| if (Q_UNLIKELY(d->surface == EGL_NO_SURFACE)) |
| qCritical("Failed to create EGL window surface: 0x%x", eglGetError()); |
| return S_OK; |
| }); |
| } |
| } |
| |
| QT_END_NAMESPACE |