Skip to content

Lifecycle

Taiizor edited this page Jun 5, 2026 · 3 revisions

Lifecycle

This page traces the runtime lifecycle of the Sucrose process stack: the cold start from Sucrose.Launcher.exe (single-instance gate → Configure() → tray icon + signal channel + Discord), how the wallpaper auto-restores on launch, the configuration helpers every process runs first, and the two teardown paths — "Exit" (leave the wallpaper running) versus "Close" (full stack teardown). It is a developer reference and complements the Architecture Overview (who exists) with when and in what order things happen.

🖼️ Diagram needed: A timeline/sequence diagram of the cold start: Launcher OnStartupInstance.BasicConfigure()TrayIconManager.Start() (engine auto-restore, Reportdog, update check) → signal channel → Discord.

Contents

The Launcher is the entry point

Sucrose.Launcher.exe (src/Launcher/Sucrose.Launcher/App.xaml.cs) is the process that starts when the user opens Sucrose. It is a WPF app with no main window — it lives entirely in the system tray, with ShutdownMode = ShutdownMode.OnExplicitShutdown.

Cold start sequence

The Launcher's OnStartup:

  1. SRHR.SetLanguage(SMMG.Culture) — set the UI language from the saved Culture setting (default = the current UI two-letter ISO language).
  2. Set ShutdownMode = OnExplicitShutdown.
  3. Single-instance gate: SSSHI.Basic(SMMRM.Launcher, SMMRA.Launcher) (mutex {Sucrose-Wallpaper-Engine-Launcher}). See Single Instance Mutexes.
    • If true (this is the first instance), run Configure():
      • SSLMI.TrayIconManager.Start() — create the tray icon + context menu.
      • SSMI.LauncherManager.StartChannel(SSSSLSS.Handler) — start the Launcher signal channel (a FileSystemWatcher over Launcher.sgnl) using LauncherSignalService.Handler.
      • Discord.Initialize() — start the Discord Rich Presence timer.
    • If false (an instance is already running), this second copy does not stay resident: it calls SSLCI.Command() (asks the running stack to open the Portal) and then Close()s itself.

So double-launching the Launcher means "open the settings window," not "create a second tray icon."

In words:

User runs Sucrose.Launcher.exe
  -> OnStartup: Instance.Basic("{...-Launcher}", "Sucrose.Launcher.exe") == true
     -> Configure()
        -> TrayIconManager.Start()    (tray icon + menu; auto-restore wallpaper; Reportdog; update check)
        -> LauncherManager.StartChannel(LauncherSignalService.Handler)
        -> Discord.Initialize()       (5s timer; presence if Discord running + enabled)

Tray-start side effects (auto-restore)

When TrayIconManager.Start() runs, it also performs three actions regardless of any user click:

  • SSLCE.Command(false)Engine.Command(false): if a wallpaper is configured and no engine is currently running, start the wallpaper engine now. This is how the wallpaper auto-restores on launch — users do not re-apply each boot. Run.Start() reads the selected wallpaper's SucroseInfo JSON, maps its Type to the configured engine, and (when PerformanceCounter is on) first spawns Backgroundog via ✔Backgroundog✖<Backgroundog>, then spawns the engine via ✔Live✖<engine.exe>.
  • SSLCRG.Command()Reportdog.Command(): spawn Sucrose.Reportdog.exe (via Commandog) to flush pending telemetry / error reports.
  • If SMMU.Auto (auto-update on): SSLCU.Command(false) — a silent update check (spawns Sucrose.Update.exe via Commandog).

Tray visibility honors the AppVisible setting (default true).

Common per-process configuration

