Service: --run-service host + registration CLI (install/uninstall/start/stop)
Local checkpoint - NOT for public release. - RemSoundService (ServiceBase): hosts ServiceSendHost.RunLoop on a worker thread, OnStop cancels + joins. Added System.ServiceProcess.ServiceController package. - ServiceControl: install (sc.exe create, auto-start, careful binPath quoting) / uninstall (stop + delete) / start / stop / status. Status query is unprivileged (menu can poll it); the mutating verbs self-elevate via ShellExecute runas. - Program.cs: early guards for --run-service (blocks in the SCM dispatcher) and the one-shot elevated verbs, before the single-instance lock (the service is a separate role and must never take the interactive lock). - Self-test "Service registration args" verifies the sc create binPath quoting survives a spaced exe path. Gate 20/20. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e648bee531
commit
5dbbe4dd71
@@ -0,0 +1,66 @@
|
||||
using System.ServiceProcess;
|
||||
using RemSound.Core;
|
||||
|
||||
namespace RemSound.App;
|
||||
|
||||
/// <summary>
|
||||
/// The RemSound Windows service (send-only lock-screen streaming). Hosts <see cref="ServiceSendHost"/>
|
||||
/// on a background thread; the host's own RunLoop yields to the interactive app via
|
||||
/// <see cref="InteractivePresence"/>. Started by the SCM when Program.cs is launched with
|
||||
/// <see cref="ServiceControl.RunVerb"/>.
|
||||
/// </summary>
|
||||
public sealed class RemSoundService : ServiceBase
|
||||
{
|
||||
private readonly CancellationTokenSource cts = new();
|
||||
private Thread? worker;
|
||||
private ServiceSendHost? host;
|
||||
private RemSoundLog? log;
|
||||
|
||||
public RemSoundService()
|
||||
{
|
||||
ServiceName = ServiceControl.ServiceName;
|
||||
CanStop = true;
|
||||
CanShutdown = true;
|
||||
CanPauseAndContinue = false;
|
||||
}
|
||||
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
log = new RemSoundLog { Enabled = SafeServiceLogging() };
|
||||
log.Event("service: OnStart");
|
||||
host = ServiceSendHost.FromConfig(msg => log?.Event(msg));
|
||||
worker = new Thread(() =>
|
||||
{
|
||||
try { host.RunLoop(cts.Token); }
|
||||
catch (Exception ex) { log?.Event($"service: run loop crashed {ex.GetType().Name}: {ex.Message}"); }
|
||||
})
|
||||
{ IsBackground = true, Name = "remsound-service" };
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
log?.Event("service: OnStop");
|
||||
try { cts.Cancel(); } catch { }
|
||||
try { worker?.Join(5000); } catch { }
|
||||
try { host?.Dispose(); } catch { }
|
||||
}
|
||||
|
||||
protected override void OnShutdown() => OnStop();
|
||||
|
||||
private static bool SafeServiceLogging()
|
||||
{
|
||||
try { return AppConfig.Load().ServiceLoggingEnabled; }
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing) { try { cts.Dispose(); } catch { } }
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <summary>Blocks in the SCM dispatcher until the service is stopped. Called from Program.cs
|
||||
/// when launched with the run verb.</summary>
|
||||
public static void RunAsService() => ServiceBase.Run(new RemSoundService());
|
||||
}
|
||||
Reference in New Issue
Block a user