Skip to content

Commit 0b22571

Browse files
NonaryCopilot
andcommitted
Merge remote-tracking branch 'vibe/vibe'
Resolve merge conflicts across nvhttp.cpp, custom_actions.wxs, and VibeshineInstaller.cs. Keeps all Vibepollo-specific features (permission system, UUID-based app launching, input-only mode, allow_display_changes, preinstall migration cleanup, service recovery helpers) while porting upstream improvements: - Add null-guard on mail::man before raising switch_display event during virtual display recovery (prevents potential null deref) - Wrap runtime config overrides in update_runtime_overrides guard to preserve active session state when a second client connects - Remove PruneWebDir custom action from WiX (upstream fix) - Add competing product pre-uninstall and restart propagation logic - Improve installer warning message clarity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 2335aba + 321671a commit 0b22571

File tree

9 files changed

+267
-54
lines changed

9 files changed

+267
-54
lines changed

.rgignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated and tool output.
2+
build/
3+
out/
4+
CMakeFiles/
5+
cmake-build-*/
6+
test-results/
7+
tmp/
8+
venv/
9+
node_modules/
10+
.npm/
11+
12+
# Ignore common nested build output directories.
13+
**/build/**
14+
**/out/**
15+
**/CMakeFiles/**
16+
**/cmake-build-*/
17+
**/node_modules/**
18+
**/venv/**
19+
**/test-results/**
20+
21+
# Third-party policy:
22+
# Keep third-party mostly excluded and only allow folders we actively modify.
23+
third-party/*
24+
!third-party/libwebrtc/
25+
!third-party/libwebrtc/**
26+
!third-party/tray/
27+
!third-party/tray/**
28+
!third-party/libdisplaydevice/
29+
!third-party/libdisplaydevice/**
30+
31+
# Handle underscore variant for compatibility with shared command snippets.
32+
third_party/*
33+
!third_party/libwebrtc/
34+
!third_party/libwebrtc/**
35+
!third_party/tray/
36+
!third_party/tray/**
37+
!third_party/libdisplaydevice/
38+
!third_party/libdisplaydevice/**

cmake/packaging/common.cmake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ endforeach()
3333

3434
# install built vite assets
3535
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/assets/web"
36-
DESTINATION "${SUNSHINE_ASSETS_DIR}")
36+
DESTINATION "${SUNSHINE_ASSETS_DIR}"
37+
COMPONENT assets)
3738

3839
# platform specific packaging
3940
if(WIN32)

packaging/windows/bootstrapper/VibeshineInstaller.cs

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,9 +1192,9 @@ private static string BuildVibeshineInstallWarning(InstallerRunner.InstalledProd
11921192
: string.Empty;
11931193

11941194
return "Vibeshine" + versionSuffix + " was detected on this PC.\n\n"
1195-
+ "Vibeshine does not carry over Vibeshine settings.\n"
1195+
+ "Vibepollo does not carry over Vibeshine settings.\n"
11961196
+ "If you intend to stay in the Sunshine ecosystem, Vibeshine is recommended instead.\n\n"
1197-
+ "If this is intentional, continue with Vibeshine.\n"
1197+
+ "If this is intentional, continue with Vibepollo.\n"
11981198
+ "Continuing will uninstall Vibeshine before installation.";
11991199
}
12001200

@@ -1204,7 +1204,7 @@ private static string BuildApolloInstallWarning(InstallerRunner.InstalledProduct
12041204
: string.Empty;
12051205

12061206
return "Apollo" + versionSuffix + " was detected on this PC.\n\n"
1207-
+ "Vibeshine replaces Apollo and cannot be installed while Apollo is installed.\n"
1207+
+ "Vibepollo replaces Apollo and cannot be installed while Apollo is installed.\n"
12081208
+ "Continuing will uninstall Apollo before installation.\n\n"
12091209
+ "Click Uninstall Apollo to proceed.";
12101210
}
@@ -1217,8 +1217,8 @@ private string BuildLegacySunshineMigrationWarning() {
12171217
versionSuffix = " (v" + _legacySunshineRegistration.DisplayVersion + ")";
12181218
}
12191219

1220-
return "Sunshine" + versionSuffix + " was detected on this PC.\n\n"
1221-
+ "Vibeshine replaces Sunshine and will automatically uninstall Sunshine first, then install Vibeshine.\n"
1220+
return "Legacy Sunshine" + versionSuffix + " was detected on this PC.\n\n"
1221+
+ "Vibepollo replaces Sunshine. The bootstrapper will uninstall Sunshine first, then start the installation.\n"
12221222
+ "No settings will be lost during this migration.\n\n"
12231223
+ "Click Uninstall Sunshine to proceed.";
12241224
}
@@ -1230,7 +1230,7 @@ private string BuildLegacyApolloMigrationWarning() {
12301230
}
12311231

12321232
return "Legacy Apollo" + versionSuffix + " was detected on this PC.\n\n"
1233-
+ "Vibeshine replaces legacy Apollo and will automatically uninstall it first, then install Vibeshine.\n"
1233+
+ "Vibepollo replaces legacy Apollo and will automatically uninstall it first, then install Vibepollo.\n"
12341234
+ "No settings will be carried over.\n\n"
12351235
+ "Click Uninstall Apollo to proceed.";
12361236
}
@@ -2604,6 +2604,24 @@ public static InstallerResult RunInteractiveInstall(
26042604
return RunElevatedBootstrapperInstall(arguments, installDirectory, installVirtualDisplayDriver, saveInstallLogs);
26052605
}
26062606

2607+
var uninstallCompetingProductsResult = UninstallInstalledProducts(
2608+
"install_remove_competing",
2609+
true,
2610+
false,
2611+
false,
2612+
false,
2613+
false,
2614+
new[] { InstalledProductKind.Apollo, InstalledProductKind.Vibepollo, InstalledProductKind.Sunshine });
2615+
var competingProductsRequireRestart = uninstallCompetingProductsResult.ExitCode == 3010;
2616+
if (!uninstallCompetingProductsResult.Succeeded) {
2617+
return new InstallerResult {
2618+
Operation = InstallerOperation.Install,
2619+
ExitCode = uninstallCompetingProductsResult.ExitCode,
2620+
Message = BuildCompetingProductUninstallFailureMessage(uninstallCompetingProductsResult.Message),
2621+
LogPath = uninstallCompetingProductsResult.LogPath
2622+
};
2623+
}
2624+
26072625
var msiPath = ResolveMsiPath(arguments.MsiPathOverride);
26082626
var migrationCleanupResult = RunPreinstallMigrationCleanup("preinstall", true, false);
26092627
if (migrationCleanupResult.ExitCode != 0) {
@@ -2625,13 +2643,17 @@ public static InstallerResult RunInteractiveInstall(
26252643
logPath,
26262644
CreatePropertyArgument("INSTALL_ROOT", installDirectory),
26272645
"INSTALL_SUDOVDA=" + (installVirtualDisplayDriver ? "1" : "0"),
2646+
"SKIP_REMOVE_CONFLICTING_PRODUCTS=1",
26282647
"REBOOT=ReallySuppress",
26292648
"SUPPRESSMSGBOXES=1"
26302649
};
26312650
TryAppendSameProductReinstallProperties(args, msiPath);
26322651

26332652
var exitCode = RunMsiexec(args, true, false);
26342653
exitCode = RetryInstallWithSameProductReinstallIfNeeded(exitCode, args, msiPath, true, false);
2654+
if (exitCode == 0 && competingProductsRequireRestart) {
2655+
exitCode = 3010;
2656+
}
26352657
if (exitCode != 0 && exitCode != 3010) {
26362658
TryRecoverServiceStateAfterFailedInstall();
26372659
}
@@ -3037,13 +3059,41 @@ public static InstallerResult RunCli(InstallerArguments arguments) {
30373059
cliArgs.Add(logPath);
30383060
}
30393061

3062+
var uninstallCompetingProducts = ShouldPreUninstallCompetingProducts(cliArgs);
3063+
var competingProductsRequireRestart = false;
3064+
if (uninstallCompetingProducts) {
3065+
var uninstallCompetingProductsResult = UninstallInstalledProducts(
3066+
"cli_remove_competing",
3067+
arguments.IsCliQuietMode(),
3068+
true,
3069+
false,
3070+
false,
3071+
false,
3072+
new[] { InstalledProductKind.Apollo, InstalledProductKind.Vibepollo, InstalledProductKind.Sunshine });
3073+
if (!uninstallCompetingProductsResult.Succeeded) {
3074+
return new InstallerResult {
3075+
Operation = InstallerOperation.Install,
3076+
ExitCode = uninstallCompetingProductsResult.ExitCode,
3077+
Message = BuildCompetingProductUninstallFailureMessage(uninstallCompetingProductsResult.Message),
3078+
LogPath = uninstallCompetingProductsResult.LogPath
3079+
};
3080+
}
3081+
competingProductsRequireRestart = uninstallCompetingProductsResult.ExitCode == 3010;
3082+
}
3083+
if (uninstallCompetingProducts && !HasProperty(cliArgs, "SKIP_REMOVE_CONFLICTING_PRODUCTS")) {
3084+
cliArgs.Add("SKIP_REMOVE_CONFLICTING_PRODUCTS=1");
3085+
}
3086+
30403087
var exitCode = RunMsiexec(cliArgs, arguments.IsCliQuietMode(), true);
30413088
exitCode = RetryInstallWithSameProductReinstallIfNeeded(
30423089
exitCode,
30433090
cliArgs,
30443091
installMsiPath,
30453092
arguments.IsCliQuietMode(),
30463093
true);
3094+
if (exitCode == 0 && competingProductsRequireRestart) {
3095+
exitCode = 3010;
3096+
}
30473097
if (exitCode != 0 && exitCode != 3010) {
30483098
TryRecoverServiceStateAfterFailedInstall();
30493099
}
@@ -3336,6 +3386,14 @@ private static bool IsServiceRunning(string serviceName) {
33363386
}
33373387
}
33383388

3389+
private static string BuildCompetingProductUninstallFailureMessage(string uninstallMessage) {
3390+
var prefix = "Failed to uninstall Apollo, Vibepollo, or Sunshine before starting Vibepollo installation.";
3391+
if (string.IsNullOrWhiteSpace(uninstallMessage)) {
3392+
return prefix;
3393+
}
3394+
return prefix + " " + uninstallMessage;
3395+
}
3396+
33393397
private static InstallerResult UninstallInstalledProducts(
33403398
string logPhase,
33413399
bool hiddenWindow,
@@ -3645,6 +3703,17 @@ private static bool HasLogSwitch(List<string> args) {
36453703
string.Equals(arg, "/log", StringComparison.OrdinalIgnoreCase));
36463704
}
36473705

3706+
private static bool ShouldPreUninstallCompetingProducts(List<string> args) {
3707+
var operation = args.FirstOrDefault(IsOperationSwitch);
3708+
if (string.IsNullOrWhiteSpace(operation)) {
3709+
return false;
3710+
}
3711+
3712+
return string.Equals(operation, "/i", StringComparison.OrdinalIgnoreCase)
3713+
|| string.Equals(operation, "/package", StringComparison.OrdinalIgnoreCase)
3714+
|| string.Equals(operation, "/a", StringComparison.OrdinalIgnoreCase);
3715+
}
3716+
36483717
private static string BuildLogPath(string phase) {
36493718
var timestamp = DateTime.UtcNow.ToString("yyyyMMdd_HHmmss");
36503719
return Path.Combine(Path.GetTempPath(), "vibeshine_" + phase + "_" + timestamp + ".log");

packaging/windows/wix/custom_actions.wxs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,6 @@
126126
<CustomAction Id="UpdatePathRemove" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Return="check" Impersonate="no" />
127127
<CustomAction Id="UninstallGamepad" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no" />
128128

129-
<!-- Prune stale web assets directory prior to installing new files (avoids stale hashed JS/CSS) -->
130-
<CustomAction Id="PruneWebDir" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Return="ignore" Impersonate="no" />
131-
132129
<!-- Uninstall-time actions (deferred, elevated) -->
133130
<CustomAction Id="RestoreNvPrefsUndo" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="deferred" Return="check" Impersonate="no" />
134131
<CustomAction Id="RemoveConflictingProducts" BinaryKey="RemoveConflictingProductsVbs" VBScriptCall="RemoveConflictingProducts" Execute="deferred" Return="check" />
@@ -166,11 +163,6 @@
166163
Property="UpdatePathAdd"
167164
Value="&quot;[SystemFolder]cmd.exe&quot; /C &quot;&quot;[INSTALL_ROOT]scripts\update-path.bat&quot; add&quot;"/>
168165

169-
<!-- Immediate setter for pruning installed web assets directory before file copy -->
170-
<CustomAction Id="SetPruneWebDir"
171-
Property="PruneWebDir"
172-
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -NoProfile -ExecutionPolicy Bypass -Command &quot;$p='[INSTALL_ROOT]assets\web'; if (Test-Path -LiteralPath $p) { Remove-Item -LiteralPath $p -Recurse -Force -ErrorAction SilentlyContinue }&quot;"/>
173-
174166

175167
<!-- Guard NV prefs restore on uninstall: only run if sunshine.exe still exists -->
176168
<CustomAction Id="SetRestoreNvPrefsUndo"
@@ -191,10 +183,10 @@
191183
<!-- Immediate setter for quiet stop service -->
192184
<CustomAction Id="SetStopSvcQuietImmediate"
193185
Property="StopSvcQuietImmediate"
194-
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -ExecutionPolicy Bypass -NoProfile -Command &quot;&amp; { $names=@('ApolloService','SunshineService','sunshinesvc'); foreach($n in $names){ $svc=Get-Service -Name $n -ErrorAction SilentlyContinue; if($svc){ if($svc.Status -ne 'Stopped'){ Stop-Service -Name $n -Force -ErrorAction SilentlyContinue; $svc.WaitForStatus('Stopped','00:00:30') } } } }&quot;"/>
186+
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -ExecutionPolicy Bypass -NoProfile -Command &quot;&amp; { $names=@('ApolloService','SunshineService','VibeshineService','sunshinesvc'); foreach($n in $names){ $svc=Get-Service -Name $n -ErrorAction SilentlyContinue; if($svc){ if($svc.Status -ne 'Stopped'){ Stop-Service -Name $n -Force -ErrorAction SilentlyContinue; $svc.WaitForStatus('Stopped','00:00:30') } } } }&quot;"/>
195187
<CustomAction Id="SetStopSvcQuiet"
196188
Property="StopSvcQuiet"
197-
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -ExecutionPolicy Bypass -NoProfile -Command &quot;&amp; { foreach ($name in @('SunshineService','VibeshineService', 'ApolloService')) { $svc = Get-Service -Name $name -ErrorAction SilentlyContinue; if ($svc) { if ($svc.Status -ne 'Stopped') { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue; $svc.WaitForStatus('Stopped','00:00:30') } } } }&quot;"/>
189+
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -ExecutionPolicy Bypass -NoProfile -Command &quot;&amp; { foreach ($name in @('ApolloService','SunshineService','VibeshineService','sunshinesvc')) { $svc = Get-Service -Name $name -ErrorAction SilentlyContinue; if ($svc) { if ($svc.Status -ne 'Stopped') { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue; $svc.WaitForStatus('Stopped','00:00:30') } } } }&quot;"/>
198190
<!-- Immediate setter to prune everything except the config directory -->
199191
<CustomAction Id="SetPruneButConfig"
200192
Property="PruneButConfig"
@@ -207,7 +199,7 @@
207199
<!-- Immediate setters for quiet process kill and service removal on manual uninstall -->
208200
<CustomAction Id="SetKillProcsQuietImmediate"
209201
Property="KillProcsQuietImmediate"
210-
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -NoProfile -ExecutionPolicy Bypass -Command &quot;$n='sunshine','sunshinesvc','sunshine_wgc_capture','playnite_launcher'; foreach($p in $n){ Get-Process -Name $p -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue }&quot;"/>
202+
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -NoProfile -ExecutionPolicy Bypass -Command &quot;$n='sunshine','sunshinesvc','sunshine_wgc_capture','playnite_launcher','apollo','apollosvc'; foreach($p in $n){ Get-Process -Name $p -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue }&quot;"/>
211203
<CustomAction Id="SetKillProcsQuiet"
212204
Property="KillProcsQuiet"
213205
Value="&quot;[SystemFolder]WindowsPowerShell\v1.0\powershell.exe&quot; -NoProfile -ExecutionPolicy Bypass -Command &quot;$n='sunshine','sunshinesvc','sunshine_wgc_capture','playnite_launcher','apollo','apollosvc'; foreach($p in $n){ Get-Process -Name $p -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue }&quot;"/>

packaging/windows/wix/patch_custom_actions.wxs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</Property>
1515
<Property Id="REMOVEVIRTUALDISPLAYDRIVER" Value="0" Secure="yes"/>
1616
<Property Id="INSTALL_SUDOVDA" Value="1" Secure="yes"/>
17+
<Property Id="SKIP_REMOVE_CONFLICTING_PRODUCTS" Value="0" Secure="yes"/>
1718
<Condition Message="Administrator privileges are required.">Privileged</Condition>
1819
<Property Id="VIGEMBUS_PRESENT">
1920
<RegistrySearch Id="Search_ViGEmBus_Service64" Root="HKLM" Key="SYSTEM\CurrentControlSet\Services\ViGEmBus" Name="DisplayName" Type="raw" Win64="yes"/>
@@ -30,11 +31,7 @@
3031
</Feature>
3132
<InstallExecuteSequence>
3233
<!-- Remove conflicting products before installation starts. -->
33-
<Custom Action="RemoveConflictingProducts" After="InstallInitialize">NOT Installed AND NOT REMOVE</Custom>
34-
35-
<!-- Prune web assets folder before installing files to avoid stale hashed assets on upgrade/repair -->
36-
<Custom Action="SetPruneWebDir" Before="InstallFiles">NOT REMOVE</Custom>
37-
<Custom Action="PruneWebDir" After="SetPruneWebDir">NOT REMOVE</Custom>
34+
<Custom Action="RemoveConflictingProducts" After="InstallInitialize">NOT Installed AND NOT REMOVE AND SKIP_REMOVE_CONFLICTING_PRODUCTS &lt;&gt; "1"</Custom>
3835

3936
<!-- Pre-validate stop/kill when UI can't prompt -->
4037
<Custom Action="SetKillProcsQuietImmediate" Before="InstallValidate">(Installed AND NOT REMOVE AND UILevel &lt; 5) OR (REMOVE = "ALL" AND NOT UPGRADINGPRODUCTCODE)</Custom>

src/nvhttp.cpp

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,10 @@ namespace nvhttp {
546546
}
547547

548548
// Force the capture thread to reinitialize so it rebinds to the recreated display.
549-
mail::man->event<int>(mail::switch_display)->raise(-1);
549+
if (mail::man) {
550+
// -1 means "reinit only; keep display selection logic intact".
551+
mail::man->event<int>(mail::switch_display)->raise(-1);
552+
}
550553
BOOST_LOG(info) << "Virtual display recovery: requested capture reinit to pick up recreated display.";
551554
}
552555
};
@@ -2104,6 +2107,10 @@ namespace nvhttp {
21042107
host_audio = util::from_view(get_arg(args, "localAudioPlayMode"));
21052108

21062109
bool no_active_sessions = (rtsp_stream::session_count() == 0);
2110+
// Runtime overrides are global process state. Do not reapply them while
2111+
// another RTSP session is active, otherwise a second client can mutate
2112+
// active stream limits (e.g. fps/encoding-related settings) mid-session.
2113+
const bool update_runtime_overrides = no_active_sessions;
21072114

21082115
// Apply per-application runtime config overrides before we build session metadata or
21092116
// prepare display/capture so the effective config is used everywhere.
@@ -2124,36 +2131,40 @@ namespace nvhttp {
21242131
}
21252132
});
21262133

2127-
try {
2128-
// Find the target app and apply its config overrides (if any), then layer on client overrides.
2129-
std::unordered_map<std::string, std::string> overrides;
2130-
const auto apps = proc::proc.get_apps();
2131-
const auto app_iter = std::find_if(apps.begin(), apps.end(), [&](const auto &app) {
2132-
if (!appuuid_str.empty()) {
2133-
return app.uuid == appuuid_str;
2134+
if (update_runtime_overrides) {
2135+
try {
2136+
// Find the target app and apply its config overrides (if any), then layer on client overrides.
2137+
std::unordered_map<std::string, std::string> overrides;
2138+
const auto apps = proc::proc.get_apps();
2139+
const auto app_iter = std::find_if(apps.begin(), apps.end(), [&](const auto &app) {
2140+
if (!appuuid_str.empty()) {
2141+
return app.uuid == appuuid_str;
2142+
}
2143+
return app.id == appid_str;
2144+
});
2145+
if (app_iter != apps.end()) {
2146+
overrides = app_iter->config_overrides;
21342147
}
2135-
return app.id == appid_str;
2136-
});
2137-
if (app_iter != apps.end()) {
2138-
overrides = app_iter->config_overrides;
2139-
}
21402148

2141-
if (named_cert_p) {
2142-
for (const auto &[k, v] : named_cert_p->config_overrides) {
2143-
overrides.insert_or_assign(k, v);
2149+
if (named_cert_p) {
2150+
for (const auto &[k, v] : named_cert_p->config_overrides) {
2151+
overrides.insert_or_assign(k, v);
2152+
}
21442153
}
2145-
}
21462154

2147-
config::set_runtime_config_overrides(std::move(overrides));
2148-
runtime_overrides_applied = true;
2155+
config::set_runtime_config_overrides(std::move(overrides));
2156+
runtime_overrides_applied = true;
21492157

2150-
// Re-apply config so overrides take effect in config::video/config::input/etc.
2151-
config::apply_config_now();
2152-
} catch (...) {
2153-
// If something goes wrong, fall back to global config only.
2154-
config::clear_runtime_config_overrides();
2155-
config::apply_config_now();
2156-
runtime_overrides_applied = true;
2158+
// Re-apply config so overrides take effect in config::video/config::input/etc.
2159+
config::apply_config_now();
2160+
} catch (...) {
2161+
// If something goes wrong, fall back to global config only.
2162+
config::clear_runtime_config_overrides();
2163+
config::apply_config_now();
2164+
runtime_overrides_applied = true;
2165+
}
2166+
} else {
2167+
BOOST_LOG(debug) << "Launch while an RTSP session is already active; preserving current runtime overrides.";
21572168
}
21582169

21592170
// Prevent interleaving with hot-apply while we prep/start a session

0 commit comments

Comments
 (0)