Every process's constructor or Main runs the same two configuration helpers before anything else:

  • SSDHR.Configure() (Shared.Dependency.Helper.Runtime) — RELEASE-only. Points the process at the bundled private .NET runtime by setting environment variables DOTNET_ROOT (and the (x86)/(arm64) variants), DOTNET_MULTILEVEL_LOOKUP=0, DOTNET_ROLL_FORWARD=LatestMajor, and prepending the runtime folder <parent>/Sucrose.Runtime plus the app dir to PATH. (Debug builds use the machine's installed runtime — the path manipulation happens only in RELEASE.)
  • SSDHG.Configure() (Shared.Dependency.Helper.Graphic) — calls EnsureHighPerformance(exe) so the process is registered for the high-performance GPU on multi-GPU laptops.

Then each process sets its thread culture from the Culture setting. WPF apps set ShutdownMode; the console apps (Commandog, Backgroundog, Reportdog) additionally set Console.InputEncoding/OutputEncoding = UTF8 so the (U+2714) / (U+2716) command delimiters are not mangled.

Build-level runtime/GC config from Directory.Build.targets (all non-library outputs): ThreadPoolMinThreads=10, ThreadPoolMaxThreads=500, UseWindowsThreadPool=true; ServerGarbageCollection=true, ConcurrentGarbageCollection=true, RetainVMGarbageCollection=false; TieredCompilation=true, InvariantGlobalization=false.

Discord Rich Presence lifecycle

Discord.Initialize() starts a DispatcherTimer (InitializeTimer) ticking every 5 seconds. On each tick it checks whether the user enabled Discord (SMMH.DiscordConnect) and whether Discord.exe or DiscordPTB.exe is running (ApplicationNames = ["Discord.exe", "DiscordPTB.exe"]):

  • If yes and not yet initialized → Client.Initialize() then SetPresence().
  • If Discord is gone / disabled → ClearPresence() and stop the refresh timer.

An optional auto-refresh timer (RefreshTimer) runs at interval SMMH.DiscordRefreshDelay seconds and re-randomizes the presence text/images. The Discord application id is 1126294965950103612 (library: DiscordRPC). On shutdown the Launcher's OnExit runs Discord.Dispose()LauncherManager.StopChannel()TrayIconManager.Dispose()Close(). See Discord Rich Presence.

Opening the Portal (second launch)

User clicks tray "Open" (or runs Launcher again)
  -> Interface.Command() -> spawn Commandog "✔Interface✖<Portal.exe>"
     -> Commandog: Process.Start(Portal.exe)
        -> Portal OnStartup: Instance.Basic("{...-Portal}", "Sucrose.Portal.exe")
           -> if first: build Generic Host (DI), Security.Apply(), host.Start() -> MainWindow
           -> if already running: Close() (no second window)

Reload, cycle, and the wallpaper engine

  • Reload (tray) → ✔RestartLive✖Unknown → Commandog stops the live engine + browser subprocesses, kills Sucrose.Backgroundog.exe, runs Run.Start(), waits 1500 ms, and retries if the engine is not running.
  • Cycle (slideshow advance) → ✔Cycyling✖... → same as Reload but without killing Backgroundog.

Killing/starting the engine is handled by Kill.Stop() / Kill.StopSubprocess() (which also resumes and kills msedgewebview2 / CefSharp.BrowserSubprocess child processes whose command line contains Sucrose) and Run.Start(). See Commandog Dispatcher.

Teardown: Exit vs. Close

The tray menu's last item label and behavior depend on the AppExit setting (default false), via Close.cs (src/Shared/Sucrose.Shared.Launcher/Command/Close.cs):

AppExit Tray label Behavior
false (default) "Exit" Only the Launcher itself exits. The wallpaper engine and the background services keep running.
true "Close" A full teardown: stop the engine (Kill.Stop / StopSubprocess), then kill (if running), in order — Undo, Portal, Update, Property, Watchdog, Commandog, Reportdog, Backgroundog — release the tray, then Process.GetCurrentProcess().Kill() + Environment.Exit(0).

Sucrose.Undo.exe is a separate full-removal path (the uninstaller), not the same as Close. See Undo Internals.

Crash path

Any process throws
  -> Watchdog.Start(app, ex, show, log)
     -> spawn Commandog "✔Watchdog✖<Watchdog.exe>✖<Base64(app✖ex✖show✖log)>"
        -> Watchdog: write Cache/Report/<guid>.json -> Kill(app) -> (if show) Dark/Light error dialog
... later ...
Reportdog loop -> upload Cache/Report/*.json   (only if ExceptionData / TelemetryData enabled)

Global exception handling is wired identically in Launcher / Portal / Watchdog / Property (handlers for ThreadException, FirstChanceException, UnhandledException, UnobservedTaskException, DispatcherUnhandledException). See Crash Reporting.

See also

Home

Getting Started

Wallpaper Types

Using Sucrose

Settings Reference

Creating Wallpapers

Engine Reference

Automation & Command Line

Architecture & Internals

Data, Files & Diagnostics

Building & Contributing

Help & Support

Clone this wiki locally