This commit is contained in:
Connor McLaughlin 2022-07-14 23:50:24 +10:00
parent 15dc8d038f
commit 5e278032c2
21 changed files with 1366 additions and 881 deletions

View File

@ -17,8 +17,8 @@
#include "dma.h"
#include "fmt/chrono.h"
#include "fmt/format.h"
#include "gpu.h"
#include "game_database.h"
#include "gpu.h"
#include "gte.h"
#include "host.h"
#include "host_display.h"
@ -226,7 +226,7 @@ bool System::IsShutdown()
bool System::IsValid()
{
return s_state != State::Shutdown && s_state != State::Starting;
return s_state == State::Running || s_state == State::Paused;
}
bool System::IsStartupCancelled()
@ -900,18 +900,15 @@ bool System::BootSystem(std::shared_ptr<SystemBootParameters> parameters)
{
Host::OnSystemStarting();
if (!parameters->state_stream)
{
if (parameters->filename.empty())
Log_InfoPrintf("Boot Filename: <BIOS/Shell>");
else
Log_InfoPrintf("Boot Filename: %s", parameters->filename.c_str());
}
if (parameters->filename.empty())
Log_InfoPrintf("Boot Filename: <BIOS/Shell>");
else
Log_InfoPrintf("Boot Filename: %s", parameters->filename.c_str());
// In Challenge mode, do not allow loading a save state under any circumstances
// If it's present, drop it
if (Cheevos::IsChallengeModeActive())
parameters->state_stream.reset();
parameters->save_state.clear();
if (!Host::AcquireHostDisplay())
{
@ -946,6 +943,29 @@ bool System::BootSystem(std::shared_ptr<SystemBootParameters> parameters)
UpdateSoftwareCursor();
g_spu.GetOutputStream()->PauseOutput(false);
Host::OnSystemStarted();
if (!parameters->save_state.empty())
{
// try to load the state, if it fails, bail out
std::unique_ptr<ByteStream> stream =
ByteStream::OpenFile(parameters->save_state.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
if (!stream)
{
Host::ReportErrorAsync(
Host::TranslateString("System", "Error"),
fmt::format(Host::TranslateString("System", "Failed to load save state file '{}' for booting.").GetCharArray(),
parameters->save_state));
DestroySystem();
return false;
}
if (!DoLoadState(stream.get(), false, true))
{
DestroySystem();
return false;
}
}
return true;
}
@ -999,34 +1019,26 @@ void System::DestroySystem()
bool System::LoadState(const char* filename)
{
if (!IsValid())
return false;
std::unique_ptr<ByteStream> stream = ByteStream::OpenFile(filename, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
if (!stream)
return false;
Host::AddFormattedOSDMessage(5.0f, Host::TranslateString("OSDMessage", "Loading state from '%s'..."), filename);
if (IsValid())
SaveUndoLoadState();
if (!InternalLoadState(stream.get()))
{
SaveUndoLoadState();
Host::ReportFormattedErrorAsync(
"Load State Error", Host::TranslateString("OSDMessage", "Loading state from '%s' failed. Resetting."), filename);
if (!InternalLoadState(stream.get()))
{
Host::ReportFormattedErrorAsync("Load State Error",
Host::TranslateString("OSDMessage", "Loading state from '%s' failed. Resetting."),
filename);
if (m_undo_load_state)
UndoLoadState();
if (m_undo_load_state)
UndoLoadState();
return false;
}
}
else
{
auto boot_params = std::make_shared<SystemBootParameters>();
boot_params->state_stream = std::move(stream);
if (!BootSystem(std::move(boot_params)))
return false;
return false;
}
ResetPerformanceCounters();
@ -1066,23 +1078,6 @@ bool System::InternalBoot(const SystemBootParameters& params)
s_startup_cancelled.store(false);
s_region = g_settings.region;
if (params.state_stream)
{
if (!DoLoadState(params.state_stream.get(), params.force_software_renderer, true))
{
InternalShutdown();
return false;
}
if (g_settings.start_paused || params.override_start_paused.value_or(false))
{
DebugAssert(s_state == State::Running);
s_state = State::Paused;
}
return true;
}
// Load CD image up and detect region.
Common::Error error;
std::unique_ptr<CDImage> media;
@ -1214,6 +1209,11 @@ bool System::InternalBoot(const SystemBootParameters& params)
// Good to go.
s_state = (g_settings.start_paused || params.override_start_paused.value_or(false)) ? State::Paused : State::Running;
if (s_state == State::Paused)
Host::OnSystemPaused();
else
Host::OnSystemResumed();
return true;
}
@ -1406,13 +1406,18 @@ void System::RecreateSystem()
DestroySystem();
auto boot_params = std::make_shared<SystemBootParameters>();
boot_params->state_stream = std::move(stream);
if (!BootSystem(std::move(boot_params)))
{
Host::ReportErrorAsync("Error", "Failed to boot system after recreation.");
return;
}
if (!DoLoadState(stream.get(), false, false))
{
DestroySystem();
return;
}
ResetPerformanceCounters();
ResetThrottler();
Host::RenderDisplay();
@ -2742,7 +2747,7 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool force)
s_running_game_code = entry->serial;
s_running_game_title = entry->title;
}
if (image && image->HasSubImages() && g_settings.memory_card_use_playlist_title)
{
std::string image_title(image->GetMetadata("title"));
@ -3520,63 +3525,6 @@ bool System::SaveStateToSlot(bool global, s32 slot)
return SaveState(save_path.c_str());
}
bool System::CanResumeSystemFromFile(const char* filename)
{
if (Host::GetBaseBoolSettingValue("Main", "SaveStateOnExit", true) && !Cheevos::IsChallengeModeActive())
{
#if 0
const GameListEntry* entry = m_game_list->GetEntryForPath(filename);
if (entry)
return !entry->code.empty();
else
return !System::GetGameCodeForPath(filename, true).empty();
#else
Panic("Fixme");
#endif
}
return false;
}
bool System::ResumeSystemFromState(const char* filename, bool boot_on_failure)
{
if (!BootSystem(std::make_shared<SystemBootParameters>(filename)))
return false;
const bool global = System::GetRunningCode().empty();
if (global)
{
Host::ReportErrorAsync("Error",
fmt::format("Cannot resume system with undetectable game code from '{}'.", filename));
if (!boot_on_failure)
{
DestroySystem();
return true;
}
}
else
{
const std::string path = GetGameSaveStateFileName(System::GetRunningCode().c_str(), -1);
if (FileSystem::FileExists(path.c_str()))
{
if (!LoadState(path.c_str()) && !boot_on_failure)
{
DestroySystem();
return false;
}
}
else if (!boot_on_failure)
{
Host::ReportErrorAsync("Error", fmt::format("Resume save state not found for '{}' ('{}').",
System::GetRunningCode(), System::GetRunningTitle()));
DestroySystem();
return false;
}
}
return true;
}
bool System::ResumeSystemFromMostRecentState()
{
const std::string path = GetMostRecentResumeSaveStatePath();

View File

@ -24,10 +24,10 @@ struct SystemBootParameters
~SystemBootParameters();
std::string filename;
std::string save_state;
std::optional<bool> override_fast_boot;
std::optional<bool> override_fullscreen;
std::optional<bool> override_start_paused;
std::unique_ptr<ByteStream> state_stream;
u32 media_playlist_index = 0;
bool load_image_to_ram = false;
bool force_software_renderer = false;
@ -335,12 +335,6 @@ std::optional<ExtendedSaveStateInfo> GetUndoSaveStateInfo();
/// Undoes a load state, i.e. restores the state prior to the load.
bool UndoLoadState();
/// Returns true if the specified file/disc image is resumable.
bool CanResumeSystemFromFile(const char* filename);
/// Loads the resume save state for the given game. Optionally boots the game anyway if loading fails.
bool ResumeSystemFromState(const char* filename, bool boot_on_failure);
/// Loads the most recent resume save state. This may be global or per-game.
bool ResumeSystemFromMostRecentState();

View File

@ -22,22 +22,25 @@ DebuggerWindow::DebuggerWindow(QWidget* parent /* = nullptr */)
DebuggerWindow::~DebuggerWindow() = default;
void DebuggerWindow::onEmulationPaused(bool paused)
void DebuggerWindow::onEmulationPaused()
{
if (paused)
{
setUIEnabled(true);
refreshAll();
refreshBreakpointList();
}
else
{
setUIEnabled(false);
}
setUIEnabled(true);
refreshAll();
refreshBreakpointList();
{
QSignalBlocker sb(m_ui.actionPause);
m_ui.actionPause->setChecked(paused);
m_ui.actionPause->setChecked(true);
}
}
void DebuggerWindow::onEmulationResumed()
{
setUIEnabled(false);
{
QSignalBlocker sb(m_ui.actionPause);
m_ui.actionPause->setChecked(false);
}
}
@ -381,7 +384,8 @@ void DebuggerWindow::setupAdditionalUi()
void DebuggerWindow::connectSignals()
{
QtHostInterface* hi = g_emu_thread;
connect(hi, &QtHostInterface::emulationPaused, this, &DebuggerWindow::onEmulationPaused);
connect(hi, &QtHostInterface::systemPaused, this, &DebuggerWindow::onEmulationPaused);
connect(hi, &QtHostInterface::systemResumed, this, &DebuggerWindow::onEmulationResumed);
connect(hi, &QtHostInterface::debuggerMessageReported, this, &DebuggerWindow::onDebuggerMessageReported);
connect(m_ui.actionPause, &QAction::toggled, this, &DebuggerWindow::onPauseActionToggled);

View File

@ -25,7 +25,8 @@ Q_SIGNALS:
void closed();
public Q_SLOTS:
void onEmulationPaused(bool paused);
void onEmulationPaused();
void onEmulationResumed();
protected:
void closeEvent(QCloseEvent* event);

View File

@ -0,0 +1,427 @@
#include "displaywidget.h"
#include "common/assert.h"
#include "common/bitutils.h"
#include "common/log.h"
#include "mainwindow.h"
#include "qthost.h"
#include "qtutils.h"
#include <QtCore/QDebug>
#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtGui/QWindowStateChangeEvent>
#include <cmath>
#if !defined(_WIN32) && !defined(APPLE)
#include <qpa/qplatformnativeinterface.h>
#endif
Log_SetChannel(DisplayWidget);
DisplayWidget::DisplayWidget(QWidget* parent) : QWidget(parent)
{
// 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()
{
#ifdef _WIN32
if (m_clip_mouse_enabled)
ClipCursor(nullptr);
#endif
}
qreal DisplayWidget::devicePixelRatioFromScreen() const
{
const QScreen* screen_for_ratio = screen();
if (!screen_for_ratio)
screen_for_ratio = QGuiApplication::primaryScreen();
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
}
int DisplayWidget::scaledWindowWidth() const
{
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioFromScreen())), 1);
}
int DisplayWidget::scaledWindowHeight() const
{
return std::max(static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen())), 1);
}
std::optional<WindowInfo> DisplayWidget::getWindowInfo()
{
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*>(winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(winId());
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
const QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("xcb"))
{
wi.type = WindowInfo::Type::X11;
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
wi.window_handle = reinterpret_cast<void*>(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
{
qCritical() << "Unknown PNI platform " << platform_name;
return std::nullopt;
}
#endif
m_last_window_width = wi.surface_width = static_cast<u32>(scaledWindowWidth());
m_last_window_height = wi.surface_height = static_cast<u32>(scaledWindowHeight());
m_last_window_scale = wi.surface_scale = static_cast<float>(devicePixelRatioFromScreen());
return wi;
}
void DisplayWidget::updateRelativeMode(bool master_enable)
{
bool relative_mode = master_enable && InputManager::HasPointerAxisBinds();
#ifdef _WIN32
// prefer ClipCursor() over warping movement when we're using raw input
bool clip_cursor = relative_mode && InputManager::IsUsingRawInput();
if (m_relative_mouse_enabled == relative_mode && m_clip_mouse_enabled == clip_cursor)
return;
Log_InfoPrintf("updateRelativeMode(): relative=%s, clip=%s", relative_mode ? "yes" : "no",
clip_cursor ? "yes" : "no");
if (!clip_cursor && m_clip_mouse_enabled)
{
m_clip_mouse_enabled = false;
ClipCursor(nullptr);
}
#else
if (m_relative_mouse_enabled == relative_mode)
return;
Log_InfoPrintf("updateRelativeMode(): relative=%s", relative_mode ? "yes" : "no");
#endif
if (relative_mode)
{
#ifdef _WIN32
m_relative_mouse_enabled = !clip_cursor;
m_clip_mouse_enabled = clip_cursor;
#else
m_relative_mouse_enabled = true;
#endif
m_relative_mouse_start_pos = QCursor::pos();
updateCenterPos();
grabMouse();
}
else if (m_relative_mouse_enabled)
{
m_relative_mouse_enabled = false;
QCursor::setPos(m_relative_mouse_start_pos);
releaseMouse();
}
}
void DisplayWidget::updateCursor(bool master_enable)
{
#ifdef _WIN32
const bool hide = master_enable && (m_should_hide_cursor || m_relative_mouse_enabled || m_clip_mouse_enabled);
#else
const bool hide = master_enable && (m_should_hide_cursor || m_relative_mouse_enabled);
#endif
if (m_cursor_hidden == hide)
return;
m_cursor_hidden = hide;
if (hide)
setCursor(Qt::BlankCursor);
else
unsetCursor();
}
void DisplayWidget::updateCenterPos()
{
#ifdef _WIN32
if (m_clip_mouse_enabled)
{
RECT rc;
if (GetWindowRect(reinterpret_cast<HWND>(winId()), &rc))
ClipCursor(&rc);
}
else if (m_relative_mouse_enabled)
{
RECT rc;
if (GetWindowRect(reinterpret_cast<HWND>(winId()), &rc))
{
m_relative_mouse_center_pos.setX(((rc.right - rc.left) / 2) + rc.left);
m_relative_mouse_center_pos.setY(((rc.bottom - rc.top) / 2) + rc.top);
SetCursorPos(m_relative_mouse_center_pos.x(), m_relative_mouse_center_pos.y());
}
}
#else
if (m_relative_mouse_enabled)
{
// we do a round trip here because these coordinates are dpi-unscaled
m_relative_mouse_center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
QCursor::setPos(m_relative_mouse_center_pos);
m_relative_mouse_center_pos = QCursor::pos();
}
#endif
}
QPaintEngine* DisplayWidget::paintEngine() const
{
return nullptr;
}
bool DisplayWidget::event(QEvent* event)
{
switch (event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
const QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
if (key_event->isAutoRepeat())
return true;
// For some reason, Windows sends "fake" key events.
// Scenario: Press shift, press F1, release shift, release F1.
// Events: Shift=Pressed, F1=Pressed, Shift=Released, **F1=Pressed**, F1=Released.
// To work around this, we keep track of keys pressed with modifiers in a list, and
// discard the press event when it's been previously activated. It's pretty gross,
// but I can't think of a better way of handling it, and there doesn't appear to be
// any window flag which changes this behavior that I can see.
const u32 key = QtUtils::KeyEventToCode(key_event);
const Qt::KeyboardModifiers modifiers = key_event->modifiers();
const bool pressed = (key_event->type() == QEvent::KeyPress);
const auto it = std::find(m_keys_pressed_with_modifiers.begin(), m_keys_pressed_with_modifiers.end(), key);
if (it != m_keys_pressed_with_modifiers.end())
{
if (pressed)
return true;
else
m_keys_pressed_with_modifiers.erase(it);
}
else if (modifiers != Qt::NoModifier && modifiers != Qt::KeypadModifier && pressed)
{
m_keys_pressed_with_modifiers.push_back(key);
}
emit windowKeyEvent(key, pressed);
return true;
}
case QEvent::MouseMove:
{
const QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
if (!m_relative_mouse_enabled)
{
const qreal dpr = devicePixelRatioFromScreen();
const QPoint mouse_pos = mouse_event->pos();
const float scaled_x = static_cast<float>(static_cast<qreal>(mouse_pos.x()) * dpr);
const float scaled_y = static_cast<float>(static_cast<qreal>(mouse_pos.y()) * dpr);
emit windowMouseMoveEvent(false, scaled_x, scaled_y);
}
else
{
// On windows, we use winapi here. The reason being that the coordinates in QCursor
// are un-dpi-scaled, so we lose precision at higher desktop scalings.
float dx = 0.0f, dy = 0.0f;
#ifndef _WIN32
const QPoint mouse_pos = QCursor::pos();
if (mouse_pos != m_relative_mouse_center_pos)
{
dx = static_cast<float>(mouse_pos.x() - m_relative_mouse_center_pos.x());
dy = static_cast<float>(mouse_pos.y() - m_relative_mouse_center_pos.y());
QCursor::setPos(m_relative_mouse_center_pos);
}
#else
POINT mouse_pos;
if (GetCursorPos(&mouse_pos))
{
dx = static_cast<float>(mouse_pos.x - m_relative_mouse_center_pos.x());
dy = static_cast<float>(mouse_pos.y - m_relative_mouse_center_pos.y());
SetCursorPos(m_relative_mouse_center_pos.x(), m_relative_mouse_center_pos.y());
}
#endif
if (dx != 0.0f || dy != 0.0f)
emit windowMouseMoveEvent(true, dx, dy);
}
return true;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
{
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
emit windowMouseButtonEvent(static_cast<int>(button_index + 1u), event->type() != QEvent::MouseButtonRelease);
// don't toggle fullscreen when we're bound.. that wouldn't end well.
if (event->type() == QEvent::MouseButtonDblClick &&
static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton &&
!InputManager::HasAnyBindingsForKey(InputManager::MakePointerButtonKey(0, 0)) &&
Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true))
{
g_emu_thread->toggleFullscreen();
}
return true;
}
case QEvent::Wheel:
{
const QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
emit windowMouseWheelEvent(wheel_event->angleDelta());
return true;
}
// According to https://bugreports.qt.io/browse/QTBUG-95925 the recommended practice for handling DPI change is
// responding to paint events
case QEvent::Paint:
case QEvent::Resize:
{
QWidget::event(event);
const float dpr = devicePixelRatioFromScreen();
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));
// avoid spamming resize events for paint events (sent on move on windows)
if (m_last_window_width != scaled_width || m_last_window_height != scaled_height || m_last_window_scale != dpr)
{
m_last_window_width = scaled_width;
m_last_window_height = scaled_height;
m_last_window_scale = dpr;
emit windowResizedEvent(scaled_width, scaled_height, dpr);
}
updateCenterPos();
return true;
}
case QEvent::Move:
{
updateCenterPos();
return true;
}
case QEvent::Close:
{
// 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.
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true),
Q_ARG(bool, false));
event->ignore();
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);
}
}
DisplayContainer::DisplayContainer() : QStackedWidget(nullptr) {}
DisplayContainer::~DisplayContainer() = default;
bool DisplayContainer::IsNeeded(bool fullscreen, bool render_to_main)
{
#if defined(_WIN32) || defined(__APPLE__)
return false;
#else
if (!fullscreen && render_to_main)
return false;
// We only need this on Wayland because of client-side decorations...
const QString platform_name = QGuiApplication::platformName();
return (platform_name == QStringLiteral("wayland"));
#endif
}
void DisplayContainer::setDisplayWidget(DisplayWidget* widget)
{
Assert(!m_display_widget);
m_display_widget = widget;
addWidget(widget);
}
DisplayWidget* DisplayContainer::removeDisplayWidget()
{
DisplayWidget* widget = m_display_widget;
Assert(widget);
m_display_widget = nullptr;
removeWidget(widget);
return widget;
}
bool DisplayContainer::event(QEvent* event)
{
if (event->type() == QEvent::Close)
{
// 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.
QMetaObject::invokeMethod(g_main_window, "requestShutdown", Q_ARG(bool, true), Q_ARG(bool, true),
Q_ARG(bool, false));
event->ignore();
return true;
}
const bool res = QStackedWidget::event(event);
if (!m_display_widget)
return res;
switch (event->type())
{
case QEvent::WindowStateChange:
{
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
emit m_display_widget->windowRestoredEvent();
}
break;
default:
break;
}
return res;
}

