Qt: add log level settings
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux-aarch64.sh, gcc, rpcs3/rpcs3-ci-jammy-aarch64:1.9, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (/rpcs3/.ci/build-linux.sh, gcc, rpcs3/rpcs3-ci-jammy:1.9, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1, rpcs3/rpcs3-binaries-linux-arm64, /rpcs3/.ci/build-linux-aarch64.sh, clang, rpcs3/rpcs3-ci-jammy-aarch64:1.9, ubuntu-24.04-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} (d812f1254a1157c80fd402f94446310560f54e5f, rpcs3/rpcs3-binaries-linux, /rpcs3/.ci/build-linux.sh, clang, rpcs3/rpcs3-ci-jammy:1.9, ubuntu-24.04) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (0, 51ae32f468089a8169aaf1567de355ff4a3e0842, rpcs3/rpcs3-binaries-mac, Intel) (push) Waiting to run
Build RPCS3 / RPCS3 Mac ${{ matrix.name }} (1, 8e21bdbc40711a3fccd18fbf17b742348b0f4281, rpcs3/rpcs3-binaries-mac-arm64, Apple Silicon) (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang ${{ matrix.arch }} (aarch64, clang, clangarm64, ARM64, windows-11-arm) (push) Waiting to run
Build RPCS3 / RPCS3 Windows Clang ${{ matrix.arch }} (x86_64, clang, clang64, X64, windows-2025) (push) Waiting to run
Build RPCS3 / RPCS3 FreeBSD (push) Waiting to run

This commit is contained in:
Megamouse 2026-03-04 19:31:50 +01:00
parent 69384d7bb4
commit 0d80e300a0
16 changed files with 279 additions and 31 deletions

@ -1 +1 @@
Subproject commit 05c44fcd18074836e21e1eda9fc02b3a4a1529b5
Subproject commit 51a5d623e3fde1f58829a56ba910f1cb33596222

View file

@ -416,7 +416,7 @@ void cfg::encode(YAML::Emitter& out, const cfg::_base& rhs)
out << YAML::BeginMap;
for (const auto& np : static_cast<const log_entry&>(rhs).get_map())
{
if (np.second == logs::level::notice) continue;
if (np.second == logs::level::_default) continue;
out << YAML::Key << np.first;
out << YAML::Value << fmt::format("%s", np.second);
}

View file

@ -20,19 +20,19 @@ namespace aarch64
sp
};
static const char* gpr_names[] =
[[maybe_unused]] static const char* gpr_names[] =
{
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9",
"x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19",
"x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29", "x30"
};
static const char* spr_names[] =
[[maybe_unused]] static const char* spr_names[] =
{
"xzr", "pc", "sp"
};
static const char* spr_asm_names[] =
[[maybe_unused]] static const char* spr_asm_names[] =
{
"xzr", ".", "sp"
};

View file

@ -826,6 +826,7 @@
<ClCompile Include="rpcs3qt\call_stack_list.cpp" />
<ClCompile Include="rpcs3qt\camera_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\gui_game_info.cpp" />
<ClCompile Include="rpcs3qt\log_level_dialog.cpp" />
<ClCompile Include="rpcs3qt\permissions.cpp" />
<ClCompile Include="rpcs3qt\ps_move_tracker_dialog.cpp" />
<ClCompile Include="rpcs3qt\cheat_manager.cpp" />
@ -1578,6 +1579,7 @@
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\hex_validator.h" />
<ClInclude Include="rpcs3qt\log_level_dialog.h" />
<ClInclude Include="rpcs3qt\movie_item.h" />
<ClInclude Include="rpcs3qt\movie_item_base.h" />
<ClInclude Include="rpcs3qt\numbered_widget_item.h" />

View file

@ -1275,6 +1275,9 @@
<ClCompile Include="Input\mouse_gyro_handler.cpp">
<Filter>Io</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\log_level_dialog.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1517,6 +1520,9 @@
<ClInclude Include="Input\mouse_gyro_handler.h">
<Filter>Io</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\log_level_dialog.h">
<Filter>Gui\settings</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClInclude Include="resource.h">

View file

