A suite of Windows 11 apps focused around one of the most under-loved pieces of UX ever.
These are my projects for replacing, and adding to, the Windows 11 tray system apps. For the best effect, I recommend removing the stock Windows tray icons with Windhawk.
|
|
Project status: These apps should all be considered to be in alpha. They're exceptionally stable, but there are still large gaps in some apps that need to be completed.
Downloads Page | Grab the latest installer for whichever app(s) you'd like, or download the bundled suite. Then install it. Done.
Installer_{AppName}_{version}.exeis a single-app installer.Installer_TrayAppDotNET_{version}.exeis the app bundle, with a GUI selector for which apps to install.
These apps can all run portably. To these apps, there is not much difference between running portable vs being installed. All app settings and logs are saved to %LocalAppData%\TrayAppDotNET by default.
In theory, any app settings you may need are properly exposed in the app's own settings UI. If this is not enough, the vast majority of parameters/constants for any given app are stored to and read from that app's own settings.xml file.
In the future, apps will also generate a file for UI constants. Every. single. UI constant. is defined in AXAML.
|
Control the actual brightness of external displays, as well as Windows brightness (laptops) and Windows Night Light. I kept running into DDC failures with Twinkle Tray, and I don't like Electron. I took inspiration from the fluent layout it used, so many thanks to Xander Franfangos for that! This was the first "major" TADN app I made. There are quite a few features. Features
|
|
|
Not to be confused with the very popular "Fan Control" project. I find that app cumbersome, and I find it odd its not open source. Like Fan Control, this project is essentially a UX wrapper around LibreHardwareMonitor, and nothing more. |
As simple as it gets. The still-embedded Win10 Windows Network flyout works great, so this little app just invokes it. Still comes with some quick-access features via its tray context menu. |
|
A "reimplementation" of the Windows 11 Task Manager. The official Task Manager has become far too stuttery for me. While "fixing that", some extra bells and whistles were added. Features
|
A volume mixer and audio endpoint manager. EarTrumpet users will find this familiar. I took inspiration from it in this design. Originally, I'd modified EarTrumpet to sort its mixer bottom-up like this, to minimize mouse movement distance. Since that project seems to have been abandoned, I took a crack at the whole thing. Features
|
All apps come with the following features:
- Flyout undocking (where applicable)
- Searchable settings (with fuzzing)
- Live theme customization
- Rebindable global hotkeys (Task Manager instead has fixed table and window shortcuts plus an optional Ctrl+Shift+Esc takeover)
- Update system
- Crash recovery
- Customizable tray menus (where applicable)
User documentation for every app and the shared features is in documentation/docs.
I use Visual Studio 2026 with this project. Here are the necessary tools to compile it yourself.
- The .NET SDK pinned in
global.json(.NET 10) - Visual Studio Build Tools 17 with:
- Desktop C++ environment
- MSVC v143
- Windows 11 SDK 10
This mono-repo is set up such that every app also has its own slnx file, if opening only one app is desired. Fan Control vendors LibreHardwareMonitor and its other dependencies as git submodules, so clone with --recurse-submodules (or run git submodule update --init --recursive).
# Everything, Debug (apps, Common, generators, installer factory, tests)
dotnet build TrayAppDotNET.slnx -c Debug -p:Platform=x64
dotnet test TrayAppDotNET.slnx -c Debug -p:Platform=x64 --no-build
# One app, Release: publishes Native AOT and stamps Installer_<App>_<build>.exe beside it
dotnet build VolumeTrayAppDotNET\src\VolumeTrayAppDotNET.csproj -c Release -p:Platform=x64
# Add -p:TrayAppDotNETStampAppInstaller=false to skip the installer stamping while iteratingThe installer elevated-worker tests in TrayAppDotNETInstaller.Tests and Task Manager's opt-in kill-helper smoke test touch elevation; see .agents/PROJECT_MAP.md for the test projects, build files and CI workflows (.github/workflows/tests.yml runs every test project on pushes and pull requests).
| Argument | Intended use | Behavior |
|---|---|---|
| No arguments | User | Start the app normally under the crash watcher. |
--autostart |
Startup shortcut | Mark a sign-in launch. The Run on startup shortcut passes it, the watcher forwards it to every monitored process it starts, and apps with a Start minimized option only honor that option when it is present. |
--hidden |
Installer | Start in the notification area without showing a window, whatever the startup settings say. The installer passes it to the apps it launches when it finishes, and the watcher forwards it to every monitored process it starts. Only Task Manager opens a window at startup, so it is the only app this changes. |
--settings-dir <path> |
User/script | Use <path> instead of %LOCALAPPDATA%\TrayAppDotNET\<App> for settings.xml, logs and the app's other data files. Relative paths resolve against the working directory and environment variables such as %USERPROFILE% expand. The folder is created on first use. If the value after the flag is missing or is not a usable folder path, the app exits with code 2; a path that ends in a backslash must not be quoted. The watcher, the rendering restart, Restart from installed, the update restart and Brightness's Night Light and DDC helpers keep the override. Task Manager's elevated helpers don't receive it and write no files; the broker's log lines go to the app's own log. Turning on Run on startup from an instance with an override writes it into the startup shortcut, and a new override folder does not turn Run on startup on by itself. The single-instance rule is unchanged, so starting a second copy with a different folder still replaces the running one: it asks the running copy to exit through its normal exit path and ends it by force only after five seconds. The Windows uninstall entry passes no override, so removing settings there deletes only the default folder. |
--installer or --install-gui |
User | Open the one-page installer. Local installation is selected by default. Selecting system installation causes Windows to display a UAC prompt after Install is clicked. The window writes no settings and creates no startup shortcut. |
--install-headless <scope> |
User/script | Install without opening a window, print progress lines ([NN%] stage) and the result to the parent console or redirected standard output, then exit without starting the installed copy. <scope> is local or system. System scope causes Windows to display a UAC prompt. |
--install <scope> |
User/script | Compatibility alias for --install-headless <scope>. |
--installlocal |
User/script | Install locally without opening a window, then start the installed instance (through the desktop shell with --hidden when the caller is elevated). |
--installsystem |
User/script | Install system-wide without opening a window, display the Windows UAC prompt, then start the installed instance (through the desktop shell with --hidden when the caller is elevated). |
| `--desktop-shortcut <true | false>` | User/script |
| `--start-menu-shortcut <true | false>` | User/script |
| `--run-as-administrator <true | false>` | Installer/script |
--uninstall <installDir> --scope <scope> |
App/Windows uninstall entry | Open the uninstaller for the supplied installation. Confirming a system uninstall causes Windows to display a UAC prompt; declining it keeps the window open with the reason. The window writes no settings. |
--uninstall-gui <installDir> --scope <scope> |
App/Windows uninstall entry | Alias for --uninstall. |
--uninstall-headless <scope> |
User/script | Uninstall without opening a window, printing progress lines ([NN%] stage) to the parent console or redirected standard output. <scope> is local (or user, localappdata) or system (or programfiles); store scopes are rejected. System scope causes Windows to display a UAC prompt. |
| `--delete-settings <true | false>` | User/script |
--scope <scope> |
App helper | Select an installation for uninstall operations. Accepted values are user, local, localappdata, system, programfiles, store, and windowsstore. |
--watcher |
App helper | Run the crash watcher process. It restarts the app after a crash, stops when the app exits with code 0 or 1, and after exit code 3 while it sees Windows ending the session restarts it only once the session turns out to continue. Exit code 3 at any other time counts as a crash. Every restart counts toward the repeated-crash limit. |
--monitored --watcher-pid <pid> |
App helper | Run the monitored app process owned by the watcher with the supplied watcher PID. |
--watcher-pid <pid> |
App helper | Supplies the watcher PID to a monitored app instance. |
--install-system --source <sourceExe> --build <buildNumber> |
App helper | Continue a system installation after Windows has displayed the UAC prompt, then write system uninstall and shortcut metadata. |
--sync-start-menu [--remove-scope <scope>] |
App helper | Reconcile the Start Menu shortcuts of every profile. Refused (exit code 1) when not elevated. |
| `--uninstall-prepare --scope [--delete-settings <true | false>] [--settings-dir ]` | App helper |
--progress-pipe <name> |
App helper | Named pipe through which an elevated install or uninstall helper reports its progress stages to the process that started it. |
--remove-scope <scope> |
App helper | Scope value consumed by --sync-start-menu when removing shortcuts for an uninstalling scope. |
--update-apply |
App helper | Run by the installed exe itself: copy the downloaded package into a staging folder inside the install folder, verify its SHA-256, and replace the app's payload files as a journaled transaction after the running app exits. Windows displays a UAC prompt only when the installation directory requires elevation. |
--update-restart |
App helper | Restart the installed app after update commit or rollback, then clean the staging directory. This process is deliberately not elevated. |
- Apps: .NET 10, Avalonia 12, compiled to Native AOT.
- Installers: .NET Framework 4.8, WPF (
Installer_<App>_<version>.exeand the suite installer; each app also has its own built-in install and uninstall windows)
I make use of frontier LLM's heavily in this project. For what it's worth, I am actually a software engineer; I do actually read and review what gets written, as well as use my own fingers to write code myself sometimes - not just prompts.
I am not about to spend 6 months slogging through a thousand Windows API's and ABI's just for one feature to barely work, or another 2 months refactoring the entire codebase for any number of reasons. However, the architecture and design choices are all done by me. I keep stylesheets around to somewhat enforce my own standards upon LLMs.
I allow LLMs complete reign over spamming test code. I do not write test-driven code, and any broken tests will inevitably be cleaned by an LLM. The more "coverage", the merrier.
My AI code philosophy aligns closely with Nonary's if you want to read more. | Link
These projects don't use semver. There is no API contract to uphold, and I would prefer not assigning bizzare meaning to semvers that would cause them to look like they're flying all over.
The TrayAppDotNET version increments every single release. If an app isn't scheduled to have a new release, or has no changes, the publish workflow will go grab the previous release artifact for that app and re-publish it. This is done so it is consistently simple to download build artifacts.
Note: The installer wrapper has no published version and simply aligns itself with the latest TrayAppDotNET release. Any re-published apps will be packaged into the latest installer release.
All these apps are fully localized with resx, but not currently integrated with any translation providers (Weblate, etc.).
Localization files live in {APP}/src/Localization/Strings.{LANGUAGE}.resx.
There is a localization file for each app, the installer, and one in the Common project for strings shared across all apps.





