diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-06-17 01:46:09 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-06-17 01:46:09 -0500 |
| commit | 05aa96d20ca726908a7d8b1943f86f0c4e11bf18 (patch) | |
| tree | bd45913d1c7a033088f3456e40c8b44c72818ace /src | |
| parent | 30d2c801a01eeb140ddd5adf8676f2626ecff3fa (diff) | |
Prepare Nexus-safe release cleanup
Diffstat (limited to 'src')
29 files changed, 609 insertions, 667 deletions
diff --git a/src/src/envshortcut.cpp b/src/src/envshortcut.cpp index 60786d2..2baeeac 100644 --- a/src/src/envshortcut.cpp +++ b/src/src/envshortcut.cpp @@ -46,6 +46,13 @@ static QString sanitizeDesktopName(const QString& s) return result; } +static QString shellQuote(const QString& s) +{ + QString result = s; + result.replace("'", "'\"'\"'"); + return "'" + result + "'"; +} + // --------------------------------------------------------------------------- // PE icon extractor — reads the largest icon embedded in a Windows .exe // and returns it as a QImage. Returns a null QImage on failure. @@ -443,6 +450,33 @@ bool Shortcut::add(Locations loc) QDir().mkpath(QFileInfo(path).absolutePath()); + const QString script = scriptPath(loc); + if (script.isEmpty()) { + return false; + } + + QFile scriptFile(script); + if (!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + return false; + } + + QTextStream scriptOut(&scriptFile); + scriptOut << "#!/usr/bin/env bash\n"; + scriptOut << "set -euo pipefail\n"; + if (!m_workingDirectory.isEmpty()) { + scriptOut << "cd " << shellQuote(m_workingDirectory) << "\n"; + } + scriptOut << "exec " << shellQuote(m_target); + if (!m_arguments.isEmpty()) { + scriptOut << " " << m_arguments; + } + scriptOut << "\n"; + scriptFile.close(); + scriptFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | + QFileDevice::ExeOwner | QFileDevice::ReadGroup | + QFileDevice::ExeGroup | QFileDevice::ReadOther | + QFileDevice::ExeOther); + QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { return false; @@ -463,12 +497,7 @@ bool Shortcut::add(Locations loc) if (!m_description.isEmpty()) { out << "Comment=" << m_description << "\n"; } - // .desktop Exec values require quoting paths that contain spaces - out << "Exec=\"" << m_target << "\""; - if (!m_arguments.isEmpty()) { - out << " " << m_arguments; - } - out << "\n"; + out << "Exec=\"" << script << "\"\n"; if (!m_workingDirectory.isEmpty()) { out << "Path=" << m_workingDirectory << "\n"; } @@ -481,7 +510,10 @@ bool Shortcut::add(Locations loc) file.close(); // Make it executable (required by some desktop environments) - file.setPermissions(file.permissions() | QFileDevice::ExeUser); + file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | + QFileDevice::ExeOwner | QFileDevice::ReadGroup | + QFileDevice::ExeGroup | QFileDevice::ReadOther | + QFileDevice::ExeOther); return true; } @@ -492,7 +524,12 @@ bool Shortcut::remove(Locations loc) if (path.isEmpty()) { return false; } - return QFile::remove(path); + const bool removedShortcut = QFile::remove(path); + const QString script = scriptPath(loc); + if (!script.isEmpty()) { + QFile::remove(script); + } + return removedShortcut; } QString Shortcut::shortcutPath(Locations loc) const @@ -504,6 +541,15 @@ QString Shortcut::shortcutPath(Locations loc) const return dir + "/" + shortcutFilename(); } +QString Shortcut::scriptPath(Locations loc) const +{ + const QString dir = shortcutDirectory(loc); + if (dir.isEmpty()) { + return {}; + } + return dir + "/" + scriptFilename(); +} + QString Shortcut::shortcutDirectory(Locations loc) { if (loc == Desktop) { @@ -535,6 +581,19 @@ QString Shortcut::shortcutFilename() const return base + ".desktop"; } +QString Shortcut::scriptFilename() const +{ + if (m_name.isEmpty()) { + return "fluorine-launch.sh"; + } + + QString base = m_name; + if (!m_instanceName.isEmpty()) { + base = sanitizeDesktopName(m_instanceName) + "-" + base; + } + return sanitizeDesktopName(base) + ".sh"; +} + QString toString(Shortcut::Locations loc) { switch (loc) { diff --git a/src/src/envshortcut.h b/src/src/envshortcut.h index 4c9675b..66cfdda 100644 --- a/src/src/envshortcut.h +++ b/src/src/envshortcut.h @@ -89,18 +89,20 @@ private: int m_iconIndex;
QString m_workingDirectory;
- // returns the path where the shortcut file should be saved
- //
- QString shortcutPath(Locations loc) const;
-
- // returns the directory where the shortcut file should be saved
- //
- static QString shortcutDirectory(Locations loc) ;
-
- // returns the filename of the shortcut file that should be used when saving
- //
- QString shortcutFilename() const;
-};
+ // returns the path where the shortcut file should be saved + // + QString shortcutPath(Locations loc) const; + QString scriptPath(Locations loc) const; + + // returns the directory where the shortcut file should be saved + // + static QString shortcutDirectory(Locations loc); + + // returns the filename of the shortcut file that should be used when saving + // + QString shortcutFilename() const; + QString scriptFilename() const; +}; // returns a string representation of the given location
//
diff --git a/src/src/fuseconnector.cpp b/src/src/fuseconnector.cpp index fb2a518..c5b5718 100644 --- a/src/src/fuseconnector.cpp +++ b/src/src/fuseconnector.cpp @@ -56,69 +56,6 @@ namespace { namespace fs = std::filesystem; -bool parseVersionTriplet(const char* text, int* major, int* minor, int* patch) -{ - if (text == nullptr || major == nullptr || minor == nullptr || patch == nullptr) { - return false; - } - - char* end = nullptr; - const long ma = std::strtol(text, &end, 10); - if (end == text || *end != '.') { - return false; - } - const long mi = std::strtol(end + 1, &end, 10); - if (*end != '.') { - return false; - } - const long pa = std::strtol(end + 1, &end, 10); - if (ma < 0 || mi < 0 || pa < 0) { - return false; - } - - *major = static_cast<int>(ma); - *minor = static_cast<int>(mi); - *patch = static_cast<int>(pa); - return true; -} - -bool runtimeLibfuseHasStableIoUring() -{ -#if defined(FUSE_CAP_OVER_IO_URING) && FUSE_VERSION >= FUSE_MAKE_VERSION(3, 18) - int major = 0; - int minor = 0; - int patch = 0; - if (!parseVersionTriplet(fuse_pkgversion(), &major, &minor, &patch)) { - return false; - } - return major > 3 || (major == 3 && (minor > 18 || (minor == 18 && patch >= 2))); -#else - return false; -#endif -} - -void recordRequestTransport(fuse_req_t req) noexcept -{ - if (req == nullptr) { - return; - } - - auto* ctx = static_cast<Mo2FsContext*>(fuse_req_userdata(req)); - if (ctx == nullptr) { - return; - } - -#if defined(FUSE_CAP_OVER_IO_URING) && FUSE_VERSION >= FUSE_MAKE_VERSION(3, 18) - if (fuse_req_is_uring(req)) { - ctx->uring_request_count.fetch_add(1, std::memory_order_relaxed); - } else { - ctx->legacy_request_count.fetch_add(1, std::memory_order_relaxed); - } -#else - ctx->legacy_request_count.fetch_add(1, std::memory_order_relaxed); -#endif -} - std::string decodeProcMountField(const std::string& in) { std::string out; @@ -272,21 +209,18 @@ void wrap_init(void* userdata, struct fuse_conn_info* conn) noexcept void wrap_lookup(fuse_req_t req, fuse_ino_t parent, const char* name) noexcept { - recordRequestTransport(req); try { mo2_lookup(req, parent, name); } MO2_TRY_REPLY(req, "lookup", parent, EIO) } void wrap_getattr(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_getattr(req, ino, fi); } MO2_TRY_REPLY(req, "getattr", ino, EIO) } void wrap_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_opendir(req, ino, fi); } MO2_TRY_REPLY(req, "opendir", ino, EIO) } @@ -294,7 +228,6 @@ void wrap_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noe void wrap_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_readdir(req, ino, size, off, fi); } MO2_TRY_REPLY(req, "readdir", ino, EIO) } @@ -302,14 +235,12 @@ void wrap_readdir(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, void wrap_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_readdirplus(req, ino, size, off, fi); } MO2_TRY_REPLY(req, "readdirplus", ino, EIO) } void wrap_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_open(req, ino, fi); } MO2_TRY_REPLY(req, "open", ino, EIO) } @@ -317,7 +248,6 @@ void wrap_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexce void wrap_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_read(req, ino, size, off, fi); } MO2_TRY_REPLY(req, "read", ino, EIO) } @@ -325,7 +255,6 @@ void wrap_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, void wrap_write(fuse_req_t req, fuse_ino_t ino, const char* buf, size_t size, off_t off, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_write(req, ino, buf, size, off, fi); } MO2_TRY_REPLY(req, "write", ino, EIO) } @@ -333,7 +262,6 @@ void wrap_write(fuse_req_t req, fuse_ino_t ino, const char* buf, size_t size, void wrap_create(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_create(req, parent, name, mode, fi); } MO2_TRY_REPLY(req, "create", parent, EIO) } @@ -341,7 +269,6 @@ void wrap_create(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mod void wrap_rename(fuse_req_t req, fuse_ino_t parent, const char* name, fuse_ino_t newparent, const char* newname, unsigned int flags) noexcept { - recordRequestTransport(req); try { mo2_rename(req, parent, name, newparent, newname, flags); } MO2_TRY_REPLY(req, "rename", parent, EIO) } @@ -349,56 +276,48 @@ void wrap_rename(fuse_req_t req, fuse_ino_t parent, const char* name, void wrap_setattr(fuse_req_t req, fuse_ino_t ino, struct stat* attr, int to_set, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_setattr(req, ino, attr, to_set, fi); } MO2_TRY_REPLY(req, "setattr", ino, EIO) } void wrap_unlink(fuse_req_t req, fuse_ino_t parent, const char* name) noexcept { - recordRequestTransport(req); try { mo2_unlink(req, parent, name); } MO2_TRY_REPLY(req, "unlink", parent, EIO) } void wrap_mkdir(fuse_req_t req, fuse_ino_t parent, const char* name, mode_t mode) noexcept { - recordRequestTransport(req); try { mo2_mkdir(req, parent, name, mode); } MO2_TRY_REPLY(req, "mkdir", parent, EIO) } void wrap_rmdir(fuse_req_t req, fuse_ino_t parent, const char* name) noexcept { - recordRequestTransport(req); try { mo2_rmdir(req, parent, name); } MO2_TRY_REPLY(req, "rmdir", parent, EIO) } void wrap_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_release(req, ino, fi); } MO2_TRY_REPLY(req, "release", ino, EIO) } void wrap_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_releasedir(req, ino, fi); } MO2_TRY_REPLY(req, "releasedir", ino, EIO) } void wrap_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup) noexcept { - recordRequestTransport(req); try { mo2_forget(req, ino, nlookup); } catch (...) { fuse_reply_none(req); } } void wrap_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_flush(req, ino, fi); } MO2_TRY_REPLY(req, "flush", ino, EIO) } @@ -406,7 +325,6 @@ void wrap_flush(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info* fi) noexc void wrap_fsync(fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info* fi) noexcept { - recordRequestTransport(req); try { mo2_fsync(req, ino, datasync, fi); } MO2_TRY_REPLY(req, "fsync", ino, EIO) } @@ -640,15 +558,8 @@ bool FuseConnector::mount( std::vector<std::string> argvStorage = { "mo2fuse", "-o", "fsname=mo2linux", "-o", "noatime", "-o", "default_permissions", "-o", "max_read=1048576"}; - const bool requestIoUring = runtimeLibfuseHasStableIoUring(); - if (requestIoUring) { - argvStorage.push_back("-o"); - argvStorage.push_back("io_uring"); - } - std::fprintf(stderr, - "[VFS] libfuse=%s headers=%d.%d io_uring_mount_option=%d\n", - fuse_pkgversion(), FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION, - requestIoUring ? 1 : 0); + std::fprintf(stderr, "[VFS] libfuse=%s headers=%d.%d\n", + fuse_pkgversion(), FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION); std::vector<char*> argv; argv.reserve(argvStorage.size()); diff --git a/src/src/instancemanager.cpp b/src/src/instancemanager.cpp index 066c0bd..1b95b5e 100644 --- a/src/src/instancemanager.cpp +++ b/src/src/instancemanager.cpp @@ -228,7 +228,12 @@ void Instance::updateIni() // updating the settings since some of these values might have been missing s.game().setName(m_gameName); - s.game().setDirectory(m_gameDir); + if (!m_gameDirAutoDetectedFromInvalidIni) { + s.game().setDirectory(m_gameDir); + } else { + log::warn("preserving existing gamePath in ini {} after auto-detecting {}", + iniPath(), m_gameDir); + } s.game().setSelectedProfileName(m_profile); if (!m_gameVariant.isEmpty()) { @@ -241,6 +246,7 @@ void Instance::setGame(const QString& name, const QString& dir) { m_gameName = name; m_gameDir = dir; + m_gameDirAutoDetectedFromInvalidIni = false; } void Instance::setVariant(const QString& name) @@ -276,6 +282,7 @@ Instance::SetupResults Instance::getGamePlugin(PluginContainer& plugins) game->gameDirectory().absolutePath()); m_gameDir = game->gameDirectory().absolutePath(); + m_gameDirAutoDetectedFromInvalidIni = true; } else { // game seems to be gone completely log::warn("game plugin {} found no game installation at all", diff --git a/src/src/instancemanager.h b/src/src/instancemanager.h index 0d3becd..0208e3e 100644 --- a/src/src/instancemanager.h +++ b/src/src/instancemanager.h @@ -190,11 +190,12 @@ public: std::vector<Object> objectsForDeletion() const;
private:
- QString m_dir;
- bool m_portable;
- QString m_gameName, m_gameDir, m_gameVariant, m_baseDir;
- MOBase::IPluginGame* m_plugin{nullptr};
- QString m_profile;
+ QString m_dir; + bool m_portable; + QString m_gameName, m_gameDir, m_gameVariant, m_baseDir; + bool m_gameDirAutoDetectedFromInvalidIni{false}; + MOBase::IPluginGame* m_plugin{nullptr}; + QString m_profile; // figures out the game plugin for this instance
//
diff --git a/src/src/instancemanagerdialog.cpp b/src/src/instancemanagerdialog.cpp index 06522dd..567026f 100644 --- a/src/src/instancemanagerdialog.cpp +++ b/src/src/instancemanagerdialog.cpp @@ -240,15 +240,6 @@ InstanceManagerDialog::InstanceManagerDialog(PluginContainer& pc, QWidget* paren s.setValue("fluorine/vfs_root_builder", checked); }); - connect(ui->steamOverlayCheckBox, &QCheckBox::toggled, [&](bool checked) { - const auto* inst = singleSelection(); - if (!inst) return; - const QString ini = inst->iniPath(); - if (ini.isEmpty()) return; - QSettings s(ini, QSettings::IniFormat); - s.setValue("fluorine/steam_overlay", checked); - }); - connect(ui->switchToInstance, &QPushButton::clicked, [&] { openSelectedInstance(); }); @@ -835,9 +826,6 @@ void InstanceManagerDialog::fillData(const Instance& ii) ui->vfsRootBuilderCheckBox->setChecked(s.value("fluorine/vfs_root_builder", true).toBool()); ui->vfsRootBuilderCheckBox->blockSignals(false); - ui->steamOverlayCheckBox->blockSignals(true); - ui->steamOverlayCheckBox->setChecked(s.value("fluorine/steam_overlay", false).toBool()); - ui->steamOverlayCheckBox->blockSignals(false); } else { ui->prefixPath->clear(); ui->protonVersion->clear(); @@ -850,9 +838,6 @@ void InstanceManagerDialog::fillData(const Instance& ii) ui->vfsRootBuilderCheckBox->blockSignals(true); ui->vfsRootBuilderCheckBox->setChecked(false); ui->vfsRootBuilderCheckBox->blockSignals(false); - ui->steamOverlayCheckBox->blockSignals(true); - ui->steamOverlayCheckBox->setChecked(false); - ui->steamOverlayCheckBox->blockSignals(false); } } diff --git a/src/src/instancemanagerdialog.ui b/src/src/instancemanagerdialog.ui index 6a4e5ee..64928f5 100644 --- a/src/src/instancemanagerdialog.ui +++ b/src/src/instancemanagerdialog.ui @@ -353,16 +353,6 @@ </property> </widget> </item> - <item row="13" column="0" colspan="2"> - <widget class="QCheckBox" name="steamOverlayCheckBox"> - <property name="text"> - <string>Enable Steam Overlay</string> - </property> - <property name="toolTip"> - <string>Inject the Steam overlay (Shift+Tab) into the game. Requires Steam running, the game's Steam App ID set, and that you own the game on Steam. Combines LD_PRELOAD of gameoverlayrenderer.so with the Steam Vulkan overlay layer (needed for DXVK-rendered games like Skyrim/Fallout).</string> - </property> - </widget> - </item> </layout> </widget> </item> diff --git a/src/src/mainwindow.cpp b/src/src/mainwindow.cpp index ca92499..c70772b 100644 --- a/src/src/mainwindow.cpp +++ b/src/src/mainwindow.cpp @@ -673,6 +673,8 @@ void MainWindow::resetButtonIcons() MainWindow::~MainWindow() { try { + m_ArchiveListWriter.writeImmediately(true); + m_OrganizerCore.pluginsWriter().writeImmediately(true); cleanup(); m_OrganizerCore.setUserInterface(nullptr); @@ -2245,7 +2247,8 @@ void MainWindow::readSettings() s.geometry().restoreToolbars(this); s.geometry().restoreState(ui->splitter); s.geometry().restoreState(ui->categoriesSplitter); - s.geometry().restoreVisibility(ui->menuBar); + ui->menuBar->show(); + ui->toolBar->show(); s.geometry().restoreVisibility(ui->statusBar); FilterWidget::setOptions(s.interface().filterOptions()); @@ -2341,7 +2344,6 @@ void MainWindow::storeSettings() s.geometry().saveGeometry(this); s.geometry().saveDocks(this); - s.geometry().saveVisibility(ui->menuBar); s.geometry().saveVisibility(ui->statusBar); s.geometry().saveToolbars(this); s.geometry().saveState(ui->splitter); diff --git a/src/src/moapplication.cpp b/src/src/moapplication.cpp index 94e5b02..16ffb62 100644 --- a/src/src/moapplication.cpp +++ b/src/src/moapplication.cpp @@ -43,9 +43,11 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "tutorialmanager.h" #include <QDebug> #include <QDesktopServices> +#include <QEvent> #include <QFile> #include <QPainter> #include <QProxyStyle> +#include <QRegularExpression> #include <QSslSocket> #include <QStringList> #include <QStyleFactory> @@ -498,6 +500,16 @@ int MOApplication::run(MOMultiProcess& multiProcess) // main window is about to be destroyed m_nexus->getAccessManager()->setTopLevelWidget(nullptr); + + try { + if (m_core != nullptr) { + m_core->saveCurrentProfileForShutdown(); + } + } catch (const std::exception& e) { + log::error("failed to save current profile during shutdown: {}", e.what()); + } catch (...) { + log::error("failed to save current profile during shutdown: unknown exception"); + } } // reset geometry if the flag was set from the settings dialog @@ -650,11 +662,19 @@ void MOApplication::resetForRestart() // clear instance and profile overrides InstanceManager::singleton().clearOverrides(); - m_core = {}; + if (m_core != nullptr) { + m_core->saveCurrentProfileForShutdown(); + } + m_plugins = {}; + QCoreApplication::removePostedEvents(nullptr); + + m_core = {}; m_nexus = {}; m_settings = {}; m_instance = {}; + + QCoreApplication::removePostedEvents(nullptr); } bool MOApplication::setStyleFile(const QString& styleName) @@ -772,6 +792,78 @@ bool MOApplication::notify(QObject* receiver, QEvent* event) namespace { +QString styleSheetFontSizeOverride(int fontSize) +{ + if (fontSize <= 0) { + return {}; + } + + return QStringLiteral("\n\n/* Fluorine QSS font size override */\n" + "QWidget { font-size: %1px; }\n") + .arg(fontSize); +} + +QString resolveStyleSheetUrl(const QString& url, const QString& baseDir) +{ + const QString trimmed = url.trimmed(); + if (trimmed.isEmpty() || trimmed.startsWith(':') || trimmed.startsWith('/') || + trimmed.contains("://")) { + return trimmed; + } + + return QUrl::fromLocalFile(QDir(baseDir).absoluteFilePath(trimmed)).toString(); +} + +QString resolveRelativeStyleSheetUrls(const QString& stylesheet, + const QString& baseDir) +{ + static const QRegularExpression urlRe( + QStringLiteral(R"(url\(\s*(['"]?)([^'")]+)\1\s*\))"), + QRegularExpression::CaseInsensitiveOption); + + QString result; + qsizetype last = 0; + auto matches = urlRe.globalMatch(stylesheet); + while (matches.hasNext()) { + const auto match = matches.next(); + result += stylesheet.mid(last, match.capturedStart() - last); + result += QStringLiteral("url(%1)") + .arg(resolveStyleSheetUrl(match.captured(2), baseDir)); + last = match.capturedEnd(); + } + result += stylesheet.mid(last); + return result; +} + +QString applyQssFontSize(const QString& stylesheet, int fontSize) +{ + if (fontSize <= 0) { + return stylesheet; + } + + static const QRegularExpression fontSizeRe( + QStringLiteral(R"(font-size\s*:\s*[^;{}]+;)"), + QRegularExpression::CaseInsensitiveOption); + + QString result = stylesheet; + result.replace(fontSizeRe, QStringLiteral("font-size: %1px;").arg(fontSize)); + result += styleSheetFontSizeOverride(fontSize); + return result; +} + +std::optional<QString> loadStyleSheet(const QString& fileName, int fontSize) +{ + QFile stylesheet(fileName); + if (!stylesheet.open(QFile::ReadOnly | QFile::Text)) { + log::error("failed to open stylesheet file {}", fileName); + return {}; + } + + QString content = QString::fromUtf8(stylesheet.readAll()); + content = resolveRelativeStyleSheetUrls(content, QFileInfo(fileName).absolutePath()); + return applyQssFontSize(content, fontSize); +} + QStringList extractTopStyleSheetComments(QFile& stylesheet) { if (!stylesheet.open(QFile::ReadOnly)) { @@ -903,6 +995,9 @@ static void createStylesheetCaseShims(const QString& dirPath) void MOApplication::updateStyle(const QString& fileName) { + const int qssFontSize = + m_settings != nullptr ? m_settings->interface().qssFontSize() : 0; + if (QStyleFactory::keys().contains(fileName)) { setStyleSheet(""); setStyle(new ProxyStyle(QStyleFactory::create(fileName))); @@ -914,7 +1009,9 @@ void MOApplication::updateStyle(const QString& fileName) createStylesheetCaseShims(QFileInfo(fileName).absolutePath()); setStyle(new ProxyStyle(QStyleFactory::create( extractBaseStyleFromStyleSheet(stylesheet, m_defaultStyle)))); - setStyleSheet(QString("file:///%1").arg(fileName)); + if (auto content = loadStyleSheet(fileName, qssFontSize)) { + setStyleSheet(*content); + } } else { log::warn("invalid stylesheet: {}", fileName); } diff --git a/src/src/modlistview.cpp b/src/src/modlistview.cpp index 89e0faa..36bee5f 100644 --- a/src/src/modlistview.cpp +++ b/src/src/modlistview.cpp @@ -702,6 +702,66 @@ void ModListView::updateGroupByProxy() } } +void ModListView::applyDefaultHeaderState() +{ + // hide these columns by default + header()->setSectionHidden(ModList::COL_CONTENT, true); + header()->setSectionHidden(ModList::COL_MODID, true); + header()->setSectionHidden(ModList::COL_UPLOADER, true); + header()->setSectionHidden(ModList::COL_GAME, true); + header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + header()->setSectionHidden(ModList::COL_NOTES, true); + + // resize mod list to fit content + for (int i = 0; i < header()->count(); ++i) { + header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + + header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); +} + +void ModListView::forceHeaderVisibilityRefresh() +{ + // restoreState() does not reliably emit resize/visibility signals for each + // section, so force our proxy's visible-column bookkeeping to catch up. + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + const int sectionSize = header()->sectionSize(column); + header()->resizeSection(column, sectionSize + 1); + header()->resizeSection(column, sectionSize); + } +} + +bool ModListView::headerStateLooksBroken() const +{ + int visibleCount = 0; + int tinyCount = 0; + + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + if (header()->isSectionHidden(column)) { + continue; + } + + ++visibleCount; + if (header()->sectionSize(column) < 50) { + ++tinyCount; + } + } + + return visibleCount >= 8 && tinyCount >= visibleCount - 1; +} + +void ModListView::syncColumnVisibilityFromHeader() +{ + if (m_sortProxy == nullptr) { + return; + } + + for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { + m_sortProxy->setColumnVisible( + column, !header()->isSectionHidden(column) && header()->sectionSize(column) > 0); + } +} + void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindow* mw, Ui::MainWindow* mwui) { @@ -798,6 +858,9 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo [=, this](int logicalIndex, int oldSize, int newSize) { m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); }); + connect(header(), &QHeaderView::geometriesChanged, [=, this] { + syncColumnVisibilityFromHeader(); + }); setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(this, ModList::COL_FLAGS, 120)); @@ -809,38 +872,6 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo setItemDelegateForColumn(ModList::COL_VERSION, new ModListVersionDelegate(this, core.settings())); - m_restoringHeaderState = true; - const bool headerRestored = m_core->settings().geometry().restoreState(header()); - m_restoringHeaderState = false; - - if (headerRestored) { - // hack: force the resize-signal to be triggered because restoreState doesn't seem - // to do that - for (int column = 0; column <= ModList::COL_LASTCOLUMN; ++column) { - int sectionSize = header()->sectionSize(column); - header()->resizeSection(column, sectionSize + 1); - header()->resizeSection(column, sectionSize); - } - } else { - // hide these columns by default - header()->setSectionHidden(ModList::COL_CONTENT, true); - header()->setSectionHidden(ModList::COL_MODID, true); - header()->setSectionHidden(ModList::COL_UPLOADER, true); - header()->setSectionHidden(ModList::COL_GAME, true); - header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - header()->setSectionHidden(ModList::COL_NOTES, true); - - // resize mod list to fit content - for (int i = 0; i < header()->count(); ++i) { - header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - - header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); - } - - // prevent the name-column from being hidden - header()->setSectionHidden(ModList::COL_NAME, false); - // we need QueuedConnection for the download/archive dropped otherwise the // installation starts within the drop-event and it's not possible to drag&drop // in the manual installer @@ -901,8 +932,20 @@ void ModListView::restoreState(const Settings& s) s.widgets().restoreIndex(ui.groupBy); m_restoringHeaderState = true; - s.geometry().restoreState(header()); + const bool headerRestored = s.geometry().restoreState(header()); m_restoringHeaderState = false; + if (headerRestored && !headerStateLooksBroken()) { + forceHeaderVisibilityRefresh(); + } else { + if (headerRestored) { + log::warn("discarding broken mod list header state"); + } + applyDefaultHeaderState(); + } + + // prevent the name-column from being hidden + header()->setSectionHidden(ModList::COL_NAME, false); + syncColumnVisibilityFromHeader(); s.widgets().restoreTreeExpandState(this); diff --git a/src/src/modlistview.h b/src/src/modlistview.h index a5c079c..839e777 100644 --- a/src/src/modlistview.h +++ b/src/src/modlistview.h @@ -314,12 +314,16 @@ private: // private functions //
void refreshExpandedItems();
- // refresh the group-by proxy, if the index is -1 will refresh the
- // current one (e.g. when changing the sort column)
- //
- void updateGroupByProxy();
-
-public: // member variables
+ // refresh the group-by proxy, if the index is -1 will refresh the + // current one (e.g. when changing the sort column) + // + void updateGroupByProxy(); + void applyDefaultHeaderState(); + void forceHeaderVisibilityRefresh(); + bool headerStateLooksBroken() const; + void syncColumnVisibilityFromHeader(); + +public: // member variables OrganizerCore* m_core{nullptr};
std::unique_ptr<FilterList> m_filters;
CategoryFactory* m_categories;
diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp index 242ae1c..64772fa 100644 --- a/src/src/organizercore.cpp +++ b/src/src/organizercore.cpp @@ -340,23 +340,44 @@ OrganizerCore::OrganizerCore(Settings& settings) OrganizerCore::~OrganizerCore() { - m_RefresherThread.exit(); - m_RefresherThread.wait(); - - if (m_StructureDeleter.joinable()) { - m_StructureDeleter.join(); + try { + saveCurrentProfileForShutdown(); + } catch (const std::exception& e) { + log::error("failed to save current profile during OrganizerCore shutdown: {}", + e.what()); + } catch (...) { + log::error( + "failed to save current profile during OrganizerCore shutdown: unknown exception"); } - saveCurrentProfile(); + try { + m_RefresherThread.exit(); + m_RefresherThread.wait(); + + if (m_StructureDeleter.joinable()) { + m_StructureDeleter.join(); + } + } catch (const std::exception& e) { + log::error("failed while stopping OrganizerCore worker threads: {}", e.what()); + } catch (...) { + log::error("failed while stopping OrganizerCore worker threads: unknown exception"); + } - // profile has to be cleaned up before the modinfo-buffer is cleared - m_CurrentProfile.reset(); + try { + // profile has to be cleaned up before the modinfo-buffer is cleared + m_CurrentProfile.reset(); - ModInfo::clear(); - m_ModList.setProfile(nullptr); - // NexusInterface::instance()->cleanup(); + ModInfo::clear(); + m_ModList.setProfile(nullptr); + // NexusInterface::instance()->cleanup(); - delete m_DirectoryStructure; + delete m_DirectoryStructure; + m_DirectoryStructure = nullptr; + } catch (const std::exception& e) { + log::error("failed while clearing OrganizerCore state: {}", e.what()); + } catch (...) { + log::error("failed while clearing OrganizerCore state: unknown exception"); + } } void OrganizerCore::storeSettings() @@ -2462,6 +2483,51 @@ void OrganizerCore::saveCurrentProfile() storeSettings(); } +void OrganizerCore::saveCurrentProfileForShutdown() +{ + if (m_CurrentProfileSavedForShutdown) { + return; + } + + try { + saveCurrentProfile(); + } catch (const std::exception& e) { + log::error("failed to save current profile during shutdown: {}", e.what()); + } catch (...) { + log::error("failed to save current profile during shutdown: unknown exception"); + } + + try { + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + } catch (const std::exception& e) { + log::error("failed to flush mod list during shutdown: {}", e.what()); + } catch (...) { + log::error("failed to flush mod list during shutdown: unknown exception"); + } + + try { + m_PluginListsWriter.writeImmediately(true); + } catch (const std::exception& e) { + log::error("failed to flush plugin list during shutdown: {}", e.what()); + } catch (...) { + log::error("failed to flush plugin list during shutdown: unknown exception"); + } + + try { + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(true); + } + } catch (const std::exception& e) { + log::error("failed to flush archive list during shutdown: {}", e.what()); + } catch (...) { + log::error("failed to flush archive list during shutdown: unknown exception"); + } + + m_CurrentProfileSavedForShutdown = true; +} + ProcessRunner OrganizerCore::processRunner() { return ProcessRunner(*this, m_UserInterface); diff --git a/src/src/organizercore.h b/src/src/organizercore.h index 973ee64..f5aad22 100644 --- a/src/src/organizercore.h +++ b/src/src/organizercore.h @@ -442,12 +442,14 @@ public: // - group to add the function to
// - if immediateIfReady is true, the function will be called immediately if no
// directory update is running
- boost::signals2::connection onNextRefresh(std::function<void()> const& func,
- RefreshCallbackGroup group,
- RefreshCallbackMode mode);
-
-public: // IPluginDiagnose interface
- std::vector<unsigned int> activeProblems() const override;
+ boost::signals2::connection onNextRefresh(std::function<void()> const& func, + RefreshCallbackGroup group, + RefreshCallbackMode mode); + + void saveCurrentProfileForShutdown(); + +public: // IPluginDiagnose interface + std::vector<unsigned int> activeProblems() const override; QString shortDescription(unsigned int key) const override;
QString fullDescription(unsigned int key) const override;
bool hasGuidedFix(unsigned int key) const override;
@@ -559,10 +561,11 @@ private: QString m_GameName;
MOBase::IPluginGame* m_GamePlugin{nullptr};
ModDataContentHolder m_Contents;
-
- std::shared_ptr<Profile> m_CurrentProfile;
-
- Settings& m_Settings;
+ + std::shared_ptr<Profile> m_CurrentProfile; + bool m_CurrentProfileSavedForShutdown = false; + + Settings& m_Settings; SelfUpdater m_Updater;
class FluorineUpdater* m_FluorineUpdater = nullptr;
diff --git a/src/src/plugincontainer.cpp b/src/src/plugincontainer.cpp index e2ca421..277b1d9 100644 --- a/src/src/plugincontainer.cpp +++ b/src/src/plugincontainer.cpp @@ -8,9 +8,11 @@ #include <QCoreApplication> #include <QDirIterator> #include <QMessageBox> +#include <QSet> #include <QToolButton> #include <QSettings> #include <cstdio> +#include <utility> #include <boost/fusion/algorithm/iteration/for_each.hpp> #include <boost/fusion/include/at_key.hpp> #include <boost/fusion/include/for_each.hpp> @@ -333,8 +335,14 @@ PluginContainer::PluginContainer(OrganizerCore* organizer) PluginContainer::~PluginContainer() { + try { + unloadPlugins(); + } catch (const std::exception& e) { + log::error("failed to unload plugins during shutdown: {}", e.what()); + } catch (...) { + log::error("failed to unload plugins during shutdown: unknown exception"); + } m_Organizer = nullptr; - unloadPlugins(); } void PluginContainer::startPlugins(IUserInterface* userInterface) @@ -1140,6 +1148,48 @@ void PluginContainer::unloadPlugins() m_Organizer->settings().plugins().clearPlugins(); } + QSet<QString> seenProxiedPaths; + std::vector<std::pair<IPluginProxy*, QString>> proxiedPaths; + const auto objects = bf::at_key<QObject>(m_Plugins); + for (QObject* object : objects) { + auto* plugin = qobject_cast<IPlugin*>(object); + if (!plugin) { + continue; + } + + const auto req = m_Requirements.find(plugin); + if (req == m_Requirements.end()) { + continue; + } + + if (auto* proxy = req->second.m_Organizer) { + proxy->disconnectSignals(); + } + + if (auto* game = qobject_cast<IPluginGame*>(object)) { + unregisterGame(game); + } + + auto* pluginProxy = req->second.proxy(); + if (pluginProxy) { + const QString path = filepath(plugin); + if (!seenProxiedPaths.contains(path)) { + proxiedPaths.emplace_back(pluginProxy, path); + seenProxiedPaths.insert(path); + } + } + } + + for (const auto& [proxy, path] : proxiedPaths) { + try { + proxy->unload(path); + } catch (const std::exception& e) { + log::error("failed to unload proxied plugin '{}': {}", path, e.what()); + } catch (...) { + log::error("failed to unload proxied plugin '{}': unknown exception", path); + } + } + bf::for_each(m_Plugins, [](auto& t) { t.second.clear(); }); @@ -1151,8 +1201,17 @@ void PluginContainer::unloadPlugins() while (!m_PluginLoaders.empty()) { QPluginLoader* loader = m_PluginLoaders.back(); m_PluginLoaders.pop_back(); - if ((loader != nullptr) && !loader->unload()) { - log::debug("failed to unload {}: {}", loader->fileName(), loader->errorString()); + if (loader != nullptr) { + try { + if (!loader->unload()) { + log::debug("failed to unload {}: {}", loader->fileName(), + loader->errorString()); + } + } catch (const std::exception& e) { + log::error("failed to unload {}: {}", loader->fileName(), e.what()); + } catch (...) { + log::error("failed to unload {}: unknown exception", loader->fileName()); + } } delete loader; } diff --git a/src/src/processrunner.cpp b/src/src/processrunner.cpp index a0deae2..36108b8 100644 --- a/src/src/processrunner.cpp +++ b/src/src/processrunner.cpp @@ -1,10 +1,8 @@ #include "processrunner.h" #include "env.h" #include "envmodule.h" -#include "fluorineconfig.h" #include "instancemanager.h" #include "iuserinterface.h" -#include "knowngames.h" #include "organizercore.h" #include <iplugingame.h> @@ -19,7 +17,6 @@ #include <QMetaObject> #include <QPointer> #include <QProcess> -#include <QSettings> #include <QThread> #include <cerrno> @@ -34,31 +31,6 @@ using namespace MOBase; -static QString steamAppIdFromFluorineConfig() -{ - if (auto cfg = FluorineConfig::load(); cfg.has_value() && cfg->app_id != 0) { - return QString::number(cfg->app_id); - } - - return {}; -} - -static QString steamAppIdFromKnownGame(const QString& gameName) -{ - if (const auto* knownGame = findKnownGameByTitle(gameName); - knownGame != nullptr && knownGame->steam_app_id != nullptr) { - return QString::fromLatin1(knownGame->steam_app_id); - } - - return {}; -} - -static bool steamOverlayEnabled(const Settings& settings) -{ - const QSettings instanceIni(settings.filename(), QSettings::IniFormat); - return instanceIni.value("fluorine/steam_overlay", false).toBool(); -} - void adjustForVirtualized(const IPluginGame* game, spawn::SpawnParameters& sp, const Settings& settings) { @@ -1212,27 +1184,6 @@ std::optional<ProcessRunner::Results> ProcessRunner::runBinary() } } - const bool resolveOverlaySteamAppId = m_sp.useProton && steamOverlayEnabled(settings); - - if (resolveOverlaySteamAppId && m_sp.steamAppID.trimmed().isEmpty()) { - const QString knownSteamId = steamAppIdFromKnownGame(game->gameName()); - if (!knownSteamId.isEmpty()) { - m_sp.steamAppID = knownSteamId; - log::debug("process runner: using known-game steam app id '{}' for '{}'", - m_sp.steamAppID, game->gameName()); - } - } - - if (resolveOverlaySteamAppId && m_sp.steamAppID.trimmed().isEmpty()) { - const QString configSteamId = steamAppIdFromFluorineConfig(); - if (!configSteamId.isEmpty()) { - m_sp.steamAppID = configSteamId; - log::debug("process runner: using Fluorine config steam app id '{}' " - "for launch", - m_sp.steamAppID); - } - } - if (!checkSteam(parent, m_sp, game->gameDirectory(), m_sp.steamAppID, settings)) { return Error; } diff --git a/src/src/profile.cpp b/src/src/profile.cpp index d7dc83e..212da8d 100644 --- a/src/src/profile.cpp +++ b/src/src/profile.cpp @@ -151,8 +151,15 @@ Profile::Profile(const Profile& reference) Profile::~Profile() { + try { + m_ModListWriter.writeImmediately(true); + } catch (const std::exception& e) { + log::error("failed to flush mod list during profile shutdown: {}", e.what()); + } catch (...) { + log::error("failed to flush mod list during profile shutdown: unknown exception"); + } + delete m_Settings; - m_ModListWriter.writeImmediately(true); } void Profile::findProfileSettings() diff --git a/src/src/protonlauncher.cpp b/src/src/protonlauncher.cpp index e7da03a..0c876ec 100644 --- a/src/src/protonlauncher.cpp +++ b/src/src/protonlauncher.cpp @@ -633,12 +633,6 @@ ProtonLauncher& ProtonLauncher::setSteamDrm(bool useSteamDrm) return *this; } -ProtonLauncher& ProtonLauncher::setSteamOverlay(bool useSteamOverlay) -{ - m_useSteamOverlay = useSteamOverlay; - return *this; -} - ProtonLauncher& ProtonLauncher::setUseSLR(bool useSLR) { m_useSLR = useSLR; @@ -714,7 +708,7 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const return false; } - if (m_useSteamDrm || m_useSteamOverlay) { + if (m_useSteamDrm) { ensureSteamRunning(); } @@ -766,32 +760,26 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const pressureVesselImportantPaths << protonDir; } - // Steam overlay needs gameoverlayrenderer.so visible inside the - // pressure-vessel container. Steam dirs are usually mapped already, - // but bind them explicitly to be safe — pressure-vessel's defaults - // change between SLR versions. - if (m_useSteamOverlay) { - const QString steamPath = detectSteamPath(); - if (!steamPath.isEmpty()) { - slrArgs << QStringLiteral("--filesystem=%1/ubuntu12_32").arg(steamPath) - << QStringLiteral("--filesystem=%1/ubuntu12_64").arg(steamPath); - } - } - // Expose dedicated xrandr dir so container sees our injected xrandr // (steamrt4 ships without it, required by Proton-GE protonfixes). // Pressure-vessel forces PATH=/usr/bin:/bin inside the container and // ignores host PATH, so we prepend via `env PATH=...` as the command. QStringList containerCmd; + containerCmd << QStringLiteral("/usr/bin/env"); { const QString xrandrDir = QDir::homePath() + "/.local/share/fluorine/steamrt/xrandr-bin"; if (QDir(xrandrDir).exists()) { slrArgs << QStringLiteral("--filesystem=%1").arg(xrandrDir); - containerCmd << QStringLiteral("/usr/bin/env") - << QStringLiteral("PATH=%1:/usr/bin:/bin").arg(xrandrDir); + containerCmd << QStringLiteral("PATH=%1:/usr/bin:/bin").arg(xrandrDir); } } + if (m_steamAppId != 0) { + const QString appId = QString::number(m_steamAppId); + containerCmd << QStringLiteral("STEAM_COMPAT_APP_ID=%1").arg(appId) + << QStringLiteral("SteamAppId=%1").arg(appId) + << QStringLiteral("SteamGameId=%1").arg(appId); + } containerCmd << protonScript << protonArgs; slrArgs << "--"; @@ -836,64 +824,11 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const if (m_steamAppId != 0) { const QString appId = QString::number(m_steamAppId); + env.insert("STEAM_COMPAT_APP_ID", appId); env.insert("SteamAppId", appId); env.insert("SteamGameId", appId); } - // Steam overlay injection. Requires (a) Steam client running (handled - // above), (b) the game owned on the running Steam account, (c) the env - // triplet SteamAppId/SteamGameId/SteamOverlayGameId pointing at the real - // Steam app id so the overlay's IPC handshake matches an installed app, - // (d) gameoverlayrenderer.so preloaded for legacy GL/X11 hooks, and (e) - // the Steam Vulkan overlay layer enabled for DXVK-rendered games (most - // modern titles via Proton — Bethesda games included). We don't add any - // wrapper process (no reaper) so Steam's "in-game" indicator may stay - // blank, but the overlay itself still attaches because the .so/layer - // hook the running process directly. - if (m_useSteamOverlay && m_steamAppId != 0 && !steamPath.isEmpty()) { - const QString appId = QString::number(m_steamAppId); - env.insert("SteamOverlayGameId", appId); - - const QString preload32 = - steamPath + "/ubuntu12_32/gameoverlayrenderer.so"; - const QString preload64 = - steamPath + "/ubuntu12_64/gameoverlayrenderer.so"; - - QStringList preloads; - if (QFileInfo::exists(preload32)) preloads << preload32; - if (QFileInfo::exists(preload64)) preloads << preload64; - - if (preloads.isEmpty()) { - MOBase::log::warn( - "Steam overlay enabled but gameoverlayrenderer.so not found under " - "'{}/ubuntu12_{{32,64}}' — skipping overlay env", - steamPath); - } else { - const QString existing = env.value("LD_PRELOAD"); - const QString joined = preloads.join(":"); - env.insert("LD_PRELOAD", - existing.isEmpty() ? joined : existing + ":" + joined); - - // Force-enable the Vulkan overlay implicit layer. Steam ships - // VkLayer_VALVE_steam_overlay as an *implicit* Vulkan layer that is - // normally auto-loaded for Steam-launched processes; pressure-vessel - // and some loader configurations skip it unless explicitly enabled. - env.insert("ENABLE_VK_LAYER_VALVE_steam_overlay_1", "1"); - // Also clear the disable-flag in case a Proton wrapper script set it. - env.remove("DISABLE_VK_LAYER_VALVE_steam_overlay_1"); - - MOBase::log::info( - "Steam overlay enabled (appid={}, LD_PRELOAD+={}, " - "ENABLE_VK_LAYER_VALVE_steam_overlay_1=1)", - appId, joined); - } - } else if (m_useSteamOverlay && m_steamAppId == 0) { - MOBase::log::warn( - "Steam overlay requested but no Steam App ID is set for this " - "executable — overlay needs an appid for Steam to recognise the " - "game; skipping"); - } - // When Steam DRM is disabled (e.g. GOG games), set UMU_ID so that // Proton-GE skips the built-in steam.exe bridge. Without this, Proton // tries to initialise the Steam client which causes an assertion failure diff --git a/src/src/protonlauncher.h b/src/src/protonlauncher.h index f0ce09b..7ea41eb 100644 --- a/src/src/protonlauncher.h +++ b/src/src/protonlauncher.h @@ -22,7 +22,6 @@ public: ProtonLauncher& setSteamAppId(uint32_t id); ProtonLauncher& setWrapper(const QString& wrapperCmd); ProtonLauncher& setSteamDrm(bool useSteamDrm); - ProtonLauncher& setSteamOverlay(bool useSteamOverlay); ProtonLauncher& setUseSLR(bool useSLR); ProtonLauncher& setStoreVariant(const QString& variant); ProtonLauncher& addEnvVar(const QString& key, const QString& value); @@ -57,7 +56,6 @@ private: uint32_t m_steamAppId{0}; QStringList m_wrapperCommands; bool m_useSteamDrm{true}; - bool m_useSteamOverlay = false; bool m_useSLR = true; QString m_storeVariant; // "GOG", "Epic", or empty for Steam QMap<QString, QString> m_envVars; diff --git a/src/src/settings.cpp b/src/src/settings.cpp index 7f8a447..df4275d 100644 --- a/src/src/settings.cpp +++ b/src/src/settings.cpp @@ -2120,6 +2120,28 @@ void InterfaceSettings::setStyleName(const QString& name) set(m_Settings, "Settings", "style", name); } +int InterfaceSettings::qssFontSize() const +{ + const int size = get<int>(m_Settings, "Settings", "qss_font_size", 0); + if (size < 0) { + return 0; + } + if (size > 48) { + return 48; + } + return size; +} + +void InterfaceSettings::setQssFontSize(int size) +{ + if (size < 0) { + size = 0; + } else if (size > 48) { + size = 48; + } + set(m_Settings, "Settings", "qss_font_size", size); +} + bool InterfaceSettings::collapsibleSeparators(Qt::SortOrder order) const { return get<bool>(m_Settings, "Settings", diff --git a/src/src/settings.h b/src/src/settings.h index 9c4023e..68a668e 100644 --- a/src/src/settings.h +++ b/src/src/settings.h @@ -590,6 +590,11 @@ public: std::optional<QString> styleName() const; void setStyleName(const QString& name); + // QSS font size override in pixels; 0 means use the stylesheet defaults + // + int qssFontSize() const; + void setQssFontSize(int size); + // whether to use collapsible separators when possible // bool collapsibleSeparators(Qt::SortOrder order) const; diff --git a/src/src/settingsdialog.ui b/src/src/settingsdialog.ui index fe57b49..833a23e 100644 --- a/src/src/settingsdialog.ui +++ b/src/src/settingsdialog.ui @@ -441,6 +441,32 @@ </widget> </item> <item> + <widget class="QLabel" name="qssFontSizeLabel"> + <property name="text"> + <string>Font size</string> + </property> + <property name="buddy"> + <cstring>qssFontSizeSpinBox</cstring> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="qssFontSizeSpinBox"> + <property name="toolTip"> + <string>Overrides font-size values in QSS themes. Default uses the theme value.</string> + </property> + <property name="specialValueText"> + <string>Default</string> + </property> + <property name="suffix"> + <string> px</string> + </property> + <property name="maximum"> + <number>48</number> + </property> + </widget> + </item> + <item> <spacer name="horizontalSpacer_5"> <property name="orientation"> <enum>Qt::Horizontal</enum> @@ -1843,16 +1869,6 @@ If you disable this feature, MO will only display official DLCs this way. Please </property> </widget> </item> - <item row="5" column="0" colspan="4"> - <widget class="QCheckBox" name="fuseIoUringCheckBox"> - <property name="toolTip"> - <string>Enable the kernel FUSE io_uring transport for new VFS mounts. Requires administrator authentication and a VFS remount; Fluorine falls back automatically when unsupported.</string> - </property> - <property name="text"> - <string>Enable FUSE io_uring</string> - </property> - </widget> - </item> <item row="6" column="0"> <widget class="QLabel" name="label_64"> <property name="text"> diff --git a/src/src/settingsdialogproton.cpp b/src/src/settingsdialogproton.cpp index fdb8cbd..f4815c2 100644 --- a/src/src/settingsdialogproton.cpp +++ b/src/src/settingsdialogproton.cpp @@ -11,7 +11,6 @@ #include "steamdetection.h" #include "slrmanager.h" #include <atomic> -#include <QCheckBox> #include <QComboBox> #include <QCoreApplication> #include <QDateTime> @@ -32,115 +31,11 @@ #include <QSettings> #include <QScopeGuard> #include <QStandardPaths> -#include <QSignalBlocker> #include <QVBoxLayout> namespace { std::atomic<ProtonSettingsTab*> g_activeInstallTab = nullptr; - -constexpr const char* kFuseIoUringParameter = - "/sys/module/fuse/parameters/enable_uring"; - -enum class FuseIoUringState -{ - Unsupported, - Disabled, - Enabled, - Unknown -}; - -FuseIoUringState readFuseIoUringState() -{ - QFile parameter(QString::fromUtf8(kFuseIoUringParameter)); - if (!parameter.exists()) { - return FuseIoUringState::Unsupported; - } - - if (!parameter.open(QIODevice::ReadOnly | QIODevice::Text)) { - return FuseIoUringState::Unknown; - } - - const QByteArray value = parameter.readAll().trimmed().toUpper(); - if (value == "Y" || value == "1") { - return FuseIoUringState::Enabled; - } - if (value == "N" || value == "0") { - return FuseIoUringState::Disabled; - } - - return FuseIoUringState::Unknown; -} - -QString findPrivilegeHelper(QStringList* arguments) -{ - const QString command = - QStringLiteral("printf Y > %1").arg(QString::fromUtf8(kFuseIoUringParameter)); - - const QString pkexec = QStandardPaths::findExecutable(QStringLiteral("pkexec")); - if (!pkexec.isEmpty()) { - *arguments = {QStringLiteral("/bin/sh"), QStringLiteral("-c"), command}; - return pkexec; - } - - const QString sudo = QStandardPaths::findExecutable(QStringLiteral("sudo")); - if (!sudo.isEmpty()) { - const QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - if (!env.value(QStringLiteral("SUDO_ASKPASS")).isEmpty()) { - *arguments = {QStringLiteral("-A"), QStringLiteral("/bin/sh"), - QStringLiteral("-c"), command}; - return sudo; - } - - const QFileInfo stdinInfo(QStringLiteral("/proc/self/fd/0")); - const QString stdinTarget = stdinInfo.symLinkTarget(); - if (stdinInfo.exists() && - (stdinTarget.startsWith(QStringLiteral("/dev/pts/")) || - stdinTarget == QStringLiteral("/dev/tty"))) { - *arguments = {QStringLiteral("/bin/sh"), QStringLiteral("-c"), command}; - return sudo; - } - } - - return {}; -} - -QString enableFuseIoUring() -{ - QStringList arguments; - const QString helper = findPrivilegeHelper(&arguments); - if (helper.isEmpty()) { - return QObject::tr("Install pkexec/polkit, or configure sudo askpass, then try " - "again."); - } - - QProcess process; - process.start(helper, arguments); - if (!process.waitForStarted(5000)) { - return QObject::tr("Failed to start %1.").arg(helper); - } - - if (!process.waitForFinished(120000)) { - process.kill(); - process.waitForFinished(3000); - return QObject::tr("Timed out waiting for administrator authentication."); - } - - if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { - const QString stderrText = QString::fromLocal8Bit(process.readAllStandardError()) - .trimmed(); - if (!stderrText.isEmpty()) { - return stderrText; - } - return QObject::tr("Administrator authentication was cancelled or failed."); - } - - if (readFuseIoUringState() != FuseIoUringState::Enabled) { - return QObject::tr("The kernel did not accept the FUSE io_uring setting."); - } - - return {}; -} } ProtonSettingsTab::ProtonSettingsTab(Settings& s, SettingsDialog& d) @@ -203,8 +98,6 @@ ProtonSettingsTab::ProtonSettingsTab(Settings& s, SettingsDialog& d) &ProtonSettingsTab::onBrowsePrefixLocation); QObject::connect(ui->downloadSLRButton, &QPushButton::clicked, this, &ProtonSettingsTab::onDownloadSLR); - QObject::connect(ui->fuseIoUringCheckBox, &QCheckBox::clicked, this, - &ProtonSettingsTab::onFuseIoUringToggled); QObject::connect(&m_installWatcher, &QFutureWatcher<InstallResult>::finished, this, &ProtonSettingsTab::onInstallFinished); @@ -291,8 +184,6 @@ void ProtonSettingsTab::refreshState() ui->openPrefixFolderButton->setEnabled(!m_busy && active); ui->winetricksButton->setEnabled(!m_busy && active); ui->protonVersionCombo->setEnabled(!m_busy); - - refreshFuseIoUringState(); } void ProtonSettingsTab::setBusy(bool busy) @@ -442,74 +333,6 @@ void ProtonSettingsTab::onDownloadSLR() progress->show(); } -void ProtonSettingsTab::refreshFuseIoUringState() -{ - QSignalBlocker blocker(ui->fuseIoUringCheckBox); - Q_UNUSED(blocker); - - const FuseIoUringState state = readFuseIoUringState(); - ui->fuseIoUringCheckBox->setVisible(true); - - switch (state) { - case FuseIoUringState::Enabled: - ui->fuseIoUringCheckBox->setChecked(true); - ui->fuseIoUringCheckBox->setEnabled(false); - ui->fuseIoUringCheckBox->setToolTip( - tr("FUSE io_uring is enabled in the kernel. It applies to new VFS " - "mounts after relaunch/remount.")); - break; - case FuseIoUringState::Disabled: - ui->fuseIoUringCheckBox->setChecked(false); - ui->fuseIoUringCheckBox->setEnabled(!m_busy); - ui->fuseIoUringCheckBox->setToolTip( - tr("Enable the kernel FUSE io_uring transport for new VFS mounts. " - "Requires administrator authentication and a VFS remount; Fluorine " - "falls back automatically when unsupported.")); - break; - case FuseIoUringState::Unsupported: - ui->fuseIoUringCheckBox->setChecked(false); - ui->fuseIoUringCheckBox->setEnabled(false); - ui->fuseIoUringCheckBox->setToolTip( - tr("This kernel does not expose %1. FUSE io_uring requires kernel " - "support for CONFIG_FUSE_IO_URING.") - .arg(QString::fromUtf8(kFuseIoUringParameter))); - break; - case FuseIoUringState::Unknown: - ui->fuseIoUringCheckBox->setChecked(false); - ui->fuseIoUringCheckBox->setEnabled(!m_busy); - ui->fuseIoUringCheckBox->setToolTip( - tr("Fluorine could not read the kernel FUSE io_uring setting. Click " - "to try enabling it with administrator authentication.")); - break; - } -} - -void ProtonSettingsTab::onFuseIoUringToggled(bool checked) -{ - if (!checked) { - refreshFuseIoUringState(); - return; - } - - MOBase::log::info("[VFS] user requested enabling FUSE io_uring via settings UI"); - - const QString error = enableFuseIoUring(); - if (!error.isEmpty()) { - MOBase::log::warn("[VFS] failed to enable FUSE io_uring: {}", error); - QMessageBox::warning(parentWidget(), tr("FUSE io_uring"), - tr("Could not enable FUSE io_uring:\n%1").arg(error)); - refreshFuseIoUringState(); - return; - } - - MOBase::log::info("[VFS] kernel FUSE io_uring parameter is now enabled"); - QMessageBox::information( - parentWidget(), tr("FUSE io_uring"), - tr("FUSE io_uring is enabled. Restart the game/VFS mount before testing; " - "existing mounts keep their current transport.")); - refreshFuseIoUringState(); -} - void ProtonSettingsTab::onBrowsePrefixLocation() { const QString dir = QFileDialog::getExistingDirectory( diff --git a/src/src/settingsdialogproton.h b/src/src/settingsdialogproton.h index 321e5a2..5eee3bd 100644 --- a/src/src/settingsdialogproton.h +++ b/src/src/settingsdialogproton.h @@ -33,11 +33,9 @@ private: void onWinetricks(); void onBrowsePrefixLocation(); void onDownloadSLR(); - void onFuseIoUringToggled(bool checked); static QString ensureWinetricks(); static QString findProtonWine(const QString& protonPath); - void refreshFuseIoUringState(); void runPrefixSetupDialog(uint32_t appId, const QString& prefixPath, const QString& protonName, const QString& protonPath); diff --git a/src/src/settingsdialogtheme.cpp b/src/src/settingsdialogtheme.cpp index a88396b..205789a 100644 --- a/src/src/settingsdialogtheme.cpp +++ b/src/src/settingsdialogtheme.cpp @@ -14,9 +14,10 @@ using namespace MOBase; ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab(s, d)
{
- // style
- addStyles();
- selectStyle();
+ // style + addStyles(); + selectStyle(); + selectQssFontSize(); // colors
ui->colorTable->load(s);
@@ -33,14 +34,23 @@ ThemeSettingsTab::ThemeSettingsTab(Settings& s, SettingsDialog& d) : SettingsTab void ThemeSettingsTab::update()
{
// style
- const QString oldStyle = settings().interface().styleName().value_or("");
- const QString newStyle =
- ui->styleBox->itemData(ui->styleBox->currentIndex()).toString();
-
- if (oldStyle != newStyle) {
- settings().interface().setStyleName(newStyle);
- emit settings().styleChanged(newStyle);
- }
+ const QString oldStyle = settings().interface().styleName().value_or(""); + const QString newStyle = + ui->styleBox->itemData(ui->styleBox->currentIndex()).toString(); + const int oldQssFontSize = settings().interface().qssFontSize(); + const int newQssFontSize = ui->qssFontSizeSpinBox->value(); + + if (oldStyle != newStyle) { + settings().interface().setStyleName(newStyle); + } + + if (oldQssFontSize != newQssFontSize) { + settings().interface().setQssFontSize(newQssFontSize); + } + + if (oldStyle != newStyle || oldQssFontSize != newQssFontSize) { + emit settings().styleChanged(newStyle); + } // colors
ui->colorTable->commitColors();
@@ -90,13 +100,18 @@ void ThemeSettingsTab::selectStyle() const int currentID =
ui->styleBox->findData(settings().interface().styleName().value_or(""));
- if (currentID != -1) {
- ui->styleBox->setCurrentIndex(currentID);
- }
-}
-
-void ThemeSettingsTab::onExploreStyles()
-{
+ if (currentID != -1) { + ui->styleBox->setCurrentIndex(currentID); + } +} + +void ThemeSettingsTab::selectQssFontSize() +{ + ui->qssFontSizeSpinBox->setValue(settings().interface().qssFontSize()); +} + +void ThemeSettingsTab::onExploreStyles() +{ // Open the instance's stylesheets directory (where custom themes from
// modlists live), or the user data dir as fallback.
QString ssPath;
diff --git a/src/src/settingsdialogtheme.h b/src/src/settingsdialogtheme.h index 0d60f5c..2e22b0b 100644 --- a/src/src/settingsdialogtheme.h +++ b/src/src/settingsdialogtheme.h @@ -13,10 +13,11 @@ public: void update() override;
-private:
- void addStyles();
- void selectStyle();
- static void onExploreStyles();
-};
+private: + void addStyles(); + void selectStyle(); + void selectQssFontSize(); + static void onExploreStyles(); +}; #endif // SETTINGSDIALOGGENERAL_H
diff --git a/src/src/spawn.cpp b/src/src/spawn.cpp index 701304b..5deaa1f 100644 --- a/src/src/spawn.cpp +++ b/src/src/spawn.cpp @@ -22,7 +22,6 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. #include "env.h" #include "envmodule.h" #include "fluorineconfig.h" -#include "knowngames.h" #include "protonlauncher.h" #include "settings.h" #include "shared/appconfig.h" @@ -193,38 +192,6 @@ uint32_t parseSteamAppId(const QString& steamAppId) return (ok ? n : 0u); } -static uint32_t steamAppIdFromFluorineConfig() -{ - if (auto cfg = FluorineConfig::load(); cfg.has_value()) { - return cfg->app_id; - } - - return 0; -} - -static uint32_t steamAppIdFromKnownGame(const QString& gameName) -{ - if (const auto* knownGame = findKnownGameByTitle(gameName); - knownGame != nullptr && knownGame->steam_app_id != nullptr) { - bool ok = false; - const auto n = QString::fromLatin1(knownGame->steam_app_id).toUInt(&ok); - return (ok ? n : 0u); - } - - return 0; -} - -static bool steamOverlayEnabledForCurrentInstance() -{ - const Settings* instanceForLaunch = Settings::maybeInstance(); - if (!instanceForLaunch) { - return false; - } - - const QSettings instanceIni(instanceForLaunch->filename(), QSettings::IniFormat); - return instanceIni.value("fluorine/steam_overlay", false).toBool(); -} - QString firstExistingSetting(const QSettings& settings, const QStringList& keys) { for (const QString& key : keys) { @@ -440,31 +407,6 @@ int spawn(const SpawnParameters& sp, pid_t& processId) logSpawning(sp, bin + " " + sp.arguments); uint32_t steamAppId = parseSteamAppId(sp.steamAppID); - const bool resolveOverlaySteamAppId = - sp.useProton && steamOverlayEnabledForCurrentInstance(); - if (resolveOverlaySteamAppId && steamAppId == 0) { - const Settings* instanceForLaunch = Settings::maybeInstance(); - if (instanceForLaunch) { - const auto gameName = instanceForLaunch->game().name(); - if (gameName && !gameName->trimmed().isEmpty()) { - steamAppId = steamAppIdFromKnownGame(*gameName); - if (steamAppId != 0) { - MOBase::log::debug("spawn: using known-game steam app id '{}' " - "for '{}'", - steamAppId, *gameName); - } - } - } - } - if (resolveOverlaySteamAppId && steamAppId == 0) { - steamAppId = steamAppIdFromFluorineConfig(); - if (steamAppId != 0) { - MOBase::log::debug("spawn: using Fluorine config steam app id '{}' " - "for Proton launch", - steamAppId); - } - } - ProtonLauncher launcher; launcher.setBinary(bin) .setArguments(argList) @@ -476,17 +418,14 @@ int spawn(const SpawnParameters& sp, pid_t& processId) // Read per-instance settings from the instance INI (not the global QSettings). const Settings* instanceForLaunch = Settings::maybeInstance(); bool useSteamDrm = true; - bool useSteamOverlay = false; QString storeVariant; if (instanceForLaunch) { const QSettings instanceIni(instanceForLaunch->filename(), QSettings::IniFormat); useSteamDrm = instanceIni.value("fluorine/steam_drm", true).toBool(); - useSteamOverlay = instanceIni.value("fluorine/steam_overlay", false).toBool(); storeVariant = instanceIni.value("game_edition").toString().trimmed(); } launcher.setSteamDrm(useSteamDrm) - .setSteamOverlay(useSteamOverlay) .setStoreVariant(storeVariant) .setUseSLR(true); diff --git a/src/src/steamdetection.cpp b/src/src/steamdetection.cpp index 512b1fa..c1c3f7f 100644 --- a/src/src/steamdetection.cpp +++ b/src/src/steamdetection.cpp @@ -4,6 +4,7 @@ #include <QDirIterator> #include <QFile> #include <QFileInfo> +#include <QSet> #include <QStandardPaths> #include <uibase/log.h> @@ -69,10 +70,22 @@ QStringList findSteamLibraryPaths() auto addLibrary = [&](const QString& path) { const QString cleanPath = QDir::cleanPath(path); - if (cleanPath.isEmpty() || libraries.contains(cleanPath)) + if (cleanPath.isEmpty()) { return; - if (QFileInfo::exists(QDir(cleanPath).filePath(QStringLiteral("steamapps")))) - libraries.append(cleanPath); + } + + const QFileInfo info(cleanPath); + const QString canonicalPath = info.canonicalFilePath(); + const QString libraryPath = + canonicalPath.isEmpty() ? info.absoluteFilePath() : canonicalPath; + const QString normalizedLibraryPath = QDir::cleanPath(libraryPath); + + if (normalizedLibraryPath.isEmpty() || libraries.contains(normalizedLibraryPath)) { + return; + } + if (QFileInfo::exists(QDir(normalizedLibraryPath).filePath(QStringLiteral("steamapps")))) { + libraries.append(normalizedLibraryPath); + } }; addLibrary(steamPath); @@ -225,6 +238,36 @@ QVector<SteamProtonInfo> findSteamProtons() }), protons.end()); + QSet<QString> seenPaths; + QSet<QString> seenNames; + QVector<SteamProtonInfo> uniqueProtons; + uniqueProtons.reserve(protons.size()); + + for (SteamProtonInfo& proton : protons) { + const QFileInfo pathInfo(proton.path); + const QString canonicalPath = pathInfo.canonicalFilePath(); + const QString pathKey = + QDir::cleanPath(canonicalPath.isEmpty() ? proton.path : canonicalPath); + const QString nameKey = proton.name.trimmed().toCaseFolded(); + + if ((!pathKey.isEmpty() && seenPaths.contains(pathKey)) || + (!nameKey.isEmpty() && seenNames.contains(nameKey))) { + MOBase::log::debug("Skipping duplicate Proton '{}' at '{}'", + proton.name, proton.path); + continue; + } + + if (!pathKey.isEmpty()) { + seenPaths.insert(pathKey); + } + if (!nameKey.isEmpty()) { + seenNames.insert(nameKey); + } + uniqueProtons.append(std::move(proton)); + } + + protons = std::move(uniqueProtons); + // Sort: Experimental first, then by name descending (newest first). std::sort(protons.begin(), protons.end(), [](const SteamProtonInfo& a, const SteamProtonInfo& b) { diff --git a/src/src/vfs/mo2filesystem.cpp b/src/src/vfs/mo2filesystem.cpp index 7b15d04..9a65017 100644 --- a/src/src/vfs/mo2filesystem.cpp +++ b/src/src/vfs/mo2filesystem.cpp @@ -93,57 +93,40 @@ struct OpTimer OpTimer& operator=(const OpTimer&) = delete; }; -bool fuseHasFeature(const struct fuse_conn_info* conn, uint64_t flag) +bool fuseHasFeature(const struct fuse_conn_info* conn, uint32_t flag) { if (conn == nullptr) { return false; } -#ifdef FUSE_CAP_OVER_IO_URING - return (conn->capable_ext & flag) != 0 || - (flag <= UINT32_MAX && (conn->capable & static_cast<uint32_t>(flag)) != 0); -#else - return (conn->capable & static_cast<uint32_t>(flag)) != 0; -#endif + return (conn->capable & flag) != 0; } -bool fuseWantsFeature(const struct fuse_conn_info* conn, uint64_t flag) +bool fuseWantsFeature(const struct fuse_conn_info* conn, uint32_t flag) { if (conn == nullptr) { return false; } -#if defined(FUSE_CAP_OVER_IO_URING) && FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17) - return fuse_get_feature_flag(const_cast<struct fuse_conn_info*>(conn), flag); -#else - return (conn->want & static_cast<uint32_t>(flag)) != 0; -#endif + return (conn->want & flag) != 0; } -bool fuseRequestFeature(struct fuse_conn_info* conn, uint64_t flag) +bool fuseRequestFeature(struct fuse_conn_info* conn, uint32_t flag) { if (conn == nullptr) { return false; } -#if defined(FUSE_CAP_OVER_IO_URING) && FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17) - return fuse_set_feature_flag(conn, flag); -#else - if ((conn->capable & static_cast<uint32_t>(flag)) == 0) { + if ((conn->capable & flag) == 0) { return false; } - conn->want |= static_cast<uint32_t>(flag); + conn->want |= flag; return true; -#endif } -void fuseDropFeature(struct fuse_conn_info* conn, uint64_t flag) +void fuseDropFeature(struct fuse_conn_info* conn, uint32_t flag) { if (conn == nullptr) { return; } -#if defined(FUSE_CAP_OVER_IO_URING) && FUSE_VERSION >= FUSE_MAKE_VERSION(3, 17) - fuse_unset_feature_flag(conn, flag); -#else - conn->want &= ~static_cast<uint32_t>(flag); -#endif + conn->want &= ~flag; } void maybeLogCounters(Mo2FsContext* ctx) @@ -222,18 +205,13 @@ void maybeLogCounters(Mo2FsContext* ctx) static_cast<unsigned long long>( ctx->retained_ro_fd_evictions.load(std::memory_order_relaxed))); std::fprintf(stderr, - "[VFS] io bytes_read=%llu bytes_written=%llu cow_writes=%llu " - "transport_uring=%llu transport_legacy=%llu\n", + "[VFS] io bytes_read=%llu bytes_written=%llu cow_writes=%llu\n", static_cast<unsigned long long>( ctx->read_bytes.load(std::memory_order_relaxed)), static_cast<unsigned long long>( ctx->write_bytes.load(std::memory_order_relaxed)), static_cast<unsigned long long>( - ctx->cow_write_count.load(std::memory_order_relaxed)), - static_cast<unsigned long long>( - ctx->uring_request_count.load(std::memory_order_relaxed)), - static_cast<unsigned long long>( - ctx->legacy_request_count.load(std::memory_order_relaxed))); + ctx->cow_write_count.load(std::memory_order_relaxed))); { size_t lookupSize = 0; size_t attrSize = 0; @@ -1331,15 +1309,6 @@ void mo2_init(void* userdata, struct fuse_conn_info* conn) fuseRequestFeature(conn, FUSE_CAP_EXPIRE_ONLY); } - bool uringCapable = false; - bool uringWanted = false; -#ifdef FUSE_CAP_OVER_IO_URING - uringCapable = fuseHasFeature(conn, FUSE_CAP_OVER_IO_URING); - if (uringCapable) { - uringWanted = fuseRequestFeature(conn, FUSE_CAP_OVER_IO_URING); - } -#endif - // Maximize async I/O slots (default is 12). The kernel will still only // dispatch as many as there are actual concurrent requests, so higher // values just raise the ceiling without wasting memory. @@ -1366,8 +1335,7 @@ void mo2_init(void* userdata, struct fuse_conn_info* conn) std::fprintf(stderr, "[VFS] init: auto_inval=%d explicit_inval=%d readdirplus=%d " - "no_opendir=%d uring_capable=%d uring_wanted=%d " - "max_bg=%u max_readahead=%u\n", + "no_opendir=%d max_bg=%u max_readahead=%u\n", fuseWantsFeature(conn, FUSE_CAP_AUTO_INVAL_DATA) ? 1 : 0, fuseWantsFeature(conn, FUSE_CAP_EXPLICIT_INVAL_DATA) ? 1 : 0, fuseWantsFeature(conn, FUSE_CAP_READDIRPLUS) ? 1 : 0, @@ -1376,19 +1344,13 @@ void mo2_init(void* userdata, struct fuse_conn_info* conn) #else 0, #endif - uringCapable ? 1 : 0, - uringWanted ? 1 : 0, conn->max_background, conn->max_readahead); std::fprintf(stderr, "[VFS] init_caps: libfuse=%s headers=%d.%d proto=%u.%u " - "capable=0x%08x capable_ext=0x%016llx want=0x%08x " - "want_ext=0x%016llx max_write=%u max_read=%u\n", + "capable=0x%08x want=0x%08x max_write=%u max_read=%u\n", fuse_pkgversion(), FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION, conn->proto_major, conn->proto_minor, conn->capable, - static_cast<unsigned long long>(conn->capable_ext), - conn->want, - static_cast<unsigned long long>(conn->want_ext), - conn->max_write, conn->max_read); + conn->want, conn->max_write, conn->max_read); } void mo2_lookup(fuse_req_t req, fuse_ino_t parent, const char* name) diff --git a/src/src/vfs/mo2filesystem.h b/src/src/vfs/mo2filesystem.h index 111b1b9..f8d5330 100644 --- a/src/src/vfs/mo2filesystem.h +++ b/src/src/vfs/mo2filesystem.h @@ -153,8 +153,6 @@ struct Mo2FsContext std::atomic<uint64_t> read_bytes{0}; std::atomic<uint64_t> write_bytes{0}; std::atomic<uint64_t> cow_write_count{0}; - std::atomic<uint64_t> uring_request_count{0}; - std::atomic<uint64_t> legacy_request_count{0}; // CPU snapshot from previous stats tick (microseconds, from getrusage). // Used to compute per-tick CPU delta so we can distinguish disk-bound vs // CPU-bound slowness in the VFS layer. |
