Skip to content

Commit ff04164

Browse files
committed
feat: unify restart helper and fix AppImage icon on Wayland
- Consolidate WindowsRestartHelper into a single RestartHelper that branches per platform internally; callers just invoke RestartHelper.RestartApp(). - Add Linux/Photino restart: a detached setsid helper waits for the process to exit then relaunches $APPIMAGE (the FUSE-mounted binary path vanishes on exit). - AppImage AppRun now installs the icon into the user icon theme and refreshes the GTK/KDE caches, so KDE/GNOME can resolve the window icon. Under Wayland there is no _NET_WM_ICON, so the taskbar/titlebar icon is matched via the window app_id -> .desktop entry -> themed Icon name.
1 parent aa6a80e commit ff04164

4 files changed

Lines changed: 173 additions & 75 deletions

File tree

Desktop/Ui/Pages/Dash/Components/ModuleManagerItem.razor

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,7 @@
198198
private async Task Restart()
199199
{
200200
await ConfigManager.SaveNow();
201-
#if WINDOWS
202-
WindowsRestartHelper.RestartApp();
203-
#endif
201+
RestartHelper.RestartApp();
204202
}
205203

206204
private ImmutableArray<SemVersion>? GetAvailableVersions()

Desktop/Utils/RestartHelper.cs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
using System.Diagnostics;
2+
3+
namespace OpenShock.Desktop.Utils;
4+
5+
/// <summary>
6+
/// Restarts the running application. The mechanism is platform specific, but callers
7+
/// just invoke <see cref="RestartApp"/> regardless of platform.
8+
/// </summary>
9+
public static class RestartHelper
10+
{
11+
#if WINDOWS
12+
private static readonly string PowerShellLogFile = Path.Combine(Constants.LogsFolder, "restart.log");
13+
#endif
14+
15+
public static void RestartApp()
16+
{
17+
#if WINDOWS
18+
var currentExePath = Environment.ProcessPath;
19+
var processId = Environment.ProcessId;
20+
21+
// PowerShell helper: wait for this process to exit (up to 30s), then relaunch.
22+
var scriptContent = $$"""
23+
Start-Transcript -Path '{{PowerShellLogFile}}'
24+
$processID = {{processId}}
25+
$timeout = New-TimeSpan -Seconds 30
26+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
27+
Write-Host "Waiting for process with ID $processID to stop running..."
28+
do {
29+
$process = Get-Process -Id $processID -ErrorAction SilentlyContinue
30+
if ($process -ne $null) {
31+
Write-Host "Still waiting for process with ID $processID to stop running... $sw.Elapsed"
32+
Start-Sleep -Seconds 1
33+
}
34+
} while ($process -ne $null -and $sw.Elapsed -lt $timeout)
35+
36+
if ($process -eq $null) {
37+
$exePath = '{{currentExePath}}'
38+
Write-Host "Starting $exePath after $sw.Elapsed"
39+
Start-Process -FilePath $exePath
40+
Start-Sleep -Seconds 2
41+
}
42+
else {
43+
Write-Host "Process with ID $processID is still running. Timed out after $($timeout.TotalSeconds) seconds."
44+
Write-Host "Please make sure the process with ID $processID is not running before starting a new instance."
45+
Start-Sleep -Seconds 10
46+
}
47+
Stop-Transcript
48+
""";
49+
50+
var startInfo = new ProcessStartInfo
51+
{
52+
FileName = "powershell.exe",
53+
Arguments = $"-Command \"{scriptContent}\"",
54+
UseShellExecute = false,
55+
RedirectStandardOutput = true,
56+
CreateNoWindow = true
57+
};
58+
59+
Process.Start(startInfo);
60+
61+
if (Application.Current != null)
62+
{
63+
Application.Current.Quit();
64+
return;
65+
}
66+
67+
Environment.Exit(0);
68+
#elif PHOTINO
69+
var pid = Environment.ProcessId;
70+
71+
// When running from an AppImage, Environment.ProcessPath points inside the
72+
// FUSE mount (e.g. /tmp/.mount_XXXX/usr/bin/OpenShock.Desktop), which is
73+
// unmounted as soon as we exit. The AppImage runtime exposes the real path
74+
// of the .AppImage file via $APPIMAGE, so relaunch that instead.
75+
var appImage = Environment.GetEnvironmentVariable("APPIMAGE");
76+
var target = !string.IsNullOrEmpty(appImage) ? appImage : Environment.ProcessPath;
77+
78+
if (string.IsNullOrEmpty(target))
79+
{
80+
Environment.Exit(0);
81+
return;
82+
}
83+
84+
// Detached helper: wait for this process to fully exit (up to ~30s), then
85+
// relaunch the target. Passed as positional args so paths with spaces are safe:
86+
// $1 = target executable, $2 = pid to wait on
87+
const string script =
88+
"""
89+
target="$1"
90+
pid="$2"
91+
i=0
92+
while kill -0 "$pid" 2>/dev/null; do
93+
sleep 0.5
94+
i=$((i + 1))
95+
[ "$i" -ge 60 ] && break
96+
done
97+
exec "$target"
98+
""";
99+
100+
var startInfo = new ProcessStartInfo
101+
{
102+
UseShellExecute = false,
103+
};
104+
105+
// setsid detaches the helper into its own session so it survives our exit and
106+
// does not inherit a controlling terminal. Fall back to a plain shell if it is
107+
// somehow unavailable (setsid ships with util-linux and is present on all
108+
// mainstream distros, but be defensive).
109+
try
110+
{
111+
startInfo.FileName = "setsid";
112+
startInfo.ArgumentList.Add("/bin/sh");
113+
startInfo.ArgumentList.Add("-c");
114+
startInfo.ArgumentList.Add(script);
115+
startInfo.ArgumentList.Add("sh"); // $0
116+
startInfo.ArgumentList.Add(target); // $1
117+
startInfo.ArgumentList.Add(pid.ToString()); // $2
118+
Process.Start(startInfo);
119+
}
120+
catch
121+
{
122+
startInfo.FileName = "/bin/sh";
123+
startInfo.ArgumentList.Clear();
124+
startInfo.ArgumentList.Add("-c");
125+
startInfo.ArgumentList.Add(script);
126+
startInfo.ArgumentList.Add("sh"); // $0
127+
startInfo.ArgumentList.Add(target); // $1
128+
startInfo.ArgumentList.Add(pid.ToString()); // $2
129+
Process.Start(startInfo);
130+
}
131+
132+
Environment.Exit(0);
133+
#else
134+
// No restart mechanism for other targets (e.g. the web host); no-op.
135+
#endif
136+
}
137+
}

