From 3a148058861099499268722d4558c0963d76af19 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Sun, 26 Apr 2026 15:45:45 -0500 Subject: Per-instance Steam overlay toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New "Enable Steam Overlay" checkbox in Instance Manager (off by default, key fluorine/steam_overlay). When enabled and the executable has a Steam App ID, ProtonLauncher now: - LD_PRELOADs gameoverlayrenderer.so (32+64 bit) from the running Steam install for legacy GL/X11 hooks, - sets SteamOverlayGameId so Steam's IPC handshake matches the app, - exports ENABLE_VK_LAYER_VALVE_steam_overlay_1=1 and clears the DISABLE_... counterpart so the Steam Vulkan implicit layer loads even under pressure-vessel (this is what makes overlay work for DXVK-rendered Bethesda games — most "manual" overlay setups skip this and only get the GL hook), - ensures Steam is running, and - exposes /ubuntu12_{32,64} as SLR --filesystem= mounts so the .so paths resolve inside the container. No reaper wrapper, so Steam's "in-game" indicator may stay blank, but the overlay itself attaches because the .so/Vulkan-layer hook the process directly. Requires the user to actually own the game on the running Steam account. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/src/instancemanagerdialog.cpp | 16 +++++++++ src/src/instancemanagerdialog.ui | 10 ++++++ src/src/protonlauncher.cpp | 74 ++++++++++++++++++++++++++++++++++++++- src/src/protonlauncher.h | 2 ++ src/src/spawn.cpp | 9 +++-- 5 files changed, 107 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/src/instancemanagerdialog.cpp b/src/src/instancemanagerdialog.cpp index 46966c5..96511cc 100644 --- a/src/src/instancemanagerdialog.cpp +++ b/src/src/instancemanagerdialog.cpp @@ -252,6 +252,15 @@ 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(); }); @@ -829,6 +838,10 @@ void InstanceManagerDialog::fillData(const Instance& ii) ui->vfsRootBuilderCheckBox->blockSignals(true); 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(); @@ -841,6 +854,9 @@ 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 6ec5455..2bf1d0a 100644 --- a/src/src/instancemanagerdialog.ui +++ b/src/src/instancemanagerdialog.ui @@ -350,6 +350,16 @@ + + + + Enable Steam Overlay + + + 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). + + + diff --git a/src/src/protonlauncher.cpp b/src/src/protonlauncher.cpp index d247965..8aecd47 100644 --- a/src/src/protonlauncher.cpp +++ b/src/src/protonlauncher.cpp @@ -409,6 +409,12 @@ 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; @@ -484,7 +490,7 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const return false; } - if (m_useSteamDrm) { + if (m_useSteamDrm || m_useSteamOverlay) { ensureSteamRunning(); } @@ -530,6 +536,18 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const slrArgs << QStringLiteral("--filesystem=%1").arg(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 @@ -592,6 +610,60 @@ bool ProtonLauncher::launchWithProton(qint64& pid) const 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 c9b5de8..9026921 100644 --- a/src/src/protonlauncher.h +++ b/src/src/protonlauncher.h @@ -21,6 +21,7 @@ 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); @@ -54,6 +55,7 @@ private: uint32_t m_steamAppId; QStringList m_wrapperCommands; bool m_useSteamDrm; + bool m_useSteamOverlay = false; bool m_useSLR = true; QString m_storeVariant; // "GOG", "Epic", or empty for Steam QMap m_envVars; diff --git a/src/src/spawn.cpp b/src/src/spawn.cpp index 756b075..0d41401 100644 --- a/src/src/spawn.cpp +++ b/src/src/spawn.cpp @@ -805,16 +805,19 @@ int spawn(const SpawnParameters &sp, pid_t &processId) { const Settings *instanceForLaunch = Settings::maybeInstance(); bool useSteamDrm = true; // default bool useSLR = true; // default on + bool useSteamOverlay = false; // default off QString storeVariant; if (instanceForLaunch) { const QSettings instanceIni(instanceForLaunch->filename(), QSettings::IniFormat); - useSteamDrm = instanceIni.value("fluorine/steam_drm", true).toBool(); - useSLR = instanceIni.value("fluorine/use_slr", true).toBool(); - storeVariant = instanceIni.value("game_edition").toString().trimmed(); + useSteamDrm = instanceIni.value("fluorine/steam_drm", true).toBool(); + useSLR = instanceIni.value("fluorine/use_slr", 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(useSLR); -- cgit v1.3.1