Qt: Use QWindow as display surface

This commit is contained in:
TheLastRar
2025-11-16 22:29:57 +01:00
committed by lightningterror
parent e5d94e255b
commit cfea84b934
8 changed files with 283 additions and 429 deletions
+84 -136
View File
@@ -29,20 +29,12 @@
#include <qpa/qplatformnativeinterface.h>
#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<int>(std::ceil(static_cast<qreal>(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<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioF())), 1);
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatio())), 1);
}
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
int DisplaySurface::scaledWindowHeight() const
{
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWidget(this));
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatio())), 1);
}
std::optional<WindowInfo> DisplaySurface::getWindowInfo()
{
std::optional<WindowInfo> ret(QtUtils::GetWindowInfoForWindow(this));
if (ret.has_value())
{
m_last_window_width = ret->surface_width;
@@ -72,7 +72,7 @@ std::optional<WindowInfo> 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<QWidget*>(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<float>(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<float>(static_cast<qreal>(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<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * dpr)), 1));
const u32 scaled_height = static_cast<u32>(std::max(static_cast<int>(std::ceil(static_cast<qreal>(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<QCloseEvent*>(event));
return true;
}
case QEvent::WindowStateChange:
{
QWidget::event(event);
if (static_cast<QWindowStateChangeEvent*>(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<QCloseEvent*>(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<QCloseEvent*>(event));
return true;
case QEvent::WindowStateChange:
{
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
emit m_display_widget->windowRestoredEvent();
}
break;
emit windowRestoredEvent();
return false;
case QEvent::ChildRemoved:
if (static_cast<QChildEvent*>(event)->child() == m_container)
{
object->removeEventFilter(this);
m_container = nullptr;
}
return false;
default:
break;
return false;
}
return res;
}
+13 -32
View File
@@ -3,22 +3,25 @@
#pragma once
#include "common/WindowInfo.h"
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QWidget>
#include <QtGui/QWindow>
#include <optional>
#include <vector>
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<int> 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;
};
+93 -184
View File
@@ -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<QWidget*>(this) : static_cast<QWidget*>(m_ui.mainContainer);
return static_cast<QWidget*>(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<QWidget*>(m_display_container) : static_cast<QWidget*>(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<WindowInfo> 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<WindowInfo> 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<QWidget*>(m_display_container) : static_cast<QWidget*>(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<WindowInfo> 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<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
createDisplayWidget(fullscreen, render_to_main);
std::optional<WindowInfo> wi = m_display_widget->getWindowInfo();
std::optional<WindowInfo> 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<WindowInfo> 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<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(width) / dpr)), 1));
height = static_cast<qint32>(std::max(static_cast<int>(std::lroundf(static_cast<float>(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<QWidget*>(m_display_container) : static_cast<QWidget*>(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)
+3 -4
View File
@@ -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;
+3 -3
View File
@@ -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)
+2 -2
View File
@@ -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<WindowInfo> acquireRenderWindow(bool recreate_window);
void connectDisplaySignals(DisplayWidget* widget);
void connectDisplaySignals(DisplaySurface* widget);
void releaseRenderWindow();
void startBackgroundControllerPollTimer();
-66
View File
@@ -8,10 +8,8 @@
#include <QtCore/QtGlobal>
#include <QtCore/QMetaObject>
#include <QtGui/QAction>
#include <QtGui/QGuiApplication>
#include <QtGui/QDesktopServices>
#include <QtGui/QKeyEvent>
#include <QtGui/QScreen>
#include <QtWidgets/QComboBox>
#include <QtWidgets/QDialog>
#include <QtWidgets/QHeaderView>
@@ -40,8 +38,6 @@
#if defined(_WIN32)
#include "common/RedtapeWindows.h"
#include <Shlobj.h>
#elif !defined(APPLE)
#include <qpa/qplatformnativeinterface.h>
#endif
namespace QtUtils
@@ -254,68 +250,6 @@ namespace QtUtils
widget->resize(width, height);
}
std::optional<WindowInfo> 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<void*>(widget->winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(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<void*>(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<u32>(static_cast<qreal>(widget->width()) * dpr);
wi.surface_height = static_cast<u32>(static_cast<qreal>(widget->height()) * dpr);
wi.surface_scale = static_cast<float>(dpr);
// Query refresh rate, we need it for sync.
std::optional<float> 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<float>(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;
+85 -2
View File
@@ -9,11 +9,20 @@
#include <QtCore/QMetaType>
#include <QtCore/QString>
#include <QtCore/QAbstractItemModel>
#include <QtGui/QGuiApplication>
#include <QtGui/QWindow>
#if !defined(_WIN32) and !defined(__APPLE__)
#include <qpa/qplatformnativeinterface.h>
#endif
#include <QtGui/QScreen>
#include <functional>
#include <initializer_list>
#include <string_view>
#include <type_traits>
#include <optional>
#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<WindowInfo> GetWindowInfoForWidget(QWidget* widget);
/// Returns the common window info structure for a Qt Window/Widget.
template <class T>
requires std::is_base_of_v<QWidget, T> || std::is_base_of_v<QWindow, T>
std::optional<WindowInfo> 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<void*>(window->winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(window->winId());
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
const QString platform_name = QGuiApplication::platformName();
QWindow* windowHandle;
if constexpr (std::is_base_of_v<QWidget, T>)
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<void*>(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<QWidget, T>)
dpr = window->devicePixelRatioF();
else
dpr = window->devicePixelRatio();
wi.surface_width = static_cast<u32>(static_cast<qreal>(window->width()) * dpr);
wi.surface_height = static_cast<u32>(static_cast<qreal>(window->height()) * dpr);
wi.surface_scale = static_cast<float>(dpr);
// Query refresh rate, we need it for sync.
std::optional<float> 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<float>(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 <typename T>