DirMon.exe — feature documentation
Directory Monitor
DirMon v2.0 for Windows
DirMon watches one directory — and everything beneath it — and records file
events the moment they happen: additions, removals, modifications, and renames.
Changes arrive in real time through the Windows ReadDirectoryChangesW
API rather than polling, and a rule-based filter engine shapes what you see in a
50,000-entry log.
- Windows
- Win32 + DirectX 11
- Dear ImGui interface
- Real-time — no polling
1 watched directory (always recursive) · 4 event types · 6 filter operators · 50,000-entry log
At a glance
The essentials in one place.
- Monitoring scope
- One directory at a time; subdirectories are always included.
- Capture method
ReadDirectoryChangesW, real time — the disk is not polled.- Change mask
- File name, directory name, and last write (fixed).
- Event types
- Added, Removed, Modified, Renamed — plus gray system rows.
- Log capacity
- 50,000 entries in a ring buffer, oldest-first eviction.
- Event queue
- 8,192 entries; overflow coalesces into a single marker row.
- Filtering
- Include / Exclude / Highlight rules with 6 operators; live re-projection.
- Find
- Case-insensitive substring over visible rows; wraps around.
- Export
- UTF-8 text with BOM, CRLF line endings — the visible rows.
- Persistence
settings.jsonbeside the executable: rules + UI state.- Startup
- Nothing auto-starts; monitoring begins when you press Start.
- Predecessor
- DirMon v1.1 dialog app, kept in the same solution.
DirMon is a native Windows application: a Win32 host with a DirectX 11 renderer and a Dear ImGui interface. It is the modern successor to the legacy DirMon v1.1 dialog app, replacing the old hard-coded “smart filter” with a general rule-based filter engine (see Filter rules and About and legacy).
Main window tour
Everything lives in a single full-viewport window. From top to bottom: toolbar, find strip, banners, allowlist badge, filter editor, log list, status bar.
1 · Toolbar
Icon buttons, 48 px at the default font size — they scale with font zoom. Left to right:
- Start / Stop monitoring — a green play icon while stopped, a red stop icon while running. Starting opens the folder picker.
- Export — save the log to a text file (see Working with the log).
- Clear all logs — clears the log after a confirmation dialog.
- Added, Removed, Modified, Renamed — event-type toggles. A pushed, blue button means the type is on.
- Find — opens the find strip.
- Always on top — keeps the window above other applications; the setting persists.
- Zoom out · current font size · Zoom in — the base font adjusts from 12 to 28 px in steps of 2; the toolbar scales with it. The zoom level is session-only and is not written to settings.
- About — opens the About dialog.
2 · Find bar
A modeless strip below the toolbar: a search input (hint “Find in log…”), Next (↓), Previous (↑), and Close (×), plus a “(no match)” hint when the query finds nothing. Details under Working with the log.
3 · Banner area
Yellow, at-most-one-line messages about settings persistence:
- If
settings.jsoncannot be written: “WARNING: could not write settings.json (read-only media?). Settings will not persist for this session.” - If
settings.jsonwas corrupt or has an unsupported version: “Notice: settings.json was corrupt — it was renamed to settings.broken.json and defaults were loaded.”, with a Dismiss button.
4 · Allowlist badge
Shown whenever at least one Include rule is enabled: “Allowlist active: N include rules” plus a Disable all includes button that switches every Include rule off in one click — the quickest way out of an accidental allowlist. See Filter rules.
5 · Filter rules editor
A collapsible “Filter rules (N)” header, where N is the total rule count. Add Rule appends a new rule that defaults to Exclude with the is operator. Each rule row has: an enabled checkbox, the Action combo (Include / Exclude / Highlight), the Operator combo, a pattern text box, and a delete button (X). While an allowlist is active, enabled Exclude rules carry a gray “(shadowed)” tag — explained in Filter rules.
6 · Log list
A scrolling table with four columns — type icon, Time, Path, Note. The header row stays frozen while rows scroll, columns can be resized by dragging, and rendering is virtualized, so a full 50,000-entry log stays responsive. When there is nothing to show: “No log entries (toggle a type and Start a watch).” Left-click selects a row; right-click opens its context menu. Hovering a truncated path shows the full text.
7 · Status bar
A legend of the four event types — the same shapes and colors as the log rows — followed by counters. Full reference in Status bar reference.
Dialogs and menus
- About dialog — “DirMon v2.0”, “Directory Monitor”, “Copyright (C) 2003-2026 epocsoft.com”, and a link to https://www.epocsoft.com that opens in the default browser. OK or Esc closes it.
- Clear confirmation — “Clear all log entries?” with the stored count, “This cannot be undone.”, and a “Don’t show again this session” checkbox. With the checkbox ticked, later clears from the toolbar skip the dialog for the rest of the session.
-
Row context menu (right-click on a file row) —
Include <path>,Exclude <path>,Highlight <path>,Clear all logs,Copy this line. Each of the first three items adds an enabled is rule for that row’s full path. Note that the context menu’s Clear all logs clears immediately, without the confirmation dialog.
Monitoring directories
One directory at a time, watched recursively, in real time. Monitoring starts and stops on your command — never on its own.
Starting a watch
- Press the toolbar Start button or Ctrl+O. Any running watch stops first, a folder picker opens, and the chosen directory is watched immediately.
- Monitoring never starts automatically on launch. Even with a restored last directory, nothing runs until you press Start.
- The four event-type toggles do not change what the watcher asks Windows for — everything arrives, and events of a switched-off type are dropped and counted (see By-toggle in Status bar reference).
If you cancel the folder picker while a previous directory is remembered, the watch still starts — on that remembered directory. On a first run, with nothing remembered, canceling the picker aborts the start.
What gets watched
- Exactly one directory at a time. Starting a new watch replaces the old one.
- Recursion into subdirectories is always on — there is no non-recursive mode.
-
The change mask is fixed: file name, directory name, and last write — in API
terms,
FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE. In practice: files and folders being created, deleted, or renamed, and files being written to.
Stopping
- Toolbar Stop or Shift+F5.
- Stopping does not clear the log — entries stay for review and export. Clearing is a separate action.
- There is no pause or resume; a watch is either running or stopped.
Queue and overflow
Events flow from the watcher thread through an internal queue of 8,192 entries into the log. If the queue saturates — a burst faster than the UI can drain — the incoming event is dropped and counted, the oldest queued item is discarded, and a single gray Queue overflow system row records it. Whenever the dropped count is above zero, the status bar shows “(queue overflow dropped N)” in red.
Event types and colors
Every log row carries a color, a shape, and — in exports and clipboard text — a one-character marker.
| Marker | Icon | Type | Color | What it means |
|---|---|---|---|---|
| + | Added | #4CD959 rgb(76,217,89) |
A file or folder was created under the watched directory. | |
| – | Removed | #F24D4D rgb(242,77,77) |
A file or folder was deleted. | |
| * | Modified | #FAA61A rgb(250,166,26) |
A file’s contents changed (last-write notification). | |
| ! | Renamed | #33BFE6 rgb(51,191,230) |
A file or folder was renamed — Path holds the old name, Note the new one. | |
| # | System | #A6A6A6 rgb(166,166,166) |
Watch started, Watch stopped, Watch error, and Queue overflow rows. |
- Timestamps are local time, in
HH:MM:SS.mmmformat. - System rows — Watch started, Watch stopped, and Watch error show the watched directory in the Path column; a Watch error also carries the Windows error number. System rows are never deduplicated.
-
The highlight effect — a matching Highlight rule tints the entire row
a warm yellow,
rgba(255,217,26,0.35), with bold text. The event color is preserved; highlight carries no visibility meaning (see Filter rules).
For the source-minded: the renderer stores these as ImGui float colors — Added (0.30, 0.85, 0.35), Removed (0.95, 0.30, 0.30), Modified (0.98, 0.65, 0.10), Renamed (0.20, 0.75, 0.90), System (0.65, 0.65, 0.65).
A realistic sample
| Time | Path | Note | |
|---|---|---|---|
| 09:41:02.317 | C:\REPO\SRC | ||
| 09:41:02.331 | C:\REPO\SRC\main.cpp | ||
| 09:41:02.940 | C:\REPO\SRC\main.cpp | ||
| 09:41:05.840 | C:\REPO\SRC\old.cpp | C:\REPO\SRC\new.cpp | |
| 09:41:07.102 | C:\REPO\SRC\temp.txt | ||
| 09:41:12.002 | C:\REPO\SRC |
Illustrative rows, colors and column meanings exactly as in the app. The first and last rows are Watch started / Watch stopped system rows (the Path column carries the watched directory). The highlighted row shows the effect of a Highlight rule: warm-yellow tint, bold text, event color preserved. A rename shows the old path under Path and the new path under Note.
Filter rules
A rule-based filter engine replaces the legacy “smart filter”. Rules are matched live against the stored log — the view re-projects the moment anything changes, with no watch restart.
Anatomy of a rule
Every rule is one flat object with four parts:
- Action — Include, Exclude, or Highlight: what happens on a match.
- Operator — how the pattern is compared to the path.
- Pattern — the text to match.
- Enabled — a per-rule switch; disabled rules are ignored entirely.
Add Rule in the editor appends a new rule that defaults to
Exclude with the is operator. Rules live in
settings.json and persist between sessions
(see Settings reference).
How patterns match
- Patterns are plain text — no wildcards, no regular expressions.
- Matching is case-insensitive and runs against the entry’s full absolute path.
- A renamed entry matches if either its old or its new path matches.
- An empty pattern matches nothing. An empty rule set shows everything.
- Rule order is irrelevant to the outcome — visibility does not depend on position in the list.
Actions
| Action | Effect on a matching row |
|---|---|
| Include | Admits the row. While any Include rule is enabled, only rows an Include matches are visible (allowlist mode, below). |
| Exclude | Hides the row — unless an allowlist is active, in which case it has no effect (shadowed). |
| Highlight | Tints the row warm yellow with bold text. Never affects visibility — a Highlight match cannot reveal a hidden row. |
Operators
All six compare the pattern against the full absolute path, case-insensitively:
| Operator | Matches when | Example |
|---|---|---|
| is | The path equals the pattern exactly. | c:\repo\src\main.cpp |
| is not | The path is anything else. | — |
| begins with | The path starts with the pattern. | C:\REPO\ |
| ends with | The path ends with the pattern. | .cpp |
| contains | The pattern appears somewhere inside the path. | \SRC\ |
| excludes | The pattern appears nowhere in the path — the opposite of contains. | \\vendor\\ |
Allowlist semantics
The filter runs in one of two modes, decided by whether any Include rule is enabled:
- Allowlist off (no enabled Include rules): every row is visible unless an Exclude rule matches it.
- Allowlist on (at least one enabled Include rule): only rows that an Include matches are visible. Exclude rules then hide nothing at all — they are tagged “(shadowed)” in the rules list, and the allowlist badge appears above the editor with a Disable all includes shortcut. Include wins any direct conflict; the shadow tag records exactly that.
- Highlight is orthogonal: it tints rows that are already visible and never reveals a hidden one.
Any rule edit — add, change, toggle, delete — re-projects the entire stored log instantly (up to 50,000 entries), and incoming events follow the same rules. No watch restart is needed.
Quick rules from a row
Right-click a file row and pick Include <path>,
Exclude <path>, or Highlight <path> to add an
enabled is rule on that row’s full path — precise, and a noisy path
is silenced in two clicks. Rename rows create the rule on the new path.
Worked example
These are the three rules from the settings example. Rule 1 admits anything under the repo’s source folder; rule 2 is a vendor exclusion; rule 3 tints one specific file:
- Include · contains ·
C:\REPO\SRC\ - Exclude · contains ·
\\vendor\\ - Highlight · is ·
c:\repo\src\main.cpp
Rule 1 is enabled, so an allowlist is active. Walking a few events through the engine:
| Event | Path | Result | Why |
|---|---|---|---|
| Added | C:\REPO\SRC\main.cpp | Visible · highlighted | Matches Include 1 (contains C:\REPO\SRC\); Highlight 3 matches it exactly (is, case-insensitive). |
| Added | C:\REPO\SRC\vendor\lib.cpp | Visible | Include 1 admits it. Exclude 2 also matches — but while the allowlist is active, an exclude hides nothing; the rule is tagged (shadowed). |
| Added | C:\REPO\DOC\readme.md | Hidden | No Include rule matches — the allowlist admits nothing else. |
| Renamed | C:\REPO\TMP\a.txt → C:\REPO\SRC\b.txt | Visible | The new path matches Include 1 — a rename matches when either path matches. |
| Modified | C:\OTHER\main.cpp | Hidden | No Include rule matches — and a Highlight match alone could not reveal it. |
Disable Include 1 — or click Disable all includes in the badge —
and the allowlist lifts: every row becomes visible except matches of
Exclude 2, which starts hiding \\vendor\\ paths again.
Working with the log
A scrolling, resizable, virtualized table of everything the watch captured — plus the tools to search, copy, and export it.
Columns
| Column | Width | Contents |
|---|---|---|
| (type icon) | Fixed, narrow | The colored shape marking the event type (see Event types and colors). Hovering it names the type. |
| Time | Fixed | Local timestamp, HH:MM:SS.mmm. |
| Path | Stretches 2× | The full path — for renames, the old path. Hover shows the full text when clipped. |
| Note | Stretches 1× | Empty for most rows; the new path for renames. |
The header row stays frozen while rows scroll, columns can be resized by dragging their edges, and rendering is virtualized — only visible rows are drawn, so a full log stays smooth. When empty, the list reads: “No log entries (toggle a type and Start a watch).”
Storage: ring buffer and dedupe
- The log holds up to 50,000 entries in a ring buffer. When it fills, the oldest entry is evicted first. Evicted entries are counted and cannot be brought back by re-filtering.
- Consecutive identical file events — same type, same path, same old path — are collapsed into a single row. System rows are never deduplicated.
- The evicted and deduped counters are lifetime totals: they survive Clear. The per-type counters do not — see Status bar reference.
Find
- Open with Ctrl+F or the toolbar Find button; close with Esc or the × button. The strip is modeless — it stays open while you work with the log.
- Case-insensitive substring match over the visible rows’ paths. A rename also matches on its old path.
- Enter or Next (↓) jumps forward; Previous (↑) jumps back. The search wraps around at the ends.
- A hit is selected and scrolled to the center of the list. A miss shows “(no match)” beside the input.
Context menu and copy
Right-click a file row for Include <path>,
Exclude <path>, Highlight <path> (each adds an
enabled is rule on the full path — renames use the new path),
Clear all logs, and Copy this line.
Copy this line puts the row on the clipboard as
HH:MM:SS.mmm path — a rename copies
old -> new.
Export
- Toolbar Export opens a save dialog with the default name
DirMon-Export.txtand a Text files (*.txt) filter. - Files are written as UTF-8 with a BOM and CRLF line endings, so non-ASCII paths survive intact.
- Export writes the currently visible rows — the same rows the log list shows after filter rules and type toggles.
- Line format:
HH:MM:SS.mmm <marker> path. Renames export asold -> new; Watch error rows append the Windows error number as(error N).
09:41:02.331 + C:\REPO\SRC\main.cpp
09:41:02.940 * C:\REPO\SRC\main.cpp
09:41:05.840 ! C:\REPO\SRC\old.cpp -> C:\REPO\SRC\new.cpp
09:41:07.102 - C:\REPO\SRC\temp.txt
09:41:09.550 # C:\REPO\SRC
Example lines; system rows (Watch started / stopped / error, Queue overflow) use the
# marker and carry the watched directory as their path.
Status bar reference
The legend at the left repeats the log row icons and colors, one per event type, each followed by its count; the rest of the bar summarizes storage and filtering.
Format
Added N Removed N Modified N Renamed N | Hidden N By-toggle N | Stored N (capacity 50000, evicted N, deduped N) | Rules N
With data in it, the same bar reads, for example:
Added 12 Removed 3 Modified 47 Renamed 2 | Hidden 5 By-toggle 18 | Stored 64 (capacity 50000, evicted 0, deduped 9) | Rules 3
Whenever the internal queue has overflowed at least once, a red “(queue overflow dropped N)” is appended after the counters.
Counters
| Segment | Meaning |
|---|---|
| Added / Removed / Modified / Renamed | Events counted per type since the last Clear. A switched-off type’s events are not counted here — they land in By-toggle. |
| Hidden | Stored entries not currently visible — hidden by filter rules and/or a switched-off type toggle. |
| By-toggle | Events dropped at capture time because their type toggle was switched off. |
| Stored | Entries currently held in the ring buffer. |
| capacity 50000 | The ring buffer’s size. |
| evicted | Entries dropped by oldest-first eviction. Lifetime total — survives Clear. |
| deduped | Consecutive identical file events collapsed. Lifetime total — survives Clear. |
| Rules | Number of filter rules, enabled or not. |
Clear resets the per-type counts, Hidden, and Stored — but not evicted or deduped, which are lifetime totals. The type toggles count double duty: they gate newly captured events (By-toggle) and hide already-stored rows of that type when the view is re-projected (Hidden).
Keyboard shortcuts
Everything important is one chord away.
| Shortcut | Action |
|---|---|
| Ctrl+F | Open the Find strip and focus the input. |
| Ctrl+O | Start monitoring — opens the folder picker. |
| Shift+F5 | Stop monitoring. |
| Enter | In the find field: jump to the next match. |
| Esc | Close the Find strip, the About dialog, or the Clear confirmation. |
While the About or Clear-log dialog is open, the shortcuts yield to the dialog. Font zoom is mouse-only: the toolbar buttons adjust the base font from 12 to 28 px in steps of 2, session-only — the size is not written to settings.
Settings reference
One JSON file — settings.json, stored beside
DirMon.exe — holds the filter rules and the UI state.
Schema version 1.
Example file
{
"version": 1,
"rules": [
{ "action": "include", "op": "contains", "pattern": "C:\\REPO\\SRC\\", "enabled": true },
{ "action": "exclude", "op": "contains", "pattern": "\\\\vendor\\\\", "enabled": true },
{ "action": "highlight", "op": "is", "pattern": "c:\\repo\\src\\main.cpp", "enabled": true }
],
"ui": { "alwaysOnTop": false, "lastDir": "", "watchAdded": true, "watchModified": false,
"watchRemoved": true, "watchRenamed": true, "windowH": 640, "windowW": 1024 }
}
Backslashes are escaped JSON-style — a single \ in the editor is
\\ in the file. The three rules above are walked through in the
worked example.
Keys
| Key | Meaning |
|---|---|
| version | Schema version. Must be 1; anything else and the file is treated as corrupt. |
| rules | Array of filter rules — one flat object each. Order is irrelevant to outcomes. |
| rules[].action | include, exclude, or highlight. |
| rules[].op | is, is_not, begins_with, ends_with, contains, or excludes — the same six operators the UI shows, spelled with underscores in the file. |
| rules[].pattern | Plain text, matched case-insensitively against full absolute paths. |
| rules[].enabled | The per-rule switch. |
| ui.alwaysOnTop | The always-on-top toggle. |
| ui.lastDir | The last watched directory, used when starting a watch. |
| ui.watchAdded / watchRemoved / watchModified / watchRenamed | Event-type toggle states. They gate newly captured events and hide already-stored rows of that type when the view is re-projected. |
| ui.windowW / windowH | Window size in pixels, clamped to [200, 32767] on load. |
Behavior
- Loaded at startup. A missing file simply means defaults.
- A corrupt file — or an unsupported version — is renamed to
settings.broken.json, defaults load, and a dismissible banner explains what happened. - Unknown keys are ignored.
- Saves are debounced: about one second after the last change, plus a synchronous flush on exit.
- If the write fails — read-only media, permissions — the session continues in memory and a yellow banner warns that settings will not persist.
About and legacy
The About dialog (toolbar About) shows the application icon, the “DirMon v2.0” title, “Directory Monitor”, “Copyright (C) 2003-2026 epocsoft.com”, and a link to https://www.epocsoft.com that opens in the default browser. OK or Esc closes it.
Legacy note
DirMon is the modern successor to the legacy DirMon v1.1 dialog application, which remains in the same solution untouched. The old app’s “smart filter” — a hard-coded ignore list for system files — and its “Ignore file with extensions” checkbox are gone, replaced by the general rule-based filter engine: any path can be included, excluded, or highlighted, and the rules persist in settings.json. Both apps share the same watcher backend, so event behavior is identical.
