feat(windows): add ViGEmBus driver management API and UI integration (#4625)

Introduces backend API endpoints for ViGEmBus status and installation, updates Windows build scripts to handle ViGEmBus versioning and installer download, and integrates ViGEmBus status and installation controls into the web UI. Removes legacy PowerShell scripts for gamepad driver management and related NSIS installer commands.
This commit is contained in:
David Lane 2026-01-25 12:06:51 -05:00 committed by GitHub
commit 7e286b90b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 360 additions and 72 deletions

View file

@ -21,6 +21,21 @@
</ul>
<a class="btn btn-danger" href="./troubleshooting#logs">View Logs</a>
</div>
<!-- ViGEmBus Warning -->
<div class="alert alert-warning" v-if="platform === 'windows' && controllerEnabled && vigembus && (!vigembus.installed || !vigembus.version_compatible)">
<div style="line-height: 32px;">
<i class="fas fa-triangle-exclamation" style="font-size: 32px;margin-right: 0.25em;"></i>
<div v-if="!vigembus.installed">
<p><strong>{{ $t('index.vigembus_not_installed_title') }}</strong></p>
<p>{{ $t('index.vigembus_not_installed_desc') }}</p>
</div>
<div v-else-if="!vigembus.version_compatible">
<p><strong>{{ $t('index.vigembus_outdated_title') }}</strong></p>
<p>{{ $t('index.vigembus_outdated_desc', { version: vigembus.version }) }}</p>
</div>
<a class="btn btn-warning" href="./troubleshooting#vigembus">{{ $t('index.fix_now') }}</a>
</div>
</div>
<!-- Version -->
<div class="card p-2 my-4">
<div class="card-body" v-if="version">
@ -94,18 +109,32 @@
preReleaseVersion: null,
loading: true,
logs: null,
platform: "",
controllerEnabled: false,
vigembus: null,
}
},
async created() {
try {
let config = await fetch("./api/config").then((r) => r.json());
this.notifyPreReleases = config.notify_pre_releases;
this.platform = config.platform;
this.controllerEnabled = config.controller !== "disabled";
this.version = new SunshineVersion(null, config.version);
console.log("Version: ", this.version.version)
this.githubVersion = new SunshineVersion(await fetch("https://api.github.com/repos/LizardByte/Sunshine/releases/latest").then((r) => r.json()), null);
console.log("GitHub Version: ", this.githubVersion.version)
this.preReleaseVersion = new SunshineVersion((await fetch("https://api.github.com/repos/LizardByte/Sunshine/releases").then((r) => r.json())).find(release => release.prerelease), null);
console.log("Pre-Release Version: ", this.preReleaseVersion.version)
// Fetch ViGEmBus status only on Windows when controller is enabled
if (this.platform === 'windows' && this.controllerEnabled) {
try {
this.vigembus = await fetch("./api/vigembus/status").then((r) => r.json());
} catch (e) {
console.error("Failed to fetch ViGEmBus status:", e);
}
}
} catch (e) {
console.error(e);
}

View file

@ -381,6 +381,7 @@
"index": {
"description": "Sunshine is a self-hosted game stream host for Moonlight.",
"download": "Download",
"fix_now": "Fix Now",
"installed_version_not_stable": "You are running a pre-release version of Sunshine. You may experience bugs or other issues. Please report any issues you encounter. Thank you for helping to make Sunshine a better software!",
"loading_latest": "Loading latest release...",
"new_pre_release": "A new Pre-Release Version is Available!",
@ -388,6 +389,10 @@
"startup_errors": "<b>Attention!</b> Sunshine detected these errors during startup. We <b>STRONGLY RECOMMEND</b> fixing them before streaming.",
"version_dirty": "Thank you for helping to make Sunshine a better software!",
"version_latest": "You are running the latest version of Sunshine",
"vigembus_not_installed_desc": "Virtual gamepad support will not work without the ViGEmBus driver. Click the button below to install it.",
"vigembus_not_installed_title": "ViGEmBus Driver Not Installed",
"vigembus_outdated_desc": "You are running an outdated version of ViGEmBus (v{version}). Version 1.17 or higher is required for proper gamepad support. Click the button below to update.",
"vigembus_outdated_title": "ViGEmBus Driver Outdated",
"welcome": "Hello, Sunshine!"
},
"navbar": {
@ -451,7 +456,17 @@
"unpair_single_no_devices": "There are no paired devices.",
"unpair_single_success": "However, the device(s) may still be in an active session. Use the 'Force Close' button above to end any open sessions.",
"unpair_single_unknown": "Unknown Client",
"unpair_title": "Unpair Devices"
"unpair_title": "Unpair Devices",
"vigembus_compatible": "ViGEmBus is installed and compatible.",
"vigembus_current_version": "Current Version",
"vigembus_desc": "ViGEmBus is required for virtual gamepad support. Install or update the driver if it's missing or outdated (version 1.17 or higher required).",
"vigembus_incompatible": "ViGEmBus version is too old. Please install version 1.17 or higher.",
"vigembus_install": "ViGEmBus Driver",
"vigembus_install_button": "Install ViGEmBus v{version}",
"vigembus_install_error": "Failed to install ViGEmBus driver.",
"vigembus_install_success": "ViGEmBus driver installed successfully! You may need to restart your computer.",
"vigembus_force_reinstall_button": "Force Reinstall ViGEmBus v{version}",
"vigembus_not_installed": "ViGEmBus is not installed."
},
"welcome": {
"confirm_password": "Confirm password",

View file

@ -40,6 +40,45 @@
<Navbar></Navbar>
<div class="container">
<h1 class="my-4">{{ $t('troubleshooting.troubleshooting') }}</h1>
<!-- ViGEmBus Installation -->
<div class="card p-2 my-4" v-if="platform === 'windows' && controllerEnabled">
<div class="card-body">
<h2 id="vigembus">{{ $t('troubleshooting.vigembus_install') }}</h2>
<br>
<p style="white-space: pre-line">{{ $t('troubleshooting.vigembus_desc') }}</p>
<div v-if="vigembus.installed && vigembus.version">
<p><strong>{{ $t('troubleshooting.vigembus_current_version') }}:</strong> {{ vigembus.version }}</p>
<div class="alert alert-success" v-if="vigembus.version_compatible">
{{ $t('troubleshooting.vigembus_compatible') }}
</div>
<div class="alert alert-danger" v-if="!vigembus.version_compatible">
{{ $t('troubleshooting.vigembus_incompatible') }}
</div>
</div>
<div class="alert alert-warning" v-else-if="!vigembus.installed">
{{ $t('troubleshooting.vigembus_not_installed') }}
</div>
<div class="alert alert-success" v-if="vigemBusInstallStatus === true">
{{ $t('troubleshooting.vigembus_install_success') }}
</div>
<div class="alert alert-danger" v-if="vigemBusInstallStatus === false">
{{ vigemBusInstallError || $t('troubleshooting.vigembus_install_error') }}
</div>
<div>
<button
:class="vigembus.installed && vigembus.version === vigembus.packaged_version ? 'btn btn-danger' : 'btn btn-primary'"
:disabled="vigemBusInstallPressed"
@click="installViGEmBus">
<template v-if="vigembus.installed && vigembus.version === vigembus.packaged_version">
{{ $t('troubleshooting.vigembus_force_reinstall_button', { version: vigembus.packaged_version }) }}
</template>
<template v-else>
{{ $t('troubleshooting.vigembus_install_button', { version: vigembus.packaged_version }) }}
</template>
</button>
</div>
</div>
</div>
<!-- Force Close App -->
<div class="card p-2 my-4">
<div class="card-body">
@ -169,8 +208,18 @@
restartPressed: false,
showApplyMessage: false,
platform: "",
controllerEnabled: false,
unpairAllPressed: false,
unpairAllStatus: null,
vigembus: {
installed: false,
version: '',
version_compatible: false,
packaged_version: '',
},
vigemBusInstallPressed: false,
vigemBusInstallStatus: null,
vigemBusInstallError: null,
};
},
computed: {
@ -186,6 +235,11 @@
.then((r) => r.json())
.then((r) => {
this.platform = r.platform;
this.controllerEnabled = r.controller !== "disabled";
// Fetch ViGEmBus status only on Windows when gamepad is enabled
if (this.platform === 'windows' && this.controllerEnabled) {
this.refreshViGEmBusStatus();
}
});
this.logInterval = setInterval(() => {
@ -207,7 +261,7 @@
},
closeApp() {
this.closeAppPressed = true;
fetch("./api/apps/close", {
fetch("./api/apps/close", {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -223,7 +277,7 @@
},
unpairAll() {
this.unpairAllPressed = true;
fetch("./api/clients/unpair-all", {
fetch("./api/clients/unpair-all", {
method: "POST",
headers: {
"Content-Type": "application/json"
@ -240,12 +294,12 @@
});
},
unpairSingle(uuid) {
fetch("./api/clients/unpair", {
method: "POST",
fetch("./api/clients/unpair", {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ uuid })
},
body: JSON.stringify({ uuid })
}).then(() => {
this.showApplyMessage = true;
this.refreshClients();
@ -285,11 +339,11 @@
},
ddResetPersistence() {
this.ddResetPressed = true;
fetch("/api/reset-display-device-persistence", {
fetch("/api/reset-display-device-persistence", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
headers: {
"Content-Type": "application/json"
}
})
.then((r) => r.json())
.then((r) => {
@ -300,6 +354,55 @@
}, 5000);
});
},
refreshViGEmBusStatus() {
fetch("/api/vigembus/status")
.then((r) => r.json())
.then((r) => {
this.vigembus = {
installed: r.installed || false,
version: r.version || '',
version_compatible: r.version_compatible || false,
packaged_version: r.packaged_version,
};
})
.catch((err) => {
console.error("Failed to fetch ViGEmBus status:", err);
});
},
installViGEmBus() {
this.vigemBusInstallPressed = true;
this.vigemBusInstallStatus = null;
this.vigemBusInstallError = null;
fetch("/api/vigembus/install", {
method: "POST",
headers: {
"Content-Type": "application/json"
}
})
.then((r) => r.json())
.then((r) => {
this.vigemBusInstallPressed = false;
this.vigemBusInstallStatus = r.status;
if (!r.status && r.error) {
this.vigemBusInstallError = r.error;
}
setTimeout(() => {
this.vigemBusInstallStatus = null;
this.vigemBusInstallError = null;
}, 10000);
// Refresh status after installation attempt
this.refreshViGEmBusStatus();
})
.catch((err) => {
this.vigemBusInstallPressed = false;
this.vigemBusInstallStatus = false;
this.vigemBusInstallError = err.message;
setTimeout(() => {
this.vigemBusInstallStatus = null;
this.vigemBusInstallError = null;
}, 10000);
});
},
},
});