Every settings.yaml key reaches its sampler through the SAME chain. Learn the pattern once and each section below only lists that setting's differences — do not re-derive the chain per setting.
AppSettings (settings.yaml, YamlDotNet)
→ App reads it at startup and on every settings-page change
→ TaskbarWindow.SetXxx(...) (UI thread; marshals to the taskbar thread)
→ SystemSampler volatile field (single writer = UI thread, reader = taskbar tick)
→ per-tick hand-off to the sampler (SystemSampler.Sample)
→ re-applied to every FRESH sampler in TaskbarWindow.Start()
Conventions that hold for all of them:
- null = the default, and only the non-default state is written to yaml. A missing key must behave exactly like the documented default.
Start()re-apply is mandatory. An explorer restart recreates the overlay and a brand-newSystemSampler; without the re-apply the recreate path silently reverts to defaults (that is how a disabled metric would come back to life).WM_APP_SET_METRICSonly when the OVERLAY LAYOUT changes — i.e. the sampling mask (§2) and the GPU 任务栏显示 content (§4: its row count, and so the width, changes with it). Everything else takes effect on the next tick — posting a WM_APP for a non-layout setting is pointless churn.- Each sampler's calibration/derivation lives at its own code site; this file only
records the wiring. Field-by-field yaml semantics are in
src/AppSettings.cs.
| Setter | TaskbarWindow.SetMergeSamePathProcesses(bool) |
| Sampler state | SystemSampler.SetMergeByPath (volatile, next tick) |
| WM_APP | no — the overlay layout does not change |
Handed each tick to the five per-process samplers, which merge same-exe-path rows
(ProcessListMerger.MergeByPath) before their top-N cut. Semantics and the svchost
exemption: gotchas.md §26.
| Setter | TaskbarWindow.SetMetricSamplingMask(int) → SystemSampler.SetEnabledMask |
| Sampler state | SystemSampler._enabledMask (volatile, next tick) |
| WM_APP | YES — WM_APP_SET_METRICS: the slot is hidden and the overlay re-lays-out/resizes |
App converts the five nullable booleans to/from one int mask (SamplingMaskOf), bit order
= overlay hit-slot order (SystemSampler.MaskCpu … MaskNet). This mask is separate
from TaskbarWindow._clickDisabledMask (pinned-window press suppression) — unpinning must
never re-enable a sampling-disabled slot. Details, including the re-enable baseline
priming: gotchas.md §25.
| Setter | TaskbarWindow.SetDiskDisplay(mode, index) / SetGpuDisplay(mode, index) |
| Sampler state | SystemSampler volatile mode+index pairs |
| WM_APP | no — the layout does not change |
Per-tick hand-off to DiskSampler.Sample(mode, index) / GpuSampler.Sample(mode, index, tempIndex, pdhEngines) (the last argument is ProcessGpuSampler's per-(adapter, engine) PDH
map — the Win10 utilization/name fallback, gated by GpuSampler.NeedsPdhEngineData,
gotchas §12; tempIndex belongs to §4 below). The samplers query every device each tick
regardless; the mode only selects the headline,
and a mode/index change clears the history so the chart never mixes semantics. Defaults
and the missing-device fallback: gotchas.md §28.
| Setter | TaskbarWindow.SetGpuTempDisplay(content, tempIndex, fahrenheit) → SystemSampler.SetGpuTempConfig(index, fahrenheit) |
| Sampler state | SystemSampler volatile index+unit; the CONTENT is TaskbarWindow layout state (_gpuTaskbarContent, read by every ComputeLayout) |
| WM_APP | YES — WM_APP_SET_METRICS: slot 3's form changes with the content — 温度 swaps the utilization row for a temperature row, 两者 packs BOTH rows as ONE 网络-style two-row block (usage over temperature, the single "GPU" label vertically centred; a dedicated cell, with the horizontal grid padding a trailing hole before it — same width as the old two stacked rows). ResizeForLayout is a no-op when the width didn't move, so the source/unit combo changes reuse the same message harmlessly |
Temperature itself is already sampled every tick for every adapter (DXCore
AdapterTemperatureCelsius, driver-gated — the detail popup has always shown it); this
chain only adds the numeric headline (GpuSample.TempValid/TempCelsius, always Celsius)
and the taskbar row. tempIndex = -1/null picks the HOTTEST adapter (mirroring the
utilization's 最高利用率 default); a specific "GPU N" that is absent falls back to the max,
while a present-but-unsupported adapter stays "--". The unit is display-side only —
GpuTempText.Format is the ONE formatter for the taskbar row and the detail popup's
per-adapter texts alike. Orthogonal to: GpuSamplingEnabled (采样 gates the data, this
gates the row) and GpuDisplay (显示方式 still picks the utilization headline). Layout
consequences (5 packed positions, 4 groups, both rows are hit slot 3):
TaskbarWindow.ComputeLayout and its ASCII table.
| Setter | TaskbarWindow.SetNetAdapter(id) |
| Sampler state | SystemSampler volatile id |
| WM_APP | no |
Per-tick NetSampler.Sample(id). The picker's items are enumerated by App when the
settings window opens (App.EnumerateNetAdapters, non-loopback, sorted) — a one-time
~275ms cost that must stay off the per-tick path. Pinned-adapter semantics:
gotchas.md §29.
| Setter | TaskbarWindow.SetPublicIpLookup(bool) |
| Sampler state | SystemSampler volatile → per-tick NetInfoSampler.PublicIpLookupEnabled |
| WM_APP | no |
Gates the what-is-my-ip HTTP lookups and the 公网延迟 ICMP probe. Off drops the cached
address and resets the next-try timestamp; re-enabling fetches immediately rather than
waiting out the cadence. gotchas.md §30.
| Setter | TaskbarWindow.SetClashApi(enabled, address, secret) |
| Sampler state | SystemSampler volatile triple |
| WM_APP | no |
Per-tick ClashSampler.SetEndpoint(...). null on the sampler means "off", and BOTH
the switch being off and network sampling being off call SetEndpoint(null) so the poll
thread sleeps completely. A null/empty address is replaced by ClashSampler.DefaultAddress
(127.0.0.1:9090) at the SystemSampler hand-off, so a stock core works with no setup.
Settings card behavior: the switch reports immediately; the two TextBoxes report through a
500ms single-shot debounce (a yaml write + sampler retarget must not fire per keystroke);
测试连接 is a separate one-shot GET /version probe on Task.Run (never the UI
thread). Full design: gotchas.md §31.
Not part of the sampler chain: UpdateChecker.CheckOnce runs once at startup from
OnStartup, so a change takes effect on the next launch (no live plumbing). The one exception
is a pending retry: after a network-level failure the single retry 5 minutes out re-reads the
live config, so switching 检查更新 off cancels it and a 更新源 change is honored — nothing to
wire up, it reads the same AppSettings instance. See
gotchas.md §33.
9. 悬浮模式 — FloatingMode + FloatingX/FloatingY + FloatingTopmost + FloatingEdgeHide + FloatingFullscreenHide + FloatingOpacity
Not a sampler chain either — this one moves the WINDOW. The shape is still the same
(field → volatile → WM_APP → re-applied on every fresh Start()), only the payload differs:
| Setter | TaskbarWindow.SetFloatingMode(on, x, y) / SetFloatingTopmost(bool) / SetFloatingEdgeHide(bool) / SetFloatingFullscreenHide(bool) / SetFloatingDark(bool) / SetFloatingOpacity(double) |
| Taskbar state | _floating / _floatingTopmost / _floatingEdgeHide / _floatingFullscreenHide / _floatingDark / _floatingOpacity (+ _floatingX/_floatingY — the home position) |
| WM_APP | WM_APP_SET_FLOATING = the form flipped → the window is DESTROYED and App's recreate loop builds the other form (150ms, ConsumeQuickRestart); WM_APP_UPDATE_FLOATING = 置顶显示, 透明度, or the app theme changed → re-applied in place, no rebuild; WM_APP_SET_FLOAT_EDGE_HIDE = 贴边隐藏 flipped → the dock is re-derived from the home (ON at an edge slides out, OFF slides home); WM_APP_SET_FLOAT_FULLSCREEN_HIDE = 全屏时隐藏 flipped → the fullscreen probe runs at once (ON with a fullscreen foreground hides immediately, OFF restores unconditionally) |
Two entry points, one handler each: the 设置 → 外观 switches AND the overlay right-click
menu's checkable 悬浮模式/置顶显示/贴边隐藏/全屏时隐藏 items (App.EnsureTaskbarMenu) both call
OnFloatingModeChanged/OnFloatingTopmostChanged/OnFloatingEdgeHideChanged/
OnFloatingFullscreenHideChanged; an open
SettingsWindow is pushed back via SyncFloatingMode/SyncFloatingTopmost/
SyncFloatingEdgeHide/SyncFloatingFullscreenHide, and the menu re-reads _config on every
Opened (置顶显示/贴边隐藏/全屏时隐藏
grey out outside the floating form, same rule as their settings cards).
FloatingMode(null = off): 悬浮模式 on/off. The window form — parent, ex-style, backdrop — is creation-time state, so this is the one setting that rebuilds the window instead of mutating it (Start()is the single construction site for either form).FloatingX/FloatingY(null = never dragged): the widget's home in screen px. Read byStart()(which records the opening spot it computed as the home when none is saved), and written by App fromTaskbarWindow.FloatingPositionChangedwhen a drag ends — the only settings write triggered by the taskbar thread (marshaled to the UI thread). The home is also what the widget returns to after stepping aside for a detail window that covers it (SetFloatingKeepOut→WM_APP_SET_FLOAT_KEEPOUT, a runtime-state push, not a setting); a dodge is never written back here.FloatingTopmost(null = on): live, no rebuild — it moves BOTH ex-style bits (WS_EX_TOPMOSTfor the band,WS_EX_NOACTIVATEbecause the non-topmost form is an ordinary window that must be activatable to be raiseable, §36 of the gotchas).FloatingEdgeHide(null = off): 贴边隐藏, floating form only — a drop within 8 DIP of the work area's 左/右/上 edge docks the widget there (slides out, a strip stays; hover peeks it back, mouse-out re-hides). Live, no rebuild — and the dock itself is DERIVED from the home (FloatingX/FloatingY, which is why a docked home reopens hidden), so nothing but the switch and the home are stored. Mechanism + the slide/peek/armed rules:gotchas.md§38. Settings card + right-click menu item (checkable, greyed outside the floating form).FloatingFullscreenHide(null = on): 全屏时隐藏, floating form only — a fullscreen app in the foreground on the widget's monitor (borderless game, F11 video, slideshow; NOT a maximized window) hides the widget until the foreground stops being fullscreen. Live, no rebuild — the switch is the ONLY stored state; the fullscreen condition is probed by the tick (FullscreenAppOnScreen), and the hide/restore is a plainSW_HIDE/SW_SHOWNOACTIVATEthat touches no positional state. Mechanism + the visibility-only rules:gotchas.md§39. Settings card + right-click menu item (checkable, greyed outside the floating form).FloatingOpacity(null = 1.0; 0.2–1.0,double): the widget's BACKGROUND opacity — brush-alpha scaling inApplyTaskbarTheme(NOTWS_EX_LAYERED; scope, why and the 0.2 floor: §36): the card's alpha IS the value (100% = fully opaque card), the card's outline scales with it, text/labels/interaction fills keep fixed alphas (文字不要有透明度). Live per slider step (the widget re-tints under the thumb; App debounces only the yaml write, 500ms — the Clash text boxes' reason); clamped in the setter because settings.yaml is hand-editable. Settings-page only — a percent slider has no menu shape, andFloatingOpacityCardgreys out outside the floating form likeFloatingTopmostCard.FloatingDarkis NOT in settings.yaml — it is the effective APP theme (ThemeManager.Current.ActualApplicationTheme), pushed by App at startup and from theActualApplicationThemeChangedhook, because the floating widget's material is the detail popup's (the taskbar form instead follows the SYSTEM theme).
Full mechanism, the measured material choice and the creation-time rules:
gotchas.md §36.
A TaskbarWindow-layout chain, not a sampler one — like §4's content, the value is read by
every ComputeLayout call (it threads through ComputeTargetSize, Draw, HitTest and the
UI-thread layout accessors as the trailing compact argument):
| Setter | TaskbarWindow.SetCompactLayout(bool) |
| Taskbar state | _compactLayout (volatile; no sampler state — pure geometry) |
| WM_APP | YES — WM_APP_SET_METRICS: the item pitch changes with it, so the window resizes + redraws at once (ResizeForLayout reuses the 采样-toggle dance) |
Tightens the item pitch of EVERY form — taskbar overlay and floating widget alike:
horizontal cells 70/84 → 65/78 DIP (row inset 5 → 3), vertical multi-column grid row
24 → 18 DIP, two-line block 44 → 40 (its pad 4 → 2), and — inside the multi-row strip
blocks — GPU(两者)'s three lines and 网络's ↑/↓ four stack at natural line heights as a
centred cluster instead of span-derived bands (row gaps ~8–10 → ~1.2 DIP; the plain
two-line blocks keep the derived pair). The single-column one-line strip keeps its
16-DIP pitch (already the compact form). The floors of those values are text-fit
measurements at 13px Segoe UI (the widest row is 内存 + 100% = 57.7 DIP; the widest net
value a 10-Gbit NIC's "1192.1 MB/s" ≈ 71.3 DIP) — derivation and constants: the compact
block at the top of TaskbarWindow's layout constants. Settings card (设置 → 外观,
never gated on 悬浮模式 — it applies to both forms) and a checkable right-click
menu item, both funnelling into App.OnCompactLayoutChanged (which also drops the
transient flyout, like a placement change — its anchor column moved).
开机自启动 — the scheduled task itself is the state (src/StartupTask.cs). Never add a
key for it.