@ -53,6 +53,7 @@ add_library(rpcs3_ui STATIC
localized.cpp
localized_emu.cpp
log_frame.cpp
log_level_dialog.cpp
log_viewer.cpp
main_window.cpp
memory_string_searcher.cpp

View file

@ -31,18 +31,20 @@ namespace
out << YAML::Null;
return;
}
if (node.IsMap())
{
std::vector<std::string> keys;
keys.reserve(node.size());
// generate vector of strings to be sorted using the as function from YAML documentation
for (const auto& pair : node)
{
keys.push_back(pair.first.as<std::string>());
keys.push_back(pair.first.Scalar());
}
std::sort(keys.begin(), keys.end());
// recursively generate sorted maps
// alternative implementations could have stops at specified recursion levels or maybe just the first two levels would be sorted
out << YAML::BeginMap;
for (const std::string& key : keys)
{
@ -51,15 +53,10 @@ namespace
emit_data(out, node[key]);
}
out << YAML::EndMap;
return;
}
// alternatively: an else statement could be used however I wanted to follow a similar format to the += operator so the YAML Undefined class can be ignored
else if (node.IsScalar() || node.IsSequence())
{
out << node;
}
// this exists to preserve the same functionality as before where Undefined nodes would still be output, can be removed or consolidated with the else if branch
else
out << node;
out << node;
}
// Incrementally load YAML
@ -910,11 +907,38 @@ std::string emu_settings::GetSetting(emu_settings_type type) const
return "";
}
std::map<std::string, std::string> emu_settings::GetMapSettingDefault(emu_settings_type type) const
{
if (const auto node = cfg_adapter::get_node(m_default_settings, ::at32(settings_location, type)); node && node.IsMap())
{
return node.as<std::map<std::string, std::string>>();
}
cfg_log.fatal("GetMapSettingDefault(type=%d) could not retrieve the requested node", static_cast<int>(type));
return {};
}
std::map<std::string, std::string> emu_settings::GetMapSetting(emu_settings_type type) const
{
if (const auto node = cfg_adapter::get_node(m_current_settings, ::at32(settings_location, type)); node && node.IsMap())
{
return node.as<std::map<std::string, std::string>>();
}
cfg_log.fatal("GetMapSetting(type=%d) could not retrieve the requested node", static_cast<int>(type));
return {};
}
void emu_settings::SetSetting(emu_settings_type type, const std::string& val) const
{
cfg_adapter::get_node(m_current_settings, ::at32(settings_location, type)) = val;
}
void emu_settings::SetMapSetting(emu_settings_type type, const std::map<std::string, std::string>& val) const
{
cfg_adapter::get_node(m_current_settings, ::at32(settings_location, type)) = val;
}
emu_settings_type emu_settings::FindSettingsType(const cfg::_base* node) const
{
// Add key and value to static map on first use

View file

@ -78,9 +78,18 @@ public:
/** Returns the value of the setting type.*/
std::string GetSetting(emu_settings_type type) const;
/** Returns the default map value of the setting type.*/
std::map<std::string, std::string> GetMapSettingDefault(emu_settings_type type) const;
/** Returns the value of the setting type as map.*/
std::map<std::string, std::string> GetMapSetting(emu_settings_type type) const;
/** Sets the setting type to a given value.*/
void SetSetting(emu_settings_type type, const std::string& val) const;
/** Sets the setting type to a given map value.*/
void SetMapSetting(emu_settings_type type, const std::map<std::string, std::string>& val) const;
/** Try to find the settings type for a given string.*/
emu_settings_type FindSettingsType(const cfg::_base* node) const;

View file

@ -217,6 +217,9 @@ enum class emu_settings_type
EmptyHdd0Tmp,
LimitCacheSize,
MaximumCacheSize,
// Log
Log,
};
/** A helper map that keeps track of where a given setting type is located*/
@ -434,4 +437,7 @@ inline static const std::map<emu_settings_type, cfg_location> settings_location
{ emu_settings_type::SuspendEmulationSavestateMode, { "Savestate", "Suspend Emulation Savestate Mode" }},
{ emu_settings_type::CompatibleEmulationSavestateMode, { "Savestate", "Compatible Savestate Mode" }},
{ emu_settings_type::StartSavestatePaused, { "Savestate", "Start Paused" }},
// Logs
{ emu_settings_type::Log, { "Log" }},
};

View file