View File

@ -0,0 +1,77 @@
#pragma once
#include "common/types.h"
#include "common/window_info.h"
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QWidget>
#include <optional>
class DisplayWidget final : public QWidget
{
Q_OBJECT
public:
explicit DisplayWidget(QWidget* parent);
~DisplayWidget();
QPaintEngine* paintEngine() const override;
ALWAYS_INLINE void setShouldHideCursor(bool hide) { m_should_hide_cursor = hide; }
int scaledWindowWidth() const;
int scaledWindowHeight() const;
qreal devicePixelRatioFromScreen() const;
std::optional<WindowInfo> getWindowInfo();
void updateRelativeMode(bool master_enable);
void updateCursor(bool master_enable);
Q_SIGNALS:
void windowResizedEvent(int width, int height, float scale);
void windowRestoredEvent();
void windowKeyEvent(int key_code, bool pressed);
void windowMouseMoveEvent(bool relative, float x, float y);
void windowMouseButtonEvent(int button, bool pressed);
void windowMouseWheelEvent(const QPoint& angle_delta);
protected:
bool event(QEvent* event) override;
private:
void updateCenterPos();
QPoint m_relative_mouse_start_pos{};
QPoint m_relative_mouse_center_pos{};
bool m_relative_mouse_enabled = false;
#ifdef _WIN32
bool m_clip_mouse_enabled = false;
#endif
bool m_should_hide_cursor = false;
bool m_cursor_hidden = 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();
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;
};

View File

@ -28,7 +28,7 @@
<ClCompile Include="inputbindingdialog.cpp" />
<ClCompile Include="inputbindingwidgets.cpp" />
<ClCompile Include="memoryviewwidget.cpp" />
<ClCompile Include="qtdisplaywidget.cpp" />
<ClCompile Include="displaywidget.cpp" />
<ClCompile Include="gamelistsettingswidget.cpp" />
<ClCompile Include="gamelistrefreshthread.cpp" />
<ClCompile Include="gamelistwidget.cpp" />
@ -57,7 +57,7 @@
<QtMoc Include="enhancementsettingswidget.h" />
<QtMoc Include="memorycardsettingswidget.h" />
<QtMoc Include="memorycardeditordialog.h" />
<QtMoc Include="qtdisplaywidget.h" />
<QtMoc Include="displaywidget.h" />
<QtMoc Include="generalsettingswidget.h" />
<QtMoc Include="displaysettingswidget.h" />
<QtMoc Include="hotkeysettingswidget.h" />
@ -208,6 +208,7 @@
<ClCompile Include="$(IntDir)moc_controllerbindingwidgets.cpp" />
<ClCompile Include="$(IntDir)moc_controllerglobalsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_controllersettingsdialog.cpp" />
<ClCompile Include="$(IntDir)moc_displaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_emulationsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_enhancementsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistmodel.cpp" />
@ -232,7 +233,6 @@
<ClCompile Include="$(IntDir)moc_postprocessingchainconfigwidget.cpp" />
<ClCompile Include="$(IntDir)moc_postprocessingshaderconfigwidget.cpp" />
<ClCompile Include="$(IntDir)moc_postprocessingsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
<ClCompile Include="$(IntDir)moc_qthost.cpp" />
<ClCompile Include="$(IntDir)moc_qtprogresscallback.cpp" />
<ClCompile Include="$(IntDir)moc_settingsdialog.cpp" />

View File

@ -22,7 +22,7 @@
<ClCompile Include="audiosettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_audiosettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_qtdisplaywidget.cpp" />
<ClCompile Include="qtdisplaywidget.cpp" />
<ClCompile Include="displaywidget.cpp" />
<ClCompile Include="qtprogresscallback.cpp" />
<ClCompile Include="$(IntDir)moc_qtprogresscallback.cpp" />
<ClCompile Include="generalsettingswidget.cpp" />
@ -87,6 +87,8 @@
<ClCompile Include="$(IntDir)moc_controllerbindingwidgets.cpp" />
<ClCompile Include="$(IntDir)moc_controllerglobalsettingswidget.cpp" />
<ClCompile Include="$(IntDir)moc_controllersettingsdialog.cpp" />
<ClCompile Include="gamelistrefreshthread.cpp" />
<ClCompile Include="$(IntDir)moc_gamelistrefreshthread.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="qtutils.h" />
@ -112,7 +114,7 @@
<QtMoc Include="hotkeysettingswidget.h" />
<QtMoc Include="inputbindingwidgets.h" />
<QtMoc Include="audiosettingswidget.h" />
<QtMoc Include="qtdisplaywidget.h" />
<QtMoc Include="displaywidget.h" />
<QtMoc Include="generalsettingswidget.h" />
<QtMoc Include="qtprogresscallback.h" />
<QtMoc Include="advancedsettingswidget.h" />
@ -144,6 +146,7 @@
<QtMoc Include="controllerbindingwidgets.h" />
<QtMoc Include="controllerglobalsettingswidget.h" />
<QtMoc Include="controllersettingsdialog.h" />
<QtMoc Include="gamelistrefreshthread.h" />
</ItemGroup>
<ItemGroup>
<QtUi Include="consolesettingswidget.ui" />
@ -174,6 +177,8 @@
<QtUi Include="controllerglobalsettingswidget.ui" />
<QtUi Include="controllersettingsdialog.ui" />
<QtUi Include="controllerbindingwidget_digital_controller.ui" />
<QtUi Include="emptygamelistwidget.ui" />
<QtUi Include="gamelistwidget.ui" />
</ItemGroup>
<ItemGroup>
<Natvis Include="qt5.natvis" />

View File

@ -1,22 +1,24 @@
#include "gdbconnection.h"
#include "qthost.h"
#include "common/log.h"
#include "core/gdb_protocol.h"
#include "qthost.h"
Log_SetChannel(GDBConnection);
GDBConnection::GDBConnection(QObject *parent, int descriptor)
: QThread(parent), m_descriptor(descriptor)
GDBConnection::GDBConnection(QObject* parent, int descriptor) : QThread(parent), m_descriptor(descriptor)
{
Log_InfoPrintf("(%u) Accepted new connection on GDB server", m_descriptor);
connect(&m_socket, &QTcpSocket::readyRead, this, &GDBConnection::receivedData);
connect(&m_socket, &QTcpSocket::disconnected, this, &GDBConnection::gotDisconnected);
if (m_socket.setSocketDescriptor(m_descriptor)) {
if (m_socket.setSocketDescriptor(m_descriptor))
{
g_emu_thread->pauseSystem(true, true);
}
else {
Log_ErrorPrintf("(%u) Failed to set socket descriptor: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
else
{
Log_ErrorPrintf("(%u) Failed to set socket descriptor: %s", m_descriptor,
m_socket.errorString().toUtf8().constData());
}
}
@ -31,52 +33,60 @@ void GDBConnection::receivedData()
qint64 bytesRead;
char buffer[256];
while ((bytesRead = m_socket.read(buffer, sizeof(buffer))) > 0) {
for (char c : std::string_view(buffer, bytesRead)) {
while ((bytesRead = m_socket.read(buffer, sizeof(buffer))) > 0)
{
for (char c : std::string_view(buffer, bytesRead))
{
m_readBuffer.push_back(c);
if (GDBProtocol::IsPacketInterrupt(m_readBuffer)) {
if (GDBProtocol::IsPacketInterrupt(m_readBuffer))
{
Log_DebugPrintf("(%u) > Interrupt request", m_descriptor);
g_emu_thread->pauseSystem(true, true);
m_readBuffer.erase();
}
else if (GDBProtocol::IsPacketContinue(m_readBuffer)) {
else if (GDBProtocol::IsPacketContinue(m_readBuffer))
{
Log_DebugPrintf("(%u) > Continue request", m_descriptor);
g_emu_thread->pauseSystem(false, false);
m_readBuffer.erase();
}
else if (GDBProtocol::IsPacketComplete(m_readBuffer)) {
else if (GDBProtocol::IsPacketComplete(m_readBuffer))
{
Log_DebugPrintf("(%u) > %s", m_descriptor, m_readBuffer.c_str());
writePacket(GDBProtocol::ProcessPacket(m_readBuffer));
m_readBuffer.erase();
}
}
}
if (bytesRead == -1) {
if (bytesRead == -1)
{
Log_ErrorPrintf("(%u) Failed to read from socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
}
}
void GDBConnection::onEmulationPaused(bool paused)
void GDBConnection::onEmulationPaused()
{
if (paused) {
if (m_seen_resume) {
m_seen_resume = false;
// Generate a stop reply packet, insert '?' command to generate it.
writePacket(GDBProtocol::ProcessPacket("$?#3f"));
}
}
else {
m_seen_resume = true;
// Send ack, in case GDB sent a continue request.
writePacket("+");
if (m_seen_resume)
{
m_seen_resume = false;
// Generate a stop reply packet, insert '?' command to generate it.
writePacket(GDBProtocol::ProcessPacket("$?#3f"));
}
}
void GDBConnection::onEmulationResumed()
{
m_seen_resume = true;
// Send ack, in case GDB sent a continue request.
writePacket("+");
}
void GDBConnection::writePacket(std::string_view packet)
{
Log_DebugPrintf("(%u) < %*s", m_descriptor, packet.length(), packet.data());
if (m_socket.write(packet.data(), packet.length()) == -1) {
if (m_socket.write(packet.data(), packet.length()) == -1)
{
Log_ErrorPrintf("(%u) Failed to write to socket: %s", m_descriptor, m_socket.errorString().toUtf8().constData());
}
}

View File

@ -12,7 +12,8 @@ public:
public Q_SLOTS:
void gotDisconnected();
void receivedData();
void onEmulationPaused(bool paused);
void onEmulationPaused();
void onEmulationResumed();
private:
void writePacket(std::string_view data);

View File

@ -29,7 +29,8 @@ void GDBServer::incomingConnection(qintptr descriptor)
{
Log_InfoPrint("Accepted connection on GDB server");
GDBConnection *thread = new GDBConnection(this, descriptor);
connect(g_emu_thread, &QtHostInterface::emulationPaused, thread, &GDBConnection::onEmulationPaused);
connect(g_emu_thread, &QtHostInterface::systemPaused, thread, &GDBConnection::onEmulationPaused);
connect(g_emu_thread, &QtHostInterface::systemResumed, thread, &GDBConnection::onEmulationResumed);
thread->start();
m_connections.push_back(thread);
}

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
#include "controllersettingsdialog.h"
#include "core/types.h"
#include "qtdisplaywidget.h"
#include "displaywidget.h"
#include "settingsdialog.h"
#include "ui_mainwindow.h"
@ -59,8 +59,8 @@ public Q_SLOTS:
private Q_SLOTS:
void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message);
QtDisplayWidget* createDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main);
QtDisplayWidget* updateDisplay(QThread* worker_thread, bool fullscreen, bool render_to_main);
DisplayWidget* createDisplay(bool fullscreen, bool render_to_main);
DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main, bool surfaceless);
void displaySizeRequested(qint32 width, qint32 height);
void destroyDisplay();
void focusDisplayWidget();
@ -68,10 +68,11 @@ private Q_SLOTS:
void updateMouseMode(bool paused);
void onSettingsResetToDefault();
void onEmulationStarting();
void onEmulationStarted();
void onEmulationStopped();
void onEmulationPaused(bool paused);
void onSystemStarting();
void onSystemStarted();
void onSystemDestroyed();
void onSystemPaused();
void onSystemResumed();
void onSystemPerformanceCountersUpdated(float speed, float fps, float vps, float average_frame_time,
float worst_frame_time, GPURenderer renderer, quint32 render_width,
quint32 render_height, bool render_interlaced);
@ -124,26 +125,30 @@ protected:
void dropEvent(QDropEvent* event) override;
private:
ALWAYS_INLINE QWidget* getDisplayContainer() const
{
return (m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget));
}
void setTheme(const QString& theme);
void setStyleFromSettings();
void setIconThemeFromSettings();
void setupAdditionalUi();
void connectSignals();
void addThemeToMenu(const QString& name, const QString& key);
void updateEmulationActions(bool starting, bool running, bool cheevos_challenge_mode);
void updateStatusBarWidgetVisibility();
void updateWindowTitle();
void updateWindowState(bool force_visible = false);
void setProgressBar(int current, int total);
void clearProgressBar();
QWidget* getDisplayContainer() const;
bool isShowingGameList() const;
bool isRenderingFullscreen() const;
bool isRenderingToMain() const;
bool shouldHideMouseCursor() const;
bool shouldHideMainWindow() const;
void switchToGameListView();
void switchToEmulationView();
void startGameOrChangeDiscs(const std::string& path);
void saveStateToConfig();
void restoreStateFromConfig();
void saveDisplayWindowGeometryToConfig();
@ -166,15 +171,17 @@ private:
void setGameListEntryCoverImage(const GameList::Entry* entry);
void recreate();
std::optional<bool> promptForResumeState(const QString& save_state_path);
void startGameListEntry(const GameList::Entry* entry, std::optional<s32> save_slot, std::optional<bool> fast_boot);
Ui::MainWindow m_ui;
QString m_unthemed_style_name;
GameListWidget* m_game_list_widget = nullptr;
HostDisplay* m_host_display = nullptr;
QtDisplayWidget* m_display_widget = nullptr;
QtDisplayContainer* m_display_container = nullptr;
DisplayWidget* m_display_widget = nullptr;
DisplayContainer* m_display_container = nullptr;
QProgressBar* m_status_progress_widget = nullptr;
QLabel* m_status_speed_widget = nullptr;
@ -191,14 +198,20 @@ private:
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
DebuggerWindow* m_debugger_window = nullptr;
std::string m_running_game_code;
std::string m_current_game_title;
std::string m_current_game_code;
bool m_emulation_running = false;
bool m_was_paused_by_focus_loss = false;
bool m_open_debugger_on_start = false;
bool m_relative_mouse_mode = false;
bool m_mouse_cursor_hidden = false;
bool m_display_created = false;
bool m_save_states_invalidated = false;
bool m_was_paused_on_surface_loss = false;
bool m_was_disc_change_request = false;
bool m_is_closing = false;
GDBServer* m_gdb_server = nullptr;
};

View File

@ -20,13 +20,6 @@
<iconset resource="resources/resources.qrc">
<normaloff>:/icons/duck.png</normaloff>:/icons/duck.png</iconset>
</property>
<widget class="QStackedWidget" name="mainContainer">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page"/>
<widget class="QWidget" name="page_2"/>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
@ -81,6 +74,7 @@
<addaction name="actionStartFile"/>
<addaction name="actionStartDisc"/>
<addaction name="actionStartBios"/>
<addaction name="actionStartFullscreenUI"/>
<addaction name="actionResumeLastState"/>
<addaction name="separator"/>
<addaction name="actionPowerOff"/>
@ -254,6 +248,7 @@
</attribute>
<addaction name="actionStartFile"/>
<addaction name="actionStartBios"/>
<addaction name="actionStartFullscreenUI2"/>
<addaction name="separator"/>
<addaction name="actionResumeLastState"/>
<addaction name="actionReset"/>
@ -869,6 +864,24 @@
<string>Power Off &amp;Without Saving</string>
</property>
</action>
<action name="actionStartFullscreenUI">
<property name="icon">
<iconset theme="tv-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Start Big Picture Mode</string>
</property>
</action>
<action name="actionStartFullscreenUI2">
<property name="icon">
<iconset theme="tv-2-line">
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Big Picture</string>
</property>
</action>
</widget>
<resources>
<include location="resources/resources.qrc"/>

View File

@ -1,310 +0,0 @@
#include "qtdisplaywidget.h"
#include "common/assert.h"
#include "common/bitutils.h"
#include "qthost.h"
#include "qtutils.h"
#include <QtCore/QDebug>
#include <QtGui/QGuiApplication>
#include <QtGui/QKeyEvent>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtGui/QWindowStateChangeEvent>
#include <cmath>
#if !defined(_WIN32) && !defined(APPLE)
#include <qpa/qplatformnativeinterface.h>
#endif
QtDisplayWidget::QtDisplayWidget(QWidget* parent) : QWidget(parent)
{
// 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);
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
}
QtDisplayWidget::~QtDisplayWidget() = default;
qreal QtDisplayWidget::devicePixelRatioFromScreen() const
{
QScreen* screen_for_ratio;
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
screen_for_ratio = windowHandle()->screen();
#else
screen_for_ratio = screen();
#endif
if (!screen_for_ratio)
screen_for_ratio = QGuiApplication::primaryScreen();
return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast<qreal>(1);
}
int QtDisplayWidget::scaledWindowWidth() const
{
return static_cast<int>(std::ceil(static_cast<qreal>(width()) * devicePixelRatioFromScreen()));
}
int QtDisplayWidget::scaledWindowHeight() const
{
return static_cast<int>(std::ceil(static_cast<qreal>(height()) * devicePixelRatioFromScreen()));
}
std::optional<WindowInfo> QtDisplayWidget::getWindowInfo() const
{
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*>(winId());
#elif defined(__APPLE__)
wi.type = WindowInfo::Type::MacOS;
wi.window_handle = reinterpret_cast<void*>(winId());
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
const QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("xcb"))
{
wi.type = WindowInfo::Type::X11;
wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
wi.window_handle = reinterpret_cast<void*>(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
{
qCritical() << "Unknown PNI platform " << platform_name;
return std::nullopt;
}
#endif
wi.surface_width = scaledWindowWidth();
wi.surface_height = scaledWindowHeight();
wi.surface_scale = devicePixelRatioFromScreen();
wi.surface_format = WindowInfo::SurfaceFormat::RGB8;
return wi;
}
void QtDisplayWidget::setRelativeMode(bool enabled)
{
if (m_relative_mouse_enabled == enabled)
return;
if (enabled)
{
m_relative_mouse_start_position = QCursor::pos();
const QPoint center_pos = mapToGlobal(QPoint(width() / 2, height() / 2));
QCursor::setPos(center_pos);
m_relative_mouse_last_position = center_pos;
grabMouse();
}
else
{
QCursor::setPos(m_relative_mouse_start_position);
releaseMouse();
}
m_relative_mouse_enabled = enabled;
}
QPaintEngine* QtDisplayWidget::paintEngine() const
{
return nullptr;
}
bool QtDisplayWidget::event(QEvent* event)
{
switch (event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
{
const QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
if (!key_event->isAutoRepeat())
{
emit windowKeyEvent(key_event->key(), event->type() == QEvent::KeyPress);
}
return true;
}
case QEvent::MouseMove:
{
const QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
if (!m_relative_mouse_enabled)
{
const qreal dpr = devicePixelRatioFromScreen();
const QPoint mouse_pos = mouse_event->pos();
const int scaled_x = static_cast<int>(static_cast<qreal>(mouse_pos.x()) * dpr);
const int scaled_y = static_cast<int>(static_cast<qreal>(mouse_pos.y()) * dpr);
windowMouseMoveEvent(scaled_x, scaled_y);
}
else
{
const QPoint center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
const QPoint mouse_pos = mapToGlobal(mouse_event->pos());
const int dx = mouse_pos.x() - center_pos.x();
const int dy = mouse_pos.y() - center_pos.y();
m_relative_mouse_last_position.setX(m_relative_mouse_last_position.x() + dx);
m_relative_mouse_last_position.setY(m_relative_mouse_last_position.y() + dy);
windowMouseMoveEvent(m_relative_mouse_last_position.x(), m_relative_mouse_last_position.y());
QCursor::setPos(center_pos);
#if 0
qCritical() << "center" << center_pos.x() << "," << center_pos.y();
qCritical() << "mouse" << mouse_pos.x() << "," << mouse_pos.y();
qCritical() << "dxdy" << dx << "," << dy;
#endif
}
return true;
}
case QEvent::MouseButtonPress:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonRelease:
{
const u32 button_index = CountTrailingZeros(static_cast<u32>(static_cast<const QMouseEvent*>(event)->button()));
emit windowMouseButtonEvent(static_cast<int>(button_index + 1u), event->type() != QEvent::MouseButtonRelease);
return true;
}
case QEvent::Wheel:
{
const QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
emit windowMouseWheelEvent(wheel_event->angleDelta());
return true;
}
case QEvent::Resize:
{
QWidget::event(event);
emit windowResizedEvent(scaledWindowWidth(), scaledWindowHeight());
return true;
}
case QEvent::Close:
{
emit windowClosedEvent();
QWidget::event(event);
return true;
}
case QEvent::WindowStateChange:
{
QWidget::event(event);
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
emit windowRestoredEvent();
return true;
}
case QEvent::FocusIn:
{
QWidget::event(event);
emit windowFocusEvent();
return true;
}
case QEvent::ActivationChange:
{
QWidget::event(event);
if (isActiveWindow())
emit windowFocusEvent();
return true;
}
default:
return QWidget::event(event);
}
}
QtDisplayContainer::QtDisplayContainer() : QStackedWidget(nullptr) {}
QtDisplayContainer::~QtDisplayContainer() = default;
bool QtDisplayContainer::IsNeeded(bool fullscreen, bool render_to_main)
{
#if defined(_WIN32) || defined(__APPLE__)
return false;
#else
if (fullscreen || render_to_main)
return false;
// We only need this on Wayland because of client-side decorations...
const QString platform_name = QGuiApplication::platformName();
return (platform_name == QStringLiteral("wayland"));
#endif
}
void QtDisplayContainer::setDisplayWidget(QtDisplayWidget* widget)
{
Assert(!m_display_widget);
m_display_widget = widget;
addWidget(widget);
}
QtDisplayWidget* QtDisplayContainer::removeDisplayWidget()
{
QtDisplayWidget* widget = m_display_widget;
Assert(widget);
m_display_widget = nullptr;
removeWidget(widget);
return widget;
}
bool QtDisplayContainer::event(QEvent* event)
{
const bool res = QStackedWidget::event(event);
if (!m_display_widget)
return res;
switch (event->type())
{
case QEvent::Close:
{
emit m_display_widget->windowClosedEvent();
}
break;
case QEvent::WindowStateChange:
{
if (static_cast<QWindowStateChangeEvent*>(event)->oldState() & Qt::WindowMinimized)
emit m_display_widget->windowRestoredEvent();
}
break;
case QEvent::FocusIn:
{
emit m_display_widget->windowFocusEvent();
}
break;
case QEvent::ActivationChange:
{
if (isActiveWindow())
emit m_display_widget->windowFocusEvent();
}
break;
default:
break;
}
return res;
}

View File

@ -1,63 +0,0 @@
#pragma once
#include "common/types.h"
#include "common/window_info.h"
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QWidget>
#include <optional>
class QtDisplayWidget final : public QWidget
{
Q_OBJECT
public:
QtDisplayWidget(QWidget* parent);
~QtDisplayWidget();
QPaintEngine* paintEngine() const override;
int scaledWindowWidth() const;
int scaledWindowHeight() const;
qreal devicePixelRatioFromScreen() const;
std::optional<WindowInfo> getWindowInfo() const;
void setRelativeMode(bool enabled);
Q_SIGNALS:
void windowFocusEvent();
void windowResizedEvent(int width, int height);
void windowRestoredEvent();
void windowClosedEvent();
void windowKeyEvent(int key_code, bool pressed);
void windowMouseMoveEvent(float x, float y);
void windowMouseButtonEvent(int button, bool pressed);
void windowMouseWheelEvent(const QPoint& angle_delta);
protected:
bool event(QEvent* event) override;
private:
QPoint m_relative_mouse_start_position{};
QPoint m_relative_mouse_last_position{};
bool m_relative_mouse_enabled = false;
};
class QtDisplayContainer final : public QStackedWidget
{
Q_OBJECT
public:
QtDisplayContainer();
~QtDisplayContainer();
static bool IsNeeded(bool fullscreen, bool render_to_main);
void setDisplayWidget(QtDisplayWidget* widget);
QtDisplayWidget* removeDisplayWidget();
protected:
bool event(QEvent* event) override;
private:
QtDisplayWidget* m_display_widget = nullptr;
};

View File

@ -14,6 +14,7 @@
#include "core/memory_card.h"
#include "core/spu.h"
#include "core/system.h"
#include "displaywidget.h"
#include "frontend-common/fullscreen_ui.h"
#include "frontend-common/game_list.h"
#include "frontend-common/imgui_manager.h"
@ -23,9 +24,9 @@
#include "frontend-common/vulkan_host_display.h"
#include "imgui.h"
#include "mainwindow.h"
#include "qtdisplaywidget.h"
#include "qtprogresscallback.h"
#include "qtutils.h"
#include "scmversion/scmversion.h"
#include "util/audio_stream.h"
#include "util/ini_settings_interface.h"
#include <QtCore/QCoreApplication>
@ -146,6 +147,22 @@ bool QtHost::InNoGUIMode()
return s_nogui_mode;
}
QString QtHost::GetAppNameAndVersion()
{
return QStringLiteral("DuckStation %1 (%2)").arg(g_scm_tag_str).arg(g_scm_branch_str);
}
QString QtHost::GetAppConfigSuffix()
{
#if defined(_DEBUG)
return QStringLiteral(" [Debug]");
#elif defined(_DEBUGFAST)
return QStringLiteral(" [DebugFast]");
#else
return QString();
#endif
}
bool QtHost::InitializeConfig()
{
if (!SetCriticalFolders())
@ -459,7 +476,6 @@ void QtHostInterface::bootSystem(std::shared_ptr<SystemBootParameters> params)
return;
}
emit emulationStarting();
if (!System::BootSystem(std::move(params)))
return;
@ -476,11 +492,14 @@ void QtHostInterface::resumeSystemFromState(const QString& filename, bool boot_o
return;
}
emit emulationStarting();
Panic("fixme");
#if 0
if (filename.isEmpty())
System::ResumeSystemFromMostRecentState();
else
System::ResumeSystemFromState(filename.toStdString().c_str(), boot_on_failure);
#endif
}
void QtHostInterface::resumeSystemFromMostRecentState()
@ -503,14 +522,24 @@ void QtHostInterface::onDisplayWindowKeyEvent(int key, bool pressed)
GenericInputBinding::Unknown);
}
void QtHostInterface::onDisplayWindowMouseMoveEvent(float x, float y)
void QtHostInterface::onDisplayWindowMouseMoveEvent(bool relative, float x, float y)
{
// display might be null here if the event happened after shutdown
DebugAssert(isOnWorkerThread());
if (s_host_display)
s_host_display->SetMousePosition(static_cast<s32>(x), static_cast<s32>(y));
if (!relative)
{
if (s_host_display)
s_host_display->SetMousePosition(static_cast<s32>(x), static_cast<s32>(y));
InputManager::UpdatePointerAbsolutePosition(0, x, y);
InputManager::UpdatePointerAbsolutePosition(0, x, y);
}
else
{
if (x != 0.0f)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::X, x);
if (y != 0.0f)
InputManager::UpdatePointerRelativeDelta(0, InputPointerAxis::Y, y);
}
}
void QtHostInterface::onDisplayWindowMouseButtonEvent(int button, bool pressed)
@ -563,18 +592,6 @@ void QtHostInterface::onDisplayWindowResized(int width, int height)
}
}
void QtHostInterface::onDisplayWindowFocused()
{
if (!s_host_display || !m_lost_exclusive_fullscreen)
return;
// try to restore exclusive fullscreen
m_lost_exclusive_fullscreen = false;
m_is_exclusive_fullscreen = true;
m_is_fullscreen = true;
updateDisplayState();
}
void QtHostInterface::redrawDisplayWindow()
{
if (!isOnWorkerThread())
@ -597,42 +614,43 @@ void QtHostInterface::toggleFullscreen()
return;
}
SetFullscreen(!m_is_fullscreen);
setFullscreen(!m_is_fullscreen);
}
HostDisplay* QtHostInterface::acquireHostDisplay()
void QtHostInterface::setFullscreen(bool fullscreen)
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen));
return;
}
if (!s_host_display || m_is_fullscreen == fullscreen)
return;
m_is_fullscreen = fullscreen;
updateDisplayRequested(fullscreen, m_is_rendering_to_main, m_is_surfaceless);
}
void QtHostInterface::setSurfaceless(bool surfaceless)
{
if (!isOnWorkerThread())
{
QMetaObject::invokeMethod(this, "setSurfaceless", Qt::QueuedConnection, Q_ARG(bool, surfaceless));
return;
}
if (!s_host_display || m_is_surfaceless == surfaceless)
return;
m_is_surfaceless = surfaceless;
updateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main, m_is_surfaceless);
}
static void createHostDisplay()
{
Assert(!s_host_display);
m_is_rendering_to_main = Host::GetBaseBoolSettingValue("Main", "RenderToMainWindow", true);
QtDisplayWidget* display_widget = createDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main);
if (!display_widget || !s_host_display->HasRenderDevice())
{
emit destroyDisplayRequested();
s_host_display.reset();
return nullptr;
}
if (!s_host_display->MakeRenderContextCurrent() ||
!s_host_display->InitializeRenderDevice(EmuFolders::Cache, g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation) ||
!ImGuiManager::Initialize() || !CommonHost::CreateHostDisplayResources())
{
ImGuiManager::Shutdown();
CommonHost::ReleaseHostDisplayResources();
s_host_display->DestroyRenderDevice();
emit destroyDisplayRequested();
s_host_display.reset();
return nullptr;
}
m_is_exclusive_fullscreen = s_host_display->IsFullscreen();
return s_host_display.get();
}
HostDisplay* QtHostInterface::createHostDisplay()
{
switch (g_settings.gpu_renderer)
{
case GPURenderer::HardwareVulkan:
@ -657,23 +675,49 @@ HostDisplay* QtHostInterface::createHostDisplay()
break;
#endif
}
}
HostDisplay* QtHostInterface::acquireHostDisplay()
{
createHostDisplay();
m_is_rendering_to_main = Host::GetBaseBoolSettingValue("Main", "RenderToMainWindow", true);
DisplayWidget* display_widget = createDisplayRequested(m_is_fullscreen, m_is_rendering_to_main);
if (!display_widget || !s_host_display->HasRenderDevice())
{
emit destroyDisplayRequested();
s_host_display.reset();
return nullptr;
}
if (!s_host_display->MakeRenderContextCurrent() ||
!s_host_display->InitializeRenderDevice(EmuFolders::Cache, g_settings.gpu_use_debug_device,
g_settings.gpu_threaded_presentation) ||
!ImGuiManager::Initialize() || !CommonHost::CreateHostDisplayResources())
{
ImGuiManager::Shutdown();
CommonHost::ReleaseHostDisplayResources();
s_host_display->DestroyRenderDevice();
emit destroyDisplayRequested();
s_host_display.reset();
return nullptr;
}
m_is_exclusive_fullscreen = s_host_display->IsFullscreen();
return s_host_display.get();
}
void QtHostInterface::connectDisplaySignals(QtDisplayWidget* widget)
void QtHostInterface::connectDisplaySignals(DisplayWidget* widget)
{
widget->disconnect(this);
connect(widget, &QtDisplayWidget::windowFocusEvent, this, &QtHostInterface::onDisplayWindowFocused);
connect(widget, &QtDisplayWidget::windowResizedEvent, this, &QtHostInterface::onDisplayWindowResized);
connect(widget, &QtDisplayWidget::windowRestoredEvent, this, &QtHostInterface::redrawDisplayWindow);
connect(widget, &QtDisplayWidget::windowClosedEvent, this, &QtHostInterface::powerOffSystem,
Qt::BlockingQueuedConnection);
connect(widget, &QtDisplayWidget::windowKeyEvent, this, &QtHostInterface::onDisplayWindowKeyEvent);
connect(widget, &QtDisplayWidget::windowMouseMoveEvent, this, &QtHostInterface::onDisplayWindowMouseMoveEvent);
connect(widget, &QtDisplayWidget::windowMouseButtonEvent, this, &QtHostInterface::onDisplayWindowMouseButtonEvent);
connect(widget, &QtDisplayWidget::windowMouseWheelEvent, this, &QtHostInterface::onDisplayWindowMouseWheelEvent);
connect(widget, &DisplayWidget::windowResizedEvent, this, &QtHostInterface::onDisplayWindowResized);
connect(widget, &DisplayWidget::windowRestoredEvent, this, &QtHostInterface::redrawDisplayWindow);
connect(widget, &DisplayWidget::windowKeyEvent, this, &QtHostInterface::onDisplayWindowKeyEvent);
connect(widget, &DisplayWidget::windowMouseMoveEvent, this, &QtHostInterface::onDisplayWindowMouseMoveEvent);
connect(widget, &DisplayWidget::windowMouseButtonEvent, this, &QtHostInterface::onDisplayWindowMouseButtonEvent);
connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &QtHostInterface::onDisplayWindowMouseWheelEvent);
}
void QtHostInterface::updateDisplayState()
@ -684,7 +728,7 @@ void QtHostInterface::updateDisplayState()
// this expects the context to get moved back to us afterwards
s_host_display->DoneRenderContextCurrent();
QtDisplayWidget* display_widget =
DisplayWidget* display_widget =
updateDisplayRequested(m_worker_thread, m_is_fullscreen, m_is_rendering_to_main && !m_is_fullscreen);
if (!display_widget || !s_host_display->MakeRenderContextCurrent())
Panic("Failed to make device context current after updating");
@ -736,35 +780,48 @@ void QtHostInterface::RequestExit()
emit exitRequested();
}
void QtHostInterface::onSystemStarted()
void Host::OnSystemStarting()
{
wakeThread();
stopBackgroundControllerPollTimer();
CommonHost::OnSystemStarting();
emit emulationStarted();
emit emulationPaused(false);
emit g_emu_thread->systemStarting();
}
void QtHostInterface::onSystemPaused()
void Host::OnSystemStarted()
{
emit emulationPaused(true);
startBackgroundControllerPollTimer();
renderDisplay();
CommonHost::OnSystemStarted();
g_emu_thread->wakeThread();
g_emu_thread->stopBackgroundControllerPollTimer();
emit g_emu_thread->systemStarted();
}
void QtHostInterface::onSystemResumed()
void Host::OnSystemPaused()
{
emit emulationPaused(false);
CommonHost::OnSystemPaused();
wakeThread();
stopBackgroundControllerPollTimer();
emit focusDisplayWidgetRequested();
emit g_emu_thread->systemPaused();
g_emu_thread->startBackgroundControllerPollTimer();
g_emu_thread->renderDisplay();
}
void QtHostInterface::onSystemDestroyed()
void Host::OnSystemResumed()
{
startBackgroundControllerPollTimer();
emit emulationStopped();
CommonHost::OnSystemResumed();
emit g_emu_thread->systemResumed();
g_emu_thread->wakeThread();
g_emu_thread->stopBackgroundControllerPollTimer();
}
void Host::OnSystemDestroyed()
{
CommonHost::OnSystemDestroyed();
g_emu_thread->startBackgroundControllerPollTimer();
emit g_emu_thread->systemDestroyed();
}
#if 0
@ -1323,9 +1380,6 @@ void QtHostInterface::loadState(const QString& filename)
return;
}
if (System::IsShutdown())
emit emulationStarting();
System::LoadState(filename.toStdString().c_str());
}
@ -1910,36 +1964,6 @@ void Host::CheckForSettingsChanges(const Settings& old_settings)
// TODO, e.g. render to main
}
void Host::OnSystemStarting()
{
CommonHost::OnSystemStarting();
// g_emu_thread->onSystemStarting();
}
void Host::OnSystemStarted()
{
CommonHost::OnSystemStarted();
g_emu_thread->onSystemStarted();
}
void Host::OnSystemPaused()
{
CommonHost::OnSystemPaused();
g_emu_thread->onSystemPaused();
}
void Host::OnSystemResumed()
{
CommonHost::OnSystemResumed();
g_emu_thread->onSystemResumed();
}
void Host::OnSystemDestroyed()
{
CommonHost::OnSystemDestroyed();
g_emu_thread->onSystemDestroyed();
}
void Host::OnPerformanceMetricsUpdated()
{
GPURenderer renderer = GPURenderer::Count;

View File

@ -34,7 +34,7 @@ class INISettingsInterface;
class HostDisplay;
class MainWindow;
class QtDisplayWidget;
class DisplayWidget;
Q_DECLARE_METATYPE(std::shared_ptr<SystemBootParameters>);
Q_DECLARE_METATYPE(GPURenderer);
@ -60,6 +60,11 @@ public:
ALWAYS_INLINE QEventLoop* getEventLoop() const { return m_worker_thread_event_loop; }
ALWAYS_INLINE bool isFullscreen() const { return m_is_fullscreen; }
ALWAYS_INLINE bool isRenderingToMain() const { return m_is_rendering_to_main; }
ALWAYS_INLINE bool isSurfaceless() const { return m_is_surfaceless; }
ALWAYS_INLINE bool isRunningFullscreenUI() const { return m_run_fullscreen_ui; }
void reinstallTranslator();
void populateLoadStateMenu(const char* game_code, QMenu* menu);
@ -80,17 +85,14 @@ public:
static std::vector<std::pair<QString, QString>> getAvailableLanguageList();
/// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
static HostDisplay* createHostDisplay();
HostDisplay* acquireHostDisplay();
void connectDisplaySignals(QtDisplayWidget* widget);
void connectDisplaySignals(DisplayWidget* widget);
void releaseHostDisplay();
void updateDisplay();
void renderDisplay();
void onSystemStarted();
void onSystemPaused();
void onSystemResumed();
void onSystemDestroyed();
void startBackgroundControllerPollTimer();
void stopBackgroundControllerPollTimer();
void wakeThread();
Q_SIGNALS:
void errorReported(const QString& title, const QString& message);
@ -101,13 +103,14 @@ Q_SIGNALS:
void onInputDeviceConnected(const QString& identifier, const QString& device_name);
void onInputDeviceDisconnected(const QString& identifier);
void onVibrationMotorsEnumerated(const QList<InputBindingKey>& motors);
void emulationStarting();
void emulationStarted();
void emulationStopped();
void emulationPaused(bool paused);
void systemStarting();
void systemStarted();
void systemDestroyed();
void systemPaused();
void systemResumed();
void gameListRefreshed();
QtDisplayWidget* createDisplayRequested(QThread* worker_thread, bool fullscreen, bool render_to_main);
QtDisplayWidget* updateDisplayRequested(QThread* worker_thread, bool fullscreen, bool render_to_main);
DisplayWidget* createDisplayRequested(bool fullscreen, bool render_to_main);
DisplayWidget* updateDisplayRequested(bool fullscreen, bool render_to_main, bool surfaceless);
void displaySizeRequested(qint32 width, qint32 height);
void focusDisplayWidgetRequested();
void destroyDisplayRequested();
@ -156,6 +159,8 @@ public Q_SLOTS:
void saveScreenshot();
void redrawDisplayWindow();
void toggleFullscreen();
void setFullscreen(bool fullscreen);
void setSurfaceless(bool surfaceless);
void loadCheatList(const QString& filename);
void setCheatEnabled(quint32 index, bool enabled);
void applyCheat(quint32 index);
@ -165,11 +170,10 @@ public Q_SLOTS:
private Q_SLOTS:
void doStopThread();
void onDisplayWindowMouseMoveEvent(float x, float y);
void onDisplayWindowMouseMoveEvent(bool relative, float x, float y);
void onDisplayWindowMouseButtonEvent(int button, bool pressed);
void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle);
void onDisplayWindowResized(int width, int height);
void onDisplayWindowFocused();
void onDisplayWindowKeyEvent(int key, bool pressed);
void doBackgroundControllerPoll();
@ -209,8 +213,6 @@ private:
void createBackgroundControllerPollTimer();
void destroyBackgroundControllerPollTimer();
void startBackgroundControllerPollTimer();
void stopBackgroundControllerPollTimer();
void setImGuiFont();
@ -223,7 +225,6 @@ private:
void checkRenderToMainState();
void updateDisplayState();
void queueSettingsSave();
void wakeThread();
QThread* m_original_thread = nullptr;
Thread* m_worker_thread = nullptr;
@ -235,10 +236,16 @@ private:
QTimer* m_background_controller_polling_timer = nullptr;
std::vector<QTranslator*> m_translators;
bool m_run_fullscreen_ui = false;
bool m_is_rendering_to_main = false;
bool m_is_fullscreen = false;
bool m_is_exclusive_fullscreen = false;
bool m_lost_exclusive_fullscreen = false;
bool m_is_surfaceless = false;
bool m_save_state_on_shutdown = false;
bool m_pause_on_focus_loss = false;
bool m_was_paused_by_focus_loss = false;
};
extern QtHostInterface* g_emu_thread;
@ -254,14 +261,18 @@ bool InNoGUIMode();
void RunOnUIThread(const std::function<void()>& func, bool block = false);
/// Returns the application name and version, optionally including debug/devel config indicator.
// QString GetAppNameAndVersion();
QString GetAppNameAndVersion();
/// Returns the debug/devel config indicator.
// QString GetAppConfigSuffix();
QString GetAppConfigSuffix();
/// Returns the base path for resources. This may be : prefixed, if we're using embedded resources.
// QString GetResourcesBasePath();
QString GetResourcesBasePath();
/// Thread-safe settings access.
void QueueSettingsSave();
/// VM state, safe to access on UI thread.
bool IsSystemValid();
bool IsSystemPaused();
} // namespace QtHost

View File

@ -12,6 +12,7 @@
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QScrollBar>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QStyle>
#include <QtWidgets/QTableView>
#include <QtWidgets/QTreeView>
@ -772,4 +773,54 @@ std::optional<unsigned> PromptForAddress(QWidget* parent, const QString& title,
return address;
}
QString StringViewToQString(const std::string_view& str)
{
return str.empty() ? QString() : QString::fromUtf8(str.data(), str.size());
}
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited)
{
if (widget->font().italic() != inherited)
{
QFont new_font(widget->font());
new_font.setItalic(inherited);
widget->setFont(new_font);
}
}
void SetWindowResizeable(QWidget* widget, bool resizeable)
{
if (QMainWindow* window = qobject_cast<QMainWindow*>(widget); window)
{
// update status bar grip if present
if (QStatusBar* sb = window->statusBar(); sb)
sb->setSizeGripEnabled(resizeable);
}
if ((widget->sizePolicy().horizontalPolicy() == QSizePolicy::Preferred) != resizeable)
{
if (resizeable)
{
// Min/max numbers come from uic.
widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
widget->setMinimumSize(1, 1);
widget->setMaximumSize(16777215, 16777215);
}
else
{
widget->setFixedSize(widget->size());
widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}
}
}
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height)
{
width = std::max(width, 1);
height = std::max(height, 1);
if (widget->sizePolicy().horizontalPolicy() == QSizePolicy::Fixed)
widget->setFixedSize(width, height);
widget->resize(width, height);
}
} // namespace QtUtils

