[desktop] More qt_common reorganization (#3916)

Ported from QML branch.

Main "big" change is that EmuThread is now a shared state in QtCommon,
not individually managed/passed around by GRenderWindow and MainWindow.

Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/3916
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
This commit is contained in:
crueter
2026-05-20 04:49:16 +02:00
parent 300a646a34
commit feb8c5f88e
44 changed files with 963 additions and 932 deletions
+91 -1
View File
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/file_sys/card_image.h"
#include "qt_common/util/content.h"
#include "qt_common/util/game.h"
@@ -494,5 +495,94 @@ void ImportDataDir(FrontendCommon::DataManager::DataDir data_dir, const std::str
});
}
// TODO(crueter): Port InstallFirmware et al. from QML Branch
bool CheckKeys() {
if (!ContentManager::AreKeysPresent()) {
QtCommon::Frontend::Information(
tr("Keys not installed"),
tr("Install decryption keys and restart Eden before attempting to install firmware."));
return false;
}
return true;
}
void InstallFirmware() {
if (!CheckKeys())
return;
const QString firmware_source_location =
QtCommon::Frontend::GetExistingDirectory(tr("Select Dumped Firmware Source Location"), {});
if (!firmware_source_location.isEmpty())
QtCommon::Content::InstallFirmware(firmware_source_location, false);
}
void InstallFirmwareZip() {
if (!CheckKeys())
return;
const QString firmware_zip_location = QtCommon::Frontend::GetOpenFileName(
tr("Select Dumped Firmware ZIP"), {}, tr("Zipped Archives (*.zip)"));
if (firmware_zip_location.isEmpty())
return;
const QString qCacheDir = QtCommon::Content::UnzipFirmwareToTmp(firmware_zip_location);
// In this case, it has to be done recursively, since sometimes people
// will pack it into a subdirectory after dumping
if (!qCacheDir.isEmpty()) {
QtCommon::Content::InstallFirmware(qCacheDir, true);
std::error_code ec;
std::filesystem::remove_all(std::filesystem::temp_directory_path() / "eden" / "firmware",
ec);
if (ec) {
QtCommon::Frontend::Warning(
tr("Firmware cleanup failed"),
tr("Failed to clean up extracted firmware cache.\n"
"Check write permissions in the system temp directory and try "
"again.\nOS reported error: %1")
.arg(QString::fromStdString(ec.message())));
}
}
}
void configureFilesystemProvider(const std::string& filepath) {
// Ensure all NCAs are registered before launching the game
const auto file = QtCommon::vfs->OpenFile(filepath, FileSys::OpenMode::Read);
if (!file) {
return;
}
auto loader = Loader::GetLoader(*QtCommon::system, file);
if (!loader) {
return;
}
const auto file_type = loader->GetFileType();
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
return;
}
u64 program_id = 0;
const auto res2 = loader->ReadProgramId(program_id);
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
QtCommon::provider->AddEntry(FileSys::TitleType::Application,
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
program_id, file);
} else if (res2 == Loader::ResultStatus::Success &&
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
const auto nsp = file_type == Loader::FileType::NSP
? std::make_shared<FileSys::NSP>(file)
: FileSys::XCI{file}.GetSecurePartitionNSP();
for (const auto& title : nsp->GetNCAs()) {
for (const auto& entry : title.second) {
QtCommon::provider->AddEntry(entry.first.first, entry.first.second, title.first,
entry.second->GetBaseFile());
}
}
}
}
} // namespace QtCommon::Content
+7 -1
View File
@@ -37,9 +37,12 @@ inline const QString GetKeyInstallResultString(FirmwareManager::KeyInstallResult
}
void InstallFirmware(const QString& location, bool recursive);
QString UnzipFirmwareToTmp(const QString& location);
bool CheckKeys();
void InstallFirmware();
void InstallFirmwareZip();
// Keys //
void InstallKeys();
@@ -54,6 +57,9 @@ void ExportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string&
void ImportDataDir(FrontendCommon::DataManager::DataDir dir, const std::string& user_id = "",
std::function<void()> callback = {});
// Loader //
void configureFilesystemProvider(const std::string& filepath);
// Profiles //
void FixProfiles();
} // namespace QtCommon::Content
+108 -1
View File
@@ -11,7 +11,6 @@
#include "qt_common/abstract/frontend.h"
#include "qt_common/config/uisettings.h"
#include "qt_common/qt_common.h"
#include "yuzu/util/util.h"
#include <QDesktopServices>
#include <QStandardPaths>
@@ -22,6 +21,7 @@
#include <windows.h>
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "common/fs/file.h"
#else
#include <fstream>
#include "fmt/ostream.h"
@@ -367,6 +367,7 @@ void ResetMetadata(bool show_message) {
// Uhhh //
// TODO(crueter): Make QtCommon::Shortcut
// Messages in pre-defined message boxes for less code spaghetti
inline constexpr bool CreateShortcutMessagesGUI(ShortcutMessages imsg, const QString& game_title) {
int result = 0;
@@ -533,4 +534,110 @@ void CreateHomeMenuShortcut(ShortcutTarget target) {
CreateShortcut(game_path, QLaunchId, "Switch Home Menu", target, "-qlaunch", false);
}
bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image) {
#if defined(WIN32)
#pragma pack(push, 2)
struct IconDir {
WORD id_reserved;
WORD id_type;
WORD id_count;
};
struct IconDirEntry {
BYTE width;
BYTE height;
BYTE color_count;
BYTE reserved;
WORD planes;
WORD bit_count;
DWORD bytes_in_res;
DWORD image_offset;
};
#pragma pack(pop)
const QImage source_image = image.convertToFormat(QImage::Format_RGB32);
constexpr std::array<int, 7> scale_sizes{256, 128, 64, 48, 32, 24, 16};
constexpr int bytes_per_pixel = 4;
const IconDir icon_dir{
.id_reserved = 0,
.id_type = 1,
.id_count = static_cast<WORD>(scale_sizes.size()),
};
Common::FS::IOFile icon_file(icon_path.string(), Common::FS::FileAccessMode::Write,
Common::FS::FileType::BinaryFile);
if (!icon_file.IsOpen()) {
return false;
}
if (!icon_file.Write(icon_dir)) {
return false;
}
std::size_t image_offset = sizeof(IconDir) + (sizeof(IconDirEntry) * scale_sizes.size());
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const int image_size = scale_sizes[i] * scale_sizes[i] * bytes_per_pixel;
const IconDirEntry icon_entry{
.width = static_cast<BYTE>(scale_sizes[i]),
.height = static_cast<BYTE>(scale_sizes[i]),
.color_count = 0,
.reserved = 0,
.planes = 1,
.bit_count = bytes_per_pixel * 8,
.bytes_in_res = static_cast<DWORD>(sizeof(BITMAPINFOHEADER) + image_size),
.image_offset = static_cast<DWORD>(image_offset),
};
image_offset += icon_entry.bytes_in_res;
if (!icon_file.Write(icon_entry)) {
return false;
}
}
for (std::size_t i = 0; i < scale_sizes.size(); i++) {
const QImage scaled_image = source_image.scaled(
scale_sizes[i], scale_sizes[i], Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
const BITMAPINFOHEADER info_header{
.biSize = sizeof(BITMAPINFOHEADER),
.biWidth = scaled_image.width(),
.biHeight = scaled_image.height() * 2,
.biPlanes = 1,
.biBitCount = bytes_per_pixel * 8,
.biCompression = BI_RGB,
.biSizeImage{},
.biXPelsPerMeter{},
.biYPelsPerMeter{},
.biClrUsed{},
.biClrImportant{},
};
if (!icon_file.Write(info_header)) {
return false;
}
for (int y = 0; y < scaled_image.height(); y++) {
const auto* line = scaled_image.scanLine(scaled_image.height() - 1 - y);
std::vector<u8> line_data(scaled_image.width() * bytes_per_pixel);
std::memcpy(line_data.data(), line, line_data.size());
if (!icon_file.Write(line_data)) {
return false;
}
}
}
icon_file.Close();
return true;
#elif defined(__unix__) && !defined(__APPLE__) && !defined(__ANDROID__)
// Convert and write the icon as a PNG
if (!image.save(QString::fromStdString(icon_path.string()))) {
LOG_ERROR(Frontend, "Could not write icon as PNG to file");
} else {
LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
}
return true;
#else
return false;
#endif
}
} // namespace QtCommon::Game
+8
View File
@@ -69,6 +69,14 @@ void CreateShortcut(const std::string& game_path, const u64 program_id,
std::string GetShortcutPath(ShortcutTarget target);
void CreateHomeMenuShortcut(ShortcutTarget target);
/**
* Saves a windows icon to a file
* @param path The icons path
* @param image The image to save
* @return bool If the operation succeeded
*/
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
} // namespace QtCommon::Game
#endif // QT_GAME_UTIL_H
+81
View File
@@ -0,0 +1,81 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <utility>
#include <vector>
#include "qt_common/qt_common.h"
#include "common/dynamic_library.h"
#include "common/logging.h"
#include "qt_common/util/vk.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_library.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "vulkan/vulkan_core.h"
class QWindow;
namespace VkDeviceInfo {
Record::Record(std::string_view name_, const std::vector<VkPresentModeKHR>& vsync_modes_,
bool has_broken_compute_)
: name{name_}, vsync_support{vsync_modes_}, has_broken_compute{has_broken_compute_} {}
Record::~Record() = default;
void PopulateRecords(std::vector<Record>& records, QWindow* window) try {
using namespace Vulkan;
// Create a test window with a Vulkan surface type for checking present modes.
QWindow test_window(window);
test_window.setSurfaceType(QWindow::VulkanSurface);
test_window.create();
auto wsi = QtCommon::GetWindowSystemInfo(&test_window);
vk::InstanceDispatch dld;
const auto library = OpenLibrary();
const vk::Instance instance = CreateInstance(*library, dld, VK_API_VERSION_1_1, wsi.type);
const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
vk::SurfaceKHR surface = CreateSurface(instance, wsi);
records.clear();
records.reserve(physical_devices.size());
for (const VkPhysicalDevice device : physical_devices) {
const auto physical_device = vk::PhysicalDevice(device, dld);
std::string name = physical_device.GetProperties().deviceName;
const std::vector<VkPresentModeKHR> present_modes =
physical_device.GetSurfacePresentModesKHR(*surface);
VkPhysicalDeviceDriverProperties driver_properties{};
driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
driver_properties.pNext = nullptr;
VkPhysicalDeviceProperties2 properties{};
properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
properties.pNext = &driver_properties;
dld.vkGetPhysicalDeviceProperties2(physical_device, &properties);
const auto driverID = driver_properties.driverID;
bool has_broken_compute{
Vulkan::Device::CheckBrokenCompute(driverID, properties.properties.driverVersion)};
std::string driver_string = Vulkan::vk::GetDriverName(driver_properties);
if (driver_string.empty())
driver_string = "Unknown";
name = fmt::format("{} ({})", name, driver_string);
records.push_back(VkDeviceInfo::Record(name, present_modes, has_broken_compute));
}
} catch (const Vulkan::vk::Exception& exception) {
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
}
} // namespace VkDeviceInfo
+71
View File
@@ -0,0 +1,71 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <string_view>
#include <vector>
#include "common/common_types.h"
#include "common/settings_enums.h"
#include "vulkan/vulkan_core.h"
static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
VK_PRESENT_MODE_FIFO_KHR};
// Converts a setting to a present mode (or vice versa)
static inline constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
switch (mode) {
case Settings::VSyncMode::Immediate:
return VK_PRESENT_MODE_IMMEDIATE_KHR;
case Settings::VSyncMode::Mailbox:
return VK_PRESENT_MODE_MAILBOX_KHR;
case Settings::VSyncMode::Fifo:
return VK_PRESENT_MODE_FIFO_KHR;
case Settings::VSyncMode::FifoRelaxed:
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
default:
return VK_PRESENT_MODE_FIFO_KHR;
}
}
static inline constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) {
switch (mode) {
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return Settings::VSyncMode::Immediate;
case VK_PRESENT_MODE_MAILBOX_KHR:
return Settings::VSyncMode::Mailbox;
case VK_PRESENT_MODE_FIFO_KHR:
return Settings::VSyncMode::Fifo;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return Settings::VSyncMode::FifoRelaxed;
default:
return Settings::VSyncMode::Fifo;
}
}
class QWindow;
namespace Settings {
enum class VSyncMode : u32;
}
namespace VkDeviceInfo {
// Short class to record Vulkan driver information for configuration purposes
class Record {
public:
explicit Record(std::string_view name, const std::vector<VkPresentModeKHR>& vsync_modes,
bool has_broken_compute);
~Record();
const std::string name;
const std::vector<VkPresentModeKHR> vsync_support;
const bool has_broken_compute;
};
// TODO(crueter): Port some configure_graphics.cpp stuff
void PopulateRecords(std::vector<Record>& records, QWindow* window);
} // namespace VkDeviceInfo