---
name: Core process-watch (meeting detection)
type: architecture-map-page
slice: 05-core-storage-hotkey-build
last_verified: 2026/05/09
---

# Core process-watch

> **Where you are:** [Architecture map](../README.md) → [Core, Storage, Hotkey, Build](README.md) → Core process-watch

**Plain English summary.** A lightweight "is the user in a meeting right now?" probe. One signal only: poll the running-process list and match user-editable substrings. No mic-activity heuristic, no calendar integration. If the user has opted in, Lumotia surfaces a non-modal toast so they can choose to start recording. The app never starts recording on its own from this signal.

## At a glance

- File: `crates/core/src/process_watch.rs` (123 LOC, ~40 of which are tests).
- External deps: `sysinfo 0.35`.
- Public surface: `ProcessLister` (struct), `list_running_process_names`, `match_meeting_patterns`.
- Consumers: slice 2 (a Tauri command holds a long-lived `ProcessLister` behind `Mutex` and polls every 15 s).

## What's in here

### `ProcessLister` — `crates/core/src/process_watch.rs:20`

```rust
pub struct ProcessLister { system: System }
impl ProcessLister {
    pub fn new() -> Self;
    pub fn snapshot(&mut self) -> Vec<String>;  // lowercased exe names
}
impl Default for ProcessLister { ... }
```

Reusable wrapper around a `sysinfo::System`. The system is created once with `RefreshKind::nothing().with_processes(ProcessRefreshKind::nothing())` and refreshed in place via `refresh_processes(ProcessesToUpdate::All, true)` on every call. On a busy host (~300 processes), `System::new_with_specifics` followed by `refresh_processes` walks `/proc` cold and costs ~50–100 ms; reusing the same instance reuses sysinfo's per-process bookkeeping so subsequent refreshes are dominated by diffing rather than allocation.

### `list_running_process_names()` — `crates/core/src/process_watch.rs:59`

Convenience function. Allocates a fresh `ProcessLister` per call. Hot paths should hold a long-lived `ProcessLister` instead.

### `match_meeting_patterns(process_names, patterns) -> Vec<String>` — `crates/core/src/process_watch.rs:67`

Pure function. Case-insensitive substring match. Returns the set of patterns that matched at least once, in input order, deduped. Empty / whitespace-only patterns are skipped so a stray blank entry in the user's list never matches everything.

## Data flow / contract

- The slice-2 command holds the `ProcessLister` in `tauri::State` behind a `Mutex` and polls every 15 seconds.
- Pattern list comes from user settings (the UI surfaces it as a comma-separated text box).
- Match output drives a non-modal toast in the frontend. Lumotia never starts recording itself — the user decides.

## Tests

4 tests in `crates/core/src/process_watch.rs:84-122`:

- `matches_are_case_insensitive_substrings` — happy path with `Zoom Meeting` / `Microsoft Teams` / `firefox`.
- `empty_and_whitespace_patterns_are_ignored` — guard.
- `matches_are_deduped` — duplicate `zoom` patterns produce one match.
- `list_running_returns_something_on_this_host` — smoke against the test runner's own process list.

## Watch-outs

- **`refresh_processes(ProcessesToUpdate::All, true)` is still ~5–10 ms even reused.** OK at 15 s cadence, would not be at sub-second cadence.
- **Substring match is broad.** The pattern `"zoom"` matches a process named `zoomies` (a hypothetical screensaver). User-configurable so the user owns the false positive.
- **Lowercased once, searched many times.** `process_names` are lowercased at snapshot time so the matcher itself can stay simple. If we ever want regex matching we will lose this optimisation.
- **Linux / macOS / Windows process-name conventions differ.** `Zoom` on macOS, `zoom` on Linux, `Zoom.exe` on Windows. The case-insensitive substring match handles the case differences cleanly. The `.exe` suffix on Windows is fine because the pattern is a substring.
- **Privacy.** Process names can leak metadata (other apps the user runs). The probe runs locally and the results never leave the machine, but it is a class of data to be careful with.

## See also

- [Hardware probe](core-hardware-probe.md) — sibling probe.
- [Slice 2 Tauri commands](../02-tauri-runtime/README.md) for the meeting-detection command that owns the long-lived `ProcessLister`.
