A cross-platform process manager in C#: a Process-Explorer-shaped desktop UI and an htop-shaped terminal UI over one sampling engine. The desktop UI is built on NativeForms, so the same binary puts real Win32 windows on Windows and real GTK windows on Linux without a second UI codebase.
Important
Working, with the gaps written down. Linux is complete: engine, probe, both front-ends, the six
detail views, and the privileged helper. Windows is complete bar the environment block, and has
been executed and checked against a kernel under Wine — 21 of 21 self-test checks, 47 modules,
95 handles — but not yet on a genuine Windows machine. macOS throws by design. Three desktop
features (row colours, click-to-sort, in-cell sparklines) are blocked on one missing hook in the UI
toolkit. docs/PRD.md tracks all of it box by box; §12 is the per-feature coverage
matrix and §10 the milestone state.
Rows are coloured by what kind of process they are, and three of the columns are drawn rather than written — CPU, memory and I/O history, one pixel per sample, sharing one scale so the rows can be compared with each other. The plots read as instruments rather than as part of the desktop: black ground, green graticule, filled area.
The same three histories in the terminal, drawn with the eighth-block characters
(▁▂▃▄▅▆▇█) — and an ASCII ramp on a terminal whose locale is not UTF-8, because a column of
replacement boxes is worse than no plot at all.
Both are regenerated by tools/shoot-screenshots.sh, which gives the
machine some real work first so the plots have a shape. The window is photographed from inside the
process — a runner has no compositor to grab pixels from — and the terminal is written as SVG, which
needs no font, weighs nothing, and shows which line changed in a pull request.
Three things that are usually three separate programs:
- A process explorer — the process tree with per-process CPU, memory, I/O, handles and threads; the detail views (threads, modules, open handles, environment, network endpoints); and the search that answers "which process has this file open?".
- A task manager — end a process or its tree, suspend and resume it, change priority and CPU affinity, and see at a glance what is eating the machine.
- A terminal monitor — the same data as a full-screen console UI over SSH, where no display exists and installing a desktop toolkit is not an option.
All three read from one sampling engine (ProcessManager.Core) behind one platform probe
interface. A metric is implemented once, and both front-ends get it; a front-end has no privilege to
read anything the other cannot.
ProcessManager.Core Sampling engine: snapshots, deltas, rates, history, tree building,
sort/filter. No platform code, no UI, no I/O beyond the probe.
ProcessManager.Platform.Linux /proc, /sys, cgroup v2, netlink — shipping
ProcessManager.Platform.Windows NtQuerySystemInformation, ToolHelp32, PDH — shipping
ProcessManager.Platform.MacOS libproc / sysctl — stub, throws
ProcessManager.Ui.Terminal Terminal renderer, no toolkit dependency
ProcessManager.Ui.Desktop Desktop UI on NativeForms
ProcessManager.App The one binary: CLI plus both front-ends — procman
ProcessManager.Elevated procman-helper: the only component that ever runs as root/admin
One executable carries both front-ends, so procman and procman --tui are the same program. A
headless machine still never loads GTK: a NativeForms backend that is not registered is never asked
for its native library.
Core never calls a native API; it asks an ISystemProbe for a SystemSnapshot and does the
arithmetic. That is what makes the whole engine testable against recorded /proc trees and captured
Windows structures rather than against the machine running the tests — see
PRD §9.
Platform support: Windows and Linux. macOS is a
stated future direction, not a shipped feature — the macOS probe is a
stub whose every member throws PlatformNotSupportedException with an actionable message.
procman # desktop UI (Win32 on Windows, GTK on Linux)
procman --tui # full-screen terminal UI
procman --tui --sort=cpu # start sorted by CPU, tree mode off
procman --list --json # one snapshot to stdout as JSON, then exit
procman --find "libssl" # which processes have a handle/mapping matching this?
procman --kill 1234 --tree # end a process and its descendants
procman --flat # start as a sorted list rather than a tree
procman --self-test # ask the probe about itself; the runtime checks its answer
procman --helper-check # talk to the privileged helper over its pipe, unelevated
procman --no-helper # never start the helper, even for an action that needs itThe terminal UI keeps the keys htop users already have in their fingers — F5 tree, F6 sort,
F9 kill, F10 quit, / search, \ filter, u filter by user — plus Enter for a process's
details and h to read handle counts for the visible rows. The desktop UI keeps the layout Process
Explorer users already have in their eyes: plots and per-core meters on top, the process tree below
them, and a tabbed detail pane under that — overview, threads, modules, handles, environment,
network. Click a header to sort by it, click it again to reverse; View → Select columns chooses
from sixteen, and View → Colour legend says what every row colour means.
| Green | started since the last refresh |
| Red | ended since the last refresh |
| Pale yellow | yours |
| Blue | the system's (root / SYSTEM) |
| Teal | a service |
| Grey | suspended |
| Orange | a zombie — exited, not yet reaped |
Not distinguished: packed, .NET, elevated and store processes. Telling those apart needs information neither probe collects, and a colour that is sometimes right is worse than none.
| Area | Contents |
|---|---|
| Process tree | Name, PID, PPID, user, state, CPU %, private bytes, working set / RSS, virtual size, I/O read + write rates, handle / file-descriptor count, thread count, start time, session, priority, command line, working directory |
| Per-process details | Threads (TID, state, CPU, start address) · modules and mappings (path, base, size, permissions) · handles and open files (type, name, access) · environment block · TCP/UDP endpoints · memory regions |
| System overview | Per-core CPU history, load average, memory and swap with cache breakdown, I/O throughput, network per-interface throughput, disk per-device throughput, uptime, context switches, interrupts |
| Search | One query across process names, command lines, open files, mapped modules and listening ports — the "who is holding this file" question, answered in one place |
Rows are coloured the way Process Explorer colours them — new green, exited red — in the terminal UI. The window does not colour them yet; see the limitations below for why.
Most of what this program shows needs no privileges at all. A few things do: reading another user's command line and open files, ending another user's process, and per-process network capture.
ProcessManager stays unprivileged and starts a small separate helper (procman-helper) only when
an action needs one — polkit on Linux; Windows is not implemented, because it cannot both elevate
a child and redirect its standard handles in one call. The helper accepts a fixed
set of typed requests over a private pipe, checks each one against an allowlist, and exits with the
program. It does not evaluate anything it receives, and it never runs the UI. When the helper is not
available the affected columns and actions are disabled with the reason shown, rather than the whole
program refusing to start.
See PRD §8 for the protocol and its threat model.
ProcessManager builds against the NativeForms sibling repository. Clone it next to this one:
work/
├─ ProcessManager/ # this repo
└─ NativeForms/ # https://github.com/Hawkynt/NativeFormsThe process list needs TreeListView's row-colour, cell-paint and column-click seams, which are on
NativeForms' main but not yet in a published package. When one ships, this goes back to three
PackageReferences and the sibling clone stops being necessary; the build fails with one sentence
saying so if the sibling is missing.
dotnet build ProcessManager.slnx -c Release
dotnet test ProcessManager.slnx -c Release
dotnet run --project ProcessManager.App -- --tui # terminal UI
dotnet run --project ProcessManager.App # desktop UI; needs GTK 3 on Linux
dotnet run --project ProcessManager.Benchmarks # the PRD §4 budget harness
./tools/shoot-screenshots.sh # regenerate docs/screenshots
# Everything replays against a recorded /proc tree, on any OS:
dotnet run --project ProcessManager.App -- --list --tree \
--probe-root ProcessManager.Tests/Fixtures/proc-desktopPublishing produces a single self-contained binary per platform, NativeAOT where the platform allows it. Trim and AOT warnings are build errors — see PRD §4 for the footprint budget the CI enforces.
GitHub Actions, same four-workflow layout as the other repos here:
| Workflow | Trigger | Does |
|---|---|---|
ci.yml |
push / PR | Build + test on Linux, Windows and macOS; a NativeAOT publish per RID with trim warnings as errors; a headless run of both front-ends against recorded fixtures |
_build.yml |
called | The shared publish block — NativeAOT self-contained binaries, one runner per RID (AOT cannot cross-compile), so release and nightly can never diverge |
nightly.yml |
after green CI on main |
Nightly prerelease + the sampling benchmark harness, GFS-pruned to 7 daily / 4 weekly / 3 monthly |
screenshots.yml |
manual, and at release | Re-photographs both front-ends and commits the pictures when they changed. Not on every push: a capture of a live machine differs every run, so that would be a bot commit per push |
release.yml |
manual dispatch | CI, build, changelog, and a dated vyyyyMMdd GitHub Release |
Versions are never taken from a tag: .github/workflows/scripts/version.pl --stamp rewrites each
project's own <Version>X.Y.Z</Version> to X.Y.Z.<commit count of that folder> at build time.
- Process Explorer — the tree, the handle search, the color legend
- System Informer / Process Hacker — the privilege split and the depth of the detail views
- htop — the terminal layout and its keybindings
- btop — the system graphs
- Windows Task Manager — the "what is wrong right now" first screen
These are consequences of the design, not a to-do list; the to-do list is the PRD.
- macOS does not work. The probe is a stub whose every member throws. Nothing samples, nothing renders.
- Elevation is Linux-only. The helper, its framed protocol and its polkit policy work and are
tested; Windows elevation needs a named pipe the elevated child connects back to, which is not
written. See
packaging/. - macOS is the only platform with no probe at all. Windows and Linux are both verified against
their own kernels on every push by
procman --self-test, which asks the probe about the process it is running in and has the runtime check every answer. - Sampling costs a third more than the budget says. 33 ms of CPU per 1000 processes against a
target of 25 — three files are read per process, and syscalls are the entire cost. Closing it means
dropping
statusand with it the private-memory column and the owner id, which is a worse trade than three milliseconds. Measured and written down in PRD §4 rather than left as a number nobody intends to meet. - Per-process property windows are not implemented. The detail pane shows one process at a time; Process Explorer opens several at once, which is what makes it good at comparing two of them.
- Nothing is persisted. Column choice, sort and window size are back to their defaults on every start.
- Per-process network capture needs the helper. Linux attributes sockets to processes through
/proc/netplus inode matching, which is unprivileged but coarse; anything finer needs root. - No kernel driver, ever. Everything Process Explorer does through its driver — real thread stacks with symbols, kernel object inspection, protected-process access — is out of reach here and stated as a non-goal in the PRD, rather than promised and quietly missing.
- Sampled, not traced. Rates come from differencing counters at an interval. A process that lives and dies inside one interval is a gap in the data, and the UI says so instead of drawing a zero.
If ProcessManager is useful to you, consider supporting development:
Licensed under LGPL-3.0-or-later — see LICENSE.
