Skip to content

System tray: minimise/close to tray, start minimised, launch at startup - #11

Merged
FrancescoCoding merged 5 commits into
mainfrom
feat/system-tray
Nov 1, 2026
Merged

FrancescoCoding merged 5 commits into
mainfrom
feat/system-tray

Conversation

@FrancescoCoding

@FrancescoCoding FrancescoCoding commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Closes #10.

What's new

  • Tray icon. Left click shows the window; right click opens Show Throttle / Exit.

  • Settings menu (new) with:

    • Minimise to tray: minimising hides Throttle to the tray instead of the taskbar.
    • Close to tray: the X button hides it to the tray. File > Exit and tray Exit still quit.
    • Start minimised: open in the tray instead of a window.
    • Launch at startup: start Throttle in the tray when you sign in.

    The first three are saved to %APPDATA%\Throttle\settings.json and are all off by default, so nothing changes for existing users until they opt in.

  • Double-clicking throttle.exe while it's hidden brings the existing window back.

How it works

  • Launch at startup uses a Task Scheduler logon task, not the Run registry key: Windows silently skips Run entries for apps that require elevation. The task runs with highest privileges and is started with --tray. It overrides three Task Scheduler defaults that would hurt a background shaper: the 72-hour execution limit (now none), the battery conditions (now off) and below-normal priority (now normal). The task's existence is the source of truth for the setting.
  • Background work moved to App::logic. eframe 0.36 stops calling ui() while the window is hidden, but keeps calling logic(). Snapshot draining, tray events and close handling now live there. Without this the unbounded snapshot channel would have grown by about one snapshot a second while Throttle sat in the tray.
  • A second launch signals the running instance through a named event (Global\ThrottleShowRequest) to show itself, instead of calling ShowWindow from outside. winit caches window visibility, so an outside ShowWindow would leave it unable to hide the window again.
  • New dependency: tray-icon 0.26.1 (no default features).

Testing

  • cargo fmt --check, cargo clippy --all-targets -D warnings, cargo test (29 passing, 4 new for settings and the task XML) and cargo build --release all pass.
  • The generated task XML was imported with schtasks to confirm the settings are accepted, then deleted.
  • Manual test of an elevated release build: minimise to tray, restore from the tray icon, and close to tray all worked. That run showed restoring took two clicks; this is fixed in b37889b, which hasn't had a manual re-test yet.

Also bumps the version to 0.1.3.

Review follow-up (a2a4541)

  • Starting in the tray waits for the taskbar before hiding. tray-icon reports success even when the taskbar doesn't exist yet, so at logon the window could hide with no icon to click. If the taskbar hasn't appeared after a minute, Throttle stays visible.
  • The startup check only re-points the task if the exe it starts no longer exists, so running another copy once doesn't take it over. Each task XML write uses its own temp file.
  • The show event is now created together with the single-instance lock, so a second launch during startup is queued instead of lost. A second launch also raises the error window.
  • A relaunch after an in-app update always shows the window, even with Start minimised on.
  • Not changed: the task's user still comes from USERDOMAIN/USERNAME. Under over-the-shoulder UAC elevation that names the admin account rather than the signed-in user. Using the token's SID would fix it; left for a follow-up.

…tartup

Closes #10.

- Tray icon (tray-icon crate): left click shows the window, right click
  opens Show / Exit.
- Settings menu with Minimise to tray, Close to tray, Start minimised
  (saved to %APPDATA%\Throttle\settings.json, all off by default) and
  Launch at startup.
- Launch at startup registers a Task Scheduler logon task with highest
  privileges, no time limit, normal priority and no battery conditions,
  started with --tray. The Run key cannot start an elevated app.
- Snapshot draining and tray handling move to App::logic, which keeps
  running while the window is hidden, so the unbounded snapshot channel
  does not grow in the background.
- A second launch signals the running instance through a named event to
  show itself, instead of calling ShowWindow from outside, which would
  desync winit's cached visibility.
- File > Exit, tray Exit and the update relaunch always quit, even with
  Close to tray on.
Right after a restore the window still reads as minimised for a frame, so
the minimise check hid it again. Hide only when the window becomes
minimised.
…nd launch

- Starting in the tray waits for the taskbar before hiding: tray-icon
  reports success even when the taskbar does not exist yet, so hiding on
  the first frame at logon could leave no icon to click. Gives up and
  stays visible after a minute.
- The startup check only re-points the task when the exe it starts no
  longer exists, so running another copy once no longer takes it over.
  Each task XML write uses its own temp file.
- The show event is created with the single-instance lock, so a second
  launch during startup is queued instead of lost. The error window is
  raised by a second launch too.
- A relaunch after an in-app update always shows the window, even with
  Start minimised on.
@FrancescoCoding FrancescoCoding added the enhancement New feature or request label Nov 1, 2026
@FrancescoCoding
FrancescoCoding merged commit 0be14e3 into main Nov 1, 2026
8 checks passed
@FrancescoCoding
FrancescoCoding deleted the feat/system-tray branch November 1, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request | Minimize to System Tray

1 participant