Skip to content

Notifications from a Windows machine - #115

Closed
puchedev wants to merge 13 commits into
makefunstuff:mainfrom
puchedev:windows-notifications
Closed

puchedev wants to merge 13 commits into
makefunstuff:mainfrom
puchedev:windows-notifications

Conversation

@puchedev

@puchedev puchedev commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Notifications while the app is closed now come from a Windows PC: herdr-push's watcher runs there, reads herdr-core, the PC's RAM, CPU and drives, and whether someone is at it.

Builds on #108 (Windows machines and pair, herdr-push 12); its commits are the first ones here until that one is merged. The app installs herdr-push on a Windows PC through #110 (the Python lookup), since python3 there is usually the Microsoft Store's stand-in.

What changes

All in host/herdr-push/herdr-push, behind WINDOWS = os.name == "nt"; Linux and macOS take the code they took before. Version 13.

  • herdr-core: through herdr-core connect (JSON-RPC on stdin/stdout, as the app does), only where herdr-core server status says a core runs, so it never starts one.
  • The machine: RAM and swap (GlobalMemoryStatusEx and the page files), CPU (GetSystemTimes), each fixed drive as /c, /d (GetDiskFreeSpaceExW), and the processes using the most memory (PowerShell CIM).
  • Presence: seconds since the last keyboard or mouse input (GetLastInputInfo), and whether the session is locked (its own flag from WTSQuerySessionInformationW).
  • Start-up without administrator rights: a Run value under HKCU (pythonw, so no window) starts it at sign-in, and a scheduled task of the user's, with no time limit, which install and ensure run so it starts in the desktop session, never as a child of the SSH session. ensure also mends a stale task; uninstall removes both; status says "interactive": false when nobody is signed in.
  • Locks with msvcrt where there is no fcntl; with pythonw the log goes to the log file.
  • README and host/herdr-push/README.md: what works on Windows, and that the PC has to be awake and signed in.

Checks

  • ./gradlew -p core test: 609 tests. I ran them on Windows, where 52 tests already fail on main (Java's quoting of sh -c, the Microsoft Store's python3); the same ones fail here and no others; DictationTest's timing test failed once under load and passes on its own. Linux CI is the real check (the herdr-push tests run its Linux paths).
  • By hand on Windows 11 (Python 3.13, herdr-core 0.1.18) with a phone running ntfy: the app installed herdr-push itself and the watcher started in the desktop session; with the app closed, the test push, an agent finishing and a low-RAM warning arrived; locking the PC (Win+L) made pushes ring and using it kept them quiet; ensure twice starts one watcher; uninstall removes the Run value and the task.
  • python herdr-push once --dry-run on the PC runs clean.

Not yet covered by automated tests: the Windows-only helpers (ctypes calls, the task's XML, the CIM parsing). They can be split into pure functions and tested on Linux CI in a follow-up if you'd like.

🤖 Generated with Claude Code

puchedev and others added 13 commits November 7, 2026 21:09
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tailscale's installer adds its folder to the system PATH, but sshd and every
shell already open keep the old PATH until restarted, so `pair` missed the
Tailscale name and address and the code carried only the local network one.
Fall back to %ProgramFiles%\Tailscale\tailscale.exe on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pairing screen and the README say Windows' interpreter is usually
python, and the README's Windows paragraph names what herdr-core gives
there rather than a list of screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HcPipe talks to `herdr-core connect` over its stdin and stdout like HerdrCore does over the
socket, with a 3 s deadline on every answer; core_look uses it on Windows once `server status`
says a core runs. `once` now runs there. Version 13.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
read_memory uses GlobalMemoryStatusEx (swap from the page files), read_cpu
GetSystemTimes, read_disks each fixed drive as /c, /d, and top_memory the
working set of each process from CIM through PowerShell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
typed_at reads the session's last keyboard or mouse input (GetLastInputInfo
against GetTickCount64, modulo 2**32), and screen_unlocked is False while
LogonUI.exe runs in this session or the input desktop can't be opened.
Both only mean something in the interactive session. `presence` now runs
on Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watcher runs in the signed-in user's own desktop session: install writes
the HKCU Run value and a per-user scheduled task (/it, no elevation), and
install and ensure start it through that task, never as a child of SSH. Locks
use a byte of the lock file far from the pid (Windows locks are mandatory),
run logs to the log file under pythonw.exe, status says whether a session is
interactive, and tick closes its herdr-core connections in a finally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scheduled task is created from an XML (schtasks /create /xml) instead of
the bare command line: a task made that way stops after 72 hours and doesn't
run on battery, which would kill a watcher started through it. The XML has no
time limit, no battery rules and no trigger (the Run value starts it at
sign-in, the task only runs on demand), and still runs as the current user
with no elevation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ensure recreates the task (and the Run value, when sign-in autostart is
installed) before starting the watcher; install writes the Run value first
and survives a failed task; write_json retries a busy target on Windows;
install starts the task again if the stopped watcher made it ignore the
first start; a failed temp-file delete no longer fails task creation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flag

While the PC was locked, presence said it was unlocked: LogonUI.exe is a SYSTEM
process, so ProcessIdToSessionId is refused for it, and the input desktop can
still be opened on the lock screen. The session's lock flag from the terminal
service (WTSSessionInfoEx) is asked first now, and the process list comes from
WTSEnumerateProcessesW, which gives every process its session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s kill can't hang the watcher

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
makefunstuff added a commit that referenced this pull request Nov 8, 2026
puchedev's #115, merged onto main after #108 (#117).

Co-authored-by: Manuel Puche <229537713+puchedev@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@makefunstuff

Copy link
Copy Markdown
Owner

Merged through #120, thank you @puchedev! A Windows PC now sends notifications like any other machine: herdr-core's agents, RAM, CPU, drives and whether someone is at the PC, all without admin rights. #108 was already in, so I merged main into it on our side; you're co-author of the squash. Thanks for the whole Windows series, it's a big step for the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants