diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp index ad52de4717..8fba7b52db 100644 --- a/pcsx2-qt/EmuThread.cpp +++ b/pcsx2-qt/EmuThread.cpp @@ -497,18 +497,18 @@ void EmuThread::switchRenderer(GSRendererType renderer) GetMTGS().SwitchRenderer(renderer); } -void EmuThread::changeDisc(const QString& path) +void EmuThread::changeDisc(CDVD_SourceType source, const QString& path) { if (!isOnEmuThread()) { - QMetaObject::invokeMethod(this, "changeDisc", Qt::QueuedConnection, Q_ARG(const QString&, path)); + QMetaObject::invokeMethod(this, "changeDisc", Qt::QueuedConnection, Q_ARG(CDVD_SourceType, source), Q_ARG(const QString&, path)); return; } if (!VMManager::HasValidVM()) return; - VMManager::ChangeDisc(path.toStdString()); + VMManager::ChangeDisc(source, path.toStdString()); } void EmuThread::reloadPatches() diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h index 602000ac92..0bd1f8d7c2 100644 --- a/pcsx2-qt/EmuThread.h +++ b/pcsx2-qt/EmuThread.h @@ -30,6 +30,8 @@ class DisplayWidget; struct VMBootParameters; +enum class CDVD_SourceType : uint8_t; + class EmuThread : public QThread { Q_OBJECT @@ -74,7 +76,7 @@ public Q_SLOTS: void updateEmuFolders(); void toggleSoftwareRendering(); void switchRenderer(GSRendererType renderer); - void changeDisc(const QString& path); + void changeDisc(CDVD_SourceType source, const QString& path); void reloadPatches(); void reloadInputSources(); void reloadInputBindings(); diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 29325150fb..6fb07e145c 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "common/FileSystem.h" #include "pcsx2/CDVD/CDVDaccess.h" +#include "pcsx2/CDVD/CDVDdiscReader.h" #include "pcsx2/Frontend/GameList.h" #include "pcsx2/Frontend/LogSink.h" #include "pcsx2/GSDumpReplayer.h" @@ -53,7 +55,7 @@ #include "Tools/InputRecording/NewInputRecordingDlg.h" -static constexpr char DISC_IMAGE_FILTER[] = +static constexpr char OPEN_FILE_FILTER[] = QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.elf *.irx *.m3u *.gs *.gs.xz *.gs.zst *.dump);;" "Single-Track Raw Images (*.bin *.iso);;" "Cue Sheets (*.cue);;" @@ -66,6 +68,15 @@ static constexpr char DISC_IMAGE_FILTER[] = "GS Dumps (*.gs *.gs.xz *.gs.zst);;" "Block Dumps (*.dump)"); +static constexpr char DISC_IMAGE_FILTER[] = + QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.gz *.dump);;" + "Single-Track Raw Images (*.bin *.iso);;" + "Cue Sheets (*.cue);;" + "MAME CHD Images (*.chd);;" + "CSO Images (*.cso);;" + "GZ Images (*.gz);;" + "Block Dumps (*.dump)"); + #ifdef __APPLE__ const char* MainWindow::DEFAULT_THEME_NAME = ""; #else @@ -100,10 +111,10 @@ void MainWindow::initialize() setStyleFromSettings(); setIconThemeFromStyle(); #ifdef __APPLE__ - CocoaTools::AddThemeChangeHandler(this, [](void* ctx){ + CocoaTools::AddThemeChangeHandler(this, [](void* ctx) { // This handler is called *before* the style change has propagated far enough for Qt to see it // Use RunOnUIThread to delay until it has - QtHost::RunOnUIThread([ctx = static_cast(ctx)]{ + QtHost::RunOnUIThread([ctx = static_cast(ctx)] { ctx->setStyleFromSettings(); // Qt won't notice the style change without us touching the palette in some way ctx->setIconThemeFromStyle(); }); @@ -190,6 +201,7 @@ void MainWindow::setupAdditionalUi() void MainWindow::connectSignals() { connect(m_ui.actionStartFile, &QAction::triggered, this, &MainWindow::onStartFileActionTriggered); + connect(m_ui.actionStartDisc, &QAction::triggered, this, &MainWindow::onStartDiscActionTriggered); connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBIOSActionTriggered); connect(m_ui.actionChangeDisc, &QAction::triggered, [this] { m_ui.menuChangeDisc->exec(QCursor::pos()); }); connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered); @@ -281,7 +293,7 @@ void MainWindow::connectSignals() SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionInputRecConsoleLogs, "Logging", "EnableInputRecordingLogs", false); connect(m_ui.actionInputRecConsoleLogs, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged); SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionInputRecControllerLogs, "Logging", "EnableControllerLogs", false); - connect(m_ui.actionInputRecControllerLogs, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged); + connect(m_ui.actionInputRecControllerLogs, &QAction::triggered, this, &MainWindow::onLoggingOptionChanged); // These need to be queued connections to stop crashing due to menus opening/closing and switching focus. connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress); @@ -691,8 +703,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running) void MainWindow::updateStatusBarWidgetVisibility() { - auto Update = [this](QWidget* widget, bool visible, int stretch) - { + auto Update = [this](QWidget* widget, bool visible, int stretch) { if (widget->isVisible()) { m_ui.statusBar->removeWidget(widget); @@ -985,7 +996,13 @@ void MainWindow::onGameListEntryActivated() if (s_vm_valid) { // change disc on double click - doDiscChange(QString::fromStdString(entry->path)); + if (!entry->IsDisc()) + { + QMessageBox::critical(this, tr("Error"), tr("You must select a disc to change discs.")); + return; + } + + doDiscChange(CDVD_SourceType::Iso, QString::fromStdString(entry->path)); return; } @@ -1056,11 +1073,11 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) menu.addSeparator(); populateLoadStateMenu(&menu, QString::fromStdString(entry->path), QString::fromStdString(entry->serial), entry->crc); } - else + else if (entry->IsDisc()) { action = menu.addAction(tr("Change Disc")); connect(action, &QAction::triggered, [this, entry]() { - g_emu_thread->changeDisc(QString::fromStdString(entry->path)); + g_emu_thread->changeDisc(CDVD_SourceType::Iso, QString::fromStdString(entry->path)); switchToEmulationView(); }); QtUtils::MarkActionAsDefault(action); @@ -1077,12 +1094,20 @@ void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point) void MainWindow::onStartFileActionTriggered() { - QString path = - QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr)); + const QString path(QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, tr("Start File"), QString(), tr(OPEN_FILE_FILTER), nullptr))); if (path.isEmpty()) return; - doStartDisc(path); + doStartFile(std::nullopt, path); +} + +void MainWindow::onStartDiscActionTriggered() +{ + QString path(getDiscDevicePath(tr("Start Disc"))); + if (path.isEmpty()) + return; + + doStartFile(CDVD_SourceType::Disc, path); } void MainWindow::onStartBIOSActionTriggered() @@ -1098,7 +1123,7 @@ void MainWindow::onChangeDiscFromFileActionTriggered() if (filename.isEmpty()) return; - g_emu_thread->changeDisc(filename); + g_emu_thread->changeDisc(CDVD_SourceType::Iso, filename); } void MainWindow::onChangeDiscFromGameListActionTriggered() @@ -1109,7 +1134,11 @@ void MainWindow::onChangeDiscFromGameListActionTriggered() void MainWindow::onChangeDiscFromDeviceActionTriggered() { - // TODO + QString path(getDiscDevicePath(tr("Change Disc"))); + if (path.isEmpty()) + return; + + g_emu_thread->changeDisc(CDVD_SourceType::Disc, path); } void MainWindow::onChangeDiscMenuAboutToShow() @@ -1526,7 +1555,7 @@ void MainWindow::dropEvent(QDropEvent* event) const std::string filename_str(filename.toStdString()); if (VMManager::IsSaveStateFileName(filename_str)) { - // can't load a save state without a current VM + // can't load a save state without a current VM if (s_vm_valid) { event->acceptProposedAction(); @@ -1542,10 +1571,10 @@ void MainWindow::dropEvent(QDropEvent* event) // if we're already running, do a disc change, otherwise start event->acceptProposedAction(); if (s_vm_valid) - doDiscChange(filename); + doDiscChange(CDVD_SourceType::Iso, filename); else - doStartDisc(filename); - } + doStartFile(CDVD_SourceType::Iso, filename); + } } DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main) @@ -1802,7 +1831,7 @@ void MainWindow::destroyDisplay() setCentralWidget(m_game_list_widget); m_game_list_widget->setVisible(true); m_game_list_widget->setFocus(); - } + } } void MainWindow::focusDisplayWidget() @@ -1943,6 +1972,44 @@ void MainWindow::doControllerSettings(ControllerSettingsDialog::Category categor dlg->setCategory(category); } +QString MainWindow::getDiscDevicePath(const QString& title) +{ + QString ret; + + const std::vector devices(GetOpticalDriveList()); + if (devices.empty()) + { + QMessageBox::critical(this, title, + tr("Could not find any CD/DVD-ROM devices. Please ensure you have a drive connected and " + "sufficient permissions to access it.")); + return ret; + } + + // if there's only one, select it automatically + if (devices.size() == 1) + { + ret = QString::fromStdString(devices.front()); + return ret; + } + + QStringList input_options; + for (const std::string& name : devices) + input_options.append(QString::fromStdString(name)); + + QInputDialog input_dialog(this); + input_dialog.setWindowTitle(title); + input_dialog.setLabelText(tr("Select disc drive:")); + input_dialog.setInputMode(QInputDialog::TextInput); + input_dialog.setOptions(QInputDialog::UseListViewForComboBoxItems); + input_dialog.setComboBoxEditable(false); + input_dialog.setComboBoxItems(std::move(input_options)); + if (input_dialog.exec() == 0) + return ret; + + ret = input_dialog.textValue(); + return ret; +} + void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional save_slot, std::optional fast_boot) { std::shared_ptr params = std::make_shared(); @@ -2066,7 +2133,7 @@ void MainWindow::loadSaveStateFile(const QString& filename, const QString& state if (s_vm_valid) { if (!filename.isEmpty() && m_current_disc_path != filename) - g_emu_thread->changeDisc(m_current_disc_path); + g_emu_thread->changeDisc(CDVD_SourceType::Iso, m_current_disc_path); g_emu_thread->loadState(state_filename); } else @@ -2185,12 +2252,13 @@ void MainWindow::updateSaveStateMenus(const QString& filename, const QString& se populateSaveStateMenu(m_ui.menuSaveState, serial, crc); } -void MainWindow::doStartDisc(const QString& path) +void MainWindow::doStartFile(std::optional source, const QString& path) { if (s_vm_valid) return; std::shared_ptr params = std::make_shared(); + params->source_type = source; params->filename = path.toStdString(); // we might still be saving a resume state... @@ -2207,7 +2275,7 @@ void MainWindow::doStartDisc(const QString& path) g_emu_thread->startVM(std::move(params)); } -void MainWindow::doDiscChange(const QString& path) +void MainWindow::doDiscChange(CDVD_SourceType source, const QString& path) { bool reset_system = false; if (!m_was_disc_change_request) @@ -2221,7 +2289,7 @@ void MainWindow::doDiscChange(const QString& path) switchToEmulationView(); - g_emu_thread->changeDisc(path); + g_emu_thread->changeDisc(source, path); if (reset_system) g_emu_thread->resetVM(); } @@ -2248,7 +2316,7 @@ MainWindow::VMLock MainWindow::pauseAndLockVM() MainWindow::VMLock::VMLock(QWidget* dialog_parent, bool was_paused, bool was_fullscreen) : m_dialog_parent(dialog_parent) , m_was_paused(was_paused) - , m_was_fullscreen(was_fullscreen) + , m_was_fullscreen(was_fullscreen) { } diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h index 0a593e7290..55804b9299 100644 --- a/pcsx2-qt/MainWindow.h +++ b/pcsx2-qt/MainWindow.h @@ -39,6 +39,8 @@ namespace GameList struct Entry; } +enum class CDVD_SourceType : uint8_t; + class MainWindow final : public QMainWindow { Q_OBJECT @@ -111,6 +113,7 @@ private Q_SLOTS: void onGameListEntryContextMenuRequested(const QPoint& point); void onStartFileActionTriggered(); + void onStartDiscActionTriggered(); void onStartBIOSActionTriggered(); void onChangeDiscFromFileActionTriggered(); void onChangeDiscFromGameListActionTriggered(); @@ -202,6 +205,8 @@ private: ControllerSettingsDialog* getControllerSettingsDialog(); void doControllerSettings(ControllerSettingsDialog::Category category = ControllerSettingsDialog::Category::Count); + QString getDiscDevicePath(const QString& title); + void startGameListEntry(const GameList::Entry* entry, std::optional save_slot = std::nullopt, std::optional fast_boot = std::nullopt); void setGameListEntryCoverImage(const GameList::Entry* entry); @@ -212,8 +217,8 @@ private: void populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc); void populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc); void updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc); - void doStartDisc(const QString& path); - void doDiscChange(const QString& path); + void doStartFile(std::optional source, const QString& path); + void doDiscChange(CDVD_SourceType source, const QString& path); Ui::MainWindow m_ui; diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp index 0b23c92e47..9eebaddca5 100644 --- a/pcsx2-qt/QtHost.cpp +++ b/pcsx2-qt/QtHost.cpp @@ -85,6 +85,7 @@ bool QtHost::Initialize() qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); if (!InitializeConfig()) diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h index 907dae8d2a..2f6d27044d 100644 --- a/pcsx2-qt/QtHost.h +++ b/pcsx2-qt/QtHost.h @@ -29,11 +29,14 @@ class SettingsInterface; class EmuThread; +enum class CDVD_SourceType : uint8_t; + Q_DECLARE_METATYPE(std::shared_ptr); Q_DECLARE_METATYPE(std::optional); Q_DECLARE_METATYPE(std::function); Q_DECLARE_METATYPE(GSRendererType); Q_DECLARE_METATYPE(InputBindingKey); +Q_DECLARE_METATYPE(CDVD_SourceType); namespace QtHost { diff --git a/pcsx2/Frontend/GameList.h b/pcsx2/Frontend/GameList.h index 289fddeadc..3eb2cdd40a 100644 --- a/pcsx2/Frontend/GameList.h +++ b/pcsx2/Frontend/GameList.h @@ -91,6 +91,8 @@ namespace GameList u32 crc = 0; CompatibilityRating compatibility_rating = CompatibilityRating::Unknown; + + __fi bool IsDisc() const { return (type == EntryType::PS1Disc || type == EntryType::PS2Disc); } }; const char* EntryTypeToString(EntryType type); diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index 2fc54c6152..dc5271d993 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -1294,15 +1294,15 @@ void VMManager::FrameAdvance(u32 num_frames /*= 1*/) SetState(VMState::Running); } -bool VMManager::ChangeDisc(std::string path) +bool VMManager::ChangeDisc(CDVD_SourceType source, std::string path) { - std::string old_path(CDVDsys_GetFile(CDVD_SourceType::Iso)); - CDVD_SourceType old_type = CDVDsys_GetSourceType(); + const CDVD_SourceType old_type = CDVDsys_GetSourceType(); + const std::string old_path(CDVDsys_GetFile(old_type)); - const std::string display_name(path.empty() ? std::string() : FileSystem::GetDisplayNameFromPath(path)); - CDVDsys_ChangeSource(path.empty() ? CDVD_SourceType::NoDisc : CDVD_SourceType::Iso); + const std::string display_name((source != CDVD_SourceType::Iso) ? path : FileSystem::GetDisplayNameFromPath(path)); + CDVDsys_ChangeSource(source); if (!path.empty()) - CDVDsys_SetFile(CDVD_SourceType::Iso, std::move(path)); + CDVDsys_SetFile(source, std::move(path)); const bool result = DoCDVDopen(); if (result) diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index 61a935e76f..aa1c72e25c 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -135,7 +135,7 @@ namespace VMManager /// Changes the disc in the virtual CD/DVD drive. Passing an empty will remove any current disc. /// Returns false if the new disc can't be opened. - bool ChangeDisc(std::string path); + bool ChangeDisc(CDVD_SourceType source, std::string path); /// Returns true if the specified path is an ELF. bool IsElfFileName(const std::string_view& path);