Hook: Stop Copy‑Pasting Numbers from a Dashboard with No API
You stare at a web‑based dashboard, manually copy‑pasting a handful of numbers every hour. The page has no API, no export button, and the data changes fast enough that a manual workflow becomes a bottleneck. What if you could let your Linux box watch a specific region of the screen, turn the pixels into text, and trigger an alert the moment a value changes?
In this post I walk through the exact setup I use on my home server: a PipeWire screen capture, a tiny grim script to grab a region, Tesseract OCR to read the numbers, and a Bash loop that notifies me via notify-send when the value differs from the previous run.
1. Prerequisites
| Tool | Why you need it |
|---|---|
pipewire (with pw-cli and pw-top) |
Modern screen‑capture backend on Wayland |
grim |
Simple command‑line screenshot utility that works with PipeWire |
tesseract-ocr |
OCR engine that can read the captured image |
imagemagick (optional) |
Pre‑process the image for better OCR accuracy |
jq (optional) |
Parse JSON if you decide to log results |
notify-send (or any other notifier) |
Show a desktop notification when the value changes |
All of these packages are available in the default repositories of most distros:
# Debian/Ubuntu
sudo apt install pipewire grim tesseract-ocr imagemagick jq libnotify-bin
# Fedora
sudo dnf install pipewire grim tesseract imagemagick jq libnotify
2. Capture a Fixed Region with grim
grim can capture the whole screen or a rectangle defined by geometry. I store the geometry in a variable so I can tweak it without editing the script.
# Geometry: X,Y,W,H (pixels)
REGION="1200,300,250,80" # Adjust to the exact location of the number on your dashboard
IMG_PATH="/tmp/dashboard.png"
capture_region() {
grim -g "$REGION" "$IMG_PATH"
}
Run grim -g "$REGION" - to preview the region in a viewer like feh if you need to fine‑tune the coordinates.
3. Pre‑process the Image (Optional but Recommended)
Tesseract works best with high‑contrast, binarized images. A quick ImageMagick pipeline removes noise and boosts contrast.
preprocess_image() {
convert "$IMG_PATH" \
-colorspace Gray \
-level 20%,80% \
-type Bilevel \
"$IMG_PATH"
}
If the dashboard already uses a clean font on a solid background you can skip this step.
4. Run Tesseract and Extract the Number
We only need the plain text output, so we tell Tesseract to write to stdout (-). The --psm 7 mode treats the image as a single line of text, which speeds up processing.
extract_number() {
tesseract "$IMG_PATH" stdout --psm 7 2>/dev/null | tr -d '\n' | tr -d ' '
}
The tr commands strip newlines and spaces, leaving a compact string like 42.7.
5. Detect Changes and Notify
The core loop stores the previous reading in a temporary file. When the new reading differs, we fire a notification.
STATE_FILE="/tmp/dashboard_last.txt"
check_change() {
local current=$(extract_number)
local previous=""
[ -f "$STATE_FILE" ] && previous=$(cat "$STATE_FILE")
if [[ "$current" != "$previous" ]]; then
notify-send "Dashboard Update" "Value changed: $previous → $current"
echo "$current" > "$STATE_FILE"
fi
}
6. Glue It All Together
A simple infinite loop with a sleep interval does the job. I run this as a systemd user service so it survives reboots and logs to journalctl.
#!/usr/bin/env bash
while true; do
capture_region
preprocess_image # comment out if you skipped pre‑processing
check_change
sleep 60 # check once per minute; adjust as needed
done
Save the script as ~/bin/dashboard-watcher.sh, make it executable (chmod +x), and test it manually before creating the service.
7. Optional: Systemd User Service
Create ~/.config/systemd/user/dashboard-watcher.service:
[Unit]
Description=Watch dashboard region and alert on change
After=network.target
[Service]
ExecStart=%h/bin/dashboard-watcher.sh
Restart=on-failure
[Install]
WantedBy=default.target
Enable and start it:
systemctl --user daemon-reload
systemctl --user enable --now dashboard-watcher.service
You can view logs with journalctl --user -u dashboard-watcher.service.
8. Tweaking for Accuracy
- Font size & DPI – Larger fonts and higher DPI screenshots improve OCR confidence.
-
Whitelist characters – If you only expect numbers and a decimal point, add
tesseractconfigtessedit_char_whitelist 0123456789. -
Language data – Use
tesseract-ocr-eng(default) unless the dashboard uses a different script.
9. What This Solves
- Zero manual copying – The script runs unattended.
-
Immediate alerts – Get a desktop pop‑up (or replace
notify-sendwith an email/SMS hook) the moment a KPI shifts. - Low overhead – Each cycle consumes < 0.2 s of CPU on a modest VM.
You can extend the pattern to multiple regions, feed the numbers into a time‑series database, or trigger a Home Assistant automation.
I build sites and automations for small businesses at dreamfuturestech.com.
Top comments (1)
Nice minimal loop. A few things that bit me with the same setup:
grimdoesn't go through PipeWire: it talks to the compositor directly via the wlroots screencopy protocol. So it works on Sway/Hyprland, but on GNOME it just fails. The PipeWire route on GNOME/KDE is the xdg-desktop-portal ScreenCast API, which shows a consent dialog the first time, so it's worth saying which compositor you run.grimoften can't find the display because the unit doesn't inheritWAYLAND_DISPLAY.systemctl --user import-environment WAYLAND_DISPLAY(or setting it in the unit) fixes it.-c tessedit_char_whitelist=0123456789.-plus upscaling the crop 2-3x before binarizing removes most misreads (a 5 read as an S, a stray space in the middle).Do you get false "changed" alerts from OCR noise, or is the font clean enough that it's stable?