Qt: Fix game list glitching out in X11

This commit is contained in:
Connor McLaughlin 2022-07-09 16:59:13 +10:00 committed by refractionpcsx2
parent c11ca2ff64
commit 1cee55bf45
5 changed files with 178 additions and 145 deletions

View File

@ -402,15 +402,24 @@ DisplayContainer::DisplayContainer()
DisplayContainer::~DisplayContainer() = default;
bool DisplayContainer::IsNeeded(bool fullscreen, bool render_to_main)
bool DisplayContainer::isNeeded(bool fullscreen, bool render_to_main)
{
#if defined(_WIN32) || defined(__APPLE__)
return false;
#else
if (!fullscreen && render_to_main)
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

View File

@ -75,7 +75,10 @@ public:
DisplayContainer();
~DisplayContainer();
static bool IsNeeded(bool fullscreen, bool render_to_main);
// 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();

View File

@ -85,6 +85,16 @@ const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion";
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;
@ -94,6 +104,10 @@ MainWindow::MainWindow(const QString& unthemed_style_name)
{
pxAssert(!g_main_window);
g_main_window = this;
#if !defined(_WIN32) && !defined(__APPLE__)
s_use_central_widget = DisplayContainer::isRunningOnWayland();
#endif
}
MainWindow::~MainWindow()
@ -151,6 +165,11 @@ static void makeIconsMasks(QWidget* menu)
}
}
QWidget* MainWindow::getContentParent()
{
return s_use_central_widget ? static_cast<QWidget*>(this) : static_cast<QWidget*>(m_ui.mainContainer);
}
void MainWindow::setupAdditionalUi()
{
setWindowIcon(QIcon(QStringLiteral("%1/icons/AppIconLarge.png").arg(QtHost::GetResourcesBasePath())));
@ -169,10 +188,18 @@ void MainWindow::setupAdditionalUi()
m_ui.actionViewStatusBar->setChecked(status_bar_visible);
m_ui.statusBar->setVisible(status_bar_visible);
m_game_list_widget = new GameListWidget(this);
m_game_list_widget = new GameListWidget(getContentParent());
m_game_list_widget->initialize();
m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
setCentralWidget(m_game_list_widget);
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_status_progress_widget = new QProgressBar(m_ui.statusBar);
m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
@ -697,14 +724,12 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
m_ui.actionPause->setEnabled(running);
m_ui.actionChangeDisc->setEnabled(running);
m_ui.actionScreenshot->setEnabled(running);
m_ui.actionViewSystemDisplay->setEnabled(starting_or_running);
m_ui.menuChangeDisc->setEnabled(running);
m_ui.actionSaveState->setEnabled(running);
m_ui.menuSaveState->setEnabled(running);
m_ui.menuWindowSize->setEnabled(starting_or_running);
m_ui.actionFullscreen->setEnabled(starting_or_running);
m_ui.actionViewGameProperties->setEnabled(running);
m_game_list_widget->setDisabled(starting && !running);
@ -810,7 +835,10 @@ void MainWindow::clearProgressBar()
bool MainWindow::isShowingGameList() const
{
return (centralWidget() == m_game_list_widget);
if (s_use_central_widget)
return (centralWidget() == m_game_list_widget);
else
return (m_ui.mainContainer->currentIndex() == 0);
}
bool MainWindow::isRenderingFullscreen() const
@ -819,12 +847,15 @@ bool MainWindow::isRenderingFullscreen() const
if (!display || !m_display_widget)
return false;
return (m_display_widget->parent() != this && (m_display_widget->isFullScreen() || display->IsFullscreen()));
return getDisplayContainer()->isFullScreen() || display->IsFullscreen();
}
bool MainWindow::isRenderingToMain() const
{
return (m_display_widget && m_display_widget->parent() == this);
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);
}
bool MainWindow::shouldHideMouseCursor() const
@ -834,7 +865,7 @@ bool MainWindow::shouldHideMouseCursor() const
void MainWindow::switchToGameListView()
{
if (centralWidget() == m_game_list_widget)
if (isShowingGameList())
{
m_game_list_widget->setFocus();
return;
@ -851,17 +882,11 @@ void MainWindow::switchToGameListView()
while (m_display_widget)
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
}
pxAssertMsg(!centralWidget(), "Should not have a central widget at game list switch time");
takeCentralWidget();
setCentralWidget(m_game_list_widget);
m_game_list_widget->setVisible(true);
m_game_list_widget->setFocus();
}
void MainWindow::switchToEmulationView()
{
if (!s_vm_valid || (m_display_widget && centralWidget() == m_display_widget))
if (!s_vm_valid || !isShowingGameList())
return;
// we're no longer surfaceless! this will call back to UpdateDisplay(), which will swap the widget out.
@ -1615,44 +1640,7 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
const std::string fullscreen_mode(Host::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""));
const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen());
QWidget* container;
if (DisplayContainer::IsNeeded(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;
}
else
{
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? this : nullptr);
container = m_display_widget;
}
if (fullscreen || !render_to_main)
{
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
}
if (fullscreen)
{
if (!is_exclusive_fullscreen)
container->showFullScreen();
else
container->showNormal();
}
else if (!render_to_main)
{
restoreDisplayWindowGeometryFromConfig();
container->showNormal();
}
else
{
m_game_list_widget->setVisible(false);
takeCentralWidget();
setCentralWidget(m_display_widget);
}
createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen);
// we need the surface visible.. this might be able to be replaced with something else
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
@ -1661,7 +1649,7 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
if (!wi.has_value())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
destroyDisplayWidget();
destroyDisplayWidget(true);
return nullptr;
}
@ -1671,7 +1659,7 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
Host::GetBoolSettingValue("EmuCore/GS", "ThreadedPresentation", false), Host::GetBoolSettingValue("EmuCore/GS", "UseDebugDevice", false)))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context."));
destroyDisplayWidget();
destroyDisplayWidget(true);
return nullptr;
}
@ -1681,10 +1669,13 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
updateWindowTitle();
updateWindowState();
m_display_widget->setFocus();
m_ui.actionViewSystemDisplay->setEnabled(true);
m_ui.actionFullscreen->setEnabled(true);
m_display_widget->setShouldHideCursor(shouldHideMouseCursor());
m_display_widget->updateRelativeMode(s_vm_valid && !s_vm_paused);
m_display_widget->updateCursor(s_vm_valid && !s_vm_paused);
m_display_widget->setFocus();
host_display->DoneRenderContextCurrent();
return m_display_widget;
@ -1708,7 +1699,7 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, b
// 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);
const bool needs_container = DisplayContainer::isNeeded(fullscreen, render_to_main);
if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container && !needs_container && !changing_surfaceless)
{
DevCon.WriteLn("Toggling to %s without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
@ -1740,66 +1731,19 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, b
host_display->DestroyRenderSurface();
destroyDisplayWidget();
destroyDisplayWidget(surfaceless);
// if we're going to surfaceless, we're done here
if (surfaceless)
return nullptr;
if (DisplayContainer::IsNeeded(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;
}
else
{
m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? this : nullptr);
container = m_display_widget;
}
if (fullscreen || !render_to_main)
{
container->setWindowTitle(windowTitle());
container->setWindowIcon(windowIcon());
// make sure the game list widget is still visible
if (centralWidget() != m_game_list_widget && !fullscreen)
{
setCentralWidget(m_game_list_widget);
m_game_list_widget->setVisible(true);
}
}
if (fullscreen)
{
if (!is_exclusive_fullscreen)
container->showFullScreen();
else
container->showNormal();
}
else if (!render_to_main)
{
restoreDisplayWindowGeometryFromConfig();
container->showNormal();
}
else
{
m_game_list_widget->setVisible(false);
takeCentralWidget();
setCentralWidget(m_display_widget);
m_display_widget->setFocus();
}
// we need the surface visible.. this might be able to be replaced with something else
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
createDisplayWidget(fullscreen, render_to_main, is_exclusive_fullscreen);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
if (!wi.has_value())
{
QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget"));
destroyDisplayWidget();
destroyDisplayWidget(true);
return nullptr;
}
@ -1824,6 +1768,68 @@ DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main, b
return m_display_widget;
}
void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen)
{
// If we're rendering to main and were hidden (e.g. coming back from fullscreen),
// make sure we're visible before trying to add ourselves. Otherwise Wayland breaks.
if (!fullscreen && render_to_main && !isVisible())
{
setVisible(true);
QGuiApplication::sync();
}
QWidget* container;
if (DisplayContainer::isNeeded(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;
}
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());
}
if (fullscreen)
{
if (!is_exclusive_fullscreen)
container->showFullScreen();
else
container->showNormal();
}
else if (!render_to_main)
{
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();
}
else
{
pxAssertRel(m_ui.mainContainer->count() == 1, "Has no display widget");
m_ui.mainContainer->addWidget(container);
m_ui.mainContainer->setCurrentIndex(1);
}
// We need the surface visible.
QGuiApplication::sync();
}
void MainWindow::displayResizeRequested(qint32 width, qint32 height)
{
if (!m_display_widget)
@ -1849,15 +1855,54 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
void MainWindow::destroyDisplay()
{
// Now we can safely destroy the display window.
destroyDisplayWidget();
destroyDisplayWidget(true);
// Switch back to game list view, we're not going back to display, so we can't use switchToGameListView().
if (centralWidget() != m_game_list_widget)
m_ui.actionViewSystemDisplay->setEnabled(false);
m_ui.actionFullscreen->setEnabled(false);
}
void MainWindow::destroyDisplayWidget(bool show_game_list)
{
if (!m_display_widget)
return;
if (!isRenderingFullscreen() && !isRenderingToMain())
saveDisplayWindowGeometryToConfig();
if (m_display_container)
m_display_container->removeDisplayWidget();
if (isRenderingToMain())
{
takeCentralWidget();
setCentralWidget(m_game_list_widget);
m_game_list_widget->setVisible(true);
m_game_list_widget->setFocus();
if (s_use_central_widget)
{
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);
}
}
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);
}
}
if (m_display_widget)
{
m_display_widget->deleteLater();
m_display_widget = nullptr;
}
if (m_display_container)
{
m_display_container->deleteLater();
m_display_container = nullptr;
}
}
@ -1909,33 +1954,6 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
}
}
void MainWindow::destroyDisplayWidget()
{
if (!m_display_widget)
return;
if (!isRenderingFullscreen() && !isRenderingToMain())
saveDisplayWindowGeometryToConfig();
if (m_display_container)
m_display_container->removeDisplayWidget();
if (m_display_widget == centralWidget())
takeCentralWidget();
if (m_display_widget)
{
m_display_widget->deleteLater();
m_display_widget = nullptr;
}
if (m_display_container)
{
m_display_container->deleteLater();
m_display_container = nullptr;
}
}
void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode)
{
u32 width, height;

View File

@ -200,10 +200,12 @@ private:
void switchToGameListView();
void switchToEmulationView();
QWidget* getContentParent();
QWidget* getDisplayContainer() const;
void saveDisplayWindowGeometryToConfig();
void restoreDisplayWindowGeometryFromConfig();
void destroyDisplayWidget();
void createDisplayWidget(bool fullscreen, bool render_to_main, bool is_exclusive_fullscreen);
void destroyDisplayWidget(bool show_game_list);
void setDisplayFullscreen(const std::string& fullscreen_mode);
SettingsDialog* getSettingsDialog();

View File

@ -16,6 +16,7 @@
<property name="windowTitle">
<string>PCSX2</string>
</property>
<widget class="QStackedWidget" name="mainContainer" />
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>