aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-28 01:59:16 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-05-28 01:59:16 -0500
commit07ac26ba1ca08f430bd5faae89b98d5b2704c5de (patch)
tree453d74f0a107cd998ba22c4312151b012b60b98d /src
parent2f5c85ff187d1dc49a8712e1b80c107012cb6a49 (diff)
Add FUSE io_uring toggle and update libfuse
Diffstat (limited to 'src')
-rw-r--r--src/src/settingsdialog.ui20
-rw-r--r--src/src/settingsdialogproton.cpp179
-rw-r--r--src/src/settingsdialogproton.h2
3 files changed, 195 insertions, 6 deletions
diff --git a/src/src/settingsdialog.ui b/src/src/settingsdialog.ui
index a7e2a90..50c47e0 100644
--- a/src/src/settingsdialog.ui
+++ b/src/src/settingsdialog.ui
@@ -1832,28 +1832,38 @@ If you disable this feature, MO will only display official DLCs this way. Please
</property>
</widget>
</item>
- <item row="5" column="0">
+ <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">
<string>Status:</string>
</property>
</widget>
</item>
- <item row="5" column="1" colspan="3">
+ <item row="6" column="1" colspan="3">
<widget class="QLabel" name="protonStatusLabel">
<property name="text">
<string>No Prefix</string>
</property>
</widget>
</item>
- <item row="6" column="0" colspan="4">
+ <item row="7" column="0" colspan="4">
<widget class="QProgressBar" name="protonProgressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
- <item row="7" column="0" colspan="4">
+ <item row="8" column="0" colspan="4">
<widget class="QPushButton" name="toggleInstallLog">
<property name="text">
<string>Show Install Log</string>
@@ -1866,7 +1876,7 @@ If you disable this feature, MO will only display official DLCs this way. Please
</property>
</widget>
</item>
- <item row="8" column="0" colspan="4">
+ <item row="9" column="0" colspan="4">
<widget class="QTextEdit" name="nakInstallLog">
<property name="readOnly">
<bool>true</bool>
diff --git a/src/src/settingsdialogproton.cpp b/src/src/settingsdialogproton.cpp
index 896bcb9..fdb8cbd 100644
--- a/src/src/settingsdialogproton.cpp
+++ b/src/src/settingsdialogproton.cpp
@@ -11,6 +11,7 @@
#include "steamdetection.h"
#include "slrmanager.h"
#include <atomic>
+#include <QCheckBox>
#include <QComboBox>
#include <QCoreApplication>
#include <QDateTime>
@@ -26,15 +27,120 @@
#include <QFutureWatcher>
#include <QMetaObject>
#include <QProcess>
+#include <QProcessEnvironment>
#include <QProgressDialog>
#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)
@@ -97,6 +203,8 @@ 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);
@@ -183,6 +291,8 @@ 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)
@@ -332,6 +442,74 @@ 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(
@@ -560,4 +738,3 @@ void ProtonSettingsTab::onInstallFinished()
ui->protonStatusLabel->setText(tr("Done"));
refreshState();
}
-
diff --git a/src/src/settingsdialogproton.h b/src/src/settingsdialogproton.h
index 5eee3bd..321e5a2 100644
--- a/src/src/settingsdialogproton.h
+++ b/src/src/settingsdialogproton.h
@@ -33,9 +33,11 @@ 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);