IdleLauncherTray is a portable Windows tray utility that waits for the machine to become genuinely idle and then launches a selected target.
It is a Windows GUI exe (no console) that:
- Starts in the system tray
- Tracks physical keyboard/mouse idle time and ignores injected automation input such as
SendKeys - Optionally blocks injected input while the launched target is tracked as running
- Optionally counts XInput gamepad activity as user activity
- Launches a chosen .exe, .scr, .bat, .cmd, .lnk, .msi, .ps1, .vbs, .jar, or .py target once both conditions are met:
- Input idle is greater than or equal to the configured number of minutes
- Total CPU usage is less than or equal to the configured threshold (10% to 50%)
- Stores settings in
%APPDATA%\IdleLauncherTray\config.json - Writes logs to
%APPDATA%\IdleLauncherTray\IdleLauncherTray.log - Supports optional Run at startup via
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
This project is intentionally portable:
- The app runs from wherever you place the executable
- It does not copy itself into
%APPDATA% - If Run at startup is enabled, the registry entry points to the current executable path
That means startup is only valid as long as the executable remains at the same path. If you move, rename, or replace the portable build, toggle Run at startup off and back on so the registry entry is refreshed.
Version 2.4.0 is a production-hardening pass that closes 12 code-review findings with zero new dependencies and no behavioral change to the happy path (verified with a clean dotnet build and a single-file publish smoke test that still produces exactly one IdleLauncherTray.exe). Project version metadata is now 2.4.0.
Medium-severity fixes:
- The
TrayAppContextconstructor now wraps its entire init body in try/catch and shuts down cleanly on failure, so a throwing init step can no longer leak the tray icon, menu, hooks, or CPU monitor - Tray-icon swaps now detach
NotifyIcon.Iconbefore disposing the previousIcon, preventing a repaint from hitting a disposed icon ConfigManager.Savedeletes its.tmpstaging file in afinallyblock so a crash mid-save can no longer orphan it- All state-mutating tray-menu handlers are wrapped so an unexpected exception logs cleanly and surfaces a single dialog instead of escaping to the WinForms message pump
- The self-delete safety check now requires the cleanup path to exactly match the app's base directory (defense in depth for the
--cleanup-folderpath) - Automatic idle-triggered launches now retry once on transient failure (e.g. an AV scanner briefly holding the target file) before disarming
Low-severity fixes:
- The CPU monitor detects FILETIME counter regression / system-clock jumps and re-baselines instead of returning a garbage reading from unsigned underflow
- Workstation-lock failures now log the translated Win32 message (e.g. "A required privilege is not held by the client") instead of a bare error code
- Deferred folder cleanup now logs the spawned child PID, and the child logs the eventual delete result, so silent cleanup failures show up in the log
- Magic menu-option arrays and balloon-tip length limits were extracted to named constants/fields
LaunchEvaluationbecame a sealedrecordfor auto equality/hash/ToString- The logger caches its resolved log path instead of recomputing it on every write
Version 2.3 carries forward the v2.2 hardening work and adds the following production-readiness changes:
- Automatic idle-triggered launch failures no longer show blocking modal error dialogs
- Failed automatic launches now log, show a tray notification, and disarm until fresh user activity begins a new idle episode
- The system idle fail-safe window now defaults to
6000 msso it matches the 5-second monitor cadence - When keyboard or mouse hook installation is degraded, Windows-reported idle is used conservatively to avoid false idle launches while the user is still active
- Missing low-level hooks are now retried opportunistically during runtime instead of staying in a partial-install state forever
- Hook callback exceptions now fail open and are logged once instead of risking unstable callback behavior
- Target paths are normalized to canonical full paths (including environment-variable expansion) while preserving UNC/network-path compatibility
- Supported launch targets now include
.cmd,.lnk,.msi,.ps1,.vbs,.jar, and.pyin addition to.exe,.scr, and.bat - Uninstall now uses an internal deferred cleanup helper instead of a shell-built
cmd.exe /cdelete command - Includes a Visual Studio publish profile for a framework-dependent single-file
win-x64publish - Updates project version metadata to
2.3.0
The tray menu exposes:
- Run at startup
- Idle timer
- CPU Threshold
- Application
- Choose
.exe,.scr,.bat,.cmd,.lnk,.msi,.ps1,.vbs,.jar, or.py - Set optional launch arguments
- Choose
- Options
- Block injected input while running
- Lock PC on App Close
- Count gamepad input as activity
- Choose / enable / reset custom tray icon
- Run Now
- Uninstall (remove settings + startup)
- Exit
IdleLauncherTray starts targets with Windows shell execution, so Windows handles each file the same way it would from Explorer or the Run dialog. This is what allows shortcuts, installers, scripts, Java archives, and Python files to launch through their registered file associations.
Supported target extensions:
.exe.scr.bat.cmd.lnk.msi.ps1.vbs.jar.py
Optional arguments are passed to the selected target. Screensaver targets (.scr) automatically receive /s before any user-provided arguments so they start full-screen.
IdleLauncherTray tracks the immediate process handle returned by Windows. Direct long-running .exe and .scr targets usually provide the most reliable tracking.
Some supported target types may be short-lived or may launch through another host process:
.batand.cmdtargets are tracked through the command shell process that Windows starts..lnktargets depend on the shortcut target and how Windows resolves it..msitargets may hand off to Windows Installer, request elevation, or return before installation UI closes..ps1,.vbs,.jar, and.pytargets depend on their registered host applications, such as PowerShell, Windows Script Host, Java, or Python.
If the immediate process exits quickly after launching a child process, IdleLauncherTray treats the target as closed. That affects features tied to process lifetime, including Block injected input while running and Lock PC on App Close.
When Options -> Lock PC on App Close is enabled, IdleLauncherTray calls the Windows workstation lock API after the tracked process for an idle-triggered launch exits.
This is intentionally limited to targets launched automatically by the idle trigger. A manual Run Now launch does not lock the PC on close.
Because the app tracks the immediate process returned by Windows, launcher stubs, shortcuts, scripts, installers, or batch files that spawn a child process and exit immediately may not behave exactly like a long-running direct .exe or .scr. In those cases the workstation lock is based on the tracked process handle that was actually returned.
The log file is stored at:
%APPDATA%\IdleLauncherTray\IdleLauncherTray.log
The log captures operational details that are useful when diagnosing why a launch did or did not happen, including idle readiness transitions, hook health, automatic-launch failure handling, and workstation lock attempts after idle-triggered app exits.
- Open
IdleLauncherTray.slnin Visual Studio. - Build the solution.
- Run the executable.
- You should see the tray icon.
The tray menu entry Uninstall (remove settings + startup) removes:
- the startup registry value, if present
%APPDATA%\IdleLauncherTray\contents such as config, logs, and optional custom tray icon
Because the application is portable, Uninstall does not delete the portable executable itself.
This source package includes a ready-to-use Visual Studio publish profile:
IdleLauncherTray\Properties\PublishProfiles\IdleLauncherTray_v2_3_FrameworkDependent_SingleExe.pubxml
That profile publishes a:
- single-file Windows executable
- framework-dependent deployment (
.NET 10must already be installed on the target machine) win-x64build- with single-file compression intentionally disabled, because .NET only supports bundle compression for self-contained publishes
In Visual Studio:
- Right click the project.
- Choose Publish.
- Select the included
IdleLauncherTray_v2_3_FrameworkDependent_SingleExeprofile. - Publish the project.
From the repository root:
dotnet publish IdleLauncherTray/IdleLauncherTray.csproj \
-c Release \
-r win-x64 \
--self-contained false \
-p:PublishSingleFile=true \
-p:UseAppHost=true \
-p:EnableCompressionInSingleFile=false \
-p:PublishReadyToRun=false \
-p:PublishTrimmed=false \
-p:DebugType=embedded \
-p:DebugSymbols=true \
-o publish/IdleLauncherTray-v2.4.0-win-x64-framework-dependent-singlefileThe resulting release is a portable framework-dependent Windows executable. Target machines must already have the .NET 10 desktop runtime installed.