A lightweight Windows 11 tray application that displays your next upcoming meeting from a public calendar ICS (.ics) feed.
It shows a dynamic tooltip with the next meeting title and time, and notifies you when the meeting is about to start. Optionally, it shows a hover/preview window with more details.
System tray showing the dynamic tooltip for the next meeting
Context menu with Open Meeting, Refresh, Set Calendar URL, Exit
Hover window showing upcoming meeting details and quick actions
- Tray icon with dynamic tooltip showing:
Next: <Title> (In X min|In Y h|Mon HH:mm) - Auto balloon notification when a meeting is within 15 minutes (once per meeting)
- Context menu: Open Meeting, Refresh, Set Calendar URL, Exit
- Stores configuration in
%APPDATA%/ComingUpNext/config.json - Lightweight ICS parsing (skips malformed events)
- Configurable refresh interval (default 5 minutes) via
RefreshMinutesinconfig.jsonor "Set Refresh Minutes" context menu option - Sound Intro: Optional MP3 announcement that plays before a meeting, timed to end exactly when the meeting starts. Configure via Settings or
SoundIntroPathinconfig.json. Use "Test Sound Intro" from the context menu to verify playback. Playback is automatically suppressed when a microphone is actively in use (e.g. during a Teams or Zoom call) so it won't interrupt ongoing conversations.
Provide a publicly accessible .ics URL (can be from Outlook, Google Calendar, etc.). The app reads VEVENT blocks and uses:
SUMMARY→ Meeting titleDTSTART→ Start time (all-day events treated as midnight local)DTEND→ End time (if missing/invalid defaults to +1 hour)URL→ Meeting link (if present)DESCRIPTION→ Fallback scan for firsthttp/httpslink ifURLabsent (helpful for Teams links embedded in description)
Example snippet:
BEGIN:VEVENT SUMMARY:Daily Standup DTSTART:20251030T090000Z DTEND:20251030T091500Z URL:https://teams.microsoft.com/l/meetup-join/... END:VEVENT
Timezone & recurrence handling:
TZID=parameters onDTSTART/DTENDare respected (converted from that zone to local).- UTC times (
Zsuffix) converted to local. - Floating times (no TZ) treated as local.
- Date-only values (
YYYYMMDD) treated as all-day starting at local midnight. - Simple weekly
RRULEpatterns (e.g.FREQ=WEEKLY;BYDAY=MO,WE;INTERVAL=1;UNTIL=20261102T160000Z) are expanded for upcoming occurrences (up to 3 months lookahead or UNTIL limit) including exclusions via matchingEXDATE. EXDATEentries remove specific occurrences from recurrence expansion.
Limitations: Advanced recurrence (monthly rules, BYSETPOS, COUNT, exceptions with time shifts) is not yet supported.
Requires .NET 9 SDK.
# Build dotnet build # Run (from project directory) cd src/ComingUpNextTray dotnet run
On first run, use the tray icon context menu "Set Calendar URL" to paste the ICS URL. Optionally adjust the refresh interval with "Set Refresh Minutes" (1-1440). The default is 5 minutes. You can force an immediate reload at any time with the "Refresh" context menu item (it disables while fetching to avoid overlapping requests).
Produce a single-file self-contained executable:
dotnet publish src/ComingUpNextTray/ComingUpNextTray.csproj -c Release -r win-x64 --self-contained true /p:PublishSingleFile=true /p:IncludeAllContentForSelfExtract=true -o publish
The output folder publish will contain the EXE. Version metadata (AssemblyVersion/FileVersion/Product Version) is taken from the <Version> property inside ComingUpNextTray.csproj.
An example WiX v5 setup is provided under installer/.
Prerequisites:
- .NET 10 SDK
- WiX Toolset v5 (installed automatically by the build script if missing)
Build the MSI (auto-reads <Version> from the csproj):
cd installer ./build.ps1 -Configuration Release -Runtime win-x64
Result: installer/ComingUpNextTray-X.Y.Z.msi where X.Y.Z is the version from the project file.
To override the version just for a build (without editing the csproj), pass -Version:
./build.ps1 -Configuration Release -Runtime win-x64 -Version 1.2.3
This stamps the EXE (assembly/file/product) and the MSI Package version consistently.
Adjustments:
- Update GUID placeholders in
installer/Product.wxs(generate new GUIDs viaNew-Guid). - Change
Manufacturer,Version, or add more components (e.g., config file defaults). - To auto-start for current user you can add a shortcut component pointing to Startup folder or set Run key (advanced customization).
Silent install example:
msiexec /i ComingUpNextTray-1.0.0.msi /qn
Uninstall:
msiexec /x ComingUpNextTray-1.0.0.msi /qn
If you modify published output location or add resources, re-run the build script to regenerate harvested components.
This repository includes two small CLI tools under the tools/ folder useful for debugging and reproducing the app's behavior against .ics feeds.
-
CalendarInspector (
tools/CalendarInspector)- Purpose: Download or read an ICS payload and print diagnostic information including raw
VEVENTblocks, parsed entries and a recurrence expansion log. Useful to inspect what the parser actually sees and how recurrences expand. - Usage (download a URL or read a file):
# URL (will attempt to GET the feed) dotnet run --project "tools\CalendarInspector\CalendarInspector.csproj" "https://example.com/calendar.ics" # Local file dotnet run --project "tools\CalendarInspector\CalendarInspector.csproj" "C:\path\to\calendar.ics"
- Notes: The inspector prints raw VEVENT blocks and a summary of parsed entries; it does not apply the app's next-meeting selection rules (it's focused on parsing diagnostics).
- Purpose: Download or read an ICS payload and print diagnostic information including raw
-
NextMeetingInspector (
tools/NextMeetingInspector)- Purpose: Mimics the tray application's selection logic and prints the single next meeting chosen by the app. It reuses
CalendarServiceandNextMeetingSelectorso behavior should match the running tray app closely. - Usage (URL-only):
dotnet run --project "tools\NextMeetingInspector\NextMeetingInspector.csproj" "https://example.com/calendar.ics" [ignoreFreeOrFollowing:true|false]
- The
ignoreFreeOrFollowingflag is optional and defaults totrue(matches the app's default behavior).
- The
- Notes and troubleshooting:
- The tool expects an absolute
httporhttpsURL. It uses the same HTTP fetch logic as the tray app (conditional requests, ETag/Last-Modified handling) where applicable. - Some calendar providers (Exchange/Office365) may require a public/share link or authentication; if you get
HTTP 401/403orHTTP 400, verify the URL is a public ICS export link or supply an appropriate authenticated feed. - The tool injects a browser-like
User-Agentheader to improve compatibility with servers that reject non-browser clients.
- The tool expects an absolute
- Purpose: Mimics the tray application's selection logic and prints the single next meeting chosen by the app. It reuses
Create a shortcut to the published EXE in:
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup
Settings are stored in %APPDATA%/ComingUpNext/config.json:
| Key | Type | Default | Description |
|---|---|---|---|
CalendarUrl |
string | "" |
ICS feed URL |
RefreshMinutes |
int | 5 |
Refresh interval (1–1440) |
ShowHoverWindow |
bool | true |
Show the floating hover window |
IgnoreFreeOrFollowing |
bool | true |
Skip meetings marked as Free or Following |
SoundIntroPath |
string | null |
Path to an MP3 file played before each meeting. The app detects the MP3 duration and starts playback so it ends at the meeting start time. Leave empty to disable. |
HoverWindowLeft |
int | null |
Saved hover window X position |
HoverWindowTop |
int | null |
Saved hover window Y position |
- Add Windows Toast notifications using Windows App SDK for richer UX
- Optional filtering (e.g. ignore all-day events or past events spanning multiple days)
See LICENSE.