This commit is contained in:
TheLastRar 2025-12-15 14:11:56 +07:00 committed by GitHub
commit 3571782840
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 239 additions and 53 deletions

View File

@ -133,7 +133,7 @@ void DisplaySurface::handleCloseEvent(QCloseEvent* event)
// In the latter case, it's going to destroy us, so don't let Qt do it first.
// Treat a close event while fullscreen as an exit, that way ALT+F4 closes PCSX2,
// rather than just the game.
if (QtHost::IsVMValid() && !isActuallyFullscreen())
if (QtHost::IsVMValid() && !isFullScreen())
{
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true),
Q_ARG(bool, true), Q_ARG(bool, false));
@ -147,10 +147,44 @@ void DisplaySurface::handleCloseEvent(QCloseEvent* event)
event->ignore();
}
bool DisplaySurface::isActuallyFullscreen() const
bool DisplaySurface::isFullScreen() const
{
// DisplaySurface is always in a container, so we need to check parent window
return parent()->windowState() & Qt::WindowFullScreen;
// DisplaySurface may be in a container
return (parent() ? parent()->windowState() : windowState()) & Qt::WindowFullScreen;
}
void DisplaySurface::setFocus()
{
if (m_container)
m_container->setFocus();
else
requestActivate();
}
QByteArray DisplaySurface::saveGeometry() const
{
if (m_container)
return m_container->saveGeometry();
else
{
// QWindow lacks saveGeometry, so create a dummy widget and copy geometry across.
QWidget dummy = QWidget();
dummy.setGeometry(geometry());
return dummy.saveGeometry();
}
}
void DisplaySurface::restoreGeometry(const QByteArray& geometry)
{
if (m_container)
m_container->restoreGeometry(geometry);
else
{
// QWindow lacks restoreGeometry, so create a dummy widget and copy geometry across.
QWidget dummy = QWidget();
dummy.restoreGeometry(geometry);
setGeometry(dummy.geometry());
}
}
void DisplaySurface::updateCenterPos()
@ -393,10 +427,18 @@ bool DisplaySurface::event(QEvent* event)
return event->isAccepted();
case QEvent::Move:
{
updateCenterPos();
return true;
}
// These events only work on the top level control.
// Which is this container when render to seperate or fullscreen is active (Windows).
case QEvent::Close:
handleCloseEvent(static_cast<QCloseEvent*>(event));
return true;
case QEvent::WindowStateChange:
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
emit windowRestoredEvent();
return false;
default:
return QWindow::event(event);
@ -418,7 +460,7 @@ bool DisplaySurface::eventFilter(QObject* object, QEvent* event)
return true;
// These events only work on the top level control.
// Which is this container when render to seperate or fullscreen is active.
// Which is this container when render to seperate or fullscreen is active (Non-Windows).
case QEvent::Close:
handleCloseEvent(static_cast<QCloseEvent*>(event));
return true;
@ -427,8 +469,8 @@ bool DisplaySurface::eventFilter(QObject* object, QEvent* event)
emit windowRestoredEvent();
return false;
case QEvent::ChildRemoved:
if (static_cast<QChildEvent*>(event)->child() == m_container)
case QEvent::ChildWindowRemoved:
if (static_cast<QChildWindowEvent*>(event)->child() == this)
{
object->removeEventFilter(this);
m_container = nullptr;

View File

@ -30,6 +30,12 @@ public:
void updateRelativeMode(bool enabled);
void updateCursor(bool hidden);
bool isFullScreen() const;
void setFocus();
QByteArray saveGeometry() const;
void restoreGeometry(const QByteArray& geometry);
Q_SIGNALS:
void windowResizedEvent(int width, int height, float scale);
void windowRestoredEvent();
@ -47,7 +53,6 @@ private Q_SLOTS:
void onResizeDebounceTimer();
private:
bool isActuallyFullscreen() const;
void updateCenterPos();
QPoint m_relative_mouse_start_pos{};

View File

@ -100,6 +100,13 @@ static quint32 s_current_running_crc;
static bool s_record_on_start = false;
static QString s_path_to_recording_for_record_on_start;
// DX cannot fullscreen when the display surface is in a container.
// QWindow, however, seems to lack CSD under wayland, so needs the container.
// MAC is unknown
#ifdef _WIN32
#define DISPLAY_SURFACE_WINDOW
#endif
MainWindow::MainWindow()
{
pxAssert(!g_main_window);
@ -1008,10 +1015,15 @@ void MainWindow::updateWindowTitle()
if (windowTitle() != main_title)
setWindowTitle(main_title);
if (m_display_container && !isRenderingToMain())
if (m_display_surface && !isRenderingToMain())
{
#ifdef DISPLAY_SURFACE_WINDOW
if (m_display_surface->title() != display_title)
m_display_surface->setTitle(display_title);
#else
if (m_display_container->windowTitle() != display_title)
m_display_container->setWindowTitle(display_title);
#endif
}
if (g_log_window)
@ -1040,7 +1052,13 @@ void MainWindow::updateWindowState(bool force_visible)
// Update the display widget too if rendering separately.
if (m_display_surface && !isRenderingToMain())
{
#ifdef DISPLAY_SURFACE_WINDOW
QtUtils::SetWindowResizeable(m_display_surface, resizeable);
#else
QtUtils::SetWindowResizeable(m_display_container, resizeable);
#endif
}
}
void MainWindow::setProgressBar(int current, int total)
@ -1075,12 +1093,12 @@ bool MainWindow::isRenderingFullscreen() const
if (!MTGS::IsOpen() || !m_display_surface)
return false;
return m_display_container->isFullScreen();
return m_display_surface->isFullScreen();
}
bool MainWindow::isRenderingToMain() const
{
return (m_display_surface && m_ui.mainContainer->indexOf(m_display_container) == 1);
return (m_display_container && m_ui.mainContainer->indexOf(m_display_container) == 1);
}
bool MainWindow::shouldHideMouseCursor() const
@ -1108,16 +1126,25 @@ bool MainWindow::shouldMouseLock() const
if (m_display_created == false || m_display_surface == nullptr)
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());
const 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_container->window();
if (displayWindow == nullptr)
if (!windowsHidden)
return false;
return windowsHidden && (displayWindow->isActiveWindow() || displayWindow->isFullScreen());
#ifdef DISPLAY_SURFACE_WINDOW
if (isRenderingToMain())
{
const auto* displayWindow = window();
return displayWindow ? (displayWindow->isActiveWindow() || displayWindow->isFullScreen()) : false;
}
else
return m_display_surface->isActive() || m_display_surface->isFullScreen();
#else
const auto* displayWindow = isRenderingToMain() ? window() : m_display_container->window();
return displayWindow ? (displayWindow->isActiveWindow() || displayWindow->isFullScreen()) : false;
#endif
}
bool MainWindow::shouldAbortForMemcardBusy(const VMLock& lock)
@ -1180,7 +1207,7 @@ void MainWindow::switchToEmulationView()
g_emu_thread->setVMPaused(false);
if (m_display_surface)
m_display_container->setFocus();
m_display_surface->setFocus();
}
void MainWindow::refreshGameList(bool invalidate_cache, bool popup_on_error)
@ -2129,7 +2156,7 @@ void MainWindow::onVMResumed()
if (m_display_surface)
{
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
}
}
@ -2449,21 +2476,25 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
if (!is_fullscreen && !is_rendering_to_main)
saveDisplayWindowGeometryToConfig();
#ifdef DISPLAY_SURFACE_WINDOW
auto* displayWindow = m_display_surface;
#else
auto* displayWindow = m_display_container;
#endif
if (fullscreen)
{
m_display_container->showFullScreen();
}
displayWindow->showFullScreen();
else
{
// Needs to exit fullscreen before resizing
displayWindow->showNormal();
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_container->setGeometry(geometry());
displayWindow->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
m_display_container->showNormal();
}
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
updateWindowState();
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
@ -2500,7 +2531,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
updateWindowState();
updateDisplayWidgetCursor();
m_display_container->setFocus();
m_display_surface->setFocus();
return wi;
}
@ -2517,9 +2548,14 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
m_display_surface = new DisplaySurface();
if (fullscreen || !render_to_main)
{
#ifdef DISPLAY_SURFACE_WINDOW
m_display_surface->setTitle(windowTitle());
m_display_surface->setIcon(windowIcon());
#else
m_display_container = m_display_surface->createWindowContainer();
m_display_container->setWindowTitle(windowTitle());
m_display_container->setWindowIcon(windowIcon());
#endif
}
else
{
@ -2528,20 +2564,38 @@ void MainWindow::createDisplayWidget(bool fullscreen, bool render_to_main)
if (fullscreen)
{
// On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on
// On Wayland, while move/restoreGeometry can't position the window, it can influence which screen they show up on.
// Other platforms can position windows fine, but the only thing that matters here is the screen.
#ifdef DISPLAY_SURFACE_WINDOW
if (isVisible() && g_emu_thread->shouldRenderToMain())
m_display_surface->setFramePosition(pos());
else
restoreDisplayWindowGeometryFromConfig();
m_display_surface->showFullScreen();
#else
if (isVisible() && g_emu_thread->shouldRenderToMain())
m_display_container->move(pos());
else
restoreDisplayWindowGeometryFromConfig();
m_display_container->showFullScreen();
#endif
}
else if (!render_to_main)
{
#ifdef DISPLAY_SURFACE_WINDOW
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_surface->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
m_display_surface->showNormal();
#else
if (m_is_temporarily_windowed && g_emu_thread->shouldRenderToMain())
m_display_container->setGeometry(geometry());
else
restoreDisplayWindowGeometryFromConfig();
m_display_container->showNormal();
#endif
}
else
{
@ -2570,12 +2624,21 @@ 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));
#ifdef DISPLAY_SURFACE_WINDOW
if (!m_display_container)
{
// no parent - rendering to separate window. easy.
QtUtils::ResizePotentiallyFixedSizeWindow(m_display_surface, width, height);
return;
}
#else
if (!m_display_container->parent())
{
// no parent - rendering to separate window. easy.
QtUtils::ResizePotentiallyFixedSizeWindow(m_display_container, width, height);
return;
}
#endif
// 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_container->height();
@ -2608,7 +2671,7 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
if (!m_display_surface)
return;
if (!isRenderingFullscreen() && !isRenderingToMain())
if (!m_display_surface->isFullScreen() && !isRenderingToMain())
saveDisplayWindowGeometryToConfig();
if (isRenderingToMain())
@ -2622,12 +2685,18 @@ void MainWindow::destroyDisplayWidget(bool show_game_list)
}
}
// displau surface is always in a container
pxAssert(m_display_container != nullptr);
m_display_container->deleteLater();
m_display_container = nullptr;
// m_display_surface will be destroyed by the container's dtor
m_display_surface = nullptr;
if (m_display_container)
{
m_display_container->deleteLater();
m_display_container = nullptr;
// m_display_surface will be destroyed by the container's dtor
m_display_surface = nullptr;
}
else
{
m_display_surface->deleteLater();
m_display_surface = nullptr;
}
updateDisplayRelatedActions(false, false, false);
}
@ -2639,14 +2708,6 @@ void MainWindow::updateDisplayWidgetCursor()
m_display_surface->updateCursor(s_vm_valid && !s_vm_paused && shouldHideMouseCursor());
}
void MainWindow::focusDisplayWidget()
{
if (!m_display_surface || centralWidget() != m_display_container)
return;
m_display_container->setFocus();
}
void MainWindow::setupMouseMoveHandler()
{
auto mouse_cb_fn = [](int x, int y) {
@ -2677,7 +2738,11 @@ void MainWindow::checkMousePosition(int x, int y)
// logical (DIP) frame rect
const QSize logicalSize = displayWindow->size();
#ifdef DISPLAY_SURFACE_WINDOW
const QPoint logicalPosition = isRenderingToMain() ? (displayWindow->position() + displayWindow->parent()->position()) : displayWindow->position();
#else
const QPoint logicalPosition = displayWindow->position() + displayWindow->parent()->position();
#endif
// The offset to the origin of the current screen is in device-independent pixels while the origin itself is native!
// The logicalPosition is the sum of these two values, so we need to separate them and only scale the offset
@ -2708,13 +2773,13 @@ void MainWindow::checkMousePosition(int x, int y)
void MainWindow::saveDisplayWindowGeometryToConfig()
{
if (m_display_container->windowState() & Qt::WindowFullScreen)
if (m_display_surface->isFullScreen())
{
// if we somehow ended up here, don't save the fullscreen state to the config
return;
}
const QByteArray geometry = m_display_container->saveGeometry();
const QByteArray geometry = m_display_surface->saveGeometry();
const QByteArray geometry_b64 = geometry.toBase64();
const std::string old_geometry_b64 = Host::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
if (old_geometry_b64 != geometry_b64.constData())
@ -2730,15 +2795,23 @@ void MainWindow::restoreDisplayWindowGeometryFromConfig()
const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
if (!geometry.isEmpty())
{
m_display_container->restoreGeometry(geometry);
m_display_surface->restoreGeometry(geometry);
// make sure we're not loading a dodgy config which had fullscreen set...
#ifdef DISPLAY_SURFACE_WINDOW
m_display_surface->setWindowStates(m_display_surface->windowStates() & ~(Qt::WindowFullScreen | Qt::WindowActive));
#else
m_display_container->setWindowState(m_display_container->windowState() & ~(Qt::WindowFullScreen | Qt::WindowActive));
#endif
}
else
{
// default size
#ifdef DISPLAY_SURFACE_WINDOW
m_display_surface->resize(640, 480);
#else
m_display_container->resize(640, 480);
#endif
}
}
@ -3308,7 +3381,7 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
g_emu_thread->setFullscreen(false, false);
// Process events untill both EmuThread and Qt have finished exiting fullscreen
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_container->isFullScreen()))
while (QtHost::IsVMValid() && (g_emu_thread->isFullscreen() || m_display_surface->isFullScreen()))
{
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
@ -3320,7 +3393,27 @@ MainWindow::VMLock MainWindow::pauseAndLockVM()
g_main_window->raise();
g_main_window->activateWindow();
return VMLock(m_display_container, was_paused, was_fullscreen);
#ifdef DISPLAY_SURFACE_WINDOW
if (!m_display_container)
{
// Create a temporary parent for the dialog.
QWidget* dialog_parent = new QWidget();
dialog_parent->setAttribute(Qt::WA_NativeWindow);
QWindow* window_handle = dialog_parent->windowHandle();
// Set the transient parent to the display surface.
// This will position the dialog_parent over the display surface (and thus so will any dialogs)
// and also enforces the focus lock of modal dialogs against the display surface.
// This works even without showing the dialog_parent window.
window_handle->setTransientParent(m_display_surface);
return VMLock(dialog_parent, was_paused, was_fullscreen, true);
}
else
return VMLock(m_display_container, was_paused, was_fullscreen, false);
#else
return VMLock(m_display_container, was_paused, was_fullscreen, false);
#endif
}
void MainWindow::rescanFile(const std::string& path)
@ -3328,11 +3421,12 @@ void MainWindow::rescanFile(const std::string& path)
m_game_list_widget->rescanFile(path);
}
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen)
MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen, bool owns_parent)
: m_dialog_parent(dialog_parent)
, m_has_lock(true)
, m_was_paused(was_paused)
, m_was_fullscreen(was_fullscreen)
, m_owns_dialog_parent(owns_parent)
{
QtHost::LockVMWithDialog();
}
@ -3342,11 +3436,13 @@ MainWindow::VMLock::VMLock(VMLock&& lock)
, m_has_lock(lock.m_has_lock)
, m_was_paused(lock.m_was_paused)
, m_was_fullscreen(lock.m_was_fullscreen)
, m_owns_dialog_parent(lock.m_owns_dialog_parent)
{
lock.m_dialog_parent = nullptr;
lock.m_has_lock = false;
lock.m_was_paused = true;
lock.m_was_fullscreen = false;
lock.m_owns_dialog_parent = false;
}
MainWindow::VMLock::~VMLock()
@ -3354,6 +3450,12 @@ MainWindow::VMLock::~VMLock()
if (m_has_lock)
QtHost::UnlockVMWithDialog();
if (m_owns_dialog_parent && m_dialog_parent)
{
m_dialog_parent->deleteLater();
m_dialog_parent = nullptr;
}
if (m_was_fullscreen)
{
g_main_window->m_is_temporarily_windowed = false;

View File

@ -65,13 +65,14 @@ public:
void cancelResume();
private:
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen);
VMLock(QWidget* dialog_parent, bool was_paused, bool was_exclusive_fullscreen, bool owns_parent);
friend MainWindow;
QWidget* m_dialog_parent;
bool m_has_lock;
bool m_was_paused;
bool m_was_fullscreen;
bool m_owns_dialog_parent;
};
/// Default filter for opening a file.
@ -138,7 +139,6 @@ private Q_SLOTS:
void displayResizeRequested(qint32 width, qint32 height);
void mouseModeRequested(bool relative_mode, bool hide_cursor);
void releaseRenderWindow();
void focusDisplayWidget();
void setupMouseMoveHandler();
void onGameListRefreshComplete();
void onGameListRefreshProgress(const QString& status, int current, int total);