View File

@ -67,4 +67,16 @@ void FillComboBoxWithEmulationSpeeds(QComboBox* cb);
/// Prompts for an address in hex.
std::optional<unsigned> PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code);
/// Converts a std::string_view to a QString safely.
QString StringViewToQString(const std::string_view& str);
/// Sets a widget to italics if the setting value is inherited.
void SetWidgetFontForInheritedSetting(QWidget* widget, bool inherited);
/// Changes whether a window is resizable.
void SetWindowResizeable(QWidget* widget, bool resizeable);
/// Adjusts the fixed size for a window if it's not resizeable.
void ResizePotentiallyFixedSizeWindow(QWidget* widget, int width, int height);
} // namespace QtUtils

View File

@ -364,20 +364,7 @@ bool CommonHost::ParseCommandLineParameters(int argc, char* argv[],
boot_params->filename = std::move(boot_filename);
boot_params->override_fast_boot = std::move(force_fast_boot);
boot_params->override_fullscreen = std::move(force_fullscreen);
if (!state_filename.empty())
{
std::unique_ptr<ByteStream> state_stream =
ByteStream::OpenFile(state_filename.c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED);
if (!state_stream)
{
Log_ErrorPrintf("Failed to open save state file '%s'", state_filename.c_str());
return false;
}
boot_params->state_stream = std::move(state_stream);
}
boot_params->save_state = std::move(state_filename);
*out_boot_params = std::move(boot_params);
}