diff --git a/pcsx2-qt/DisplayWidget.cpp b/pcsx2-qt/DisplayWidget.cpp index 9ce77343b3..8bd900ddfe 100644 --- a/pcsx2-qt/DisplayWidget.cpp +++ b/pcsx2-qt/DisplayWidget.cpp @@ -29,20 +29,12 @@ #include #endif -DisplayWidget::DisplayWidget(QWidget* parent) - : QWidget(parent) +DisplaySurface::DisplaySurface() + : QWindow() { - // We want a native window for both D3D and OpenGL. - setAutoFillBackground(false); - setAttribute(Qt::WA_NativeWindow, true); - setAttribute(Qt::WA_NoSystemBackground, true); - setAttribute(Qt::WA_PaintOnScreen, true); - setAttribute(Qt::WA_KeyCompression, false); - setFocusPolicy(Qt::StrongFocus); - setMouseTracking(true); } -DisplayWidget::~DisplayWidget() +DisplaySurface::~DisplaySurface() { #ifdef _WIN32 if (m_clip_mouse_enabled) @@ -50,19 +42,27 @@ DisplayWidget::~DisplayWidget() #endif } -int DisplayWidget::scaledWindowWidth() const +QWidget* DisplaySurface::createWindowContainer(QWidget* parent) { - return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatioF())), 1); + m_container = QWidget::createWindowContainer(this, parent); + m_container->installEventFilter(this); + m_container->setFocusPolicy(Qt::StrongFocus); + return m_container; } -int DisplayWidget::scaledWindowHeight() const +int DisplaySurface::scaledWindowWidth() const { - return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatioF())), 1); + return std::max(static_cast(std::ceil(static_cast(width()) * devicePixelRatio())), 1); } -std::optional DisplayWidget::getWindowInfo() +int DisplaySurface::scaledWindowHeight() const { - std::optional ret(QtUtils::GetWindowInfoForWidget(this)); + return std::max(static_cast(std::ceil(static_cast(height()) * devicePixelRatio())), 1); +} + +std::optional DisplaySurface::getWindowInfo() +{ + std::optional ret(QtUtils::GetWindowInfoForWindow(this)); if (ret.has_value()) { m_last_window_width = ret->surface_width; @@ -72,7 +72,7 @@ std::optional DisplayWidget::getWindowInfo() return ret; } -void DisplayWidget::updateRelativeMode(bool enabled) +void DisplaySurface::updateRelativeMode(bool enabled) { #ifdef _WIN32 // prefer ClipCursor() over warping movement when we're using raw input @@ -104,17 +104,17 @@ void DisplayWidget::updateRelativeMode(bool enabled) #endif m_relative_mouse_start_pos = QCursor::pos(); updateCenterPos(); - grabMouse(); + setMouseGrabEnabled(true); } else if (m_relative_mouse_enabled) { m_relative_mouse_enabled = false; QCursor::setPos(m_relative_mouse_start_pos); - releaseMouse(); + setMouseGrabEnabled(false); } } -void DisplayWidget::updateCursor(bool hidden) +void DisplaySurface::updateCursor(bool hidden) { if (m_cursor_hidden == hidden) return; @@ -132,7 +132,7 @@ void DisplayWidget::updateCursor(bool hidden) } } -void DisplayWidget::handleCloseEvent(QCloseEvent* event) +void DisplaySurface::handleCloseEvent(QCloseEvent* event) { // Closing the separate widget will either cancel the close, or trigger shutdown. // In the latter case, it's going to destroy us, so don't let Qt do it first. @@ -152,28 +152,13 @@ void DisplayWidget::handleCloseEvent(QCloseEvent* event) event->ignore(); } -void DisplayWidget::destroy() +bool DisplaySurface::isActuallyFullscreen() const { - m_destroying = true; - -#ifdef __APPLE__ - // See Qt documentation, entire application is in full screen state, and the main - // window will get reopened fullscreen instead of windowed if we don't close the - // fullscreen window first. - if (isActuallyFullscreen()) - close(); -#endif - deleteLater(); + // DisplaySurface is always in a container, so we need to check parent window + return parent()->windowState() & Qt::WindowFullScreen; } -bool DisplayWidget::isActuallyFullscreen() const -{ - // I hate you QtWayland... have to check the parent, not ourselves. - QWidget* container = qobject_cast(parent()); - return container ? container->isFullScreen() : isFullScreen(); -} - -void DisplayWidget::updateCenterPos() +void DisplaySurface::updateCenterPos() { #ifdef _WIN32 if (m_clip_mouse_enabled) @@ -203,12 +188,14 @@ void DisplayWidget::updateCenterPos() #endif } -QPaintEngine* DisplayWidget::paintEngine() const -{ - return nullptr; -} - -bool DisplayWidget::event(QEvent* event) +// Keyboard focus and child windows are inconsistant across platforms; +// Windows: Can programmatically focus the child window, NVidia overlay can defocus it. +// X11: Can programmatically focus the child window. +// Wayland: Child window cannot be focused at all on most(?) DE. +// Mac: Can programmatically focus the child window. +// Thus for KB inputs we need to sometimes use the event filter. +// Mouse events are always delivered to the child window, so that seems consistant. +void DisplaySurface::handleKeyInputEvent(QEvent* event) { switch (event->type()) { @@ -229,7 +216,7 @@ bool DisplayWidget::event(QEvent* event) } if (key_event->isAutoRepeat()) - return true; + return; // For some reason, Windows sends "fake" key events. // Scenario: Press shift, press F1, release shift, release F1. @@ -246,7 +233,7 @@ bool DisplayWidget::event(QEvent* event) if (it != m_keys_pressed_with_modifiers.end()) { if (pressed) - return true; + return; else m_keys_pressed_with_modifiers.erase(it); } @@ -259,6 +246,23 @@ bool DisplayWidget::event(QEvent* event) InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), static_cast(pressed)); }); + return; + } + + default: + pxAssert(false); + return; + } +} + +bool DisplaySurface::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::KeyPress: + case QEvent::KeyRelease: + { + handleKeyInputEvent(event); return true; } @@ -268,7 +272,7 @@ bool DisplayWidget::event(QEvent* event) if (!m_relative_mouse_enabled) { - const qreal dpr = devicePixelRatioF(); + const qreal dpr = devicePixelRatio(); const QPoint mouse_pos = mouse_event->pos(); const float scaled_x = static_cast(static_cast(mouse_pos.x()) * dpr); @@ -352,9 +356,9 @@ bool DisplayWidget::event(QEvent* event) case QEvent::DevicePixelRatioChange: case QEvent::Resize: { - QWidget::event(event); + QWindow::event(event); - const float dpr = devicePixelRatioF(); + const float dpr = devicePixelRatio(); const u32 scaled_width = static_cast(std::max(static_cast(std::ceil(static_cast(width()) * dpr)), 1)); const u32 scaled_height = static_cast(std::max(static_cast(std::ceil(static_cast(height()) * dpr)), 1)); @@ -377,100 +381,44 @@ bool DisplayWidget::event(QEvent* event) return true; } - case QEvent::Close: - { - if (m_destroying) - return QWidget::event(event); - - handleCloseEvent(static_cast(event)); - return true; - } - - case QEvent::WindowStateChange: - { - QWidget::event(event); - - if (static_cast(event)->oldState() & Qt::WindowMinimized) - emit windowRestoredEvent(); - - return true; - } - default: - return QWidget::event(event); + return QWindow::event(event); } } -DisplayContainer::DisplayContainer() - : QStackedWidget(nullptr) +bool DisplaySurface::eventFilter(QObject* object, QEvent* event) { -} - -DisplayContainer::~DisplayContainer() = default; - -bool DisplayContainer::isNeeded(bool fullscreen, bool render_to_main) -{ -#if defined(_WIN32) || defined(__APPLE__) - return false; -#else - if (!isRunningOnWayland()) - return false; - - // We only need this on Wayland because of client-side decorations... - return (fullscreen || !render_to_main); -#endif -} - -bool DisplayContainer::isRunningOnWayland() -{ -#if defined(_WIN32) || defined(__APPLE__) - return false; -#else - const QString platform_name = QGuiApplication::platformName(); - return (platform_name == QStringLiteral("wayland")); -#endif -} - -void DisplayContainer::setDisplayWidget(DisplayWidget* widget) -{ - pxAssert(!m_display_widget); - m_display_widget = widget; - addWidget(widget); -} - -DisplayWidget* DisplayContainer::removeDisplayWidget() -{ - DisplayWidget* widget = m_display_widget; - pxAssert(widget); - m_display_widget = nullptr; - removeWidget(widget); - return widget; -} - -bool DisplayContainer::event(QEvent* event) -{ - if (event->type() == QEvent::Close && m_display_widget) - { - m_display_widget->handleCloseEvent(static_cast(event)); - return true; - } - - const bool res = QStackedWidget::event(event); - if (!m_display_widget) - return res; - switch (event->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: +#ifdef _WIN32 + // Nvidia overlay causes the child window to lose focus, but not its parent. + // Refocus the child window. + requestActivate(); +#endif + handleKeyInputEvent(event); + return true; + + // These events only work on the top level control. + // Which is this container when render to seperate or fullscreen is active. + case QEvent::Close: + handleCloseEvent(static_cast(event)); + return true; case QEvent::WindowStateChange: - { if (static_cast(event)->oldState() & Qt::WindowMinimized) - emit m_display_widget->windowRestoredEvent(); - } - break; + emit windowRestoredEvent(); + return false; + + case QEvent::ChildRemoved: + if (static_cast(event)->child() == m_container) + { + object->removeEventFilter(this); + m_container = nullptr; + } + return false; default: - break; + return false; } - - return res; } diff --git a/pcsx2-qt/DisplayWidget.h b/pcsx2-qt/DisplayWidget.h index 63334cdf42..2cf0edebfb 100644 --- a/pcsx2-qt/DisplayWidget.h +++ b/pcsx2-qt/DisplayWidget.h @@ -3,22 +3,25 @@ #pragma once #include "common/WindowInfo.h" -#include -#include +#include #include #include class QCloseEvent; -class DisplayWidget final : public QWidget +class DisplaySurface final : public QWindow { Q_OBJECT public: - explicit DisplayWidget(QWidget* parent); - ~DisplayWidget(); + explicit DisplaySurface(); + ~DisplaySurface(); - QPaintEngine* paintEngine() const override; + // while QWindow can be used directly as a window, Popups requre a QWidget parent. + // Additionally, we use saveGeometry/restoreGeometry for render to seperate window mode + // but those functions only exist on QWidget. + // Thus, we always need a container widget. + QWidget* createWindowContainer(QWidget* parent = nullptr); int scaledWindowWidth() const; int scaledWindowHeight() const; @@ -28,15 +31,15 @@ public: void updateRelativeMode(bool enabled); void updateCursor(bool hidden); - void handleCloseEvent(QCloseEvent* event); - void destroy(); - Q_SIGNALS: void windowResizedEvent(int width, int height, float scale); void windowRestoredEvent(); protected: + void handleCloseEvent(QCloseEvent* event); + void handleKeyInputEvent(QEvent* event); bool event(QEvent* event) override; + bool eventFilter(QObject* object, QEvent* event) override; private: bool isActuallyFullscreen() const; @@ -49,34 +52,12 @@ private: bool m_clip_mouse_enabled = false; #endif bool m_cursor_hidden = false; - bool m_destroying = false; std::vector m_keys_pressed_with_modifiers; u32 m_last_window_width = 0; u32 m_last_window_height = 0; float m_last_window_scale = 1.0f; -}; -class DisplayContainer final : public QStackedWidget -{ - Q_OBJECT - -public: - DisplayContainer(); - ~DisplayContainer(); - - // Wayland is broken in lots of ways, so we need to check for it. - static bool isRunningOnWayland(); - - static bool isNeeded(bool fullscreen, bool render_to_main); - - void setDisplayWidget(DisplayWidget* widget); - DisplayWidget* removeDisplayWidget(); - -protected: - bool event(QEvent* event) override; - -private: - DisplayWidget* m_display_widget = nullptr; + QWidget* m_container = nullptr; }; diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 8f30882c1d..9968dddabd 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -86,16 +86,6 @@ const char* MainWindow::DISC_IMAGE_FILTER = QT_TRANSLATE_NOOP("MainWindow", "All MainWindow* g_main_window = nullptr; -#if defined(_WIN32) || defined(__APPLE__) -static const bool s_use_central_widget = false; -#else -// Qt Wayland is broken. Any sort of stacked widget usage fails to update, -// leading to broken window resizes, no display rendering, etc. So, we mess -// with the central widget instead. Which we can't do on xorg, because it -// breaks window resizing there... -static bool s_use_central_widget = false; -#endif - // UI thread VM validity. static bool s_vm_valid = false; static bool s_vm_paused = false; @@ -113,19 +103,6 @@ MainWindow::MainWindow() { pxAssert(!g_main_window); g_main_window = this; - - // Native window rendering is broken in wayland. - // Let's work around it by disabling it for every widget besides - // DisplayWidget. - // Additionally, alien widget rendering is much more performant, so we - // should have a nice responsiveness boost in our UI :) - // QTBUG-133919, reported upstream by govanify - QGuiApplication::setAttribute(Qt::AA_NativeWindows, false); - QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); - -#if !defined(_WIN32) && !defined(__APPLE__) - s_use_central_widget = DisplayContainer::isRunningOnWayland(); -#endif } MainWindow::~MainWindow() @@ -192,7 +169,7 @@ static void makeIconsMasks(QWidget* menu) QWidget* MainWindow::getContentParent() { - return s_use_central_widget ? static_cast(this) : static_cast(m_ui.mainContainer); + return static_cast(m_ui.mainContainer); } void MainWindow::setupAdditionalUi() @@ -219,15 +196,7 @@ void MainWindow::setupAdditionalUi() m_game_list_widget = new GameListWidget(getContentParent()); m_game_list_widget->initialize(); m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles()); - if (s_use_central_widget) - { - m_ui.mainContainer = nullptr; // setCentralWidget() will delete this - setCentralWidget(m_game_list_widget); - } - else - { - m_ui.mainContainer->addWidget(m_game_list_widget); - } + m_ui.mainContainer->addWidget(m_game_list_widget); m_status_progress_widget = new QProgressBar(m_ui.statusBar); m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); @@ -532,7 +501,7 @@ void MainWindow::recreate() if (was_display_created) { g_emu_thread->setSurfaceless(true); - while (m_display_widget || !g_emu_thread->isSurfaceless()) + while (m_display_surface || !g_emu_thread->isSurfaceless()) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); m_display_created = false; @@ -1033,11 +1002,10 @@ void MainWindow::updateWindowTitle() if (windowTitle() != main_title) setWindowTitle(main_title); - if (m_display_widget && !isRenderingToMain()) + if (m_display_container && !isRenderingToMain()) { - QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); - if (container->windowTitle() != display_title) - container->setWindowTitle(display_title); + if (m_display_container->windowTitle() != display_title) + m_display_container->setWindowTitle(display_title); } if (g_log_window) @@ -1052,7 +1020,7 @@ void MainWindow::updateWindowState(bool force_visible) const bool hide_window = !isRenderingToMain() && shouldHideMainWindow(); const bool disable_resize = Host::GetBoolSettingValue("UI", "DisableWindowResize", false); - const bool has_window = s_vm_valid || m_display_widget; + const bool has_window = s_vm_valid || m_display_surface; // Need to test both valid and display widget because of startup (vm invalid while window is created). const bool visible = force_visible || !hide_window || !has_window; @@ -1065,8 +1033,8 @@ void MainWindow::updateWindowState(bool force_visible) QtUtils::SetWindowResizeable(this, resizeable); // Update the display widget too if rendering separately. - if (m_display_widget && !isRenderingToMain()) - QtUtils::SetWindowResizeable(getDisplayContainer(), resizeable); + if (m_display_surface && !isRenderingToMain()) + QtUtils::SetWindowResizeable(m_display_container, resizeable); } void MainWindow::setProgressBar(int current, int total) @@ -1093,26 +1061,20 @@ void MainWindow::clearProgressBar() bool MainWindow::isShowingGameList() const { - if (s_use_central_widget) - return (centralWidget() == m_game_list_widget); - else - return (m_ui.mainContainer->currentIndex() == 0); + return (m_ui.mainContainer->currentIndex() == 0); } bool MainWindow::isRenderingFullscreen() const { - if (!MTGS::IsOpen() || !m_display_widget) + if (!MTGS::IsOpen() || !m_display_surface) return false; - return getDisplayContainer()->isFullScreen(); + return m_display_container->isFullScreen(); } bool MainWindow::isRenderingToMain() const { - if (s_use_central_widget) - return (m_display_widget && centralWidget() == m_display_widget); - else - return (m_display_widget && m_ui.mainContainer->indexOf(m_display_widget) == 1); + return (m_display_surface && m_ui.mainContainer->indexOf(m_display_container) == 1); } bool MainWindow::shouldHideMouseCursor() const @@ -1137,14 +1099,14 @@ bool MainWindow::shouldMouseLock() const if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false)) return false; - if (m_display_created == false || m_display_widget == nullptr && !isRenderingToMain()) + if (m_display_created == false || m_display_surface == nullptr && !isRenderingToMain()) return false; bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) && (!m_controller_settings_window || m_controller_settings_window->isHidden()) && (!m_settings_window || m_settings_window->isHidden()); - auto* displayWindow = isRenderingToMain() ? window() : m_display_widget->window(); + auto* displayWindow = isRenderingToMain() ? window() : m_display_container->window(); if (displayWindow == nullptr) return false; @@ -1194,7 +1156,7 @@ void MainWindow::switchToGameListView() // switch to surfaceless. we have to wait until the display widget is gone before we swap over. g_emu_thread->setSurfaceless(true); - while (m_display_widget) + while (m_display_surface) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1); } } @@ -1211,8 +1173,8 @@ void MainWindow::switchToEmulationView() if (s_vm_paused && !m_was_paused_on_surface_loss) g_emu_thread->setVMPaused(false); - if (m_display_widget) - m_display_widget->setFocus(); + if (m_display_surface) + m_display_container->setFocus(); } void MainWindow::refreshGameList(bool invalidate_cache, bool popup_on_error) @@ -1349,7 +1311,7 @@ void MainWindow::requestExit(bool allow_confirm) void MainWindow::checkForSettingChanges() { - if (m_display_widget) + if (m_display_surface) updateDisplayWidgetCursor(); updateWindowState(); @@ -1359,10 +1321,10 @@ void MainWindow::checkForSettingChanges() std::optional MainWindow::getWindowInfo() { - if (!m_display_widget || isRenderingToMain()) - return QtUtils::GetWindowInfoForWidget(this); - else if (QWidget* widget = getDisplayContainer()) - return QtUtils::GetWindowInfoForWidget(widget); + if (!m_display_surface || isRenderingToMain()) + return QtUtils::GetWindowInfoForWindow(this); + else if (m_display_surface) + return QtUtils::GetWindowInfoForWindow(m_display_surface); else return std::nullopt; } @@ -1595,7 +1557,7 @@ void MainWindow::onSaveStateMenuAboutToShow() void MainWindow::onStartFullscreenUITriggered() { - if (m_display_widget) + if (m_display_surface) g_emu_thread->stopFullscreenUI(); else g_emu_thread->startFullscreenUI(Host::GetBaseBoolSettingValue("UI", "StartFullscreen", false)); @@ -2033,7 +1995,7 @@ void MainWindow::onVMPaused() updateStatusBarWidgetVisibility(); m_last_fps_status = m_status_verbose_widget->text(); m_status_verbose_widget->setText(tr("Paused")); - if (m_display_widget) + if (m_display_surface) updateDisplayWidgetCursor(); } @@ -2055,10 +2017,10 @@ void MainWindow::onVMResumed() updateStatusBarWidgetVisibility(); m_status_verbose_widget->setText(m_last_fps_status); m_last_fps_status = QString(); - if (m_display_widget) + if (m_display_surface) { updateDisplayWidgetCursor(); - m_display_widget->setFocus(); + m_display_container->setFocus(); } } @@ -2090,7 +2052,7 @@ void MainWindow::onVMStopped() return; } - if (m_display_widget) + if (m_display_surface) updateDisplayWidgetCursor(); else switchToGameListView(); @@ -2360,22 +2322,17 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, DevCon.WriteLn("acquireRenderWindow() recreate=%s fullscreen=%s render_to_main=%s surfaceless=%s", recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false", surfaceless ? "true" : "false"); - QWidget* container = m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget); const bool is_fullscreen = isRenderingFullscreen(); const bool is_rendering_to_main = isRenderingToMain(); - const bool changing_surfaceless = (!m_display_widget != surfaceless); + const bool changing_surfaceless = (!m_display_surface != surfaceless); if (m_display_created && !recreate_window && fullscreen == is_fullscreen && is_rendering_to_main == render_to_main && !changing_surfaceless) { - return m_display_widget ? m_display_widget->getWindowInfo() : WindowInfo(); + return m_display_surface ? m_display_surface->getWindowInfo() : WindowInfo(); } // Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off. - // .. except on Wayland, where everything tends to break if you don't recreate. - const bool has_container = (m_display_container != nullptr); - const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main); - if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && has_container == needs_container && - !needs_container && !changing_surfaceless) + if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main && !changing_surfaceless) { DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed")); @@ -2385,23 +2342,23 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, if (fullscreen) { - container->showFullScreen(); + m_display_container->showFullScreen(); } else { if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain()) - container->setGeometry(geometry()); + m_display_container->setGeometry(geometry()); else restoreDisplayWindowGeometryFromConfig(); - container->showNormal(); + m_display_container->showNormal(); } updateDisplayWidgetCursor(); - m_display_widget->setFocus(); + m_display_container->setFocus(); updateWindowState(); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); - return m_display_widget->getWindowInfo(); + return m_display_surface->getWindowInfo(); } destroyDisplayWidget(surfaceless); @@ -2420,7 +2377,7 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, createDisplayWidget(fullscreen, render_to_main); - std::optional wi = m_display_widget->getWindowInfo(); + std::optional wi = m_display_surface->getWindowInfo(); if (!wi.has_value()) { QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget")); @@ -2428,14 +2385,13 @@ std::optional MainWindow::acquireRenderWindow(bool recreate_window, return std::nullopt; } - g_emu_thread->connectDisplaySignals(m_display_widget); + g_emu_thread->connectDisplaySignals(m_display_surface); updateWindowTitle(); updateWindowState(); updateDisplayWidgetCursor(); - m_display_widget->setFocus(); - + m_display_container->setFocus(); return wi; } @@ -2449,61 +2405,39 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main) QGuiApplication::sync(); } - QWidget* container; - if (DisplayContainer::isNeeded(fullscreen, render_to_main)) + m_display_surface = new DisplaySurface(); + if (fullscreen || !render_to_main) { - m_display_container = new DisplayContainer(); - m_display_widget = new DisplayWidget(m_display_container); - m_display_container->setDisplayWidget(m_display_widget); - container = m_display_container; + m_display_container = m_display_surface->createWindowContainer(); + m_display_container->setWindowTitle(windowTitle()); + m_display_container->setWindowIcon(windowIcon()); } else { - m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? getContentParent() : nullptr); - container = m_display_widget; - } - - if (fullscreen || !render_to_main) - { - container->setWindowTitle(windowTitle()); - container->setWindowIcon(windowIcon()); + m_display_container = m_display_surface->createWindowContainer(getContentParent()); } if (fullscreen) { - // Don't risk doing this on Wayland, it really doesn't like window state changes, - // and positioning has no effect anyway. - if (!s_use_central_widget) - { - if (isVisible() && g_emu_thread->shouldRenderToMain()) - container->move(pos()); - else - restoreDisplayWindowGeometryFromConfig(); - } - - container->showFullScreen(); + // On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on + if (isVisible() && g_emu_thread->shouldRenderToMain()) + m_display_container->move(pos()); + else + restoreDisplayWindowGeometryFromConfig(); + m_display_container->showFullScreen(); } else if (!render_to_main) { if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain()) - container->setGeometry(geometry()); + m_display_container->setGeometry(geometry()); else restoreDisplayWindowGeometryFromConfig(); - container->showNormal(); - } - else if (s_use_central_widget) - { - m_game_list_widget->setVisible(false); - takeCentralWidget(); - m_game_list_widget->setParent(this); // takeCentralWidget() removes parent - setCentralWidget(m_display_widget); - m_display_widget->setFocus(); - update(); + m_display_container->showNormal(); } else { pxAssertRel(m_ui.mainContainer->count() == 1, "Has no display widget"); - m_ui.mainContainer->addWidget(container); + m_ui.mainContainer->addWidget(m_display_container); m_ui.mainContainer->setCurrentIndex(1); } @@ -2515,7 +2449,7 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main) void MainWindow::displayResizeRequested(qint32 width, qint32 height) { - if (!m_display_widget) + if (!m_display_surface) return; // unapply the pixel scaling factor for hidpi @@ -2523,15 +2457,15 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height) width = static_cast(std::max(static_cast(std::lroundf(static_cast(width) / dpr)), 1)); height = static_cast(std::max(static_cast(std::lroundf(static_cast(height) / dpr)), 1)); - if (m_display_container || !m_display_widget->parent()) + if (!m_display_container->parent()) { // no parent - rendering to separate window. easy. - QtUtils::ResizePotentiallyFixedSizeWindow(getDisplayContainer(), width, height); + QtUtils::ResizePotentiallyFixedSizeWindow(m_display_container, width, height); return; } // we are rendering to the main window. we have to add in the extra height from the toolbar/status bar. - const s32 extra_height = this->height() - m_display_widget->height(); + const s32 extra_height = this->height() - m_display_container->height(); QtUtils::ResizePotentiallyFixedSizeWindow(this, width, height + extra_height); } @@ -2542,7 +2476,7 @@ void MainWindow::mouseModeRequested(bool relative_mode, bool hide_cursor) m_relative_mouse_mode = relative_mode; m_hide_mouse_cursor = hide_cursor; - if (m_display_widget && !s_vm_paused) + if (m_display_surface && !s_vm_paused) updateDisplayWidgetCursor(); } @@ -2558,44 +2492,30 @@ void MainWindow::releaseRenderWindow() void MainWindow::destroyDisplayWidget(bool show_game_list) { - if (!m_display_widget) + if (!m_display_surface) return; if (!isRenderingFullscreen() && !isRenderingToMain()) saveDisplayWindowGeometryToConfig(); - if (m_display_container) - m_display_container->removeDisplayWidget(); + // Detatch display surface from container. + m_display_surface->setParent(nullptr); if (isRenderingToMain()) { - if (s_use_central_widget) + pxAssertRel(m_ui.mainContainer->indexOf(m_display_container) == 1, "Display widget in stack"); + m_ui.mainContainer->removeWidget(m_display_container); + if (show_game_list) { - pxAssertRel(centralWidget() == m_display_widget, "Display widget is currently central"); - takeCentralWidget(); - if (show_game_list) - { - m_game_list_widget->setVisible(true); - setCentralWidget(m_game_list_widget); - m_game_list_widget->resizeTableViewColumnsToFit(); - } - } - else - { - pxAssertRel(m_ui.mainContainer->indexOf(m_display_widget) == 1, "Display widget in stack"); - m_ui.mainContainer->removeWidget(m_display_widget); - if (show_game_list) - { - m_ui.mainContainer->setCurrentIndex(0); - m_game_list_widget->resizeTableViewColumnsToFit(); - } + m_ui.mainContainer->setCurrentIndex(0); + m_game_list_widget->resizeTableViewColumnsToFit(); } } - if (m_display_widget) + if (m_display_surface) { - m_display_widget->destroy(); - m_display_widget = nullptr; + m_display_surface->destroy(); + m_display_surface = nullptr; } if (m_display_container) @@ -2609,22 +2529,17 @@ void MainWindow::destroyDisplayWidget(bool show_game_list) void MainWindow::updateDisplayWidgetCursor() { - pxAssertRel(m_display_widget, "Should have a display widget"); - m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused && m_relative_mouse_mode); - m_display_widget->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor()); + pxAssertRel(m_display_surface, "Should have a display widget"); + m_display_surface->updateRelativeMode(s_vm_valid && !s_vm_paused && m_relative_mouse_mode); + m_display_surface->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor()); } void MainWindow::focusDisplayWidget() { - if (!m_display_widget || centralWidget() != m_display_widget) + if (!m_display_surface || centralWidget() != m_display_container) return; - m_display_widget->setFocus(); -} - -QWidget* MainWindow::getDisplayContainer() const -{ - return (m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget)); + m_display_container->setFocus(); } void MainWindow::setupMouseMoveHandler() @@ -2653,18 +2568,19 @@ void MainWindow::checkMousePosition(int x, int y) // physical mouse position const QPoint physicalPos(x, y); - const auto* displayWindow = getDisplayContainer()->window(); + const auto* displayWindow = m_display_surface; // logical (DIP) frame rect - QRectF logicalBounds = displayWindow->geometry(); + const QSize logicalSize = displayWindow->size(); + const QPoint logicalPosition = displayWindow->position() + displayWindow->parent()->position(); // physical frame rect - const qreal scale = displayWindow->devicePixelRatioF(); - QRectF physicalBounds( - logicalBounds.x() * scale, - logicalBounds.y() * scale, - logicalBounds.width() * scale, - logicalBounds.height() * scale); + const qreal scale = displayWindow->devicePixelRatio(); + const QRectF physicalBounds( + logicalPosition.x() * scale, + logicalPosition.y() * scale, + logicalSize.width() * scale, + logicalSize.height() * scale); if (physicalBounds.contains(physicalPos)) return; @@ -2677,14 +2593,13 @@ void MainWindow::checkMousePosition(int x, int y) void MainWindow::saveDisplayWindowGeometryToConfig() { - QWidget* container = getDisplayContainer(); - if (container->windowState() & Qt::WindowFullScreen) + if (m_display_container->windowState() & Qt::WindowFullScreen) { // if we somehow ended up here, don't save the fullscreen state to the config return; } - const QByteArray geometry = getDisplayContainer()->saveGeometry(); + const QByteArray geometry = m_display_container->saveGeometry(); const QByteArray geometry_b64 = geometry.toBase64(); const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry"); if (old_geometry_b64 != geometry_b64.constData()) @@ -2698,18 +2613,17 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig() { const std::string geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry"); const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64)); - QWidget* container = getDisplayContainer(); if (!geometry.isEmpty()) { - container->restoreGeometry(geometry); + m_display_container->restoreGeometry(geometry); // make sure we're not loading a dodgy config which had fullscreen set... - container->setWindowState(container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive)); + m_display_container->setWindowState(m_display_container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive)); } else { // default size - container->resize(640, 480); + m_display_container->resize(640, 480); } } @@ -3259,7 +3173,7 @@ MainWindow::VMLock MainWindow::pauseAndLockVM() // period when there's no window, and Qt might shut us down. So avoid it there. // On MacOS, it forces a workspace switch, which is kinda jarring. #ifndef __APPLE__ - const bool was_fullscreen = g_emu_thread->isFullscreen() && !s_use_central_widget; + const bool was_fullscreen = g_emu_thread->isFullscreen(); #else const bool was_fullscreen = false; #endif @@ -3278,25 +3192,20 @@ MainWindow::VMLock MainWindow::pauseAndLockVM() g_emu_thread->setFullscreen(false, false); - // Container could change... thanks Wayland. - QWidget* container; - while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || - !(container = getDisplayContainer()) || container->isFullScreen())) + // Process events untill both EmuThread and Qt have finished exiting fullscreen + while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_container->isFullScreen())) { QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); } } - // Now we'll either have a borderless window, or a regular window (if we were exclusive fullscreen). - QWidget* dialog_parent = getDisplayContainer(); - // Ensure main window is visible. if (!g_main_window->isVisible()) g_main_window->show(); g_main_window->raise(); g_main_window->activateWindow(); - return VMLock(dialog_parent, was_paused, was_fullscreen); + return VMLock(m_display_container, was_paused, was_fullscreen); } void MainWindow::rescanFile(const std::string& path) diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 93e72ec65c..0f5a4ed6b6 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -20,7 +20,7 @@ class QProgressBar; class AutoUpdaterDialog; -class DisplayWidget; +class DisplaySurface; class DisplayContainer; class GameListWidget; class ControllerSettingsWindow; @@ -258,7 +258,6 @@ private: bool shouldAbortForMemcardBusy(const VMLock& lock); QWidget* getContentParent(); - QWidget* getDisplayContainer() const; void saveDisplayWindowGeometryToConfig(); void restoreDisplayWindowGeometryFromConfig(); void createDisplayWidget(bool fullscreen, bool render_to_main); @@ -292,8 +291,8 @@ private: Ui::MainWindow m_ui; GameListWidget* m_game_list_widget = nullptr; - DisplayWidget* m_display_widget = nullptr; - DisplayContainer* m_display_container = nullptr; + DisplaySurface* m_display_surface = nullptr; + QWidget* m_display_container = nullptr; SettingsWindow* m_settings_window = nullptr; ControllerSettingsWindow* m_controller_settings_window = nullptr; diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 2c564e856a..e76a1cd938 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -770,12 +770,12 @@ void EmuThread::enumerateVibrationMotors() onVibrationMotorsEnumerated(qmotors); } -void EmuThread::connectDisplaySignals(DisplayWidget* widget) +void EmuThread::connectDisplaySignals(DisplaySurface* widget) { widget->disconnect(this); - connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized); - connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow); + connect(widget, &DisplaySurface::windowResizedEvent, this, &EmuThread::onDisplayWindowResized); + connect(widget, &DisplaySurface::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow); } void EmuThread::onDisplayWindowResized(int width, int height, float scale) diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h index 37627d0e56..e732b6d074 100644 --- a/pcsx2-qt/QtHost.h +++ b/pcsx2-qt/QtHost.h @@ -28,7 +28,7 @@ class SettingsInterface; -class DisplayWidget; +class DisplaySurface; struct VMBootParameters; enum class CDVD_SourceType : uint8_t; @@ -69,7 +69,7 @@ public: /// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main). std::optional acquireRenderWindow(bool recreate_window); - void connectDisplaySignals(DisplayWidget* widget); + void connectDisplaySignals(DisplaySurface* widget); void releaseRenderWindow(); void startBackgroundControllerPollTimer(); diff --git a/pcsx2-qt/QtUtils.cpp b/pcsx2-qt/QtUtils.cpp index 93fe4dad10..36155747f3 100644 --- a/pcsx2-qt/QtUtils.cpp +++ b/pcsx2-qt/QtUtils.cpp @@ -8,10 +8,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -40,8 +38,6 @@ #if defined(_WIN32) #include "common/RedtapeWindows.h" #include -#elif !defined(APPLE) -#include #endif namespace QtUtils @@ -254,68 +250,6 @@ namespace QtUtils widget->resize(width, height); } - std::optional GetWindowInfoForWidget(QWidget* widget) - { - WindowInfo wi; - - // Windows and Apple are easy here since there's no display connection. -#if defined(_WIN32) - wi.type = WindowInfo::Type::Win32; - wi.window_handle = reinterpret_cast(widget->winId()); -#elif defined(__APPLE__) - wi.type = WindowInfo::Type::MacOS; - wi.window_handle = reinterpret_cast(widget->winId()); -#else - QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); - const QString platform_name = QGuiApplication::platformName(); - if (platform_name == QStringLiteral("xcb")) - { - // Can't get a handle for an unmapped window in X, it doesn't like it. - if (!widget->isVisible()) - { - Console.WriteLn("Returning null window info for widget because it is not visible."); - return std::nullopt; - } - - wi.type = WindowInfo::Type::X11; - wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle()); - wi.window_handle = reinterpret_cast(widget->winId()); - } - else if (platform_name == QStringLiteral("wayland")) - { - wi.type = WindowInfo::Type::Wayland; - wi.display_connection = pni->nativeResourceForWindow("display", widget->windowHandle()); - wi.window_handle = pni->nativeResourceForWindow("surface", widget->windowHandle()); - } - else - { - Console.WriteLn("Unknown PNI platform '%s'.", platform_name.toUtf8().constData()); - return std::nullopt; - } -#endif - - const qreal dpr = widget->devicePixelRatioF(); - wi.surface_width = static_cast(static_cast(widget->width()) * dpr); - wi.surface_height = static_cast(static_cast(widget->height()) * dpr); - wi.surface_scale = static_cast(dpr); - - // Query refresh rate, we need it for sync. - std::optional surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi); - if (!surface_refresh_rate.has_value()) - { - // Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise. - const QScreen* widget_screen = widget->screen(); - if (!widget_screen) - widget_screen = QGuiApplication::primaryScreen(); - surface_refresh_rate = widget_screen ? static_cast(widget_screen->refreshRate()) : 0.0f; - } - - wi.surface_refresh_rate = surface_refresh_rate.value(); - INFO_LOG("Surface refresh rate: {} hz", wi.surface_refresh_rate); - - return wi; - } - QString AbstractItemModelToCSV(QAbstractItemModel* model, int role, bool useQuotes) { QString csv; diff --git a/pcsx2-qt/QtUtils.h b/pcsx2-qt/QtUtils.h index 9978ec0be3..cbccf0a320 100644 --- a/pcsx2-qt/QtUtils.h +++ b/pcsx2-qt/QtUtils.h @@ -9,11 +9,20 @@ #include #include #include +#include +#include +#if !defined(_WIN32) and !defined(__APPLE__) +#include +#endif +#include #include #include #include +#include #include +#include "common/Console.h" + class ByteStream; class QAction; @@ -83,8 +92,82 @@ namespace QtUtils /// Adjusts the fixed size for a window if it's not resizeable. void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height); - /// Returns the common window info structure for a Qt widget. - std::optional GetWindowInfoForWidget(QWidget* widget); + /// Returns the common window info structure for a Qt Window/Widget. + template + requires std::is_base_of_v || std::is_base_of_v + std::optional GetWindowInfoForWindow(T* window) + { + WindowInfo wi; + + // Windows and Apple are easy here since there's no display connection. +#if defined(_WIN32) + wi.type = WindowInfo::Type::Win32; + wi.window_handle = reinterpret_cast(window->winId()); +#elif defined(__APPLE__) + wi.type = WindowInfo::Type::MacOS; + wi.window_handle = reinterpret_cast(window->winId()); +#else + QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface(); + const QString platform_name = QGuiApplication::platformName(); + + QWindow* windowHandle; + if constexpr (std::is_base_of_v) + windowHandle = window->windowHandle(); + else + windowHandle = window; + + if (platform_name == QStringLiteral("xcb")) + { + // Can't get a handle for an unmapped window in X, it doesn't like it. + if (!window->isVisible()) + { + Console.WriteLn("Returning null window info for widget because it is not visible."); + return std::nullopt; + } + + wi.type = WindowInfo::Type::X11; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle); + wi.window_handle = reinterpret_cast(window->winId()); + } + else if (platform_name == QStringLiteral("wayland")) + { + wi.type = WindowInfo::Type::Wayland; + wi.display_connection = pni->nativeResourceForWindow("display", windowHandle); + wi.window_handle = pni->nativeResourceForWindow("surface", windowHandle); + } + else + { + Console.WriteLn("Unknown PNI platform '%s'.", platform_name.toUtf8().constData()); + return std::nullopt; + } +#endif + + qreal dpr; + if constexpr (std::is_base_of_v) + dpr = window->devicePixelRatioF(); + else + dpr = window->devicePixelRatio(); + + wi.surface_width = static_cast(static_cast(window->width()) * dpr); + wi.surface_height = static_cast(static_cast(window->height()) * dpr); + wi.surface_scale = static_cast(dpr); + + // Query refresh rate, we need it for sync. + std::optional surface_refresh_rate = WindowInfo::QueryRefreshRateForWindow(wi); + if (!surface_refresh_rate.has_value()) + { + // Fallback to using the screen, getting the rate for Wayland is an utter mess otherwise. + const QScreen* widget_screen = window->screen(); + if (!widget_screen) + widget_screen = QGuiApplication::primaryScreen(); + surface_refresh_rate = widget_screen ? static_cast(widget_screen->refreshRate()) : 0.0f; + } + + wi.surface_refresh_rate = surface_refresh_rate.value(); + INFO_LOG("Surface refresh rate: {} hz", wi.surface_refresh_rate); + + return wi; + } /// Converts a value to a QString of said value with a proper fixed width template