@ -0,0 +1,155 @@
#include "stdafx.h"
#include "log_level_dialog.h"
#include "emu_settings.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
LOG_CHANNEL(cfg_log, "CFG");
log_level_dialog::log_level_dialog(QWidget* parent, std::shared_ptr<emu_settings> emu_settings)
: QDialog(parent), m_emu_settings(emu_settings)
{
setWindowTitle(tr("Configure minimum log levels"));
setObjectName("log_level_dialog");
setAttribute(Qt::WA_DeleteOnClose);
setMinimumSize(QSize(700, 400));
const std::set<std::string> channels = logs::get_channels();
std::vector<std::pair<std::string, QString>> levels;
const auto add_level = [&levels](logs::level level, const QString& localized)
{
levels.push_back(std::pair<std::string, QString>(fmt::format("%s", level), localized));
};
add_level(logs::level::always, tr("Always"));
add_level(logs::level::fatal, tr("Fatal"));
add_level(logs::level::error, tr("Error"));
add_level(logs::level::todo, tr("Todo"));
add_level(logs::level::success, tr("Success"));
add_level(logs::level::warning, tr("Warning"));
add_level(logs::level::notice, tr("Notice"));
add_level(logs::level::trace, tr("Trace"));
const std::map<std::string, std::string> current_settings = m_emu_settings->GetMapSetting(emu_settings_type::Log);
for (const auto& [channel, level] : current_settings)
{
if (!channels.contains(channel))
{
cfg_log.warning("log_level_dialog: Ignoring unknown channel '%s' found in config file.", channel);
}
}
m_table = new QTableWidget(static_cast<int>(channels.size()), 2, this);
m_table->setHorizontalHeaderLabels({ tr("Channel"), tr("Level") });
int i = 0;
for (const std::string& channel : channels)
{
QComboBox* combo = new QComboBox();
for (const auto& [level, localized] : levels)
{
combo->addItem(localized, QString::fromStdString(level));
}
connect(combo, &QComboBox::currentIndexChanged, combo, [this, combo, ch = channel](int index)
{
if (index < 0) return;
const QVariant var = combo->itemData(index);
if (!var.canConvert<QString>()) return;
std::map<std::string, std::string> settings = m_emu_settings->GetMapSetting(emu_settings_type::Log);
settings[ch] = var.toString().toStdString();
m_emu_settings->SetMapSetting(emu_settings_type::Log, settings);
});
m_table->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(channel)));
m_table->setCellWidget(i, 1, combo);
i++;
}
QLineEdit* filter_edit = new QLineEdit(this);
filter_edit->setPlaceholderText(tr("Filter channels"));
connect(filter_edit, &QLineEdit::textChanged, this, [this](const QString& text)
{
for (int i = 0; i < m_table->rowCount(); i++)
{
if (QTableWidgetItem* item = m_table->item(i, 0))
{
m_table->setRowHidden(i, !text.isEmpty() && !item->text().contains(text, Qt::CaseInsensitive));
}
}
});
QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults);
connect(button_box, &QDialogButtonBox::clicked, this, [this, button_box, old_settings = m_emu_settings->GetMapSetting(emu_settings_type::Log)](QAbstractButton* button)
{
if (button == button_box->button(QDialogButtonBox::Ok))
{
accept();
}
else if (button == button_box->button(QDialogButtonBox::Cancel))
{
m_emu_settings->SetMapSetting(emu_settings_type::Log, old_settings);
reject();
}
else if (button == button_box->button(QDialogButtonBox::RestoreDefaults))
{
m_emu_settings->SetMapSetting(emu_settings_type::Log, m_emu_settings->GetMapSettingDefault(emu_settings_type::Log));
reload_page();
}
});
reload_page();
m_table->resizeColumnsToContents();
m_table->horizontalHeader()->stretchLastSection();
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(filter_edit);
layout->addWidget(m_table);
layout->addWidget(button_box);
setLayout(layout);
}
log_level_dialog::~log_level_dialog()
{
}
void log_level_dialog::reload_page()
{
const std::map<std::string, std::string> settings = m_emu_settings->GetMapSetting(emu_settings_type::Log);
const QString def_str = QString::fromStdString(fmt::format("%s", logs::level::_default));
for (int i = 0; i < m_table->rowCount(); i++)
{
QTableWidgetItem* item = m_table->item(i, 0);
if (!item) continue;
const std::string channel = item->text().toStdString();
if (QComboBox* combo = static_cast<QComboBox*>(m_table->cellWidget(i, 1)))
{
combo->blockSignals(true);
combo->setCurrentIndex(combo->findData(def_str));
if (settings.contains(channel))
{
if (const int index = combo->findData(QString::fromStdString(settings.at(channel))); index >= 0)
{
combo->setCurrentIndex(index);
}
}
combo->blockSignals(false);
}
}
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <QDialog>
#include <QTableWidget>
class emu_settings;
class log_level_dialog : public QDialog
{
public:
log_level_dialog(QWidget* parent, std::shared_ptr<emu_settings> emu_settings);
virtual ~log_level_dialog();
private:
void reload_page();
std::shared_ptr<emu_settings> m_emu_settings;
QTableWidget* m_table = nullptr;
};

View file

@ -23,13 +23,13 @@
#include "emu_settings_type.h"
#include "render_creator.h"
#include "microphone_creator.h"
#include "Emu/NP/rpcn_countries.h"
#include "log_level_dialog.h"
#include "Emu/NP/rpcn_countries.h"
#include "Emu/GameInfo.h"
#include "Emu/System.h"
#include "Emu/system_config.h"
#include "Emu/title.h"
#include "Emu/Audio/audio_device_enumerator.h"
#include "Loader/PSF.h"
@ -2496,6 +2496,14 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
m_emu_settings->EnhanceComboBox(ui->vulkansched, emu_settings_type::VulkanAsyncSchedulerDriver);
SubscribeTooltip(ui->gb_vulkansched, tooltips.settings.vulkan_async_scheduler);
// Log levels
SubscribeTooltip(ui->gb_log_levels, tooltips.settings.vulkan_async_scheduler);
connect(ui->pb_log_levels, &QAbstractButton::clicked, this, [this]()
{
log_level_dialog* dlg = new log_level_dialog(this, m_emu_settings);
dlg->open();
});
if (!restoreGeometry(m_gui_settings->GetValue(gui::cfg_geometry).toByteArray()))
{
// Ignore. This will most likely only fail if the setting doesn't contain any values

View file

@ -265,7 +265,7 @@
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="coreTabRightLayout" stretch="0,0,0,0">
<layout class="QVBoxLayout" name="coreTabRightLayout" stretch="0,0,0">
<item>
<widget class="QGroupBox" name="gb_spu_threads">
<property name="sizePolicy">
@ -2321,9 +2321,9 @@
</item>
<item>
<widget class="QCheckBox" name="enable_clans">
<property name="text">
<string>Enable Clans</string>
</property>
<property name="text">
<string>Enable Clans</string>
</property>
</widget>
</item>
<item>
@ -4467,7 +4467,7 @@
</item>
<item>
<widget class="QWidget" name="debug_more_stuff" native="true">
<layout class="QVBoxLayout" name="debug_more_stuff_layout" stretch="0,0,0,0,0,1">
<layout class="QVBoxLayout" name="debug_more_stuff_layout" stretch="0,0,0,0,0,0,1">
<property name="leftMargin">
<number>0</number>
</property>
@ -4545,6 +4545,22 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_log_levels">
<property name="title">
<string>Log Levels</string>
</property>
<layout class="QVBoxLayout" name="gb_log_levels_layout">
<item>
<widget class="QPushButton" name="pb_log_levels">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_debug_io">
<property name="title">

View file

@ -59,6 +59,7 @@ public:
const QString paused_savestates = tr("When this mode is on, savestates are loaded and paused on the first frame.\nThis allows players to prepare for gameplay without being thrown into the action immediately.");
const QString spu_profiler = tr("When enabled, SPU performance is measured at runtime.\nEnable only at a developer's request because when enabled it reduces performance a bit by itself.");
const QString use_ReBAR = tr("When enabled, Vulkan will try to use PCI-e resizable bar address space for GPU uploads of timing-sensitive data.\nThis yields a massive performance win on NVIDIA cards when the base framerate is low.\nFor games with very high framerates, this option can result in worse performance for all GPU vendors.\n");
const QString log_levels = tr("Set the minimum log levels for any log channels.");
// audio

View file

@ -202,7 +202,7 @@ namespace logs
for (auto&& pair : get_logger()->channels)
{
pair.second->enabled.release(level::notice);
pair.second->enabled.release(level::_default);
}
}
@ -271,18 +271,17 @@ namespace logs
}
}
std::vector<std::string> get_channels()
std::set<std::string> get_channels()
{
std::vector<std::string> result;
std::set<std::string> result;
std::lock_guard lock(g_mutex);
for (auto&& p : get_logger()->channels)
{
// Copy names removing duplicates
if (result.empty() || result.back() != p.first)
if (!p.first.empty())
{
result.push_back(p.first);
result.insert(p.first);
}
}

View file

@ -3,7 +3,7 @@
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <set>
#include <initializer_list>
#include "util/atomic.hpp"
#include "Utilities/StrFmt.h"
@ -20,6 +20,8 @@ namespace logs
warning = 5,
notice = 6,
trace = 7, // Lowest severity (usually disabled)
_default = notice
};
struct channel;
@ -163,7 +165,7 @@ namespace logs
registerer(channel& _ch);
};
// Log level control: set all channels to level::notice
// Log level control: set all channels to default level::notice
void reset();
// Log level control: set all channels to level::always
@ -179,7 +181,7 @@ namespace logs
void set_channel_levels(const std::map<std::string, logs::level, std::less<>>& map);
// Get all registered log channels
std::vector<std::string> get_channels();
std::set<std::string> get_channels();
// Helper: no additional name specified
consteval const char* make_channel_name(const char* name, const char* alt = nullptr)