aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/src/editexecutablesdialog.cpp26
-rw-r--r--src/src/editexecutablesdialog.ui23
-rw-r--r--src/src/executableslist.cpp20
-rw-r--r--src/src/executableslist.h6
-rw-r--r--src/src/processrunner.cpp3
-rw-r--r--src/src/protonlauncher.cpp73
-rw-r--r--src/src/protonlauncher.h2
-rw-r--r--src/src/spawn.cpp82
-rw-r--r--src/src/spawn.h2
9 files changed, 197 insertions, 40 deletions
diff --git a/src/src/editexecutablesdialog.cpp b/src/src/editexecutablesdialog.cpp
index e5dcda9..4c6819d 100644
--- a/src/src/editexecutablesdialog.cpp
+++ b/src/src/editexecutablesdialog.cpp
@@ -126,6 +126,12 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel,
connect(ui->hide, &QCheckBox::toggled, [&] {
save();
});
+ connect(ui->useProton, &QCheckBox::toggled, [&] {
+ save();
+ });
+ connect(ui->useTerminal, &QCheckBox::toggled, [&] {
+ save();
+ });
connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&] {
saveOrder();
});
@@ -391,6 +397,10 @@ void EditExecutablesDialog::clearEdits()
ui->minimizeToSystemTray->setChecked(false);
ui->hide->setEnabled(false);
ui->hide->setChecked(false);
+ ui->useProton->setEnabled(false);
+ ui->useProton->setChecked(true);
+ ui->useTerminal->setEnabled(false);
+ ui->useTerminal->setChecked(false);
m_lastGoodTitle = "";
}
@@ -407,6 +417,8 @@ void EditExecutablesDialog::setEdits(const Executable& e)
ui->useApplicationIcon->setChecked(e.usesOwnIcon());
ui->minimizeToSystemTray->setChecked(e.minimizeToSystemTray());
ui->hide->setChecked(e.hide());
+ ui->useProton->setChecked(e.useProton());
+ ui->useTerminal->setChecked(e.useTerminal());
m_lastGoodTitle = e.title();
@@ -453,6 +465,8 @@ void EditExecutablesDialog::setEdits(const Executable& e)
ui->forceLoadLibraries->setEnabled(true);
ui->minimizeToSystemTray->setEnabled(true);
ui->hide->setEnabled(true);
+ ui->useProton->setEnabled(true);
+ ui->useTerminal->setEnabled(true);
}
void EditExecutablesDialog::save()
@@ -524,6 +538,18 @@ void EditExecutablesDialog::save()
e->flags(e->flags() & (~Executable::Hide));
}
+ if (ui->useProton->isChecked()) {
+ e->flags(e->flags() | Executable::UseProton);
+ } else {
+ e->flags(e->flags() & (~Executable::UseProton));
+ }
+
+ if (ui->useTerminal->isChecked()) {
+ e->flags(e->flags() | Executable::UseTerminal);
+ } else {
+ e->flags(e->flags() & (~Executable::UseTerminal));
+ }
+
setDirty(true);
}
diff --git a/src/src/editexecutablesdialog.ui b/src/src/editexecutablesdialog.ui
index 005f3a1..8ac01f6 100644
--- a/src/src/editexecutablesdialog.ui
+++ b/src/src/editexecutablesdialog.ui
@@ -465,6 +465,29 @@ Right now the only case I know of where this needs to be overwritten is for the
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="useProton">
+ <property name="toolTip">
+ <string>Launch this executable through Proton (Wine compatibility layer). Disable for native Linux executables.</string>
+ </property>
+ <property name="text">
+ <string>Use Proton</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useTerminal">
+ <property name="toolTip">
+ <string>Open a terminal window showing the executable's output. Useful for debugging.</string>
+ </property>
+ <property name="text">
+ <string>Open in terminal</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>(*) Profile specific</string>
diff --git a/src/src/executableslist.cpp b/src/src/executableslist.cpp
index ea7a2e5..5410c81 100644
--- a/src/src/executableslist.cpp
+++ b/src/src/executableslist.cpp
@@ -88,6 +88,14 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s)
if (map["hide"].toBool())
flags |= Executable::Hide;
+ // Default to UseProton=true for backward compat (existing executables
+ // were always launched with Proton). Only set false if explicitly saved.
+ if (map.contains("useProton") ? map["useProton"].toBool() : true)
+ flags |= Executable::UseProton;
+
+ if (map["useTerminal"].toBool())
+ flags |= Executable::UseTerminal;
+
if (map.contains("custom")) {
// the "custom" setting only exists in older versions
needsUpgrade = true;
@@ -126,6 +134,8 @@ void ExecutablesList::store(Settings& s)
map["workingDirectory"] = item.workingDirectory();
map["steamAppID"] = item.steamAppID();
map["minimizeToSystemTray"] = item.minimizeToSystemTray();
+ map["useProton"] = item.useProton();
+ map["useTerminal"] = item.useTerminal();
v.push_back(std::move(map));
}
@@ -482,6 +492,16 @@ bool Executable::hide() const
return m_flags.testFlag(Hide);
}
+bool Executable::useProton() const
+{
+ return m_flags.testFlag(UseProton);
+}
+
+bool Executable::useTerminal() const
+{
+ return m_flags.testFlag(UseTerminal);
+}
+
void Executable::mergeFrom(const Executable& other)
{
// this happens after executables are loaded from settings and plugin
diff --git a/src/src/executableslist.h b/src/src/executableslist.h
index b402461..9ab3927 100644
--- a/src/src/executableslist.h
+++ b/src/src/executableslist.h
@@ -48,7 +48,9 @@ public:
ShowInToolbar = 0x02,
UseApplicationIcon = 0x04,
Hide = 0x08,
- MinimizeToSystemTray = 0x16
+ MinimizeToSystemTray = 0x16,
+ UseProton = 0x20,
+ UseTerminal = 0x40
};
Q_DECLARE_FLAGS(Flags, Flag);
@@ -79,6 +81,8 @@ public:
bool usesOwnIcon() const;
bool minimizeToSystemTray() const;
bool hide() const;
+ bool useProton() const;
+ bool useTerminal() const;
void mergeFrom(const Executable& other);
diff --git a/src/src/processrunner.cpp b/src/src/processrunner.cpp
index 724886b..dee7b73 100644
--- a/src/src/processrunner.cpp
+++ b/src/src/processrunner.cpp
@@ -1099,6 +1099,9 @@ ProcessRunner &ProcessRunner::setFromExecutable(const Executable &exe) {
setCustomOverwrite(customOverwrite);
setForcedLibraries(forcedLibraries);
+ m_sp.useProton = exe.useProton();
+ m_sp.useTerminal = exe.useTerminal();
+
return *this;
}
diff --git a/src/src/protonlauncher.cpp b/src/src/protonlauncher.cpp
index e2895e2..3d1741e 100644
--- a/src/src/protonlauncher.cpp
+++ b/src/src/protonlauncher.cpp
@@ -7,6 +7,7 @@
#include <QFileInfo>
#include <QProcess>
#include <QProcessEnvironment>
+#include <QStandardPaths>
#include <log.h>
@@ -134,6 +135,64 @@ QString detectSteamPath()
return {};
}
+// Detect an available terminal emulator on the system.
+QString findTerminal()
+{
+ static const char* candidates[] = {
+ "konsole", "gnome-terminal", "xfce4-terminal", "alacritty",
+ "kitty", "foot", "xterm"};
+ for (const auto* name : candidates) {
+ const QString path = QStandardPaths::findExecutable(name);
+ if (!path.isEmpty())
+ return path;
+ }
+ return {};
+}
+
+// Wrap a program + args in a terminal emulator.
+// Modifies program and arguments in-place.
+void wrapInTerminal(QString& program, QStringList& arguments)
+{
+ const QString term = findTerminal();
+ if (term.isEmpty()) {
+ MOBase::log::warn("No terminal emulator found; launching without terminal");
+ return;
+ }
+
+ const QString termName = QFileInfo(term).fileName();
+
+ // Build a shell command for the inner program + args.
+ // Append a read prompt so the terminal stays open after exit.
+ QString innerCmd = "'" + QString(program).replace("'", "'\\''") + "'";
+ for (const auto& a : arguments)
+ innerCmd += " '" + QString(a).replace("'", "'\\''") + "'";
+ innerCmd += "; echo; echo '--- Process exited with code '$?' ---'; "
+ "echo 'Press Enter to close...'; read";
+
+ QStringList termArgs;
+ if (termName == "konsole") {
+ termArgs << "--hold" << "-e" << "bash" << "-c" << innerCmd;
+ } else if (termName == "gnome-terminal") {
+ termArgs << "--" << "bash" << "-c" << innerCmd;
+ } else if (termName == "xfce4-terminal") {
+ termArgs << "--hold" << "-e" << ("bash -c " + innerCmd);
+ } else if (termName == "alacritty") {
+ termArgs << "-e" << "bash" << "-c" << innerCmd;
+ } else if (termName == "kitty") {
+ termArgs << "bash" << "-c" << innerCmd;
+ } else if (termName == "foot") {
+ termArgs << "bash" << "-c" << innerCmd;
+ } else {
+ // xterm and others
+ termArgs << "-e" << "bash" << "-c" << innerCmd;
+ }
+
+ MOBase::log::info("Launching in terminal: {} {}", term,
+ termArgs.join(' '));
+ program = term;
+ arguments = termArgs;
+}
+
bool startWithEnv(const QString& program, const QStringList& arguments,
const QString& workingDir,
const QProcessEnvironment& environment, qint64& pid)
@@ -321,6 +380,12 @@ ProtonLauncher& ProtonLauncher::addEnvVar(const QString& key, const QString& val
return *this;
}
+ProtonLauncher& ProtonLauncher::setUseTerminal(bool useTerminal)
+{
+ m_useTerminal = useTerminal;
+ return *this;
+}
+
std::pair<bool, qint64> ProtonLauncher::launch() const
{
qint64 pid = -1;
@@ -416,6 +481,10 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const
env.insert("PWD", m_workingDir);
}
+ if (m_useTerminal) {
+ wrapInTerminal(program, arguments);
+ }
+
return startWithEnv(program, arguments, m_workingDir, env, pid);
}
@@ -439,6 +508,10 @@ bool ProtonLauncher::launchDirect(qint64& pid) const
env.insert(it.key(), it.value());
}
+ if (m_useTerminal) {
+ wrapInTerminal(program, arguments);
+ }
+
return startWithEnv(program, arguments, m_workingDir, env, pid);
}
diff --git a/src/src/protonlauncher.h b/src/src/protonlauncher.h
index 363d92b..11d942a 100644
--- a/src/src/protonlauncher.h
+++ b/src/src/protonlauncher.h
@@ -23,6 +23,7 @@ public:
ProtonLauncher& setSteamDrm(bool useSteamDrm);
ProtonLauncher& setStoreVariant(const QString& variant);
ProtonLauncher& addEnvVar(const QString& key, const QString& value);
+ ProtonLauncher& setUseTerminal(bool useTerminal);
// Launch dispatch: Proton -> Direct
std::pair<bool, qint64> launch() const;
@@ -43,6 +44,7 @@ private:
QString m_storeVariant; // "GOG", "Epic", or empty for Steam
QMap<QString, QString> m_envVars;
QMap<QString, QString> m_wrapperEnvVars;
+ bool m_useTerminal = false;
};
#endif // PROTONLAUNCHER_H
diff --git a/src/src/spawn.cpp b/src/src/spawn.cpp
index 4c91fbe..b0e7eea 100644
--- a/src/src/spawn.cpp
+++ b/src/src/spawn.cpp
@@ -636,54 +636,58 @@ int spawn(const SpawnParameters &sp, pid_t &processId) {
logSpawning(sp, bin + " " + sp.arguments);
- // Read per-instance settings from the instance INI (not the global
- // QSettings).
- const Settings *instanceForLaunch = Settings::maybeInstance();
- bool useSteamDrm = true; // default
- QString storeVariant;
- if (instanceForLaunch) {
- const QSettings instanceIni(instanceForLaunch->filename(),
- QSettings::IniFormat);
- useSteamDrm = instanceIni.value("fluorine/steam_drm", true).toBool();
- // settingName() maps "General" section keys to top-level (no section
- // prefix), so game_edition is stored as a plain key under [General] in the
- // INI.
- storeVariant = instanceIni.value("game_edition").toString().trimmed();
- }
-
ProtonLauncher launcher;
launcher.setBinary(bin)
.setArguments(argList)
.setWorkingDir(cwd)
- .setSteamAppId(parseSteamAppId(sp.steamAppID))
- .setSteamDrm(useSteamDrm)
- .setStoreVariant(storeVariant);
+ .setSteamAppId(parseSteamAppId(sp.steamAppID));
- const QString prefixPath = resolvePrefixPath();
- if (prefixPath.isEmpty()) {
- MOBase::log::warn(
- "No Wine prefix configured - games may not launch correctly. "
- "Configure a prefix in Settings > Proton or via fluorine-manager.");
- } else if (!QDir(QDir(prefixPath).filePath("drive_c")).exists()) {
- MOBase::log::warn(
- "Wine prefix '{}' does not contain drive_c/ - prefix may be invalid",
- prefixPath);
- } else {
- MOBase::log::info("Using Wine prefix: {}", prefixPath);
- launcher.setPrefix(prefixPath);
- }
+ if (sp.useProton) {
+ // Read per-instance settings from the instance INI (not the global
+ // QSettings).
+ const Settings *instanceForLaunch = Settings::maybeInstance();
+ bool useSteamDrm = true; // default
+ QString storeVariant;
+ if (instanceForLaunch) {
+ const QSettings instanceIni(instanceForLaunch->filename(),
+ QSettings::IniFormat);
+ useSteamDrm = instanceIni.value("fluorine/steam_drm", true).toBool();
+ storeVariant = instanceIni.value("game_edition").toString().trimmed();
+ }
- const QString protonPath = resolveProtonPath();
- if (!protonPath.isEmpty()) {
- launcher.setProtonPath(protonPath);
- }
+ launcher.setSteamDrm(useSteamDrm)
+ .setStoreVariant(storeVariant);
- const QString wrapper =
- QSettings().value("fluorine/launch_wrapper").toString().trimmed();
- if (!wrapper.isEmpty()) {
- launcher.setWrapper(wrapper);
+ const QString prefixPath = resolvePrefixPath();
+ if (prefixPath.isEmpty()) {
+ MOBase::log::warn(
+ "No Wine prefix configured - games may not launch correctly. "
+ "Configure a prefix in Settings > Proton or via fluorine-manager.");
+ } else if (!QDir(QDir(prefixPath).filePath("drive_c")).exists()) {
+ MOBase::log::warn(
+ "Wine prefix '{}' does not contain drive_c/ - prefix may be invalid",
+ prefixPath);
+ } else {
+ MOBase::log::info("Using Wine prefix: {}", prefixPath);
+ launcher.setPrefix(prefixPath);
+ }
+
+ const QString protonPath = resolveProtonPath();
+ if (!protonPath.isEmpty()) {
+ launcher.setProtonPath(protonPath);
+ }
+
+ const QString wrapper =
+ QSettings().value("fluorine/launch_wrapper").toString().trimmed();
+ if (!wrapper.isEmpty()) {
+ launcher.setWrapper(wrapper);
+ }
+ } else {
+ MOBase::log::info("Proton disabled for this executable, launching directly");
}
+ launcher.setUseTerminal(sp.useTerminal);
+
const auto [ok, pid] = launcher.launch();
if (!ok) {
return (errno != 0 ? errno : EIO);
diff --git a/src/src/spawn.h b/src/src/spawn.h
index d922ddc..378c20f 100644
--- a/src/src/spawn.h
+++ b/src/src/spawn.h
@@ -57,6 +57,8 @@ struct SpawnParameters
QDir currentDirectory;
QString steamAppID;
bool hooked = false;
+ bool useProton = true;
+ bool useTerminal = false;
#ifdef _WIN32
HANDLE stdOut = INVALID_HANDLE_VALUE;
HANDLE stdErr = INVALID_HANDLE_VALUE;