Desktop/Utils/WindowsRestartHelper.cs

Lines changed: 0 additions & 68 deletions
This file was deleted.

packaging/appimage/AppRun

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,31 @@ if ! ldconfig -p 2>/dev/null | grep -q 'libwebkit2gtk-4\.\(0\|1\)'; then
1616
echo " Arch: sudo pacman -S webkit2gtk-4.1" >&2
1717
fi
1818

19-
# Register the openshock:// URL scheme handler the first time we run as an AppImage.
19+
# Desktop integration, done once when running as an AppImage.
2020
# $APPIMAGE is set by the AppImage runtime to the absolute path of the .AppImage file.
21-
if [ -n "${APPIMAGE:-}" ] && command -v xdg-mime >/dev/null 2>&1; then
22-
apps_dir="${XDG_DATA_HOME:-${HOME}/.local/share}/applications"
21+
# This installs:
22+
# - the icon into the user icon theme, and
23+
# - a .desktop entry (which also registers the openshock:// URL scheme handler).
24+
# Both are needed so the running window has the right icon: under Wayland (KDE/GNOME)
25+
# there is no _NET_WM_ICON, so the compositor resolves the taskbar/titlebar icon by
26+
# matching the window app_id (the program name, "OpenShock.Desktop") to a .desktop
27+
# entry and loading its Icon= name from the icon theme.
28+
if [ -n "${APPIMAGE:-}" ]; then
29+
data_dir="${XDG_DATA_HOME:-${HOME}/.local/share}"
30+
apps_dir="${data_dir}/applications"
31+
icon_dir="${data_dir}/icons/hicolor/512x512/apps"
2332
handler_file="${apps_dir}/openshock-desktop.desktop"
33+
icon_file="${icon_dir}/openshock-desktop.png"
34+
icon_src="${HERE}/usr/share/icons/hicolor/512x512/apps/openshock-desktop.png"
35+
36+
changed=0
37+
38+
if [ ! -e "${icon_file}" ] && [ -e "${icon_src}" ]; then
39+
mkdir -p "${icon_dir}"
40+
cp "${icon_src}" "${icon_file}"
41+
changed=1
42+
fi
43+
2444
if [ ! -e "${handler_file}" ]; then
2545
mkdir -p "${apps_dir}"
2646
cat > "${handler_file}" <<EOF
@@ -34,9 +54,20 @@ Terminal=false
3454
MimeType=x-scheme-handler/openshock;
3555
StartupWMClass=OpenShock.Desktop
3656
EOF
37-
xdg-mime default openshock-desktop.desktop x-scheme-handler/openshock 2>/dev/null || true
57+
command -v xdg-mime >/dev/null 2>&1 && \
58+
xdg-mime default openshock-desktop.desktop x-scheme-handler/openshock 2>/dev/null || true
59+
changed=1
60+
fi
61+
62+
if [ "${changed}" -eq 1 ]; then
3863
command -v update-desktop-database >/dev/null 2>&1 && \
3964
update-desktop-database "${apps_dir}" 2>/dev/null || true
65+
command -v gtk-update-icon-cache >/dev/null 2>&1 && \
66+
gtk-update-icon-cache -f -t "${data_dir}/icons/hicolor" 2>/dev/null || true
67+
# Nudge KDE to rebuild its application/icon cache so the change shows immediately.
68+
for kb in kbuildsycoca6 kbuildsycoca5; do
69+
command -v "${kb}" >/dev/null 2>&1 && "${kb}" >/dev/null 2>&1 && break
70+
done
4071
fi
4172
fi
4273

0 commit comments

Comments
 (0)