View File

@ -366,6 +366,25 @@ namespace QtUtils
}
}
void SetWindowResizeable(QWindow* window, bool resizeable)
{
if (resizeable)
{
// Min/max numbers come from uic.
window->setMinimumWidth(1);
window->setMinimumHeight(1);
window->setMaximumWidth(16777215);
window->setMaximumHeight(16777215);
}
else
{
window->setMinimumWidth(window->width());
window->setMinimumHeight(window->height());
window->setMaximumWidth(window->width());
window->setMaximumHeight(window->height());
}
}
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height)
{
width = std::max(width, 1);
@ -376,6 +395,22 @@ namespace QtUtils
widget->resize(width, height);
}
void ResizePotentiallyFixedSizeWindow(QWindow* window, int width, int height)
{
width = std::max(width, 1);
height = std::max(height, 1);
if (window->minimumHeight() == window->maximumHeight())
{
window->setMinimumWidth(width);
window->setMinimumHeight(height);
window->setMaximumWidth(width);
window->setMaximumHeight(height);
}
window->resize(width, height);
}
QString AbstractItemModelToCSV(QAbstractItemModel* model, int role, bool useQuotes)
{
QString csv;

View File

@ -102,9 +102,11 @@ namespace QtUtils
/// Changes whether a window is resizable.
void SetWindowResizeable(QWidget* widget, bool resizeable);
void SetWindowResizeable(QWindow* window, bool resizeable);
/// Adjusts the fixed size for a window if it's not resizeable.
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
void ResizePotentiallyFixedSizeWindow(QWindow* window, int width, int height);
/// Returns the common window info structure for a Qt Window/Widget.
template <class T>